基于小熊派WIFI-ESP8266实践(中)-多功能处理显示等大杂烩

简介: 基于小熊派WIFI-ESP8266实践(中)-多功能处理显示等大杂烩

上节,我们了解了小熊派上的ESP8266模块,这节,我们实现一个程序,让手机发指令来控制开发板上LED灯的亮灭吧,上节的文章链接如下:

基于小熊派WIFI-ESP8266实践(上)

1、了解硬件

编写程序之前先来看看ESP8266硬件模块的接口电路原理图:

640.png

以下是ESP8266模块对应底板的硬件连接:

640.png

LPUART是什么鬼?我们具体来看看STM32L431RCTx这款芯片关于LPUART的描述吧,该介绍位于STM32L431 Datasheet的第48页:

640.png

文档的意思大概是,这是一个低功耗的UART,可以以更低的时钟频率实现高波特率的通信,同时支持从停止模式唤醒且唤醒事件是可编程的,还有就是具有极低的功耗,如果是更高速度的时钟还可以更高的波特率进行数据传输。


既然是这样,我们就把它当普通串口使用就行啦!其余的功能后面用到了再去详细了解!


在软件编程之前,我们先来了解下与ESP8266通信相关的注意事项,打开开发板ESP8266相关的规格书,简要浏览一下,我们可以看到以下的描述:

640.png

2、STM32CubeMX配置

这里我们直接之前利用上次编写光强那个工程就可以了,链接如下:


基于小熊派光强传感器BH1750状态机驱动项目再度升级(带上位机曲线显示)


在此基础上添加ESP8266的串口,所以在STM32CubeMx对应的LPUART1的配置如下,其余参数默认即可,其余的关于ESP8266的上电,硬件复位这些管脚都不需要配置,因为硬件给我们做好了,我们专注于与ESP8266通信就可以了。

640.png

由于软件接收的AT指令回复有可能是不定长数据,且可能存在多个\r\n的情况,所以这里我们使用DMA来做接收会更简单一些,一般用环形缓冲实现也可以,但是STM32有这么优秀的DMA功能,我当然用!

640.png

由于AT指令是一个处理收发的过程,所以我们还需要将接收中断配置上:

640.png

3、软件编程

有了ESP8266,能做的事情很多,比如,让我们来设计几个简单的控制指令:

指令 功能
LEDON 打开底板上的LED灯
LEDOFF 关闭底板上的LED灯
LEDBLINK 让底板上的LED灯闪烁
PLOTDISPLAY 显示上位机曲线(打印数据)
PLOTCLOSEDISPLAY 关闭上位机曲线显示(取消打印数据)

指令下发代码实现框架如下:

/*wifi接收指令处理*/
static void Wifi_Recv_Cmd_Process(void)
{
    static int cmd_index = 0 ;
    char display_buf[20] = {0};
    char *cmd[] = {"LEDON", "LEDOFF", "LEDBLINK", "PLOTDISPLAY", "PLOTCLOSEDISPLAY"};
    if(strstr((char *)esp8266_info.rx_buffer, cmd[cmd_index]) != NULL)
    {
        HAL_UART_DMAStop(&hlpuart1);
        switch(cmd_index)
        {
            case 0:
                printf("接收到开灯指令\n");
                printf("接收到客户端发来的指令:%s\n", esp8266_info.rx_buffer);
                HAL_GPIO_WritePin(GPIOC, BOARD_LED_Pin, GPIO_PIN_SET);
                break ;
            case 1:
                printf("接收到关灯指令\n");
                printf("接收到客户端发来的指令:%s\n", esp8266_info.rx_buffer);
                LED_BLINK_controld(0);
                HAL_GPIO_WritePin(GPIOC, BOARD_LED_Pin, GPIO_PIN_RESET);
                break ;
            case 2:
                printf("接收到闪灯指令\n");
                printf("接收到客户端发来的指令:%s\n", esp8266_info.rx_buffer);
                LED_BLINK_controld(1);
                break ;
            case 3:
                printf("接收到显示曲线指令\n");
                printf("接收到客户端发来的指令:%s\n", esp8266_info.rx_buffer);
                plot_display_controld(1);
                break ;
            case 4:
                printf("接收到关闭显示曲线指令\n");
                printf("接收到客户端发来的指令:%s\n", esp8266_info.rx_buffer);
                plot_display_controld(0);
                break ;
        }
        LCD_Fill(0, 146, 240, 146 + 24, BLACK);
        sprintf(display_buf, "CMD:%s", cmd[cmd_index]);
        LCD_ShowString(0, 146, 240, 24, 24, (char *)display_buf); //显示字符串,字体大小16*16
        memset(esp8266_info.rx_buffer, 0, RX_BUFF_SIZE);
        HAL_UART_Receive_DMA(&hlpuart1, esp8266_info.rx_buffer, RX_BUFF_SIZE);
    }
    ++cmd_index ;
    if(5 == cmd_index)
        cmd_index = 0 ;
}

最终看到的效果:这里把数据显示做了相应的调整,我使用了手机的一个TCP/UDP测试工具,连接了ESP8266,然后下发指令,就像下面这样:

640.jpg

640.jpg

当匹配到数据中含有对应指令的时候,则执行具体的操作,并将指令显示到LCD上。


那么要实现这样,就必须把ESP8266作为服务器,手机作为客户端,客户端连接服务器后,向服务器发送指令,我们来看看esp8266.h的实现:

#ifndef __ESP8266_H
#define __ESP8266_H
#include "main.h"
/*发送数据最大长度*/
#define TX_BUFF_SIZE 50
/*接收数据最大长度*/
#define RX_BUFF_SIZE 150
/*ESP8266作为热点时的名称*/
#define WIFI_HOT_SPOT_SSID    "BearPi_ESP8266"
/*ESP8266作为热点时的密码*/
#define WIFI_HOT_SPOT_PASSWORD  "12345678"
/*AP PORT*/
#define AP_PORT 8080
typedef struct
{
    /*wifi ap运行状态机*/
    uint8_t   wifi_apr_status ;
    /*AT指令发送缓存*/
    uint8_t   tx_buffer[TX_BUFF_SIZE];
    /*接收缓存*/
    uint8_t   rx_buffer[RX_BUFF_SIZE];
    /*发送标志*/
    uint8_t   tx_flag ;
    /*multi_timer定时器句柄*/
    Timer     wifi_timer ;
    /*定时器计数值*/
    uint16_t  wifi_timer_count ;
    /*wifi完成标志*/
    uint8_t   wifi_completed_flag ;
    /*定时回调*/
    void (*wifi_timeout_cb)(void);
} wifi_ap_info ;
/*测试WIFI*/
#define WIFI_AT_TEST      "AT\r\n"
/*设置或关闭回显*/
#define WIFI_ATE_SET      "ATE%d\r\n"
/*设置WIFI模式*/
#define WIFI_AT_SET_MODE  "AT+CWMODE=%d\r\n"
/*创建WIFI热点*/
#define WIFI_AT_SAP     "AT+CWSAP=\"%s\",\"%s\",%d,%d\r\n"
/*配置多连接模式*/
#define WIFI_AT_MULTPLE   "AT+CIPMUX=%d\r\n"
/*开启服务器模式*/
#define WIFI_OPEN_SMODE   "AT+CIPSERVER=%d,%d\r\n"
/*设置与服务器的主动断开时间*/
#define WIFI_SET_STO    "AT+CIPSTO=%d\r\n"
/*查看WIFI作为服务器时的地址*/
#define WIFI_VIEW_ADDR    "AT+CIFSR\r\n"
/*每个状态机执行的超时查询时间*/
#define WIFI_TEST_TIMEOUT               1000
#define WIFI_SET_ATE_TIMEOUT            200
#define WIFI_SET_MODE_TIMEOUT           200
#define WIFI_BUILD_AP_INFO_TIMEOUT      4000
#define WIFI_CONFIG_MULTPLE_CONNECT_TIMEOUT 200
#define WIFI_OPEN_SERVER_MODE_TIMEOUT   1000
#define WIFI_VIEW_IPADDR_TIMEOUT      1000
/*每个状态机对应的序号*/
enum
{
    ITEM_WIFI_TEST = 0,
    ITEM_WIFI_SATE,
    ITEM_WIFI_SMODE,
    ITEM_WIFI_BUIAP,
    ITEM_WIFI_CMULT,
    ITEM_WIFI_OSERV,
    ITEM_WIFI_STIMO,
    ITEM_WIFI_VADDR,
    ITEM_WIFI_GDATA,
    ITEM_WIFI_ERROR = 99
};
/*ESP8266作为AP模式进行初始化*/
void Init_ESP8266_AP_Mode(void);
/*Wifi作为服务器时的服务*/
void ESP8266_AP_Mode_Setting(void);
/*wifi发送命令*/
void wifi_send_cmd(const char *format, ...);
#endif //__ESP8266_h

这里我们再次运用了multi_timer,可见我多么喜欢它!

由于代码较多,我们只挑核心部分出来讲解就可以了,其它留给读者自行实践。

1、Init_ESP8266_AP_Mode函数实现

/*ESP8266作为AP模式进行初始化*/
void Init_ESP8266_AP_Mode(void)
{
    esp8266_info.tx_flag = 1 ;
    esp8266_info.wifi_apr_status = ITEM_WIFI_TEST ;
    esp8266_info.wifi_timer_count = 0 ;
    esp8266_info.wifi_completed_flag = 1 ;
    esp8266_info.wifi_timeout_cb =  wifi_timeout_callback ;
    /*开启1ms软件定时器*/
    timer_init(&esp8266_info.wifi_timer, esp8266_info.wifi_timeout_cb, 1, 1);
    timer_start(&esp8266_info.wifi_timer);
}

这里对结构体参数进行了初始化,在这里用multi_timer开启一个1ms的软件定时器,定时时基由系统时钟产生,一次中断为1ms,主要是用来产生延时的,发送完AT指令给ESP8266后,一般要延时一段时间,再去查串口缓存区是否有ESP8266的回复数据,定时器回调函数如下:

static void wifi_timeout_callback(void)
{
    if(0 == esp8266_info.wifi_completed_flag)
        ++esp8266_info.wifi_timer_count ;
}

当esp8266_info.wifi_completed_flag标志为0时,esp8266_info.wifi_timer_count变量自加产生对应的延时,当esp8266_info.wifi_completed_flag为1时,回调函数不会做任何操作,根据这样的想法,我们简单实现发送WIFI测试指令AT\r\n

/*测试*/
void WIFI_Test(void)
{
    uint8_t ret = 0 ;
    static uint8_t err_count = 0 ;
    /*当前为发送状态*/
    if(1 == esp8266_info.tx_flag)
    {
        /*复位参数*/
        Reset_Wifi_Para();
        /*发送测试指令*/
        wifi_send_cmd(WIFI_AT_TEST);
        /*将发送状态设置为0,即为接收状态*/
        esp8266_info.tx_flag = 0 ;
        /*清空定时计数器*/
        esp8266_info.wifi_timer_count = 0 ;
        /*开启定时计数标志*/
        esp8266_info.wifi_completed_flag = 0 ;
    }
    /*当前为接收状态*/
    else
    {
        /*判断定时计数到WIFI_TEST_TIMEOUT==>1000ms了没有?*/
        if(WIFI_TEST_TIMEOUT == esp8266_info.wifi_timer_count)
        {
            /*关闭定时计数标志*/
            esp8266_info.wifi_completed_flag = 1 ;
            /*清空定时计数器*/
            esp8266_info.wifi_timer_count = 0 ;
            /*检查DMA接收缓存中是否包含OK子串*/
            ret = AT_Check_Answer("OK");
            /*失败,错误超过3次,返回出错状态*/
            if(ret != 0)
            {
                esp8266_info.tx_flag = 1 ;
                ++err_count;
                if(err_count > 3)
                {
                    err_count = 0 ;
                    esp8266_info.wifi_apr_status = ITEM_WIFI_ERROR ;
                    printf("WIFI初始化失败\n");
                }
            }
            else
            {
                esp8266_info.tx_flag = 1 ;
                /*将状态标记为下一个指令的处理流程*/
                esp8266_info.wifi_apr_status = ITEM_WIFI_SATE ;
                printf("wifi测试成功!  回复%s\n", esp8266_info.rx_buffer);
            }
        }
    }
}

复位参数的实现逻辑:

/*复位wifi收发参数*/
void Reset_Wifi_Para(void)
{
    /*停止DMA接收*/
    HAL_UART_DMAStop(&hlpuart1);
    /*清除发送接收缓存*/
    memset(esp8266_info.tx_buffer, 0, TX_BUFF_SIZE);
    memset(esp8266_info.rx_buffer, 0, RX_BUFF_SIZE);
    /*开启DMA接收*/
    HAL_UART_Receive_DMA(&hlpuart1, esp8266_info.rx_buffer, RX_BUFF_SIZE);
}

虽然这段测试AT的代码相比很多例程看起来都长,但是它没有硬延时!没有硬延时!没有硬延时!重要的事情说三遍,这里就体现状态机结合定时器超时处理的好处了,那么其他状态也是类似的实现方法,大家可以下载工程源码去自己看,最后用这么一个函数来综合体现状态机的切换:

2、ESP8266_AP_Mode_Setting函数实现

/*ESP8266作为AP模式进行设置*/
void ESP8266_AP_Mode_Setting(void)
{
    static uint8_t error_flag = 0 ;
    switch(esp8266_info.wifi_apr_status)
    {
        case ITEM_WIFI_TEST:
            WIFI_Test();
            break ;
        case ITEM_WIFI_SATE:
            WIFI_SET_ATE(1);
            break ;
        case ITEM_WIFI_SMODE:
            WIFI_SET_MODE(2);
            break ;
        case ITEM_WIFI_BUIAP:
            WIFI_BUILD_AP_INFO(WIFI_HOT_SPOT_SSID, WIFI_HOT_SPOT_PASSWORD, 1, 4);
            break ;
        case ITEM_WIFI_CMULT:
            WIFI_CONFIG_MULTPLE_CONNECT(1);
            break ;
        case ITEM_WIFI_OSERV:
            WIFI_OPEN_SERVER_MODE(1, AP_PORT);
            break ;
        case ITEM_WIFI_STIMO:
            WIFI_CONFIG_SERVER_TIMEOUT(0);
            break ;
        case ITEM_WIFI_VADDR:
            WIFI_VIEW_IPADDR();
            break ;
        case ITEM_WIFI_GDATA:
            Wifi_Recv_Cmd_Process();
            break ;
        default:
            if(0 == error_flag)
            {
                error_flag = 1 ;
                if(ITEM_WIFI_ERROR == esp8266_info.wifi_apr_status)
                {
                    printf("WIFI出错\n");
                }
            }
            break ;
    }
}

最后,我们只要在主函数中循环调用这段代码就行了:

while (1)
{
   /* USER CODE END WHILE */
   /* USER CODE BEGIN 3 */
   Light_Sensor_Service();    /*光强传感器处理和现实*/
   ESP8266_AP_Mode_Setting(); /*ESP8266 AP模式下的状态机*/
   LED_Blink_Service();       /*LED闪烁灯服务*/
   timer_loop();              /*multi_timer循环代用*/
}

3、wifi_send_cmd函数实现

wifi发送指令的实现:

/*wifi发送命令*/
void wifi_send_cmd(const char *format, ...)
{
    va_list args;
    uint32_t length;
    va_start(args, format);
    length = vsnprintf((char *)esp8266_info.tx_buffer, sizeof(esp8266_info.tx_buffer), (char *)format, args);
    va_end(args);
    HAL_UART_Transmit(&hlpuart1, (uint8_t *)esp8266_info.tx_buffer, length, HAL_MAX_DELAY);
}

发送很简单,就把这个函数当做printf函数来用就可以了,这是一个可变参函数,参数个数可以根据format做动态调整。


大致框架讲解完毕了,接下来看下效果:


发送LED灯闪烁指令:

640.jpg

发送曲线显示指令(代码默认将曲线显示用标志位做了屏蔽,这里只要看到串口有一连串数据即可):

640.jpg

640.png

例程下载

链接:https://pan.baidu.com/s/1P8yjbuljvcqZute1ToGjVQ
提取码:ni46
复制这段内容后打开百度网盘手机App,操作更方便哦

往期精彩

网红物联网开发板小熊派使用评测


基于小熊派WIFI-ESP8266实践(上)


超轻量级网红软件定时器multi_timer(51+stm32双平台实战)


基于小熊派光强传感器BH1750实践(multi_timer+状态机工程应用)


基于小熊派光强传感器BH1750状态机驱动项目升级(带LCD屏显示)


基于小熊派光强传感器BH1750状态机驱动项目再度升级(带上位机曲线显示)

目录
相关文章
|
7月前
|
小程序
微信小程序如何实现进入小程序自动连WiFi功能
微信小程序如何实现进入小程序自动连WiFi功能
221 0
|
传感器 网络协议 物联网
基于小熊派WIFI-ESP8266实践(上)
基于小熊派WIFI-ESP8266实践(上)
411 0
|
安全 Android开发
AndroidQ 以上禁用 wifi 随机mac功能
AndroidQ 以上禁用 wifi 随机mac功能
235 0
|
传感器 算法 物联网
国产芯片WiFi物联网智能插座—电耗采集功能设计
国产芯片WiFi物联网智能插座—电耗采集功能设计
国产芯片WiFi物联网智能插座—电耗采集功能设计
|
安全 物联网 芯片
国产芯片WiFi物联网智能插座—电源功能设计
国产芯片WiFi物联网智能插座—电源功能设计
国产芯片WiFi物联网智能插座—电源功能设计
|
Java Android开发 数据安全/隐私保护
|
测试技术 网络架构 iOS开发
iOS开发中WiFi相关功能总结
1.Ping域名、Ping某IP 有时候可能会遇到ping 某个域名或者ip通不通,再做下一步操作。这里的ping与传统的做get或者post请求还是有很大区别的。比如我们连接了某个WiFi,测试ping www.baidu.com,如果能ping 通,基本可以断定可以上网了,但是如果我们做了一个get 请求(url 是www.baidu.com),路由器可能重定
1937 0