点击上方"蓝字"关注我们
01、MQTT
>>>知 识 点:
1.esp8266 wifi的AT指令的使用
2.mqtt的连接、发布、订阅等
3.添加了cJSON
*说 明:
1.通过阿里云物联网平台能够获取开发板的D1、D2、D3灯和温湿度值
2.通过阿里云物联网平台能够控制开发板的D1、D2、D3灯的亮灭
3.D4灯用于表示当前连接服务器的状态。亮-已连接;灭-已断开
4.按键1用于重连阿里云物联网平台
5.按键2用于断开阿里云物联网平台
增加了cJSON对阿里云物联网平台的数据解析
ESP8266引脚.jpg
MQTT记录.txt
{
"ProductKey": "gqeaE1iKozV",
"DeviceName": "TESTDEVICE01",
"DeviceSecret": "df5a9a5c4a0b6c22e3acdf5e091a7ea7"
}
Broker Address:gqeaE1iKozV.iot-as-mqtt.cn-shanghai.aliyuncs.com
Broker Port :1883
Client ID :00001|securemode=3,signmethod=hmacsha1|
User Name:TESTDEVICE01&gqeaE1iKozV
password:C8F156AB9166B4C1000419F693A2095E3E23E3A2
属性上报:/sys/gqeaE1iKozV/${deviceName}/thing/event/property/post
属性设置:/sys/gqeaE1iKozV/${deviceName}/thing/service/property/set
//根据自己的设备名,填入属性信息即可
属性上报:/sys/gqeaE1iKozV/TESTDEVICE01/thing/event/property/post
属性设置:/sys/gqeaE1iKozV/TESTDEVICE01/thing/service/property/set
{
"method":"thing.service.property.set",
"id":"00001",
"params":{
"CurrentTemperature":20.0,
"CurrentHumidity":60.0,
"switch_led_r":1,
"switch_led_g":1,
"switch_led_b":0,
},
"version":"1.0.0"
}
阿里网络地址.txt
https://iot.console.aliyun.com/product
02、led.h
#ifndef __LED_H__
#define __LED_H__
#define CONNECT_MQTT_LED(x) PEout(14)=(x)?0:1
extern void led_init(void);
#endif
03、led.c
#include "stm32f4xx.h"
#include "sys.h"
void led_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//打开端口E的硬件时钟,就是对端口E供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
//打开端口F的硬件时钟,就是对端口F供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
//配置GPIOF的第9 10根
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//初始化
GPIO_Init(GPIOF,&GPIO_InitStructure);
//配置GPIOE的第13 14根
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//初始化
GPIO_Init(GPIOE,&GPIO_InitStructure);
PFout(9)=PFout(10)=1;
PEout(13)=PEout(14)=1;
}
04、key.h
#ifndef __KEY_H__
#define __KEY_H__
extern void key_init(void);
extern uint32_t key_sta_get(void);
#endif
05、key.c
#include "stm32f4xx.h"
#include "sys.h"
void key_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//打开端口A的硬件时钟,就是对端口A供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//打开端口E的硬件时钟,就是对端口E供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
//配置GPIOA的第0根引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//初始化
GPIO_Init(GPIOA,&GPIO_InitStructure);
//配置GPIOE的第2 3 4根引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//初始化
GPIO_Init(GPIOE,&GPIO_InitStructure);
}
uint32_t key_sta_get(void)
{
uint32_t key_sta=0;
if(PAin(0) == 0)
key_sta|=1<<0;
if(PEin(2) == 0)
key_sta|=1<<1;
if(PEin(3) == 0)
key_sta|=1<<2;
if(PEin(4) == 0)
key_sta|=1<<3;
return key_sta;
}
06、esp8266.h
#ifndef __ESP8266_H__
#define __ESP8266_H__
#include "stm32f4xx.h"
#define EN_DEBUG_ESP8266 0
//添加WIFI热点宏定义,此处根据自己的wifi作调整
//可以使用手机的热点
#define WIFI_SSID "Phonewifi"
#define WIFI_PASSWORD "12345678"
//#define WIFI_SSID "AASD"
//#define WIFI_PASSWORD "12345678"
extern uint8_t g_esp8266_tx_buf[512];
extern volatile uint8_t g_esp8266_rx_buf[512];
extern volatile uint32_t g_esp8266_rx_cnt;
extern volatile uint32_t g_esp8266_rx_end;
extern volatile uint32_t g_esp8266_transparent_transmission_sta;
extern void esp8266_init(void);
extern int32_t esp8266_self_test(void);
extern int32_t esp8266_exit_transparent_transmission (void);
extern int32_t esp8266_entry_transparent_transmission(void);
extern int32_t esp8266_connect_ap(char* ssid,char* pswd);
extern int32_t esp8266_connect_server(char* mode,char* ip,uint16_t port);
extern int32_t esp8266_disconnect_server(void);
extern void esp8266_send_bytes(uint8_t *buf,uint32_t len);
extern void esp8266_send_str(char *buf);
extern void esp8266_send_at(char *str);
extern int32_t esp8266_enable_multiple_id(uint32_t b);
extern int32_t esp8266_create_server(uint16_t port);
extern int32_t esp8266_close_server(uint16_t port);
extern int32_t esp8266_enable_echo(uint32_t b);
extern int32_t esp8266_reset(void);
#endif
07、esp8266.c
#include "stm32f4xx.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
uint8_t g_esp8266_tx_buf[512];
volatile uint8_t g_esp8266_rx_buf[512];
volatile uint32_t g_esp8266_rx_cnt=0;
volatile uint32_t g_esp8266_rx_end=0;
volatile uint32_t g_esp8266_transparent_transmission_sta=0;
void esp8266_init(void)
{
usart3_init(115200);
}
void esp8266_send_at(char *str)
{
//清空接收缓冲区
memset((void *)g_esp8266_rx_buf,0, sizeof g_esp8266_rx_buf);
//清空接收计数值
g_esp8266_rx_cnt = 0;
//串口3发送数据
usart3_send_str(str);
}
void esp8266_send_bytes(uint8_t *buf,uint32_t len)
{
usart3_send_bytes(buf,len);
}
void esp8266_send_str(char *buf)
{
usart3_send_str(buf);
}
/* 查找接收数据包中的字符串 */
int32_t esp8266_find_str_in_rx_packet(char *str,uint32_t timeout)
{
char *dest = str;
char *src = (char *)&g_esp8266_rx_buf;
//等待串口接收完毕或超时退出
while((strstr(src,dest)==NULL) && timeout)
{
delay_ms(1);
timeout--;
}
if(timeout)
return 0;
return -1;
}
/* 自检程序 */
int32_t esp8266_self_test(void)
{
esp8266_send_at("AT\r\n");
return esp8266_find_str_in_rx_packet("OK",1000);
}
/**
* 功能:连接热点
* 参数:
* ssid:热点名
* pwd:热点密码
* 返回值:
* 连接结果,非0连接成功,0连接失败
* 说明:
* 失败的原因有以下几种(UART通信和ESP8266正常情况下)
* 1. WIFI名和密码不正确
* 2. 路由器连接设备太多,未能给ESP8266分配IP
*/
int32_t esp8266_connect_ap(char* ssid,char* pswd)
{
#if 0
//不建议使用以下sprintf,占用过多的栈
char buf[128]={0};
sprintf(buf,"AT+CWJAP_CUR=\"%s\",\"%s\"\r\n",ssid,pswd);
#endif
//设置为STATION模式
esp8266_send_at("AT+CWMODE_CUR=1\r\n");
if(esp8266_find_str_in_rx_packet("OK",1000))
return -1;
//连接目标AP
//sprintf(buf,"AT+CWJAP_CUR=\"%s\",\"%s\"\r\n",ssid,pswd);
esp8266_send_at("AT+CWJAP_CUR=");
esp8266_send_at("\"");esp8266_send_at(ssid);esp8266_send_at("\"");
esp8266_send_at(",");
esp8266_send_at("\"");esp8266_send_at(pswd);esp8266_send_at("\"");
esp8266_send_at("\r\n");
if(esp8266_find_str_in_rx_packet("OK",5000))
if(esp8266_find_str_in_rx_packet("CONNECT",5000))
return -2;
return 0;
}
/* 退出透传模式 */
int32_t esp8266_exit_transparent_transmission (void)
{
esp8266_send_at ("+++");
//退出透传模式,发送下一条AT指令要间隔1秒
delay_s(1);
//记录当前esp8266工作在非透传模式
g_esp8266_transparent_transmission_sta = 0;
return 0;
}
/* 进入透传模式 */
int32_t esp8266_entry_transparent_transmission(void)
{
//进入透传模式
esp8266_send_at("AT+CIPMODE=1\r\n");
if(esp8266_find_str_in_rx_packet("OK",5000))
return -1;
delay_s(2);
//开启发送状态
esp8266_send_at("AT+CIPSEND\r\n");
if(esp8266_find_str_in_rx_packet("OK",5000))
return -2;
//记录当前esp8266工作在透传模式
g_esp8266_transparent_transmission_sta = 1;
return 0;
}
/**
* 功能:使用指定协议(TCP/UDP)连接到服务器
* 参数:
* mode:协议类型 "TCP","UDP"
* ip:目标服务器IP
* port:目标是服务器端口号
* 返回值:
* 连接结果,非0连接成功,0连接失败
* 说明:
* 失败的原因有以下几种(UART通信和ESP8266正常情况下)
* 1. 远程服务器IP和端口号有误
* 2. 未连接AP
* 3. 服务器端禁止添加(一般不会发生)
*/
int32_t esp8266_connect_server(char* mode,char* ip,uint16_t port)
{
#if 0
//使用MQTT传递的ip地址过长,不建议使用以下方法,否则导致栈溢出
//AT+CIPSTART="TCP","a10tC4OAAPc.iot-as-mqtt.cn-shanghai.aliyuncs.com",1883,该字符串占用内存过多了
char buf[128]={0};
//连接服务器
sprintf((char*)buf,"AT+CIPSTART=\"%s\",\"%s\",%d\r\n",mode,ip,port);
esp8266_send_at(buf);
#else
char buf[16]={0};
esp8266_send_at("AT+CIPSTART=");
esp8266_send_at("\""); esp8266_send_at(mode); esp8266_send_at("\"");
esp8266_send_at(",");
esp8266_send_at("\""); esp8266_send_at(ip); esp8266_send_at("\"");
esp8266_send_at(",");
sprintf(buf,"%d",port);
esp8266_send_at(buf);
esp8266_send_at("\r\n");
#endif
if(esp8266_find_str_in_rx_packet("CONNECT",5000))
if(esp8266_find_str_in_rx_packet("OK",5000))
return -1;
return 0;
}
/* 断开服务器 */
int32_t esp8266_disconnect_server(void)
{
esp8266_send_at("AT+CIPCLOSE\r\n");
if(esp8266_find_str_in_rx_packet("CLOSED",5000))
if(esp8266_find_str_in_rx_packet("OK",5000))
return -1;
return 0;
}
/* 使能多链接 */
int32_t esp8266_enable_multiple_id(uint32_t b)
{
char buf[32]={0};
sprintf(buf,"AT+CIPMUX=%d\r\n", b);
esp8266_send_at(buf);
if(esp8266_find_str_in_rx_packet("OK",5000))
return -1;
return 0;
}
/* 创建服务器 */
int32_t esp8266_create_server(uint16_t port)
{
char buf[32]={0};
sprintf(buf,"AT+CIPSERVER=1,%d\r\n", port);
esp8266_send_at(buf);
if(esp8266_find_str_in_rx_packet("OK",5000))
return -1;
return 0;
}
/* 关闭服务器 */
int32_t esp8266_close_server(uint16_t port)
{
char buf[32]={0};
sprintf(buf,"AT+CIPSERVER=0,%d\r\n", port);
esp8266_send_at(buf);
if(esp8266_find_str_in_rx_packet("OK",5000))
return -1;
return 0;
}
/* 回显打开或关闭 */
int32_t esp8266_enable_echo(uint32_t b)
{
if(b)
esp8266_send_at("ATE1\r\n");
else
esp8266_send_at("ATE0\r\n");
if(esp8266_find_str_in_rx_packet("OK",5000))
return -1;
return 0;
}
/* 复位 */
int32_t esp8266_reset(void)
{
esp8266_send_at("AT+RST\r\n");
if(esp8266_find_str_in_rx_packet("OK",10000))
return -1;
return 0;
}
08、esp8266_mqtt.h
#ifndef __ES8266_MQTT_H
#define __ES8266_MQTT_H
#include "stm32f4xx.h"
//此处是阿里云服务器的公共实例登陆配置-------------------------------------注意修改为自己的云服务设备信息!!!!
#define MQTT_BROKERADDRESS "gbfzwEQBsLm.iot-as-mqtt.cn-shanghai.aliyuncs.com"
#define MQTT_CLIENTID "00001|securemode=3,signmethod=hmacsha1|"
#define MQTT_USARNAME "TESTDEVICE01&gbfzwEQBsLm"
#define MQTT_PASSWD "4CBC23E827DF6833CAAAE8CC471C3AACF0B6DBED"
#define MQTT_PUBLISH_TOPIC "/sys/gbfzwEQBsLm/TESTDEVICE01/thing/event/property/post"
#define MQTT_SUBSCRIBE_TOPIC "/sys/gbfzwEQBsLm/TESTDEVICE01/thing/service/property/set"
//此处是阿里云服务器的企业实例登陆配置-------------------------------------注意修改为自己的云服务设备信息!!!!
//#define MQTT_BROKERADDRESS "iot-060a065f.mqtt.iothub.aliyuncs.com"
//#define MQTT_CLIENTID "0001|securemode=3,signmethod=hmacsha1|"
//#define MQTT_USARNAME "smartdevice&g850YXdgU5r"
//#define MQTT_PASSWD "A8F93BD31F6085B1AB2AE3CC311E38971B15885D"
//#define MQTT_PUBLISH_TOPIC "/sys/g850YXdgU5r/smartdevice/thing/event/property/post"
//#define MQTT_SUBSCRIBE_TOPIC "/sys/g850YXdgU5r/smartdevice/thing/service/property/set"
#define BYTE0(dwTemp) (*( char *)(&dwTemp))
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
//MQTT连接服务器
extern int32_t mqtt_connect(char *client_id,char *user_name,char *password);
//MQTT消息订阅
extern int32_t mqtt_subscribe_topic(char *topic,uint8_t qos,uint8_t whether);
//MQTT消息发布
extern uint32_t mqtt_publish_data(char *topic, char *message, uint8_t qos);
//MQTT发送心跳包
extern int32_t mqtt_send_heart(void);
extern int32_t esp8266_mqtt_init(void);
extern void mqtt_disconnect(void);
extern void mqtt_report_devices_status(void);
#endif
09、esp8266_mqtt.c
#include "stm32f4xx.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "dht11.h"
#include "esp8266.h"
#include "esp8266_mqtt.h"
const uint8_t g_packet_heart_reply[2] = {0xc0,0x00};
char g_mqtt_msg[526];
uint32_t g_mqtt_tx_len;
//MQTT发送数据
void mqtt_send_bytes(uint8_t *buf,uint32_t len)
{
esp8266_send_bytes(buf,len);
}
//发送心跳包
int32_t mqtt_send_heart(void)
{
uint8_t buf[2]={0xC0,0x00};
uint32_t cnt=2;
uint32_t wait=0;
#if 0
mqtt_send_bytes(buf,2);
return 0;
#else
while(cnt--)
{
mqtt_send_bytes(buf,2);
memset((void *)g_esp8266_rx_buf,0,sizeof(g_esp8266_rx_buf));
g_esp8266_rx_cnt=0;
wait=3000;//等待3s时间
while(wait--)
{
delay_ms(1);
//检查心跳响应固定报头
if((g_esp8266_rx_buf[0]==0xD0) && (g_esp8266_rx_buf[1]==0x00))
{
printf("心跳响应确认成功,服务器在线\r\n");
return 0;
}
}
}
printf("心跳响应确认失败,服务器离线\r\n");
return -1;
#endif
}
//MQTT无条件断开
void mqtt_disconnect(void)
{
uint8_t buf[2]={0xe0,0x00};
mqtt_send_bytes(buf,2);
esp8266_disconnect_server();
}
//MQTT连接服务器的打包函数
int32_t mqtt_connect(char *client_id,char *user_name,char *password)
{
uint32_t client_id_len = strlen(client_id);
uint32_t user_name_len = strlen(user_name);
uint32_t password_len = strlen(password);
uint32_t data_len;
uint32_t cnt=2;
uint32_t wait=0;
g_mqtt_tx_len=0;
//可变报头+Payload 每个字段包含两个字节的长度标识
data_len = 10 + (client_id_len+2) + (user_name_len+2) + (password_len+2);
//固定报头
//控制报文类型
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0x10; //MQTT Message Type CONNECT
//剩余长度(不包括固定头部)
do
{
uint8_t encodedByte = data_len % 128;
data_len = data_len / 128;
// if there are more data to encode, set the top bit of this byte
if ( data_len > 0 )
encodedByte = encodedByte | 128;
g_esp8266_tx_buf[g_mqtt_tx_len++] = encodedByte;
} while ( data_len > 0 );
//可变报头
//协议名
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0; // Protocol Name Length MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = 4; // Protocol Name Length LSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = 'M'; // ASCII Code for M
g_esp8266_tx_buf[g_mqtt_tx_len++] = 'Q'; // ASCII Code for Q
g_esp8266_tx_buf[g_mqtt_tx_len++] = 'T'; // ASCII Code for T
g_esp8266_tx_buf[g_mqtt_tx_len++] = 'T'; // ASCII Code for T
//协议级别
g_esp8266_tx_buf[g_mqtt_tx_len++] = 4; // MQTT Protocol version = 4
//连接标志
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0xc2; // conn flags
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0; // Keep-alive Time Length MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = 60; // Keep-alive Time Length LSB 60S心跳包
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE1(client_id_len);// Client ID length MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE0(client_id_len);// Client ID length LSB
memcpy(&g_esp8266_tx_buf[g_mqtt_tx_len],client_id,client_id_len);
g_mqtt_tx_len += client_id_len;
if(user_name_len > 0)
{
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE1(user_name_len); //user_name length MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE0(user_name_len); //user_name length LSB
memcpy(&g_esp8266_tx_buf[g_mqtt_tx_len],user_name,user_name_len);
g_mqtt_tx_len += user_name_len;
}
if(password_len > 0)
{
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE1(password_len); //password length MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE0(password_len); //password length LSB
memcpy(&g_esp8266_tx_buf[g_mqtt_tx_len],password,password_len);
g_mqtt_tx_len += password_len;
}
while(cnt--)
{
memset((void *)g_esp8266_rx_buf,0,sizeof(g_esp8266_rx_buf));
g_esp8266_rx_cnt=0;
mqtt_send_bytes(g_esp8266_tx_buf,g_mqtt_tx_len);
wait=3000;//等待3s时间
while(wait--)
{
delay_ms(1);
//检查连接确认固定报头
if((g_esp8266_rx_buf[0]==0x20) && (g_esp8266_rx_buf[1]==0x02))
{
if(g_esp8266_rx_buf[3] == 0x00)
{
printf("连接已被服务器端接受,连接确认成功\r\n");
return 0;//连接成功
}
else
{
switch(g_esp8266_rx_buf[3])
{
case 1:printf("连接已拒绝,不支持的协议版本\r\n");
break;
case 2:printf("连接已拒绝,不合格的客户端标识符\r\n");
break;
case 3:printf("连接已拒绝,服务端不可用\r\n");
break;
case 4:printf("连接已拒绝,无效的用户或密码\r\n");
break;
case 5:printf("连接已拒绝,未授权\r\n");
break;
default:printf("未知响应\r\n");
break;
}
return 0;
}
}
}
}
return -1;
}
//MQTT订阅/取消订阅数据打包函数
//topic 主题
//qos 消息等级
//whether 订阅/取消订阅请求包
int32_t mqtt_subscribe_topic(char *topic,uint8_t qos,uint8_t whether)
{
uint32_t cnt=2;
uint32_t wait=0;
uint32_t topiclen = strlen(topic);
uint32_t data_len = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
g_mqtt_tx_len=0;
//固定报头
//控制报文类型
if(whether)
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0x82; //消息类型和标志订阅
else
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0xA2; //取消订阅
//剩余长度
do
{
uint8_t encodedByte = data_len % 128;
data_len = data_len / 128;
// if there are more data to encode, set the top bit of this byte
if ( data_len > 0 )
encodedByte = encodedByte | 128;
g_esp8266_tx_buf[g_mqtt_tx_len++] = encodedByte;
} while ( data_len > 0 );
//可变报头
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0; //消息标识符 MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0x01; //消息标识符 LSB
//有效载荷
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE1(topiclen);//主题长度 MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE0(topiclen);//主题长度 LSB
memcpy(&g_esp8266_tx_buf[g_mqtt_tx_len],topic,topiclen);
g_mqtt_tx_len += topiclen;
if(whether)
{
g_esp8266_tx_buf[g_mqtt_tx_len++] = qos;//QoS级别
}
while(cnt--)
{
g_esp8266_rx_cnt=0;
memset((void *)g_esp8266_rx_buf,0,sizeof(g_esp8266_rx_buf));
mqtt_send_bytes(g_esp8266_tx_buf,g_mqtt_tx_len);
wait=3000;//等待3s时间
while(wait--)
{
delay_ms(1);
//检查订阅确认报头
if(g_esp8266_rx_buf[0]==0x90)
{
printf("订阅主题确认成功\r\n");
//获取剩余长度
if(g_esp8266_rx_buf[1]==3)
{
printf("Success - Maximum QoS 0 is %02X\r\n",g_esp8266_rx_buf[2]);
printf("Success - Maximum QoS 2 is %02X\r\n",g_esp8266_rx_buf[3]);
printf("Failure is %02X\r\n",g_esp8266_rx_buf[4]);
}
//获取剩余长度
if(g_esp8266_rx_buf[1]==2)
{
printf("Success - Maximum QoS 0 is %02X\r\n",g_esp8266_rx_buf[2]);
printf("Success - Maximum QoS 2 is %02X\r\n",g_esp8266_rx_buf[3]);
}
//获取剩余长度
if(g_esp8266_rx_buf[1]==1)
{
printf("Success - Maximum QoS 0 is %02X\r\n",g_esp8266_rx_buf[2]);
}
return 0;//订阅成功
}
}
}
if(cnt)
return 0; //订阅成功
return -1;
}
//MQTT发布数据打包函数
//topic 主题
//message 消息
//qos 消息等级
uint32_t mqtt_publish_data(char *topic, char *message, uint8_t qos)
{
static
uint16_t id=0;
uint32_t topicLength = strlen(topic);
uint32_t messageLength = strlen(message);
uint32_t data_len;
uint8_t encodedByte;
g_mqtt_tx_len=0;
//有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度
//QOS为0时没有标识符
//数据长度 主题名 报文标识符 有效载荷
if(qos) data_len = (2+topicLength) + 2 + messageLength;
else data_len = (2+topicLength) + messageLength;
//固定报头
//控制报文类型
g_esp8266_tx_buf[g_mqtt_tx_len++] = 0x30; // MQTT Message Type PUBLISH
//剩余长度
do
{
encodedByte = data_len % 128;
data_len = data_len / 128;
// if there are more data to encode, set the top bit of this byte
if ( data_len > 0 )
encodedByte = encodedByte | 128;
g_esp8266_tx_buf[g_mqtt_tx_len++] = encodedByte;
} while ( data_len > 0 );
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE1(topicLength);//主题长度MSB
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE0(topicLength);//主题长度LSB
memcpy(&g_esp8266_tx_buf[g_mqtt_tx_len],topic,topicLength);//拷贝主题
g_mqtt_tx_len += topicLength;
//报文标识符
if(qos)
{
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE1(id);
g_esp8266_tx_buf[g_mqtt_tx_len++] = BYTE0(id);
id++;
}
memcpy(&g_esp8266_tx_buf[g_mqtt_tx_len],message,messageLength);
g_mqtt_tx_len += messageLength;
mqtt_send_bytes(g_esp8266_tx_buf,g_mqtt_tx_len);
//咱们的Qos等级设置的是00,因此阿里云物联网平台是没有返回响应信息的
return g_mqtt_tx_len;
}
//设备状态上报
void mqtt_report_devices_status(void)
{
uint8_t led_1_sta = GPIO_ReadOutputDataBit(GPIOF,GPIO_Pin_9) ? 0:1;
uint8_t led_2_sta = GPIO_ReadOutputDataBit(GPIOF,GPIO_Pin_10) ? 0:1;
uint8_t led_3_sta = GPIO_ReadOutputDataBit(GPIOE,GPIO_Pin_13) ? 0:1;
//把开发板相关的状态变量利用sprintf函数存放到一个数组里,再把该数组利用MQTT协议打包成消息报文
//sprintf(str,"a=%d",a);
//需要更改“temperature”和“CurrentHumidity”为对应的平台设备信息;
sprintf(g_mqtt_msg,
"{\"method\":\"thing.service.property.set\",\"id\":\"0001\",\"params\":{\
\"CurrentTemperature\":%.1f,\
\"CurrentHumidity\":%.1f,\
\"switch_led_r\":%d,\
\"switch_led_g\":%d,\
\"switch_led_b\":%d,\
},\"version\":\"1.0.0\"}",
g_temp,
g_humi,
led_1_sta,
led_2_sta,
led_3_sta);
//上报信息到平台服务器
mqtt_publish_data(MQTT_PUBLISH_TOPIC,g_mqtt_msg,0);
}
int32_t esp8266_mqtt_init(void)
{
int32_t rt;
//esp8266初始化
esp8266_init();
// printf("esp8266_init");
//退出透传模式,才能输入AT指令
rt=esp8266_exit_transparent_transmission();
if(rt)
{
printf("esp8266_exit_transparent_transmission fail\r\n");
return -1;
}
printf("esp8266_exit_transparent_transmission success\r\n");
delay_s(2);
//复位模块
rt=esp8266_reset();
if(rt)
{
printf("esp8266_reset fail\r\n");
return -2;
}
printf("esp8266_reset success\r\n");
delay_s(2);
//关闭回显
rt=esp8266_enable_echo(0);
if(rt)
{
printf("esp8266_enable_echo(0) fail\r\n");
return -3;
}
printf("esp8266_enable_echo(0)success\r\n");
delay_s(2);
//连接热点
rt = esp8266_connect_ap(WIFI_SSID,WIFI_PASSWORD);
if(rt)
{
printf("esp8266_connect_ap fail\r\n");
return -4;
}
printf("esp8266_connect_ap success\r\n");
delay_s(2);
rt =esp8266_connect_server("TCP",MQTT_BROKERADDRESS,1883);
if(rt)
{
printf("esp8266_connect_server fail\r\n");
return -5;
}
printf("esp8266_connect_server success\r\n");
delay_s(2);
//进入透传模式
rt =esp8266_entry_transparent_transmission();
if(rt)
{
printf("esp8266_entry_transparent_transmission fail\r\n");
return -6;
}
printf("esp8266_entry_transparent_transmission success\r\n");
delay_s(2);
if(mqtt_connect(MQTT_CLIENTID, MQTT_USARNAME, MQTT_PASSWD))
{
printf("mqtt_connect fail\r\n");
return -7;
}
printf("mqtt_connect success\r\n");
delay_s(2);
if(mqtt_subscribe_topic(MQTT_SUBSCRIBE_TOPIC,0,1))
{
printf("mqtt_subscribe_topic fail\r\n");
return -8;
}
printf("mqtt_subscribe_topic success\r\n");
return 0;
}
10、usart.h
#ifndef __USART_H__
#define __USART_H__
extern volatile uint8_t g_usart1_rx_buf[512];
extern volatile uint32_t g_usart1_rx_cnt;
extern volatile uint32_t g_usart1_rx_end;
extern void usart1_init(uint32_t baud);
extern void usart3_init(uint32_t baud);
extern void usart3_send_str(char *str);
extern void usart3_send_bytes(uint8_t *buf,uint32_t len);
#endif
11、usart.c
#include "stm32f4xx.h"
#include "sys.h"
#include "usart.h"
#include "esp8266.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static USART_InitTypeDef USART_InitStructure;
static GPIO_InitTypeDef GPIO_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;
volatile uint8_t g_usart1_rx_buf[512];
volatile uint32_t g_usart1_rx_cnt=0;
volatile uint32_t g_usart1_rx_end=0;
#pragma import(__use_no_semihosting_swi)
struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;
int fputc(int ch, FILE *f)
{
USART_SendData(USART1,ch);
//等待数据发送成功
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
USART_ClearFlag(USART1,USART_FLAG_TXE);
return ch;
}
void _sys_exit(int return_code) {
}
void _ttywrch(int ch)
{
ch = ch;
}
void usart1_init(uint32_t baud)
{
//使能端口A硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//使能串口1硬件时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//配置PA9、PA10为复用功能引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//将PA9、PA10连接到USART1的硬件
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
//配置USART1的相关参数:波特率、数据位、校验位
USART_InitStructure.USART_BaudRate = baud;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//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(USART1, &USART_InitStructure);
//使能串口接收到数据触发中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//使能串口1工作
USART_Cmd(USART1,ENABLE);
}
void usart3_init(uint32_t baud)
{
//使能端口B硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//使能串口3硬件时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
//配置PB10、PB11为复用功能引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//将PB10、PB11连接到USART3的硬件
GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3);
//配置USART1的相关参数:波特率、数据位、校验位
USART_InitStructure.USART_BaudRate = baud;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//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(USART3, &USART_InitStructure);
//使能串口接收到数据触发中断
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//使能串口3工作
USART_Cmd(USART3,ENABLE);
}
void usart3_send_str(char *str)
{
char *p = str;
while(*p!='\0')
{
USART_SendData(USART3,*p);
p++;
//等待数据发送成功
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
USART_ClearFlag(USART3,USART_FLAG_TXE);
}
}
void usart3_send_bytes(uint8_t *buf,uint32_t len)
{
uint8_t *p = buf;
while(len--)
{
USART_SendData(USART3,*p);
p++;
//等待数据发送成功
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
USART_ClearFlag(USART3,USART_FLAG_TXE);
}
}
void USART1_IRQHandler(void)
{
uint8_t d=0;
//检测是否接收到数据
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
d=USART_ReceiveData(USART1);
g_usart1_rx_buf[g_usart1_rx_cnt++]=d;
if(g_usart1_rx_cnt >= sizeof g_usart1_rx_buf)
{
g_usart1_rx_end=1;
}
#if EN_DEBUG_ESP8266
//将接收到的数据发给串口3
USART_SendData(USART3,d);
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
#endif
//清空标志位,可以响应新的中断请求
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
void USART3_IRQHandler(void)
{
uint8_t d=0;
//检测是否接收到数据
if (USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
{
d=USART_ReceiveData(USART3);
g_esp8266_rx_buf[g_esp8266_rx_cnt++]=d;
if(g_esp8266_rx_cnt >= sizeof g_esp8266_rx_buf)
{
g_esp8266_rx_end=1;
}
#if EN_DEBUG_ESP8266
//将接收到的数据返发给PC
USART_SendData(USART1,d);
//while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
#endif
//清空标志位,可以响应新的中断请求
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
}
}
12、main.c
/****************************************************************
*名 称:基于stm32f4的mqtt
*作 者:陈堪才
*创建日期:2021/05/22
*知 识 点:
1.esp8266 wifi的AT指令的使用
2.mqtt的连接、发布、订阅等
3.添加了cJSON
*说 明:
1.通过阿里云物联网平台能够获取开发板的D1、D2、D3灯和温湿度值
2.通过阿里云物联网平台能够控制开发板的D1、D2、D3灯的亮灭
3.D4灯用于表示当前连接服务器的状态。亮-已连接;灭-已断开
4.按键1用于重连阿里云物联网平台
5.按键2用于断开阿里云物联网平台
*修改日期:
2021/05/27,增加了cJSON对阿里云物联网平台的数据解析
*****************************************************************/
#include "stm32f4xx.h"
#include "sys.h"
#include "usart.h"
#include "esp8266.h"
#include "esp8266_mqtt.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "dht11.h"
#include "key.h"
#include "tim.h"
#include "cjson.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void mqtt_cjson_parse(char *pbuf)
{
cJSON *json , *json_params, *json_id, *json_led, *json_method;
char *p = pbuf;
//解析数据包
json = cJSON_Parse(p);
if (!json)
{
cJSON_Delete(json);
json=NULL;
return;
}
//根据method键获取值
json_method = cJSON_GetObjectItem(json ,"method");
if(json_method->type == cJSON_String)
{
printf("method:%s\r\n", json_method->valuestring);
}
//根据id键获取值
json_id = cJSON_GetObjectItem(json , "id");
if(json_id->type == cJSON_String)
{
printf("id:%s\r\n", json_id->valuestring);
}
//根据params键获取值
json_params = cJSON_GetObjectItem(json , "params");
if(json_params)
{
//根据switch_led_r键获取值
json_led=cJSON_GetObjectItem(json_params , "switch_led_r");
if(json_led->type == cJSON_Number)
{
PFout(9) = !json_led->valueint;
printf("switch_led_r:%d\r\n", json_led->valueint);
}
//根据switch_led_g键获取值
json_led=cJSON_GetObjectItem(json_params , "switch_led_g");
if(json_led->type == cJSON_Number)
{
PFout(10) = !json_led->valueint;
printf("switch_led_g:%d\r\n", json_led->valueint);
}
//根据switch_led_b键获取值
json_led=cJSON_GetObjectItem(json_params , "switch_led_b");
if(json_led->type == cJSON_Number)
{
PEout(13) = !json_led->valueint;
printf("switch_led_b:%d\r\n", json_led->valueint);
}
}
cJSON_Delete(json);
json=NULL;
}
int main(void)
{
uint32_t i=0;
uint32_t delay_1ms_cnt=0;
uint8_t buf[5]={20,05,56,8,20};
uint32_t key_sta=0;
int32_t rt=0;
Delay_Init();
//led初始化
led_init();
//beep初始化
beep_init();
//温湿度传感器初始化
dht11_init();
//按键检测
key_init();
//定时器初始化
tim3_init();
//串口1初始化波特率为115200bps
usart1_init(115200);
//串口延迟一会,确保芯片内部完成全部初始化,printf无乱码输出
delay_ms(500);
//打印开机信息
printf("This is esp8266 mqtt with aliyun test by teacher.chen\r\n");
while(esp8266_mqtt_init())
{
printf("esp8266_mqtt_init ...");
delay_s(1);
}
//连接服务器状态指示灯,点亮-连接成功
CONNECT_MQTT_LED(1);
printf("esp8266 connect aliyun with mqtt success\r\n");
while(1)
{
//检查接收到数据
if(g_esp8266_rx_end && g_esp8266_transparent_transmission_sta)
{
#if 1
printf("g_esp8266_rx_buf:%s\n",g_esp8266_rx_buf);
for(i=0;i<g_esp8266_rx_cnt;i++)
{
/*
由于mqtt协议发布消息数据包 = 0x30+剩余长度+01+00+Topic主题名+json内容,例如通过阿里云物联网平台发送如下
0x30 0xE2 0x01 0x00 /thing/service/property/set{"method":"thing.service.property.set","id":"408723893","params":{"switch_led_g":1,"version":"1.0.0"}
传给cJSON时必须全为字符串,不能有0x00,否则遇到0x00会导致直接结束cJSON的。因此需要自行查找'{'开头的json内容
*/
if(g_esp8266_rx_buf[i] == '{')
{
mqtt_cjson_parse((char *)&g_esp8266_rx_buf[i]);
break;
}
}
#else
for(i=0;i<g_esp8266_rx_cnt;i++)
{
//判断的关键字符是否为 1"
//核心数据,即{"switch_led_r":1}中的“1”
if((g_esp8266_rx_buf[i]==0x31) && (g_esp8266_rx_buf[i+1]==0x22))
{
//判断控制变量
if( g_esp8266_rx_buf[i+3]=='1' )
PFout(9)=0;//控制灯亮
else
PFout(9)=1;//控制灯灭
}
//判断的关键字符是否为 2"
//核心数据,即{"switch_led_g":1}中的“1”
if((g_esp8266_rx_buf[i]==0x32) && (g_esp8266_rx_buf[i+1]==0x22))
{
//判断控制变量
if( g_esp8266_rx_buf[i+3]=='1' )
PFout(10)=0;//控制灯亮
else
PFout(10)=1;//控制灯灭
}
//判断的关键字符是否为 3"
//核心数据,即{"switch_led_b":1}中的“1”
if(g_esp8266_rx_buf[i]==0x33 && g_esp8266_rx_buf[i+1]==0x22)
{
//判断控制变量
if( g_esp8266_rx_buf[i+3]=='1' )
PEout(13)=0;//控制灯亮
else
PEout(13)=1;//控制灯灭
}
}
#endif
//清空接收缓冲区、接收计数值、接收结束标志位
memset((void *)g_esp8266_rx_buf,0,sizeof g_esp8266_rx_buf);
g_esp8266_rx_cnt=0;
g_esp8266_rx_end=0;
}
delay_1ms_cnt++;
delay_ms(1);
//6秒时间到达
if((delay_1ms_cnt % 6000) ==0)
{
if(0 == dht11_read(buf))
{
g_temp = (float)buf[2]+(float)buf[3]/10;
g_humi = (float)buf[0]+(float)buf[1]/10;
}
//上报设备状态
mqtt_report_devices_status();
}
//60秒时间到达
if((delay_1ms_cnt % 60000) ==0)
{
/* 设备端在保活时间间隔内(保护时间在mqtt_connect设置为60s),至少需要发送一次报文,包括ping请求。
连接保活时间的取值范围为30秒~1200秒。建议取值300秒以上。
从物联网平台发送CONNACK响应CONNECT消息时,开始心跳计时。收到PUBLISH、SUBSCRIBE、PING或 PUBACK消息时,会重置计时器。
*/
//发送心跳包,过于频繁发送心跳包,服务器将会持续一段时间不发送响应信息[可选]
rt = mqtt_send_heart();
if(rt == 0)
CONNECT_MQTT_LED(1);
else
CONNECT_MQTT_LED(0);
}
//按键检测
if(key_sta_get())
{
delay_ms(50);
key_sta=key_sta_get();
if(key_sta & 0x01)
{
printf("connect aliyun mqtt\r\n");
//重连阿里云物联网平台
rt = esp8266_mqtt_init();
if(rt == 0)
CONNECT_MQTT_LED(1);
else
CONNECT_MQTT_LED(0);
}
if(key_sta & 0x02)
{
printf("disconnect aliyun mqtt\r\n");
//断开阿里云物联网平台
mqtt_disconnect();
CONNECT_MQTT_LED(0);
}
}
}
}
总结
>>>
选择MQTT库:可以使用像Eclipse Paho、Mosquitto等开源MQTT库,针对嵌入式开发的有很多,诸如MQTT-C、mqtt_client等。
设置开发环境:根据你的STM32开发板,设置好开发环境,比如STM32CubeIDE或Keil等。
网络连接:确保你的STM32能够通过Wi-Fi、以太网等方式连接到互联网。通常会用到ESP8266、ESP32等Wi-Fi模块或以太网模块(如W5500)。
添加MQTT库:将选定的MQTT库添加到你的STM32项目中。如果是使用HAL库的项目,记得根据库的要求配置时钟、GPIO等。
实现MQTT客户端:
初始化网络和MQTT库。
连接到MQTT代理(Broker)。
订阅主题,发布消息。
处理接收到的消息。
故我在
点击下方卡片 关注我
↓↓↓