STM32F103+ESP-01S+MQTT协议连接华为云端(附踩坑记录)

STM32F103+ESP-01S+MQTT协议连接华为云端(附踩坑记录)

一、物料准备

硬件:

  1. STM32F103C8T6最小核心板
  2. ESP01S WIFI模块

软件:

  1. Keil
  2. esp32固件烧写软件
  3. 华为云服务器(个人免费使用,每天消息上限)

二、调试过程

调试总体思路:

  1. 烧写官方的MQTT固件(这样可以省去协议的手动组装,直接调用AT指令+传参就行了);

  2. 单片机的硬线连接,做好usart配置;

  3. wifi连接热点;

  4. MQTT连接云服务器

  5. ESP01S固件烧写

    ESP01S的芯片其实也是8266

    参考博文:https://www.cnblogs.com/xilimiss510/p/17592856.html

    官网固件库:https://docs.ai-thinker.com/固件汇总

  6. 硬线+usart配置

首先是硬件接线+USART配置,我用的是STM32F103的USART2,USART的配置基本都是通用的,注意单片机的串口引脚和所在时钟就行了。如果要接入FreeRTOS的话,注意NVIC的配置的优先级分组和整体保持一致就行了(一般是第4组)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 4位抢占优先级,0位子优先级,代码如下:

// USART2_TX:PA2,USART2_RX:PA3, void USART2_Config(int baud) { 	USART_InitTypeDef USART_InitStructure; 	NVIC_InitTypeDef NVIC_InitStructure; 	GPIO_InitTypeDef GPIO_InitStructure; 	/********* RCC配置 ******************/ 	// 打开USART2时钟 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);  	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); 	/********* NVIC配置 **********************************/     NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12;  // 抢占优先级12(>11)     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;          // 子优先级无效(分组4)     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;     NVIC_Init(&NVIC_InitStructure);  	/********* 引脚配置 **********************************/ 	 	// 映射引脚和复用功能 	//RX端口 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 	GPIO_Init(GPIOA, &GPIO_InitStructure); 	 	//TX端口 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 	GPIO_Init(GPIOA, &GPIO_InitStructure);  	/********* 串口配置 ************************************/ 	USART_InitStructure.USART_BaudRate = baud; 	USART_InitStructure.USART_WordLength = USART_WordLength_8b; 	USART_InitStructure.USART_StopBits = USART_StopBits_1; 	USART_InitStructure.USART_Parity = USART_Parity_No; 	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; 	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; 	USART_Init(USART2, &USART_InitStructure);  	// 配置中断 	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); 	 	// 打开usart 	USART_Cmd(USART2, ENABLE); }  /***************************************** usart中断服务 ********************************/  uint8_t esp8266_buf[ESP8266_BUF_SIZE]; uint16_t esp8266_cnt = 0;  //前台程序就是中断服务程序,该程序是不需要手动调用的,当中断触发之后CPU会自动跳转过来执行该函数 void USART2_IRQHandler(void) { 	//uint8_t data; 	//判断中断是否发生 	if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) 	{    		//从USART2中接收一个字节  		esp8266_buf[esp8266_cnt++] = USART_ReceiveData(USART2);  //一次只能接收一个字节    		//data = USART_ReceiveData(USART2); 		//把接收到的数据转发出去 		//USART_SendData(USART1,data); 	} }  
  1. wifi入网

esp-01s的波特率设置为115200,所以为了调试方便,USART1的波特率也调整为115200,同时xcom的波特率也调整为115200,然后按照以下示例即可发送成功测试

官方指令集参考:

https://docs.espressif.com/projects/esp-at/zh_CN/latest/esp32/AT_Command_Set/index.html

#define ESP8266_WIFI_INFO 		"AT+CWJAP="wifiusername","wifipassword"rn"  /**   * @brief  向ESP8266发送AT指令并检查响应   * @param  cmd: 要发送的AT指令字符串   * @param  resp: 期望的响应字符串   * @retval 0: 成功(接收到期望响应); 1: 失败   */ uint8_t ESP8266_SendCmd(char *cmd, char *resp) {     uint16_t timeout = 0;          // 清空接收缓冲区     ESP8266_Clear();          // 发送AT指令到ESP8266(通过USART2)     USART2_Send_Str(cmd);          // 等待响应,超时时间为5秒(500*10ms)     while(timeout++ < 500)     {         delay_ms(10);  // 等待10ms                  // 检查是否接收到期望的响应         if(strstr((char *)esp8266_buf, resp) != NULL)         { 			USART1_Send_Str((char *)esp8266_buf);             return 0;  // 成功         }     }     return 1; }  //连接wifi void ESP_Config(void) { 	ESP8266_Clear(); 	 //	while(ESP8266_SendCmd("ATrn", "OK")) //		delay_ms(50);  	//1、复位 	while(ESP8266_SendCmd("AT+RSTrn", "OK")) 		delay_ms(50); 		 	//2、设置工作站STA模式 	while(ESP8266_SendCmd("AT+CWMODE=1rn", "OK")) 		delay_ms(50); 	 	//3、连接wifi 	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP")) 		delay_ms(50); } 

注意末尾都要加"rn",字符串该转义的转义

这里连接上了可以试着测试个天气应用的api

  1. MQTT连接

官方MQTT指令集(中文)非常详细:MQTT AT 命令集 - ESP32 - — ESP-AT 用户指南 latest 文档

完整的连接发布订阅代码:

 void ESP8266_Clear(void) {     memset(esp8266_buf, 0, sizeof(esp8266_buf));     esp8266_cnt = 0; }  bool ESP8266_recv(char *substr) { 	// 检查是否接收到期望的响应 	if(strstr((char *)esp8266_buf, substr) != NULL) 	{ 		USART1_Send_Str("收到风扇控制命令"); 		USART1_Send_Str((char *)esp8266_buf); 		ESP8266_Clear(); 		return true; 	} 	return false; }  /**   * @brief  向ESP8266发送AT指令并检查响应   * @param  cmd: 要发送的AT指令字符串   * @param  resp: 期望的响应字符串   * @retval 0: 成功(接收到期望响应); 1: 失败   */ uint8_t ESP8266_SendCmd(char *cmd, char *resp) {     uint16_t timeout = 0;          // 清空接收缓冲区     ESP8266_Clear();          // 发送AT指令到ESP8266(通过USART2)     USART2_Send_Str(cmd);          // 等待响应,超时时间为5秒(500*10ms)     while(timeout++ < 500)     {         delay_ms(10);  // 等待10ms                  // 检查是否接收到期望的响应         if(strstr((char *)esp8266_buf, resp) != NULL)         { 			USART1_Send_Str((char *)esp8266_buf);             return 0;  // 成功         }     }     return 1; }  void ESP_PUB(char *cmd) {	 	ESP8266_SendCmd(cmd, "OK");	//判断OK是否是cmd的子串 }  //连接wifi、连接云端服务器 void ESP_Config(void) { 	ESP8266_Clear(); 	 //	while(ESP8266_SendCmd("ATrn", "OK")) //		delay_ms(50);  	//1、复位 	while(ESP8266_SendCmd("AT+RSTrn", "OK")) 		delay_ms(50); 		 	//2、设置工作站STA模式 	while(ESP8266_SendCmd("AT+CWMODE=1rn", "OK")) 		delay_ms(50); 	 	//3、连接wifi 	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP")) 		delay_ms(50); 	 	//4、配置用户参数 	while(ESP8266_SendCmd("AT+MQTTUSERCFG=0,1,"monitor_123","monitor","XXXXXXXXXXXXXXXXXX",0,0,""rn", "OK")) 		delay_ms(50); 	 	//5、连接云服务器 	while(ESP8266_SendCmd("AT+MQTTCONN=0,"XXXXXXXXXXXXXX",1883,0rn", "OK")) 		delay_ms(50);  	//6、订阅app控制风扇的主题 	while(ESP8266_SendCmd("AT+MQTTSUB=0,"control_fan",1rn", "OK")) 		delay_ms(50); }  // 修改云端属性(暂未启用,只通过/mesasge/up进行分发) void ESP_PUB_Properties(float temper, float humidity, bool isAlarming) { 	sprintf( 	ts, 	"AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{\"services\":[{\"service_id\":\"Monitor\"\,\"properties\":{\"temper\":10\,\"humidity\":30\,\"isAlarming\":true}}]}",1,0rn" 	); 	ESP_PUB(ts); }  // 发布消息 void ESP_PUB_Message(float temper, float humidity, bool isAlarming,bool isFanNormallyOpen) { 	memset(ts,0,300); 	sprintf( 	ts, 	"AT+MQTTPUB=0,"$oc/devices/monitor1/sys/messages/up","{\"temper\":%.1f\,\"humidity\":%.1f\,\"isAlarming\":%s\,\"isFanNormallyOpen\":%s}",1,0rn", 	temper, 	humidity, 	isAlarming?"true":"false", 	isFanNormallyOpen?"true":"false" 	); 	ESP_PUB(ts); }  

注意这里组装消息的时候有个大坑,卡了我非常之久:

 // 修改云端属性(暂未启用,只通过/mesasge/up进行分发) void ESP_PUB_Properties(float temper, float humidity, bool isAlarming) { 	sprintf( 	ts, 	"AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{\"services\":[{\"service_id\":\"Monitor\"\,\"properties\":{\"temper\":10\,\"humidity\":30\,\"isAlarming\":true}}]}",1,0rn" 	); 	ESP_PUB(ts); } 

假设要发布的JSON对象:

// 华为云属性更新的固定格式 {     "services":[         {             "service_id":"Monitor",             "properties":{                 "temper":10,                 "humidity":30             }         }     ] }  // 去除format {"services":[{"service_id":"Monitor","properties":{"temper":10,"humidity":30}}]} 

那跟据MQTT文档需要发送的指令则是

// 官方示例 AT+CWMODE=1 AT+CWJAP="ssid","password" AT+MQTTUSERCFG=0,1,"ESP32","espressif","1234567890",0,0,"" AT+MQTTCONN=0,"192.168.10.234",1883,0 AT+MQTTPUB=0,"topic",""{"timestamp":"20201121085253"}"",0,0  // 发送此命令时,请注意特殊字符是否需要转义。  // 我们的指令进行第一次转义,仔细看,这里我们将","也进行了转义!!! AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{"services":[{"service_id":"Monitor","properties":{"temper":10,"humidity":30}}]}" 

注意,这里我们不仅将数据中引号进行了转义,同时对","也进行了转义,这里转义","的原因是因为需要告诉ESP的固件程序,这个逗号是我数据中的逗号,而不是指令中的逗号!!!

指令有了,我们现在要通过sprintf进行数据的组装,所以要进行第二次转义,而这次的转义,是要将所有的""和引号进行转义,不用转义逗号,所以最终就变成了

"AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{\"services\":[{\"service_id\":\"Monitor\"\,\"properties\":{\"temper\":10\,\"humidity\":30}}]}"" 

这就是为什么要发布的数据中的逗号前有两个反斜杠

发表评论

评论已关闭。

相关文章