STM32+ESP8266+MQTT 协议连接阿里云物联网平台
- 2022 年 7 月 23 日
- 本文字数:7576 字 - 阅读完需:约 25 分钟 
一、环境介绍
单片机采用:STM32F103C8T6
上网方式:采用 ESP8266,也可以使用其他设备代替,只要支持 TCP 协议即可。比如:GSM 模块、有线网卡等。
开发软件:keil5
硬件连接功能:ESP8266 接在 STM32 的串口 3 上。通过 AT 指令与 ESP8266 进行通信。
二、实现功能
通过阿里云物联网服务器实现设备数据远程上传、下发,实现数据交互。
在当前使用的开发板上有 4 盏 LED 灯、一个蜂鸣器、4 个按键。
实现步骤阿里云官方提供了很详细的文档和对应的 SDK,可以参考一下。
文档地址:https://iot.console.aliyun.com/lk/document
 
  
  
  
  
  
 三、阿里云物联网服务器创建步骤
 
  
 说明:如果没有账号的话,先点击网页右上角,注册一个账号,并完成实名认证再继续下一步。
 
  
 产品名称根据自己情况填写。
 
  
  
 设备信息根据自己情况填写。
 
  
  
  
  
  
  
  
 下面参数根据自己情况填写。
 
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
 四、向服务器上传的数据效果
完成网页端服务器的创建之后,下面使用 STM32 开发板按下按键通过 ESP8266 将烟雾传感器数据上传到阿里云服务器。
如果连接成功的话,网页会显示在线状态。
 
 上传的数据可以在这里查看。
 
  
 五、STM32 端的 MQTT 协议核心代码
代码是标准的 MQTT 协议代码,实现过程可以参考 MQTT 协议官方文档。
 
 5.1 mqtt.c 代码
#include "aliyun_mqtt.h"
char MQTT_ClientID[100]; //MQTT_客户端IDchar MQTT_UserName[100]; //MQTT_用户名char MQTT_PassWord[100]; //MQTT_密码
u8 *mqtt_rxbuf;u8 *mqtt_txbuf;u16 mqtt_rxlen;u16 mqtt_txlen;u8 _mqtt_txbuf[256];//发送数据缓存区u8 _mqtt_rxbuf[256];//接收数据缓存区
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 00const u8 parket_connetAck[] = {0x20,0x02,0x00,0x00};const u8 parket_disconnet[] = {0xe0,0x00};const u8 parket_heart[] = {0xc0,0x00};const u8 parket_heart_reply[] = {0xc0,0x00};const u8 parket_subAck[] = {0x90,0x03};
/*函数功能: 初始化阿里云物联网服务器的登录参数*/
//密码//clientId*deviceName*productKey#// *替换为DeviceName  #替换为ProductKey  加密密钥是DeviceSecret  加密方式是HmacSHA1  //PassWord明文=clientIdmq2_iotdeviceNamemq2_iotproductKeya1WLC5GuOfx//hmacsha1加密网站:http://encode.chahuo.com///加密的密钥:DeviceSecret
void Aliyun_LoginInit(char *ProductKey,char *DeviceName,char *DeviceSecret){    sprintf(MQTT_ClientID,"%s|securemode=3,signmethod=hmacsha1|",DeviceName);    sprintf(MQTT_UserName,"%s&%s",DeviceName,ProductKey);    sprintf(MQTT_PassWord,"%s","ebc042f42a9d73ba9ead8456b652e7756895b79d");}
void MQTT_Init(void){    //缓冲区赋值  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表示失败*/u8 MQTT_Connect(char *ClientID,char *Username,char *Password){    u8 i,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  {    u8 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_SendBuf(mqtt_txbuf,mqtt_txlen);        for(j=0;j<10;j++)        {            delay_ms(50);            if(USART3_RX_FLAG)      {        USART3_RX_BUFFER[USART3_RX_CNT]='\0';        sprintf((char *)mqtt_rxbuf,"%s",USART3_RX_BUFFER);        USART3_RX_FLAG=0;        USART3_RX_CNT=0;      }      //CONNECT      if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //连接成功               {        return 0;//连接成功      }        }    }  return 1;}
/*函数功能: MQTT订阅/取消订阅数据打包函数函数参数:    topic       主题       qos         消息等级 0:最多分发一次  1: 至少分发一次  2: 仅分发一次    whether     订阅/取消订阅请求包 (1表示订阅,0表示取消订阅)返回值: 0表示成功 1表示失败*/u8 MQTT_SubscribeTopic(char *topic,u8 qos,u8 whether){        u8 i,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  {    u8 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_SendBuf(mqtt_txbuf,mqtt_txlen);        for(j=0;j<10;j++)        {            delay_ms(50);            if(USART3_RX_FLAG)      {        USART3_RX_BUFFER[USART3_RX_CNT]='\0';                strcpy((char *)mqtt_rxbuf,(char*)USART3_RX_BUFFER);        USART3_RX_FLAG=0;        USART3_RX_CNT=0;      }            if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //订阅成功               {        return 0;//订阅成功      }        }    }  return 1; //失败}
//MQTT发布数据打包函数//topic   主题 //message 消息//qos     消息等级 u8 MQTT_PublishData(char *topic, char *message, u8 qos){      int topicLength = strlen(topic);        int messageLength = strlen(message);         static u16 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  {    u8 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;          MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);    return mqtt_txlen;}
void MQTT_SentHeart(void){  MQTT_SendBuf((u8 *)parket_heart,sizeof(parket_heart));}
void MQTT_Disconnect(void){  MQTT_SendBuf((u8 *)parket_disconnet,sizeof(parket_disconnet));}
void MQTT_SendBuf(u8 *buf,u16 len){  USARTx_DataSend(USART3,buf,len);}  
5.2 mqtt.h 代码
#ifndef __FY_MQTT_H_#define __FY_MQTT_H_
#include "stm32f10x.h"#include "string.h"#include "stdio.h"#include "stdlib.h"#include "stdarg.h"#include "delay.h"#include "usart.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_客户端IDextern char MQTT_UserName[100]; //MQTT_用户名extern char MQTT_PassWord[100]; //MQTT_密码
//阿里云用户名初始化void Aliyun_LoginInit(char *ProductKey,char *DeviceName,char *DeviceSecret);//MQTT协议相关函数声明u8 MQTT_PublishData(char *topic, char *message, u8 qos);u8 MQTT_SubscribeTopic(char *topic,u8 qos,u8 whether);void MQTT_Init(void);u8 MQTT_Connect(char *ClientID,char *Username,char *Password);void MQTT_SentHeart(void);void MQTT_Disconnect(void);void MQTT_SendBuf(u8 *buf,u16 len);#endif
5.3 main.c 代码
#include "stm32f10x.h"#include "led.h"#include "delay.h"#include "key.h"#include "usart.h"#include <string.h>#include "timer.h"#include "bluetooth.h"#include "esp8266.h"#include "aliyun_mqtt.h"
//阿里云物联网服务器的设备证书#define ProductKey "a1GLM2BFQK0"#define DeviceName "iot_mq2"#define DeviceSecret "caa5b2684101072f3dfe0f04688e0a7f"
//订阅与发布的主题#define SET_TOPIC  "/sys/a1GLM2BFQK0/iot_mq2/thing/service/property/set"#define POST_TOPIC "/sys/a1GLM2BFQK0/iot_mq2/thing/event/property/post"
char mqtt_message[200];//上报数据缓存区
int main(){   u32 time_cnt=0;   u32 i;   u8 key;   LED_Init();   BEEP_Init();   KEY_Init();   USART1_Init(115200);   TIMER1_Init(72,20000); //超时时间20ms   USART2_Init(9600);//串口-蓝牙   TIMER2_Init(72,20000); //超时时间20ms   USART3_Init(115200);//串口-WIFI   TIMER3_Init(72,20000); //超时时间20ms   USART1_Printf("正在初始化WIFI请稍等.\n");   if(ESP8266_Init())   {      USART1_Printf("ESP8266硬件检测错误.\n");     }   else   {      USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode("ChinaNet-wbyq","12345678","a1WLC5GuOfx.iot-as-mqtt.cn-shanghai.aliyuncs.com",1883,1));   }       //1. 初始化阿里云登录参数    Aliyun_LoginInit(ProductKey,DeviceName,DeviceSecret);    //2. MQTT协议初始化      MQTT_Init();     //3. 连接阿里云服务器            while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord))    {        USART1_Printf("阿里云服务器连接失败,正在重试...\n");        delay_ms(500);    }    USART1_Printf("阿里云服务器连接成功.\n");        //3. 订阅主题    if(MQTT_SubscribeTopic(SET_TOPIC,0,1))    {        USART1_Printf("主题订阅失败.\n");    }    else    {        USART1_Printf("主题订阅成功.\n");    }                while(1)    {            key=KEY_Scan(0);        if(key==2)        {            time_cnt=0;            sprintf(mqtt_message,"{\"method\":\"thing.event.property.post\",\"id\":\"0000000001\",\"params\":{\"mq2\":55},\"version\":\"1.0.0\"}");            MQTT_PublishData(POST_TOPIC,mqtt_message,0);            USART1_Printf("发送状态1\r\n");        }        else if(key==3)        {            time_cnt=0;            sprintf(mqtt_message,"{\"method\":\"thing.event.property.post\",\"id\":\"0000000001\",\"params\":{\"mq2\":66},\"version\":\"1.0.0\"}");            MQTT_PublishData(POST_TOPIC,mqtt_message,0);            USART1_Printf("发送状态0\r\n");        }  
        if(USART3_RX_FLAG)        {            USART3_RX_BUFFER[USART3_RX_CNT]='\0';            for(i=0;i<USART3_RX_CNT;i++)            {                USART1_Printf("%c",USART3_RX_BUFFER[i]);            }            USART3_RX_CNT=0;            USART3_RX_FLAG=0;        }
        //定时发送心跳包,保持连接        delay_ms(10);        time_cnt++;        if(time_cnt==500)        {            MQTT_SentHeart();//发送心跳包            time_cnt=0;        }    }}
六、代码参数解释
6.1 设备证书与发布订阅主题
 
 设备证书在创建设备时保存过,如果没有保存可以在下面的页面里查看对应的值。
 
 订阅的主题在下面页面可以看到。
SET,GET,POST,ERR。 SET 用于设置(一般由单片机端使用), GET 用于获取(一般由 APP 端使用), Post 用于回复机制, ERR 用于错误。作为单片机端用的最多的两个 TOPIC 就是 SET 与 POST
 
 6.2 MQTT 登录的密码、ID、用户名、端口号、域名
MQTT 标准的 3 个参数格式在官方文档有介绍:https://help.aliyun.com/document_detail/140507.html?spm=a2c4g.11186623.6.571.1e417544OGPj2y
 
  
  
 密码的组成格式:
clientId*deviceName*productKey#其中: *替换为DeviceName  #替换为ProductKey  加密密钥是DeviceSecret  加密方式是HmacSHA1  
PassWord明文=clientIdmq2_iotdeviceNamemq2_iotproductKeya1WLC5GuOfxhmacsha1加密网站:http://encode.chahuo.com/加密的密钥:DeviceSecret
 
 6.3 上传数据
 
 这是上传数据的格式:
第一个参数是 method:后面所跟的参数可以由物模型看到。
第二个参数 id : 因为云端会连接很多个用户,所以他所下发的数据会有一个 ID 编号,我们这里任意值都行,我用的 0000000001,这里有要注意的,这个 ID 是多少不重要但是位数一定不能少。
第三个是 params:表示上传的具体数据,根据自己云端订阅的类型上传。
第四个是版本号:可以根据自己实际版本填。
版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/f6d5f9965cd1a801d9f0ed44e】。文章转载请联系作者。

DS小龙哥
之所以觉得累,是因为说的比做的多。 2022.01.06 加入
熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域










 
    
评论