STM32F103与ESP8266实现OneNet物联网数据上传实战指南

基于STM32F103+ESP8266的OneNet物联网数据上传实战

一、项目概述

本项目使用STM32F103C8T6作为主控,通过DHT11传感器采集温湿度数据,利用ESP8266 WiFi模块连接OneNet物联网平台,实现以下核心功能:

  1. 周期采集环境温湿度数据(精度:温度±2℃,湿度±5%RH)
  2. 通过MQTT协议对接中国移动OneNet平台
  3. 实现设备级心跳包(60秒)和异常重连机制
  4. 数据可视化展示与历史记录存储

二、硬件准备

组件 型号 接口方式
主控芯片 STM32F103C8T6
WiFi模块 ESP-01S UART
温湿度传感器 DHT11 GPIO
开发板 Blue Pill
USB-TTL转换器 CH340G

‌接线示意图:‌

DHT11_DATA -> PA5
ESP8266_TX -> PA3 (USART2_RX)
ESP8266_RX -> PA2 (USART2_TX)

三、开发环境配置

  1. 安装 ‌STM32CubeMX 6.9+‌
  2. 创建工程配置:
  • 系统时钟:72MHz(外部8MHz晶振)
  • USART2:115200bps,异步模式
  • GPIO:PA1设置为上拉输入
    1. 生成HAL库基础代码

    我并没有使用CubeMx,我使用的HAL代码进行的初始化。大家可以获取我的模板资源。

    四、核心代码实现

    1. DHT11驱动开发

    dht11.h
    #ifndef __DHT11_H__
    #define __DHT11_H__
    
    #include "sys.h"
    
    #define DHT11_PORT          GPIOA
    #define DHT11_PIN           GPIO_PIN_5
    #define DHT11_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE()
    
    #define DHT11_DQ_OUT(x)     do{ x ? \
                                    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET) : \
                                    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET);\
                                }while(0)
    #define DHT11_DQ_IN         HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)
                                
    void dht11_read(uint8_t *result);
    
    #endif
    
    
    dht11.c
    #include "dht11.h"
    #include "delay.h"
    #include "string.h"
    #include "stdio.h"
    
    char dht11_data[5] = {0};
    
    void dht11_gpio_input(void)
    {
        GPIO_InitTypeDef gpio_initstruct;
        DHT11_CLK_ENABLE();                         
        
        gpio_initstruct.Pin = DHT11_PIN;        
        gpio_initstruct.Mode = GPIO_MODE_INPUT;           
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;         
        HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
    }
    
    void dht11_gpio_output(void)
    {
        GPIO_InitTypeDef gpio_initstruct;
        DHT11_CLK_ENABLE();                         
        
        gpio_initstruct.Pin = DHT11_PIN;        
        gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;           
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;         
        HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
    }
    
    void dht11_start(void)
    {
        dht11_gpio_output();
        DHT11_DQ_OUT(1);
        DHT11_DQ_OUT(0);
        delay_ms(20);
        DHT11_DQ_OUT(1);
        
        dht11_gpio_input();
        while(DHT11_DQ_IN);     //等待DHT11拉低电平
        while(!DHT11_DQ_IN);    //等待DHT11拉高电平
        while(DHT11_DQ_IN);     //等待DHT11拉低电平
    }
    
    uint8_t dht11_read_byte(void)
    {
        uint8_t temp = 0;
        uint8_t i = 0;
        uint8_t read_data = 0;
        
        for(i = 0; i < 8; i++)
        {
            while(!DHT11_DQ_IN);
            delay_us(50);
            if(DHT11_DQ_IN == 1)
            {
                temp = 1;
                while(DHT11_DQ_IN);
            }
            else
                temp = 0;
            
            read_data = read_data << 1;
            read_data |= temp;
        }
        
        return read_data;
    }
    
    void dht11_read(uint8_t *result)
    {
        uint8_t i = 0;
        
        dht11_start();
        dht11_gpio_input();
        
        for(i = 0; i < 5; i++)
            dht11_data[i] = dht11_read_byte();
        
        if(dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3] == dht11_data[4])
        {
            memcpy(result, dht11_data, 4);
            printf("湿度:%d.%dRH ,", dht11_data[0], dht11_data[1]);
            printf("温度:%d.%d℃\r\n", dht11_data[2], dht11_data[3]);
        }
        
        delay_ms(2000);
    }
    

    2. ESP8266通信协议栈

    esp8266.h
    #ifndef __ESP8266_H__
    #define __ESP8266_H__
    
    #include "sys.h"
    
    #define ESP8266_RX_BUF_SIZE         128
    #define ESP8266_TX_BUF_SIZE         64
    
    #define ESP8266_EOK                 0
    #define ESP8266_ERROR               1
    #define ESP8266_ETIMEOUT            2
    #define ESP8266_EINVAL              3
    
    #define ESP8266_STA_MODE            1
    #define ESP8266_AP_MODE             2
    #define ESP8266_STA_AP_MODE         3
    
    #define ESP8266_SINGLE_CONNECTION   0
    #define ESP8266_MULTI_CONNECTION    1
    
    #define WIFI_SSID                   "HuaweiAP-1ED0_Guest"
    #define WIFI_PWD                    "gcc11111111"
    
    #define TCP_SERVER_IP               "mqtts.heclouds.com"
    #define TCP_SERVER_PORT             "1883"
    
    void esp8266_init(uint32_t baudrate);
    void esp8266_receive_data(void);
    void esp8266_send_data(char *data, uint16_t len);
    uint16_t esp8266_copy_rxdata(char *data);
    uint8_t esp8266_wait_receive(void);
    
    #endif
    
    
    esp8266.c
    
    #include "esp8266.h"
    #include "stdio.h"
    #include "string.h"
    #include "delay.h"
    #include "stdarg.h"
    
    uint8_t esp8266_rx_buf[ESP8266_RX_BUF_SIZE];
    uint8_t esp8266_tx_buf[ESP8266_TX_BUF_SIZE];
    uint16_t esp8266_cnt = 0, esp8266_cntPre = 0;
    
    UART_HandleTypeDef esp8266_handle = {0};
    void esp8266_uart_init(uint32_t baudrate)
    {
        esp8266_handle.Instance = USART2;
        esp8266_handle.Init.BaudRate = baudrate;
        esp8266_handle.Init.WordLength = UART_WORDLENGTH_8B;
        esp8266_handle.Init.StopBits = UART_STOPBITS_1;
        esp8266_handle.Init.Parity = UART_PARITY_NONE;
        esp8266_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
        esp8266_handle.Init.Mode = UART_MODE_TX_RX;
        HAL_UART_Init(&esp8266_handle);
    }
    
    void USART2_IRQHandler(void)
    {
        uint8_t receive_data = 0;
        if(__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_RXNE) != RESET)
        {
            if(esp8266_cnt >= sizeof(esp8266_rx_buf))
                esp8266_cnt = 0;
            HAL_UART_Receive(&esp8266_handle, &receive_data, 1, 1000);
            esp8266_rx_buf[esp8266_cnt++] = receive_data;
            //uart1_cnt++;
            //HAL_UART_Transmit(&uart1_handle, &receive_data, 1, 1000);
        }
    }
    
    uint8_t esp8266_wait_receive(void)
    {
        if(esp8266_cnt == 0)
            return ESP8266_ERROR;
        
        if(esp8266_cnt == esp8266_cntPre)
        {
            esp8266_cnt = 0;
            return ESP8266_EOK;
        }
        
        esp8266_cntPre = esp8266_cnt;
        return ESP8266_ERROR;
    }
    
    void esp8266_rx_clear(void)
    {
        memset(esp8266_rx_buf, 0, sizeof(esp8266_rx_buf));
        esp8266_cnt = 0;
    }
    
    void esp8266_receive_data(void)
    {
        if(esp8266_wait_receive() == ESP8266_EOK)
        {
            printf("esp8266 recv: %s\r\n", esp8266_rx_buf);
            esp8266_rx_clear();
        }
    }
    
    //void esp8266_send_data(char *fmt, ...)
    //{
    //    va_list ap;
    //    uint16_t len;
    //    
    //    va_start(ap, fmt);
    //    vsprintf((char *)esp8266_tx_buf, fmt, ap);
    //    va_end(ap);
    //    
    //    len = strlen((const char *)esp8266_tx_buf);
    //    HAL_UART_Transmit(&esp8266_handle, esp8266_tx_buf, len, 100);
    //}
    
    void esp8266_send_data(char *data, uint16_t len)
    {
        esp8266_rx_clear();
        HAL_UART_Transmit(&esp8266_handle, (unsigned char *)data, len, 100);
    }
    
    uint16_t esp8266_copy_rxdata(char *data)
    {
        memcpy(data, esp8266_rx_buf, esp8266_cntPre);
        return esp8266_cntPre;
    }
    
    uint8_t esp8266_send_command(char *cmd, char *res)
    {
        uint8_t time_out = 250;
        esp8266_rx_clear();
        HAL_UART_Transmit(&esp8266_handle, (uint8_t *)cmd, strlen(cmd), 100);
        
        while(time_out--)
        {
            if(esp8266_wait_receive() == ESP8266_EOK)
            {
                if(strstr((const char*)esp8266_rx_buf, res) != NULL)
                    return ESP8266_EOK;
            }
            delay_ms(10);
        }
        
        return ESP8266_ERROR;
    }
    
    uint8_t esp8266_at_test(void)
    {
        return esp8266_send_command("AT\r\n", "OK");
    }
    
    uint8_t esp8266_set_mode(uint8_t mode)
    {
        switch(mode)
        {
            case ESP8266_STA_MODE:
                return esp8266_send_command("AT+CWMODE=1\r\n", "OK");
            
            case ESP8266_AP_MODE:
                return esp8266_send_command("AT+CWMODE=2\r\n", "OK");
            
            case ESP8266_STA_AP_MODE:
                return esp8266_send_command("AT+CWMODE=3\r\n", "OK");
            
            default:
                return ESP8266_EINVAL;
        }
    }
    
    uint8_t esp8266_join_ap(char *ssid, char *pwd)
    {
        char cmd[64];
        sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd);
        return esp8266_send_command(cmd, "WIFI GOT IP");
    }
    
    uint8_t esp8266_connection_mode(uint8_t mode)
    {
        char cmd[64];
        sprintf(cmd, "AT+CIPMUX=%d\r\n", mode);
        return esp8266_send_command(cmd, "OK");
    }
    
    uint8_t esp8266_connect_tcp_server(char *server_ip, char *server_port)
    {
        char cmd[64];
        sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%s\r\n", server_ip, server_port);
        return esp8266_send_command(cmd, "CONNECT");
    }
    
    uint8_t esp8266_enter_unvarnished(void)
    {
        uint8_t ret;
        ret = esp8266_send_command("AT+CIPMODE=1\r\n", "OK");
        ret += esp8266_send_command("AT+CIPSEND\r\n", ">");
        if (ret == ESP8266_EOK)
            return ESP8266_EOK;
        else
            return ESP8266_ERROR;
    }
    
    
    void esp8266_init(uint32_t baudrate)
    {
        printf("esp8266初始化开始...\r\n");
        esp8266_uart_init(baudrate);
        
        //esp8266的其它初始化
        printf("1. 测试esp8266是否存在...\r\n");
        while(esp8266_at_test())
            delay_ms(500);
        
        printf("2. 设置工作模式为STA...\r\n");
        while(esp8266_set_mode(ESP8266_STA_MODE))
            delay_ms(500);
        
        printf("3. 设置单路链接模式...\r\n");
        while(esp8266_connection_mode(ESP8266_SINGLE_CONNECTION))
            delay_ms(500);
        
        printf("4. 连接wifi,SSID: %s, PWD: %s\r\n", WIFI_SSID, WIFI_PWD);
        while(esp8266_join_ap(WIFI_SSID, WIFI_PWD))
            delay_ms(1500);
        
        printf("5. 连接TCP服务器,server_ip:%s, server_port:%s\r\n", TCP_SERVER_IP, TCP_SERVER_PORT);
        while(esp8266_connect_tcp_server(TCP_SERVER_IP, TCP_SERVER_PORT))
            delay_ms(500);
        
        printf("6. 进入到透传模式...\r\n");
        while(esp8266_enter_unvarnished())
            delay_ms(500);
        
        printf("ESP8266已连接上TCP服务器并进入透传模式\r\n");
        printf("ESP8266初始化完成!\r\n");
    }
    
    
    
    //void esp8266_test(void)
    //{
    //    esp8266_send_data("this is from esp8266\r\n");
    //    esp8266_receive_data();
    //}
    
    
    

    3. MQTT协议封装

    mqtt.c
    #include "onenet.h"
    #include "esp8266.h"
    
    char MQTT_ClientID[100]; //MQTT_客户端ID
    char MQTT_UserName[100]; //MQTT_用户名
    char MQTT_PassWord[200]; //MQTT_密码
    
    uint8_t *mqtt_rxbuf;
    uint8_t *mqtt_txbuf;
    uint16_t mqtt_rxlen;
    uint16_t mqtt_txlen;
    uint8_t _mqtt_txbuf[512];//发送数据缓存区
    uint8_t _mqtt_rxbuf[512];//接收数据缓存区
    
    typedef enum
    {
        //名字         值             报文流动方向     描述
        M_RESERVED1    =0    ,    //    禁止    保留
        M_CONNECT        ,    //    客户端到服务端    客户端请求连接服务端
        M_CONNACK        ,    //    服务端到客户端    连接报文确认
        M_PUBLISH        ,    //    两个方向都允许    发布消息
        M_PUBACK        ,    //    两个方向都允许    QoS 1消息发布收到确认
        M_PUBREC        ,    //    两个方向都允许    发布收到(保证交付第一步)
        M_PUBREL        ,    //    两个方向都允许    发布释放(保证交付第二步)
        M_PUBCOMP        ,    //    两个方向都允许    QoS 2消息发布完成(保证交互第三步)
        M_SUBSCRIBE        ,    //    客户端到服务端    客户端订阅请求
        M_SUBACK        ,    //    服务端到客户端    订阅请求报文确认
        M_UNSUBSCRIBE    ,    //    客户端到服务端    客户端取消订阅请求
        M_UNSUBACK        ,    //    服务端到客户端    取消订阅报文确认
        M_PINGREQ        ,    //    客户端到服务端    心跳请求
        M_PINGRESP        ,    //    服务端到客户端    心跳响应
        M_DISCONNECT    ,    //    客户端到服务端    客户端断开连接
        M_RESERVED2        ,    //    禁止    保留
    }_typdef_mqtt_message;
    
    //连接成功服务器回应 20 02 00 00
    //客户端主动断开连接 e0 00
    const uint8_t parket_connetAck[] = {0x20,0x02,0x00,0x00};
    const uint8_t parket_disconnet[] = {0xe0,0x00};
    const uint8_t parket_heart[] = {0xc0,0x00};
    const uint8_t parket_heart_reply[] = {0xc0,0x00};
    const uint8_t parket_subAck[] = {0x90,0x03};
    
    /*
    函数功能: 初始化阿里云物联网服务器的登录参数
    */
    
    
    //密码
    //加密之前的数据格式:  clientId*deviceName*productKey#
    // *替换为DeviceName  #替换为ProductKey  加密密钥是DeviceSecret  加密方式是HmacSHA1  
    //PassWord明文=  clientIdiot_devicedeviceNameiot_deviceproductKeya1VMIfYeEEE
    //hmacsha1加密网站:http://encode.chahuo.com/
    //加密的密钥:DeviceSecret
    
    void mqtt_login_init(char *ProductKey,char *DeviceName,char *DeviceSecret)
    {
    //    sprintf(MQTT_ClientID,"%s.%s|securemode=2,signmethod=hmacsha256,timestamp=1695871022945|",ProductKey,DeviceName);
    //    sprintf(MQTT_UserName,"%s&%s",DeviceName,ProductKey);
    //    sprintf(MQTT_PassWord,"%s","a8921500839307ec3fedbbcd8c0cbc19f133f68c831dcad41fe13d92dc90b89d");
        sprintf(MQTT_ClientID,"%s", DeviceName);
        sprintf(MQTT_UserName,"%s", ProductKey);
        sprintf(MQTT_PassWord,"version=2018-10-31&res=products%%2F%s%%2Fdevices%%2F%s&et=2017881776&method=sha1&sign=%s",ProductKey,DeviceName,DEVICE_SECRET);
    }
    
    void mqtt_init(void)
    {
        mqtt_login_init(PRODUCT_KEY,DEVICE_NAME,DEVICE_SECRET);
        //缓冲区赋值
        mqtt_rxbuf = _mqtt_rxbuf;
        mqtt_rxlen = sizeof(_mqtt_rxbuf);
        mqtt_txbuf = _mqtt_txbuf;
        mqtt_txlen = sizeof(_mqtt_txbuf);
        memset(mqtt_rxbuf,0,mqtt_rxlen);
        memset(mqtt_txbuf,0,mqtt_txlen);
        
        //无条件先主动断开
        mqtt_disconnect();
        delay_ms(100);
        mqtt_disconnect();
        delay_ms(100);
    }
    
    /*
    函数功能: 登录服务器
    函数返回值: 0表示成功 1表示失败
    */
    uint8_t mqtt_connect(char *ClientID,char *Username,char *Password)
    {
    //    uint8_t i;
        uint8_t j;
        int ClientIDLen = strlen(ClientID);
        int UsernameLen = strlen(Username);
        int PasswordLen = strlen(Password);
        int DataLen;
        mqtt_txlen=0;
        //可变报头+Payload  每个字段包含两个字节的长度标识
        DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);
        
        //固定报头
        //控制报文类型
        mqtt_txbuf[mqtt_txlen++] = 0x10;        //MQTT Message Type CONNECT
        //剩余长度(不包括固定头部)
        do
        {
            uint8_t encodedByte = DataLen % 128;
            DataLen = DataLen / 128;
            // if there are more data to encode, set the top bit of this byte
            if ( DataLen > 0 )
                encodedByte = encodedByte | 128;
            mqtt_txbuf[mqtt_txlen++] = encodedByte;
        }while ( DataLen > 0 );
            
        //可变报头
        //协议名
        mqtt_txbuf[mqtt_txlen++] = 0;            // Protocol Name Length MSB    
        mqtt_txbuf[mqtt_txlen++] = 4;           // Protocol Name Length LSB    
        mqtt_txbuf[mqtt_txlen++] = 'M';            // ASCII Code for M    
        mqtt_txbuf[mqtt_txlen++] = 'Q';            // ASCII Code for Q    
        mqtt_txbuf[mqtt_txlen++] = 'T';            // ASCII Code for T    
        mqtt_txbuf[mqtt_txlen++] = 'T';            // ASCII Code for T    
        //协议级别
        mqtt_txbuf[mqtt_txlen++] = 4;                // MQTT Protocol version = 4    
        //连接标志
        mqtt_txbuf[mqtt_txlen++] = 0xc2;            // conn flags 
        mqtt_txbuf[mqtt_txlen++] = 0;                // Keep-alive Time Length MSB    
        mqtt_txbuf[mqtt_txlen++] = 100;            // Keep-alive Time Length LSB  100S心跳包  
        
        mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB    
        mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB      
        memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen);
        mqtt_txlen += ClientIDLen;
        
        if(UsernameLen > 0)
        {   
            mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen);        //username length MSB    
            mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen);        //username length LSB    
            memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen);
            mqtt_txlen += UsernameLen;
        }
        
        if(PasswordLen > 0)
        {    
            mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen);        //password length MSB    
            mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen);        //password length LSB  
            memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen);
            mqtt_txlen += PasswordLen; 
        }    
        
    //    for(i=0;i<10;i++)
    //    {
            memset(mqtt_rxbuf,0,mqtt_rxlen);
            mqtt_send_data(mqtt_txbuf,mqtt_txlen);
    //        for(j=0;j<10;j++)
    //            printf("%c",mqtt_txbuf[j]);
            for(j=0;j<10;j++)
            {
                delay_ms(50);
                if (esp8266_wait_receive() == ESP8266_EOK)
                    esp8266_copy_rxdata((char *)mqtt_rxbuf);
    
                //CONNECT
                if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1] && mqtt_rxbuf[2]==parket_connetAck[2]) //连接成功
                {
                    return 0;//连接成功
                }
            }
    //    }
        return 1;
    }
    
    /*
    函数功能: MQTT订阅/取消订阅数据打包函数
    函数参数:
        topic       主题   
        qos         消息等级 0:最多分发一次  1: 至少分发一次  2: 仅分发一次
        whether     订阅/取消订阅请求包 (1表示订阅,0表示取消订阅)
    返回值: 0表示成功 1表示失败
    */
    uint8_t mqtt_subscribe_topic(char *topic,uint8_t qos,uint8_t whether)
    {    
    //    uint8_t i;
        uint8_t j;
        mqtt_txlen=0;
        int topiclen = strlen(topic);
        
        int DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
        //固定报头
        //控制报文类型
        if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //消息类型和标志订阅
        else    mqtt_txbuf[mqtt_txlen++] = 0xA2;    //取消订阅
    
        //剩余长度
        do
        {
            uint8_t encodedByte = DataLen % 128;
            DataLen = DataLen / 128;
            // if there are more data to encode, set the top bit of this byte
            if ( DataLen > 0 )
                encodedByte = encodedByte | 128;
            mqtt_txbuf[mqtt_txlen++] = encodedByte;
        }while ( DataLen > 0 );    
        
        //可变报头
        mqtt_txbuf[mqtt_txlen++] = 0;            //消息标识符 MSB
        mqtt_txbuf[mqtt_txlen++] = 0x01;        //消息标识符 LSB
        //有效载荷
        mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主题长度 MSB
        mqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主题长度 LSB   
        memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen);
        mqtt_txlen += topiclen;
        
        if(whether)
        {
           mqtt_txbuf[mqtt_txlen++] = qos;//QoS级别
        }
        
    //    for(i=0;i<10;i++)
    //    {
            memset(mqtt_rxbuf,0,mqtt_rxlen);
            mqtt_send_data(mqtt_txbuf,mqtt_txlen);
    
            for(j=0;j<10;j++)
            {
                delay_ms(50);
                if (esp8266_wait_receive() == ESP8266_EOK)
                    esp8266_copy_rxdata((char *)mqtt_rxbuf);
    
                if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //订阅成功               
                {
                    return 0;//订阅成功
                }
            }
    //    }
        return 1; //失败
    }
    
    //MQTT发布数据打包函数
    //topic   主题 
    //message 消息
    //qos     消息等级 
    uint8_t mqtt_publish_data(char *topic, char *message, uint8_t qos)
    {  
        int topicLength = strlen(topic);    
        int messageLength = strlen(message);     
        static uint16_t id=0;
        int DataLen;
        mqtt_txlen=0;
        //有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度
        //QOS为0时没有标识符
        //数据长度             主题名   报文标识符   有效载荷
        if(qos)    DataLen = (2+topicLength) + 2 + messageLength;       
        else    DataLen = (2+topicLength) + messageLength;   
    
        //固定报头
        //控制报文类型
        mqtt_txbuf[mqtt_txlen++] = 0x30;    // MQTT Message Type PUBLISH  
    
        //剩余长度
        do
        {
            uint8_t encodedByte = DataLen % 128;
            DataLen = DataLen / 128;
            // if there are more data to encode, set the top bit of this byte
            if ( DataLen > 0 )
                encodedByte = encodedByte | 128;
            mqtt_txbuf[mqtt_txlen++] = encodedByte;
        }while ( DataLen > 0 );    
        
        mqtt_txbuf[mqtt_txlen++] = BYTE1(topicLength);//主题长度MSB
        mqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主题长度LSB 
        memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷贝主题
        mqtt_txlen += topicLength;
            
        //报文标识符
        if(qos)
        {
            mqtt_txbuf[mqtt_txlen++] = BYTE1(id);
            mqtt_txbuf[mqtt_txlen++] = BYTE0(id);
            id++;
        }
        memcpy(&mqtt_txbuf[mqtt_txlen],message,messageLength);
        mqtt_txlen += messageLength;
    
    //    int i = 0;
    //    for(i=0;i<mqtt_txlen;i++)
    //        printf("%02X ", mqtt_txbuf[i]);
    //    printf("\r\n");
        mqtt_send_data(mqtt_txbuf,mqtt_txlen);
        return mqtt_txlen;
    }
    
    uint8_t mqtt_receive_handle(uint8_t *data_received, Mqtt_RxData_Type *rx_data)
    {
        uint8_t *p;
        uint8_t encodeByte = 0;
        uint32_t multiplier = 1, Remaining_len = 0;
        uint8_t QS_level = 0;
        
        p = data_received;
        memset(rx_data, 0, sizeof(Mqtt_RxData_Type));
        
        //解析接收数据
        if((*p != 0x30)&&(*p != 0x32)&&(*p != 0x34))   //不是发布报文头
            return 1;
        
        if(*p != 0x30) QS_level = 1;    //标记qs等级不为0
        
        p++;
        //提取剩余数据长度
        do{
            encodeByte = *p++;
            Remaining_len += (encodeByte & 0x7F) * multiplier;
            multiplier *= 128;
            
            if(multiplier > 128*128*128) //超出剩余长度最大4个字节的要求,错误
                return 2;
        }while((encodeByte & 0x80) != 0);
        
        //提取主题数据长度
        rx_data->topic_len = *p++;
        rx_data->topic_len = rx_data->topic_len * 256 + *p++;
        //提取主题
        memcpy(rx_data->topic,p,rx_data->topic_len);
        p += rx_data->topic_len;
        
        if(QS_level != 0)  //跳过报文标识符
            p += 2;
        
        //提取payload
        rx_data->payload_len = Remaining_len - rx_data->topic_len - 2;
        memcpy(rx_data->payload, p, rx_data->payload_len);
        
    //    printf("topic: %s\r\n", rx_data->topic);
    //    printf("topic_len: %d\r\n", rx_data->topic_len);
    //    printf("payload: %s\r\n", rx_data->payload);
    //    printf("payload_len: %d\r\n", rx_data->payload_len);
    
        return 0;
    }
    
    void mqtt_send_response(uint8_t *id)
    {
        char buf[128] = {0};
        sprintf(buf,"{\"id\":\"%s\",\"code\":200,\"msg\":\"success\"}",id);
        
        mqtt_publish_data(RELY_PUBLISH_TOPIC,(char *)buf,0);
        
        printf("\r\n发布数据:\r\n");
        printf((const char *)buf);    //发布的数据打印出来
        printf("\r\n");
    }
    
    void mqtt_send_heart(void)
    {
        mqtt_send_data((uint8_t *)parket_heart,sizeof(parket_heart));
    }
    
    void mqtt_disconnect(void)
    {
        mqtt_send_data((uint8_t *)parket_disconnet,sizeof(parket_disconnet));
    }
    
    void mqtt_send_data(uint8_t *buf,uint16_t len)
    {
        esp8266_send_data((char *)buf, len);
    }
    
    
    mqtt.h
    #ifndef _ONENET_H_
    #define _ONENET_H_
    
    #include "string.h"
    #include "stdio.h"
    #include "stdlib.h"
    #include "stdarg.h"
    #include "delay.h"
    
    #define BYTE0(dwTemp)       (*( char *)(&dwTemp))
    #define BYTE1(dwTemp)       (*((char *)(&dwTemp) + 1))
    #define BYTE2(dwTemp)       (*((char *)(&dwTemp) + 2))
    #define BYTE3(dwTemp)       (*((char *)(&dwTemp) + 3))
        
    extern char MQTT_ClientID[100]; //MQTT_客户端ID
    extern char MQTT_UserName[100]; //MQTT_用户名
    extern char MQTT_PassWord[200]; //MQTT_密码
    
    typedef struct
    {
        uint8_t topic[512];
        uint16_t topic_len;
        uint8_t payload[512];
        uint16_t payload_len;
    } Mqtt_RxData_Type;
    
    //云服务器的设备证书
    #define PRODUCT_KEY "pC0uTV161W"
    #define DEVICE_NAME "dht11_01"
    #define DEVICE_SECRET "75AKO7FD5KBEuSJ6BTDLPFC227w%3D"
    
    //订阅与发布的主题
    #define RELY_PUBLISH_TOPIC  "$sys/pC0uTV161W/dht11_01/thing/property/set_reply"  //属性设置应答订阅主题,onenet studio定义好的
    #define SET_TOPIC  "$sys/pC0uTV161W/dht11_01/thing/property/set"
    #define POST_TOPIC "$sys/pC0uTV161W/dht11_01/thing/property/post"
    //事件上报主题
    #define EVENT_PUBLISH_TOPIC   "$sys/pC0uTV161W/dht11_01/thing/event/post"  //发布主题,onenet studio定义好的
    
    //阿里云用户名初始化
    void mqtt_login_init(char *ProductKey,char *DeviceName,char *DeviceSecret);
    //MQTT协议相关函数声明
    uint8_t mqtt_publish_data(char *topic, char *message, uint8_t qos);
    uint8_t mqtt_subscribe_topic(char *topic,uint8_t qos,uint8_t whether);
    void mqtt_init(void);
    uint8_t mqtt_connect(char *ClientID,char *Username,char *Password);
    void mqtt_send_heart(void);
    void mqtt_disconnect(void);
    void mqtt_send_data(uint8_t *buf,uint16_t len);
    void mqtt_send_response(uint8_t *id);
    uint8_t mqtt_receive_handle(uint8_t *data_received, Mqtt_RxData_Type *rx_data);
    #endif
    
    

    五、系统主程序

    #include "sys.h"
    #include "delay.h"
    #include "led.h"
    #include "uart1.h"
    #include "dht11.h"
    #include "esp8266.h"
    #include "onenet.h"
    
    int main(void)
    {
        HAL_Init();                         /* 初始化HAL库 */
        stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
        led_init();                         /* 初始化LED灯 */
        uart1_init(115200);
        esp8266_init(115200);
        printf("hello world!\r\n");
        
        printf("MQTT初始化...\r\n");
        mqtt_init();
        
        printf("MQTT连接...\r\n");
        mqtt_connect(MQTT_ClientID, MQTT_UserName, MQTT_PassWord);
    
        uint8_t data_send[512] = {0};
        uint8_t dht11_data[4] = {0};
        while(1)
        { 
            memset(dht11_data, 0, 4);
            dht11_read(dht11_data);
            sprintf((char *)data_send, "{\"id\":\"1386772172\",\"version\":\"1.0\",\"params\":{\"CurrentTemperature\":{\"value\":%d.%d},\"CurrentHumidity\":{\"value\":%d.%d}}}",
                dht11_data[2], dht11_data[3], dht11_data[0], dht11_data[1]);
            
            mqtt_publish_data(POST_TOPIC, (char *)data_send, 0);
            
            delay_ms(3000);
            
            printf("\r\n~~~~~~~~~~~~~~~~~发送心跳包~~~~~~~~~~~~~~~~~\r\n");
            mqtt_send_heart();
            printf("\r\n~~~~~~~~~~~~~~~~~发送心跳包结束~~~~~~~~~~~~~~~~~\r\n");
        }
    }
    

    六、OneNet平台配置

    1. 登录OneNet控制台
    2. 创建新产品 → 选择MQTT协议
    3. 添加设备并记录以下信息:
  • Product ID
  • Device ID
  • API Key
    1. 创建数据流模板:
  • temperature(单位:℃)
  • humidity(单位:%RH)
  • 七、常见问题排查

    1‌. ESP8266无法连接WiFi‌

  • 检查供电是否稳定(建议单独3.3V供电)
  • 确认AT指令响应格式
  • 使用AT+CWLAP扫描可用网络
  • 2‌. 数据上传失败‌

  • 检查MQTT连接参数是否正确
  • 验证JSON格式有效性
  • 查看OneNet设备日志
  • 3‌.DHT11读取超时‌

  • 检查上拉电阻(4.7KΩ)
  • 调整时序延时精度
  • 更换传感器测试
  • 八、项目优化方向

    1. 增加 ‌断线重连机制‌
    2. 实现 ‌本地数据缓存‌
    3. 添加 ‌低功耗模式‌
    4. 支持 ‌OTA固件升级‌
      完整源码获取:[https://gitee.com/bad-lemon/mcu-development-record.git]

    文章包含详细的代码实现和平台对接说明,实际开发时需根据硬件连接情况调整引脚定义。建议配合示波器调试时序问题,使用串口调试助手验证AT指令交互流程。

    作者:坏柠

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32F103与ESP8266实现OneNet物联网数据上传实战指南

    发表回复