胰芰 发表于 2025-8-8 18:06:52

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

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位子优先级,代码如下:
// 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;
uint16_t esp8266_cnt = 0;

//前台程序就是中断服务程序,该程序是不需要手动调用的,当中断触发之后CPU会自动跳转过来执行该函数
void USART2_IRQHandler(void)
{
        //uint8_t data;
        //判断中断是否发生
        if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
        {   
                //从USART2中接收一个字节
                esp8266_buf = USART_ReceiveData(USART2);//一次只能接收一个字节   
                //data = USART_ReceiveData(USART2);
                //把接收到的数据转发出去
                //USART_SendData(USART1,data);
        }
}
[*]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\"\r\n"

/**
* @brief向ESP8266发送AT指令并检查响应
* @paramcmd: 要发送的AT指令字符串
* @paramresp: 期望的响应字符串
* @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("AT\r\n", "OK"))
//                delay_ms(50);

        //1、复位
        while(ESP8266_SendCmd("AT+RST\r\n", "OK"))
                delay_ms(50);
               
        //2、设置工作站STA模式
        while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
                delay_ms(50);
       
        //3、连接wifi
        while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
                delay_ms(50);
}注意末尾都要加"\r\n",字符串该转义的转义
这里连接上了可以试着测试个天气应用的api

[*]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指令并检查响应
* @paramcmd: 要发送的AT指令字符串
* @paramresp: 期望的响应字符串
* @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("AT\r\n", "OK"))
//                delay_ms(50);

        //1、复位
        while(ESP8266_SendCmd("AT+RST\r\n", "OK"))
                delay_ms(50);
               
        //2、设置工作站STA模式
        while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "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,\"\"\r\n", "OK"))
                delay_ms(50);
       
        //5、连接云服务器
        while(ESP8266_SendCmd("AT+MQTTCONN=0,\"XXXXXXXXXXXXXX",1883,0\r\n", "OK"))
                delay_ms(50);

        //6、订阅app控制风扇的主题
        while(ESP8266_SendCmd("AT+MQTTSUB=0,\"control_fan\",1\r\n", "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,0\r\n"
        );
        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,0\r\n",
        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,0\r\n"
        );
        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}}]}\""这就是为什么要发布的数据中的逗号前有两个反斜杠

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: STM32F103+ESP-01S+MQTT协议连接华为云端(附踩坑记录)