跳转至

STM32 使用 AT 指令驱动 ESP01S 进行 MQTT 通信

项目说明

最近将记录一些使用 ESP01S 遇到的问题和解决思路。所有文章中所提及的工具和代码均整理免费分享。

手机上可以点击下方的右上角复制,然后软件就可以自动打开了。在获取的网盘链接中找到编号 B007 的项目资料。

我用夸克网盘给你分享了「32项目集」,点击链接或复制整段内容,打开「夸克APP」即可获取。 /~58423bH72w~:/ 链接:https://pan.quark.cn/s/0d502be9f165
网页上可以点击链接:https://pan.quark.cn/s/0d502be9f165

有问题可以展开联系我

微信号:Qhua_Li7

上篇笔记讲了 使用 STM32 驱动 ESP01S 进行 TCP 通信,本节内容说一下怎么使用 STM32 驱动 ESP01S 进行 MQTT 通信。

使用 STA 的工作模式,串口连接 STM32,代码在文件夹Stm32Code中,和笔记序号一致。

使用的固件是 (1471)ESP8266-AT-1M.bin。

说明一下,ESP01S接的是串口3,调试打印接的是串口1。

由于上章节已经说明了我的发送指令和读取数据框架,这章节就不做重复部分的讲解。

发送指令的方式和上章节一样,主要是接收处理处,多了一个处理接收到 MQTT 的接收符时怎么处理。

MQTT 指令

mqtt的主要指令如下。

先连接WIFI

1
2
3
AT+CWMODE=1 #设置模组进入STA模式

AT+CWJAP="ssid","password" #连接wifi

然后配置 MQTT 的相关参数,用户ID、账号、密码,这个自己随便写,注意这个用户名字配置,如果你是用的公共服务器,最好不要用test来作为你的用户ID,或者一些很通用的单词,不然很可能连接不上。

AT+MQTTUSERCFG=0,1,"用户ID","账号","密码",0,0,""

连接MQTT服务器,我这里连接EMQX的公共服务器。

AT+MQTTCONN=0,"broker.emqx.io",1883,0

发布消息:

AT+MQTTPUB=0,"ESP01S/LED","1",0,0  //发布一条topic为“ESP01S/LED”,message为“1”的数据, #QOS设置为0

订阅消息:

AT+MQTTSUB=0,"ESP01S/LED_CTRL",0        //订阅一条topic为“ESP01S/LED”,QOS为0的数据

订阅消息返回:

+MQTTSUBRECV:0,"ESP01S/LED_CTRL",2,ON    //订阅消息返回,ID,主题,字符长度,字符

MQTTfx 连接测试

我们上报的信息,想要快速查看是否真的上报成功,可以使用 mqttfx 工具进行查看。工具在我的开源文件中包含。

1、点击设置,准备创建一个连接。

2、点击此处新建一个连接。

我把这个连接创建为 NewConnect 并且配置访问服务器,这个ID建议随机生成,注意不要和你ESP01S配置的 ID 一样。

主页面点击连接即可。

然后我们订阅一个主题,主题名自己输入,需要和代码中发布的主题一致。

便可以看到发布的消息了。

我们还可以在这个上位机往服务器发布消息,发布我们之前订阅的消息。

然后 ESP01S 就会返回订阅的消息了。

STM32软件设计

初始化

初始化的时候,我们需要去连接 wifi ,连接服务器,具体操作如下:

复位->AT检测->切换Station模式->连接WIFI->配置MQTT参数->连接服务器->订阅主题

ret = esp_at_cmd(&esp01s_device, "AT+RST\r\n", "OK", 2000);
printf("AT+RESET resp: %s\n", esp01s_device.resp_buf);
if(ret != 0) while(1);
delay_ms(500);

ret = esp_at_cmd(&esp01s_device, "AT\r\n", "OK", 2000);
printf("AT resp: %s\n", esp01s_device.resp_buf);
if(ret != 0) while(1);

ret = esp_at_cmd(&esp01s_device, "AT+CWMODE=1\r\n", "OK", 2000);
printf("AT+CWMODE resp: %s\n", esp01s_device.resp_buf);
if(ret != 0) while(1);

ret = esp_at_cmd(&esp01s_device, "AT+CWJAP=\"CMCC-XJmL\",\"sR62HiPv\"\r\n", "OK", 5000);
printf("AT+CWJAP resp: %s\n", esp01s_device.resp_buf);
if(ret != 0) while(1);

ret = esp_at_cmd(&esp01s_device, "AT+MQTTUSERCFG=0,1,\"user\",\"user\",\"123\",0,0,\"\"\r\n", "OK", 2000);
printf("AT+MQTTUSERCFG resp: %s\n", esp01s_device.resp_buf); 
if(ret != 0) while(1);

ret = esp_at_cmd(&esp01s_device, "AT+MQTTCONN=0,\"broker.emqx.io\",1883,0\r\n", "OK", 5000);
printf("AT+MQTTCONN resp: %s\n", esp01s_device.resp_buf); 
if(ret != 0) while(1);

ret = esp_at_cmd(&esp01s_device, "AT+MQTTSUB=0,\"ESP01S/LED_CTRL\",0\r\n", "OK", 2000);  
printf("AT+MQTTSUB resp: %s\n", esp01s_device.resp_buf); 
if(ret != 0) while(1);

发送部分

发送部分其实就是发布指令,我是先用一个数组暂存要发布的数据,然后再发布。

1
2
3
sprintf(mqtt_put_message,"AT+MQTTPUB=0,\"ESP01S/LED_ON\",\"%s\",0,0\r\n","ON");
esp_at_cmd(&esp01s_device, mqtt_put_message , "OK", 2000);  
printf("AT+MQTTPUB resp: %s\n", esp01s_device.resp_buf);

接收部分

接收部分,在上一节的基础上,多了一个MQTT接收处理状态机状态,ESP_STATE_PARSE_MQTT.

void esp_process_data(struct esp01s *esp)
{
    uint8_t ch;
    int16_t parse_ret = 0;
    /* 主循环:从环形缓冲区读取每一个字节 */
    while (ringbuffer_getchar(&esp->rx_rb, &ch) == 1)
    {
        switch (esp->state)
        {
            /* 检测协议头的起始字符 '+' */
            case ESP_STATE_IDLE:
            {
                if (ch == '+')
                {
                    esp->header_len = 0;
                    esp->header_buf[esp->header_len++] = ch;
                    esp->state = ESP_STATE_COLLECT_HEADER;
                }
            }
            break;

            /* 收集完整的协议头,然后与协议逐一比对  */
            case ESP_STATE_COLLECT_HEADER:
            {
                if (ch == ':' || ch == ',')
                {
                    uint8_t found = 0;

                    if (memcmp(esp->header_buf, "+IPD", 4) == 0)
                    { 
                        // 清空 IPD 相关变量值,并进入解析 IPD 的解析状态
                        esp->ipd_data.link_id = 0;
                        esp->ipd_data.expected_len = 0; 
                        esp->state     = ESP_STATE_PARSE_IPD;
                        esp->ipd_state = ESP_IPD_PARSE_LINKID;
                        found = 1;
                        break;
                    }else if(memcmp(esp->header_buf, "+MQTTSUBRECV", 12) == 0)
                    {
                        // 清空 MQTT 相关变量值,并进入解析 MQTT 的解析状态
                        esp->mqtt_data.link_id = 0;
                        esp->mqtt_data.expected_len = 0; 
                        esp->mqtt_data.topic_len    = 0; 
                        esp->state      = ESP_STATE_PARSE_MQTT;
                        esp->mqtt_state = ESP_MQTT_PARSE_LINKID;
                        found = 1;
                        break;
                    }
                    if (!found)
                    {
                        esp->state = ESP_STATE_IDLE;
                    }
                }
                else if (esp->header_len < ESP_MAX_HEADER_LEN - 1)
                {
                    esp->header_buf[esp->header_len++] = ch;
                }
                else
                {
                    esp->state = ESP_STATE_IDLE;
                }
            }
            break;
        // 解析 IPD 接收数据
        case ESP_STATE_PARSE_IPD:
            parse_ret = esp_process_ipd_data(esp,ch);
            if(parse_ret == -1 || parse_ret == 1)
            {
                esp->state = ESP_STATE_IDLE;
            }
            break;

        // 解析 MQTT 接收数据
        case ESP_STATE_PARSE_MQTT:
            parse_ret = esp_process_mqtt_data(esp,ch);
            if(parse_ret == -1 || parse_ret == 1)
            {
                esp->state = ESP_STATE_IDLE;
            }
            break;
        }  
    }
}

实际的接收状态机处理部分如下:

int16_t esp_process_mqtt_data(struct esp01s *esp,uint8_t ch)
{
    int16_t ret = 0;
    switch (esp->mqtt_state)
    { 
        /* 解析第一个数字,LinkID
         * 实现说明:
         *   - 遇到数字:累加到 link_id
         *   - 遇到逗号:link_id 是 LinkID,后面还有TOPIC → 进入 ESP_MQTT_PARSE_TOPIC 
         *   - 其他字符:格式错误 → 进入 IDLE
         * -------------------------------------------------- */
        case ESP_MQTT_PARSE_LINKID:
            if (ch >= '0' && ch <= '9')
            {
                /* 数字字符,累加到 link_id */
                esp->mqtt_data.link_id = esp->mqtt_data.link_id * 10 + (ch - '0');
            }
            else if (ch == ',')
            {
                /* 遇到逗号,说明 LinkID 解析完成*/
                esp->mqtt_state = ESP_MQTT_PARSE_TOPIC;
                esp->mqtt_data.topic_len = 0;
            } 
            else
            {
                ret = -1;
            }
            break;
        /* 解析 TOPIC */
        case ESP_MQTT_PARSE_TOPIC:
            if (ch == '"')
            { 
                // 跳过,不处理,头尾的"
            }else if(ch == ',')
            {
                // 处理完成
                esp->mqtt_state = ESP_MQTT_PARSE_LENGTH;
                esp->mqtt_data.expected_len = 0;
            }
            else
            { 
                // 接收话题
                esp->mqtt_data.topic_buf[esp->mqtt_data.topic_len++] = ch;
            }  
            break;
        /* --------------------------------------------------
         * 状态:ESP_STATE_PARSE_LENGTH
         * 任务:解析数据长度
         *
         * 实现说明:
         *   - 遇到数字:累加到 expected_len
         *   - 遇到逗号:长度解析完成 → 进入 RECEIVE_DATA
         *   - 其他字符:格式错误 → 进入 IDLE
         * -------------------------------------------------- */
        case ESP_MQTT_PARSE_LENGTH:
            if (ch >= '0' && ch <= '9')
            {
                /* 数字字符,累加到 expected_len */
                esp->mqtt_data.expected_len = esp->mqtt_data.expected_len * 10 + (ch - '0');
            }
            else if (ch == ',')
            {
                /* 遇到逗号,长度解析完成 */
                esp->mqtt_state = ESP_MQTT_RECEIVE_DATA;
                esp->mqtt_data.data_len = 0;
            }
            else
            {
                ret = -1;
            }
            break;

        /* --------------------------------------------------
         * 状态:ESP_STATE_RECEIVE_DATA
         * 任务:按照解析出的长度,接收完整的数据
         *
         * 实现说明:
         *   将接收到的字节存入 data_buf
         *   当 data_len >= expected_len 时,数据接收完成
         *   调用对应协议的处理函数,然后回到 IDLE
         * -------------------------------------------------- */
        case ESP_MQTT_RECEIVE_DATA:
        {
            if (esp->mqtt_data.data_len < ESP_DATA_BUF_SIZE)
            {
                esp->mqtt_data.data_buf[esp->mqtt_data.data_len++] = ch;
                if (esp->mqtt_data.data_len >= esp->mqtt_data.expected_len)
                {
                    /* 数据接收完成,调用处理函数 */ 
                    esp_handle_mqtt(esp);
                    ret = 1;
                }
            }
            else
            { 
                ret = -1;
            }
        }
        break;
    }
    return ret;
}