ESP01S使用笔记04--STM32 使用 AT 指令驱动 ESP01S 进行 MQTT 通信

项目说明

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

githubhttps://github.com/snqx-lqh/ProjectReleasePage

gitee(国内镜像)https://gitee.com/snqx-lqh/ProjectOpenSourceReleasePage

项目属于 32 的编号 B007 ,在发布页中,找到对应资料获取方式。

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

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

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

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

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

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

MQTT 指令

mqtt的主要指令如下。

先连接WIFI

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、点击设置,准备创建一个连接。

image

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

image

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

image

主页面点击连接即可。

image

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

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

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

image

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

image

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);

发送部分

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

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;
}
posted @ 2026-05-31 12:36  少年-潜行  阅读(32)  评论(0)    收藏  举报