找回密码
 立即注册
首页 业界区 业界 STM32F103+ESP-01S+MQTT协议连接华为云端(附踩坑记录) ...

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

胰芰 2025-8-8 18:06:52
STM32F103+ESP-01S+MQTT协议连接华为云端(附踩坑记录)

一、物料准备

硬件:

  • STM32F103C8T6最小核心板
  • ESP01S WIFI模块
软件:

  • Keil
  • esp32固件烧写软件
  • 华为云服务器(个人免费使用,每天消息上限)
二、调试过程

调试总体思路:

  • 烧写官方的MQTT固件(这样可以省去协议的手动组装,直接调用AT指令+传参就行了);
  • 单片机的硬线连接,做好usart配置;
  • wifi连接热点;
  • MQTT连接云服务器
  • ESP01S固件烧写
    ESP01S的芯片其实也是8266
    参考博文:https://www.cnblogs.com/xilimiss510/p/17592856.html
    官网固件库:https://docs.ai-thinker.com/固件汇总
  • 硬线+usart配置
首先是硬件接线+USART配置,我用的是STM32F103的USART2,USART的配置基本都是通用的,注意单片机的串口引脚和所在时钟就行了。如果要接入FreeRTOS的话,注意NVIC的配置的优先级分组和整体保持一致就行了(一般是第4组)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);                          // 4位抢占优先级,0位子优先级,代码如下:
  1. // USART2_TX:PA2,USART2_RX:PA3,
  2. void USART2_Config(int baud)
  3. {
  4.         USART_InitTypeDef USART_InitStructure;
  5.         NVIC_InitTypeDef NVIC_InitStructure;
  6.         GPIO_InitTypeDef GPIO_InitStructure;
  7.         /********* RCC配置 ******************/
  8.         // 打开USART2时钟
  9.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
  10.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
  11.         /********* NVIC配置 **********************************/
  12.     NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
  13.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12;  // 抢占优先级12(>11)
  14.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;          // 子优先级无效(分组4)
  15.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  16.     NVIC_Init(&NVIC_InitStructure);
  17.         /********* 引脚配置 **********************************/
  18.        
  19.         // 映射引脚和复用功能
  20.         //RX端口
  21.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  22.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  23.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  24.        
  25.         //TX端口
  26.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  27.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  28.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  29.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  30.         /********* 串口配置 ************************************/
  31.         USART_InitStructure.USART_BaudRate = baud;
  32.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  33.         USART_InitStructure.USART_StopBits = USART_StopBits_1;
  34.         USART_InitStructure.USART_Parity = USART_Parity_No;
  35.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  36.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  37.         USART_Init(USART2, &USART_InitStructure);
  38.         // 配置中断
  39.         USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
  40.        
  41.         // 打开usart
  42.         USART_Cmd(USART2, ENABLE);
  43. }
  44. /***************************************** usart中断服务 ********************************/
  45. uint8_t esp8266_buf[ESP8266_BUF_SIZE];
  46. uint16_t esp8266_cnt = 0;
  47. //前台程序就是中断服务程序,该程序是不需要手动调用的,当中断触发之后CPU会自动跳转过来执行该函数
  48. void USART2_IRQHandler(void)
  49. {
  50.         //uint8_t data;
  51.         //判断中断是否发生
  52.         if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
  53.         {   
  54.                 //从USART2中接收一个字节
  55.                 esp8266_buf[esp8266_cnt++] = USART_ReceiveData(USART2);  //一次只能接收一个字节   
  56.                 //data = USART_ReceiveData(USART2);
  57.                 //把接收到的数据转发出去
  58.                 //USART_SendData(USART1,data);
  59.         }
  60. }
复制代码

  • wifi入网
esp-01s的波特率设置为115200,所以为了调试方便,USART1的波特率也调整为115200,同时xcom的波特率也调整为115200,然后按照以下示例即可发送成功测试
官方指令集参考:
https://docs.espressif.com/projects/esp-at/zh_CN/latest/esp32/AT_Command_Set/index.html
  1. #define ESP8266_WIFI_INFO                 "AT+CWJAP="wifiusername","wifipassword"\r\n"
  2. /**
  3.   * @brief  向ESP8266发送AT指令并检查响应
  4.   * @param  cmd: 要发送的AT指令字符串
  5.   * @param  resp: 期望的响应字符串
  6.   * @retval 0: 成功(接收到期望响应); 1: 失败
  7.   */
  8. uint8_t ESP8266_SendCmd(char *cmd, char *resp)
  9. {
  10.     uint16_t timeout = 0;
  11.    
  12.     // 清空接收缓冲区
  13.     ESP8266_Clear();
  14.    
  15.     // 发送AT指令到ESP8266(通过USART2)
  16.     USART2_Send_Str(cmd);
  17.    
  18.     // 等待响应,超时时间为5秒(500*10ms)
  19.     while(timeout++ < 500)
  20.     {
  21.         delay_ms(10);  // 等待10ms
  22.         
  23.         // 检查是否接收到期望的响应
  24.         if(strstr((char *)esp8266_buf, resp) != NULL)
  25.         {
  26.                         USART1_Send_Str((char *)esp8266_buf);
  27.             return 0;  // 成功
  28.         }
  29.     }
  30.     return 1;
  31. }
  32. //连接wifi
  33. void ESP_Config(void)
  34. {
  35.         ESP8266_Clear();
  36.        
  37. //        while(ESP8266_SendCmd("AT\r\n", "OK"))
  38. //                delay_ms(50);
  39.         //1、复位
  40.         while(ESP8266_SendCmd("AT+RST\r\n", "OK"))
  41.                 delay_ms(50);
  42.                
  43.         //2、设置工作站STA模式
  44.         while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
  45.                 delay_ms(50);
  46.        
  47.         //3、连接wifi
  48.         while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
  49.                 delay_ms(50);
  50. }
复制代码
注意末尾都要加"\r\n",字符串该转义的转义
这里连接上了可以试着测试个天气应用的api

  • MQTT连接
官方MQTT指令集(中文)非常详细:MQTT AT 命令集 - ESP32 - — ESP-AT 用户指南 latest 文档
完整的连接发布订阅代码:
  1. void ESP8266_Clear(void)
  2. {
  3.     memset(esp8266_buf, 0, sizeof(esp8266_buf));
  4.     esp8266_cnt = 0;
  5. }
  6. bool ESP8266_recv(char *substr)
  7. {
  8.         // 检查是否接收到期望的响应
  9.         if(strstr((char *)esp8266_buf, substr) != NULL)
  10.         {
  11.                 USART1_Send_Str("收到风扇控制命令");
  12.                 USART1_Send_Str((char *)esp8266_buf);
  13.                 ESP8266_Clear();
  14.                 return true;
  15.         }
  16.         return false;
  17. }
  18. /**
  19.   * @brief  向ESP8266发送AT指令并检查响应
  20.   * @param  cmd: 要发送的AT指令字符串
  21.   * @param  resp: 期望的响应字符串
  22.   * @retval 0: 成功(接收到期望响应); 1: 失败
  23.   */
  24. uint8_t ESP8266_SendCmd(char *cmd, char *resp)
  25. {
  26.     uint16_t timeout = 0;
  27.    
  28.     // 清空接收缓冲区
  29.     ESP8266_Clear();
  30.    
  31.     // 发送AT指令到ESP8266(通过USART2)
  32.     USART2_Send_Str(cmd);
  33.    
  34.     // 等待响应,超时时间为5秒(500*10ms)
  35.     while(timeout++ < 500)
  36.     {
  37.         delay_ms(10);  // 等待10ms
  38.         
  39.         // 检查是否接收到期望的响应
  40.         if(strstr((char *)esp8266_buf, resp) != NULL)
  41.         {
  42.                         USART1_Send_Str((char *)esp8266_buf);
  43.             return 0;  // 成功
  44.         }
  45.     }
  46.     return 1;
  47. }
  48. void ESP_PUB(char *cmd)
  49. {       
  50.         ESP8266_SendCmd(cmd, "OK");        //判断OK是否是cmd的子串
  51. }
  52. //连接wifi、连接云端服务器
  53. void ESP_Config(void)
  54. {
  55.         ESP8266_Clear();
  56.        
  57. //        while(ESP8266_SendCmd("AT\r\n", "OK"))
  58. //                delay_ms(50);
  59.         //1、复位
  60.         while(ESP8266_SendCmd("AT+RST\r\n", "OK"))
  61.                 delay_ms(50);
  62.                
  63.         //2、设置工作站STA模式
  64.         while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
  65.                 delay_ms(50);
  66.        
  67.         //3、连接wifi
  68.         while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
  69.                 delay_ms(50);
  70.        
  71.         //4、配置用户参数
  72.         while(ESP8266_SendCmd("AT+MQTTUSERCFG=0,1,"monitor_123","monitor","XXXXXXXXXXXXXXXXXX",0,0,""\r\n", "OK"))
  73.                 delay_ms(50);
  74.        
  75.         //5、连接云服务器
  76.         while(ESP8266_SendCmd("AT+MQTTCONN=0,"XXXXXXXXXXXXXX",1883,0\r\n", "OK"))
  77.                 delay_ms(50);
  78.         //6、订阅app控制风扇的主题
  79.         while(ESP8266_SendCmd("AT+MQTTSUB=0,"control_fan",1\r\n", "OK"))
  80.                 delay_ms(50);
  81. }
  82. // 修改云端属性(暂未启用,只通过/mesasge/up进行分发)
  83. void ESP_PUB_Properties(float temper, float humidity, bool isAlarming)
  84. {
  85.         sprintf(
  86.         ts,
  87.         "AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{\\"services\\":[{\\"service_id\\":\\"Monitor\\"\\,\\"properties\\":{\\"temper\\":10\\,\\"humidity\\":30\\,\\"isAlarming\\":true}}]}",1,0\r\n"
  88.         );
  89.         ESP_PUB(ts);
  90. }
  91. // 发布消息
  92. void ESP_PUB_Message(float temper, float humidity, bool isAlarming,bool isFanNormallyOpen)
  93. {
  94.         memset(ts,0,300);
  95.         sprintf(
  96.         ts,
  97.         "AT+MQTTPUB=0,"$oc/devices/monitor1/sys/messages/up","{\\"temper\\":%.1f\\,\\"humidity\\":%.1f\\,\\"isAlarming\\":%s\\,\\"isFanNormallyOpen\\":%s}",1,0\r\n",
  98.         temper,
  99.         humidity,
  100.         isAlarming?"true":"false",
  101.         isFanNormallyOpen?"true":"false"
  102.         );
  103.         ESP_PUB(ts);
  104. }
复制代码
注意这里组装消息的时候有个大坑,卡了我非常之久:
  1. // 修改云端属性(暂未启用,只通过/mesasge/up进行分发)
  2. void ESP_PUB_Properties(float temper, float humidity, bool isAlarming)
  3. {
  4.         sprintf(
  5.         ts,
  6.         "AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{\\"services\\":[{\\"service_id\\":\\"Monitor\\"\\,\\"properties\\":{\\"temper\\":10\\,\\"humidity\\":30\\,\\"isAlarming\\":true}}]}",1,0\r\n"
  7.         );
  8.         ESP_PUB(ts);
  9. }
复制代码
假设要发布的JSON对象:
  1. // 华为云属性更新的固定格式
  2. {
  3.     "services":[
  4.         {
  5.             "service_id":"Monitor",
  6.             "properties":{
  7.                 "temper":10,
  8.                 "humidity":30
  9.             }
  10.         }
  11.     ]
  12. }
  13. // 去除format
  14. {"services":[{"service_id":"Monitor","properties":{"temper":10,"humidity":30}}]}
复制代码
那跟据MQTT文档需要发送的指令则是
  1. // 官方示例
  2. AT+CWMODE=1
  3. AT+CWJAP="ssid","password"
  4. AT+MQTTUSERCFG=0,1,"ESP32","espressif","1234567890",0,0,""
  5. AT+MQTTCONN=0,"192.168.10.234",1883,0
  6. AT+MQTTPUB=0,"topic",""{"timestamp":"20201121085253"}"",0,0  // 发送此命令时,请注意特殊字符是否需要转义。
  7. // 我们的指令进行第一次转义,仔细看,这里我们将","也进行了转义!!!
  8. AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{"services":[{"service_id":"Monitor"\,"properties":{"temper":10\,"humidity":30}}]}"
复制代码
注意,这里我们不仅将数据中引号进行了转义,同时对","也进行了转义,这里转义","的原因是因为需要告诉ESP的固件程序,这个逗号是我数据中的逗号,而不是指令中的逗号!!!
指令有了,我们现在要通过sprintf进行数据的组装,所以要进行第二次转义,而这次的转义,是要将所有的"\"和引号进行转义,不用转义逗号,所以最终就变成了
  1. "AT+MQTTPUB=0,"$oc/devices/monitor/sys/properties/report","{\\"services\\":[{\\"service_id\\":\\"Monitor\\"\\,\\"properties\\":{\\"temper\\":10\\,\\"humidity\\":30}}]}""
复制代码
这就是为什么要发布的数据中的逗号前有两个反斜杠

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册