目录
- ESP8266简介
- ESP_01引脚图
- ESP_01引脚介绍
- ESP_01 AT指令简介
- MQTT简介
- MQTT协议部分报文格式
- 请求连接报文
- 确认链接报文
- 订阅消息报文
- 订阅确认报文
- 发表消息报文
- 发布确认
- 模块构成
- MQTT协议实现
- ESP—01实现 MQTT协议的步骤
- 初始化ESP—01
- 连接到WiFi热点
- 连接到MQTT服务器
- 登录MQTT服务器
- 订阅一个主题
- 发送一条消息
- 接收订阅的主题更新的消息
- 结束语
ESP8266简介
ESP_01引脚图
ESP_01引脚介绍
引脚 | 功能 | 工作时接线 |
3V3 | 3.3V供电,不要使用5V | 3.3V |
RX | 串口接收端 | MCU:TX |
RST | 外部复位引脚 | NC |
IO0 | I/O口0,状态:悬空:Flash下载模式或工作模式;下拉:串口下载模式 | NC |
EN | 使能端口:高电平工作,低电平不工作 | 3.3V |
IO2 | I/O口2,开机上电是禁止下拉,默认高电平 | NC |
TX | 串口发送端 | MCU:RX |
GND | 电源地线 | GND |
ESP_01 AT指令简介
功能 | AT指令 |
工作模式设置 | AT+CWMODE=2 # 1-Station 模式,2-AP 模式,3-AP & Station 模式 |
重启 | AT+RST |
查询周围热点 | AT+CWLAP |
连接到热点 | AT+CWJAP=“HOTPOINT”,“1234” |
查询已连接的热点 | AT+CWJAP? |
查询自身IP | AT+CIFSR |
连接到TCP服务器 | AT+CIPSTART=“TCP”,“192.168.1.100”,8080 |
发送数据 | AT+CIPSEND=4 \n数据 |
开启透传模式 | AT+CIPMODE=1 |
开始透传 | AT+CIPSEND |
退出透传 | +++ |
MQTT简介
MQTT协议部分报文格式
请求连接报文
请求连接报文格式
报文头 |
遗嘱主题 |
遗嘱消息 |
用户名 |
密码 |
连接请求报文头格式
Bits | Data | 说明 |
byte1 | 0x16 | 固定报头 |
byte2 | 剩余长度 | |
byte1~byte5 | 协议类型 | |
byte7 | 0x04 | 协议级别 |
byte8 | 连接标志 | |
byte9~byte10 | 保持链接 |
协议类型格式
Bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
MSB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
LSB | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
M | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
Q | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
T | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
T | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
连接标志格式
byte | User Name Flag | Password Flag | Will Retain | Will Qos | Will Qos | Will Flag | Claen Session | Reserved |
保持链接标志:两个字节的数据,代表的是连接心跳的时间。
链接标志格式
保持链接 Keep Alive MSB |
保持链接 Keep Alive LSB |
确认链接报文
确认链接报文格式
Bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 说明 |
byte1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 固定报头 |
byte2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 剩余长度 |
byte3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Session Present | 连接确认标志 |
byte4 | x | x | x | x | x | x | x | x | 连接返回码 |
订阅消息报文
订阅消息报文格式
Bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 说明 |
byte1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 固定报头 |
byte2 | 剩余长度(最多4字节) | ||||||||
byte3-byte4 | 报文标识符 | ||||||||
bytex | 有效载荷 |
报文标识符格式
描述 | |
报文标识符 | |
byte1 | MSB(0)0x00 |
byte2 | LSB(0) 0x01 |
有效载荷格式
描述 | |
主题过滤器 | |
byte1 | 长度MSB |
byte2 | 长度LSB |
byte3~N | 主题过滤器 Topic Filter |
服务质量要求 | |
byteN+1 | 0 0 0 0 0 0 X X |
订阅确认报文
订阅确认报文格式
Bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 说明 |
byte1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 固定报头 |
byte2 | 剩余长度 | ||||||||
byte3~byte4 | 报文标识符 | ||||||||
byteN | 有效载荷 |
返回码格式
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
返回码 | x | 0 | 0 | 0 | 0 | 0 | x | x |
返回码取值说明:
- 0x00:最大QoS 0
- 0x01:成功—最大QoS 1
- 0x02:成功—最大QoS 2
- ox80:Failure失败
发表消息报文
发表消息报文格式
结构 |
固定报头 |
剩余最大长度 |
可变报头 |
发布消息的内容(有效载荷=剩余长度-可变报头长度) |
固定报头格式
Bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte1 | 0 | 0 | 1 | 1 | DUP | QoS-H | QoS- | Retain |
可变报头
Bits | 说明 |
byte1 | 主题名 |
byte2 | 报文标识符(只有Qos等级为1/2时才有) |
主题名格式
描述 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
主题名 | |||||||||
byte1 | length MSB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte2 | length LSB | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
byte3 | a(0x61) | ||||||||
byte4 | /(0x2F) | ||||||||
byte5 | b(0x62) | ||||||||
报文标识符 | |||||||||
byte6 | MSB(0) | ||||||||
byte7 | LSB(0) |
发布确认
Bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 说明 |
byte1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 固定报头 |
byte2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 剩余长度 |
byte3~byte4 | 报 | 文 | 标 | 示 | 符 | 可变报头 |
模块构成
ESP01与STM32的USART1相连,组成WiFi模块。STM32的USART2与ZigBee网络的协调器相连,实现网络之间数据传输。
MQTT协议实现
ESP—01实现 MQTT协议的步骤
- 初始化ESP—01
- 连接到WiFi热点
- 连接到MQTT服务器
- 登录MQTT
- 订阅一个主题
- 发布一条消息
- 接收订阅的主题更新的消息
初始化ESP—01
- 退出透传模式
- 检查ESP—01是否存在
- 关闭uart回显
uint8_t ESP8266_Init(void)
{
ESP8266_ExitUnvarnishedTrans(); //退出透传
HAL_Delay(500);
ESP8266_ATSendString("AT+RST\r\n");
HAL_Delay(800);
if(ESP8266_Check()==0) //使用AT指令检查ESP8266是否存在
{
return 0;
}
ESP8266_ATSendString("ATE0\r\n"); //关闭回显
if(FindStr((char*)usart1_rxbuf,"OK",500)==0) //设置不成功
{
return 0;
}
return 1; //设置成功
}
连接到WiFi热点
使用AT指令连接到既定的热点
连接到MQTT服务器
调用连接到TCP服务器的指令连接到MQTT服务器,端口号为1883
登录MQTT服务器
请求登录报文格式为:协议头+主机名(长度+数据)+用户名(长度+数据)+密码(长度+数据)。
登录确认:得到登录确认报文后就证明登陆成功。
报文格式有点出入,但是除去个别变化外其余结构如上节所示。
uint8_t MQTT_Connect(char *ClientID,char *Username,char *Password)
{
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);
//固定报头
//控制报文类型
usart1_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;
usart1_txbuf[MQTT_TxLen++] = encodedByte;
}while ( DataLen > 0 );
//可变报头
//协议名
usart1_txbuf[MQTT_TxLen++] = 0; // Protocol Name Length MSB
usart1_txbuf[MQTT_TxLen++] = 4; // Protocol Name Length LSB
usart1_txbuf[MQTT_TxLen++] = 'M'; // ASCII Code for M
usart1_txbuf[MQTT_TxLen++] = 'Q'; // ASCII Code for Q
usart1_txbuf[MQTT_TxLen++] = 'T'; // ASCII Code for T
usart1_txbuf[MQTT_TxLen++] = 'T'; // ASCII Code for T
//协议级别
usart1_txbuf[MQTT_TxLen++] = 4; // MQTT Protocol version = 4
//连接标志
usart1_txbuf[MQTT_TxLen++] = 0xc2; // conn flags
usart1_txbuf[MQTT_TxLen++] = 0; // Keep-alive Time Length MSB
usart1_txbuf[MQTT_TxLen++] = 60; // Keep-alive Time Length LSB 60S心跳包
usart1_txbuf[MQTT_TxLen++] = BYTE1(ClientIDLen);// Client ID length MSB
usart1_txbuf[MQTT_TxLen++] = BYTE0(ClientIDLen);// Client ID length LSB
memcpy(&usart1_txbuf[MQTT_TxLen],ClientID,ClientIDLen);
MQTT_TxLen += ClientIDLen;
if(UsernameLen > 0)
{
usart1_txbuf[MQTT_TxLen++] = BYTE1(UsernameLen); //username length MSB
usart1_txbuf[MQTT_TxLen++] = BYTE0(UsernameLen); //username length LSB
memcpy(&usart1_txbuf[MQTT_TxLen],Username,UsernameLen);
MQTT_TxLen += UsernameLen;
}
if(PasswordLen > 0)
{
usart1_txbuf[MQTT_TxLen++] = BYTE1(PasswordLen); //password length MSB
usart1_txbuf[MQTT_TxLen++] = BYTE0(PasswordLen); //password length LSB
memcpy(&usart1_txbuf[MQTT_TxLen],Password,PasswordLen);
MQTT_TxLen += PasswordLen;
}
uint8_t cnt=2;
uint8_t wait;
while(cnt--)
{
memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));
MQTT_SendBuf(usart1_txbuf,MQTT_TxLen);
wait=30;//等待3s时间
while(wait--)
{
//CONNECT
if(usart1_rxbuf[0]==parket_connetAck[0] && usart1_rxbuf[1]==parket_connetAck[1]) //连接成功
{
return 1;//连接成功
}
HAL_Delay(100);
}
}
return 0;
}
订阅一个主题
报文格式同上节所示,有限等待后得到确认报文后就证明登录成功。
uint8_t MQTT_SubscribeTopic(char *topic,uint8_t qos,uint8_t whether)
{
MQTT_TxLen=0;
int topiclen = strlen(topic);
int DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
//固定报头
//控制报文类型
if(whether) usart1_txbuf[MQTT_TxLen++] = 0x82; //消息类型和标志订阅
else usart1_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;
usart1_txbuf[MQTT_TxLen++] = encodedByte;
}while ( DataLen > 0 );
//可变报头
usart1_txbuf[MQTT_TxLen++] = 0; //消息标识符 MSB
usart1_txbuf[MQTT_TxLen++] = 0x01; //消息标识符 LSB
//有效载荷
usart1_txbuf[MQTT_TxLen++] = BYTE1(topiclen);//主题长度 MSB
usart1_txbuf[MQTT_TxLen++] = BYTE0(topiclen);//主题长度 LSB
memcpy(&usart1_txbuf[MQTT_TxLen],topic,topiclen);
MQTT_TxLen += topiclen;
if(whether)
{
usart1_txbuf[MQTT_TxLen++] = qos;//QoS级别
}
uint8_t cnt=2;
uint8_t wait;
while(cnt--)
{
memset(usart1_rxbuf,0,sizeof(usart1_rxbuf));
MQTT_SendBuf(usart1_txbuf,MQTT_TxLen);
wait=30;//等待3s时间
while(wait--)
{
if(usart1_rxbuf[0]==parket_subAck[0] && usart1_rxbuf[1]==parket_subAck[1]) //订阅成功
{
return 1;//订阅成功
}
HAL_Delay(100);
}
}
if(cnt) return 1; //订阅成功
return 0;
}
发送一条消息
报文格式同上节所示。
uint8_t MQTT_PublishData(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;
//固定报头
//控制报文类型
usart1_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;
usart1_txbuf[MQTT_TxLen++] = encodedByte;
}while ( DataLen > 0 );
usart1_txbuf[MQTT_TxLen++] = BYTE1(topicLength);//主题长度MSB
usart1_txbuf[MQTT_TxLen++] = BYTE0(topicLength);//主题长度LSB
memcpy(&usart1_txbuf[MQTT_TxLen],topic,topicLength);//拷贝主题
MQTT_TxLen += topicLength;
//报文标识符
if(qos)
{
usart1_txbuf[MQTT_TxLen++] = BYTE1(id);
usart1_txbuf[MQTT_TxLen++] = BYTE0(id);
id++;
}
memcpy(&usart1_txbuf[MQTT_TxLen],message,messageLength);
MQTT_TxLen += messageLength;
MQTT_SendBuf(usart1_txbuf,MQTT_TxLen);
return MQTT_TxLen;
}
接收订阅的主题更新的消息
ESP—01收到数据后会通过串口发送到STM32,根据协议对数据进行拆包,将需要的数据进行转发。
结束语
以上介绍了ESP-01模块,使用到的ESP8266 的部分AT指令,MQTT协议的报文格式以及实际使用方法。关于MQTT协议的具体内容,我也是刚开始学习,如果有同学对于这里有问题或者有兴趣的话欢迎讨论学习。最后欢迎大神批评指导。