目录
- 需求
- 一、云平台项目创建
- 二、代码编写
- 1.导入MQTT包
- 2.连接阿里云
- 3.发布数据
- 三、关键代码
- 总结
需求
1.通过生活物联网平台设计一个空气质量检测仪app。
2.连接阿里云平台将硬件数据传输到云端,使手机端能够实时收到。
一、云平台项目创建
先进入阿里云生活服务平台创建一个新项目
接下来根据步骤一步一步做就行
在定义功能时,记得进行使变量类型和之前代码中定义类型保持一致。
标识符要尽可能简洁明了,后续代码中要一一对应。
最后点击发布完,扫描二维码下载一个app就能看到设备了。(云智能)
二、代码编写
1.导入MQTT包
本次数据通信模式为MQTT协议,需要按照要求拼接指令。
该指令拼接有大佬在github上发布过做好的函数,所以我们先把人家写好的包加入进来,方便我们后续的快速开发。githubMQTT协议包
要使用只需把src里的所有文件加入到工程中即可。
2.连接阿里云
连接阿里云就是先拼接报文,再将拼接好的数组通过wifi模块发送给云端阿里云。
整个包的头文件:#include "MQTTPacket.h"
拼接连接报文函数:MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options)。
参数1:存放连接报文的数组地址。
参数2:存放连接报文数组的大小。
参数3:MQTTPacket_connectData options结构体的数组。
主要就是填一下clientID,password和username。
该部分均可以在阿里云上获取。
返回值:连接报文的长度。
代码如下:
//发送连接报文,链接阿里云
void Ali_SendConnect()
{
uint8_t buf[300]={0};//存放连接报文
uint16_t buflen=0;//报文长度
MQTTPacket_connectData options = MQTTPacket_connectData_initializer;
options.clientID.cstring = clientId;
options.password.cstring = passwd;
options.username.cstring = Username;
//拼接连接报文,返回值是;连接报文的长度
buflen = MQTTSerialize_connect(buf,300,&options);
if(buflen==0)
{
printf("连接报文拼接失败\r\n");
return ;
}
printf("连接报文拼接成功\r\n");
//发送连接
U3_Sendarr(buf,buflen);
}
3.发布数据
发布数据就是将数据传输到云端。
主要就是用到MQTTSerialize_publish()函数
参数1:buf
类型:unsigned char*
描述:指向存储序列化后消息的缓冲区的指针。调用函数时,需要提供足够大小的缓冲区来存储序列化后的消息。
参数2:buflen
类型:int
描述:指定缓冲区的大小,即buf指向的内存块的最大长度。在调用函数之前,需要确保该缓冲区足够大,以防止消息序列化时溢出。
参数3:dup
类型:unsigned char
描述:指示消息是否是一个重复发布的标志。
值:0表示这是第一次发布,1表示这是一个重复发布。
参数4:qos
类型:int
描述:指定发布消息的服务质量等级(QoS级别)。
值:可以是0、1或2,分别代表不同的QoS级别,详情如下:
0:最多一次(At most once)
1:至少一次(At least once)
2:恰好一次(Exactly once)
参数5:retained
类型:unsigned char
描述:指示消息是否应保留在代理(broker)上。
值:0表示不保留,1表示保留。
参数6:packetid
类型:unsigned short
描述:消息标识符(Packet Identifier),用于标识QoS级别为1或2的消息。
值:如果QoS为0,则忽略该字段;如果QoS为1或2,则这个字段用来标识消息。
参数7:topicName
类型:char*
描述:指向发布主题名的C字符串指针。
注意:topicName必须在调用MQTTSerialize_publish函数时已经设置好。
参数8:payload
类型:unsigned char*
描述:指向要发布的消息内容的字节流的指针。
注意:payload必须在调用MQTTSerialize_publish函数时已经设置好。
参数9:payloadlen
类型:int
描述:指定payload的长度,即消息内容的字节数。
注意:payloadlen必须在调用MQTTSerialize_publish函数时已经设置好,并且与实际消息内容长度相匹配。
void MQTT_Send_Publish()
{
char baowen[900]={0};
int baowenlen;
MQTTString topicName;
topicName.cstring =TOPICNAME;
char Load_Massage[500]={0};
sprintf(Load_Massage,"{\
\"method\":\"thing.event.property.post\",\
\"id\":\"1820044024\",\
\"params\":{\
\"TVOC\":%.2f,\
\"HCHO\":%.2f,\
\"co2\":%.2f,\
\"Humidity\":%.2f,\
\"temperature\":%.2f,\
\"Smoke\":%d,\
\"lightp\":1\
},\
\"version\":\"1.0.0\"\
}",voc,ch2o,co2,hum,tem,adcData.mq2);
baowenlen=MQTTSerialize_publish(baowen,900,0,0,0,0,topicName,Load_Massage,strlen(Load_Massage));
U3_Sendarr(baowen,baowenlen);
}
payload格式:
版本和id无所谓,主要就是method和参数。
三、关键代码
main.c
#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "string.h"
#include "pwm.h"
#include "adc.h"
#include "su03t.h"
#include "dht11.h"
#include "kqm.h"
#include "key.h"
#include "RTC.h"
#include "bsp_lcd.h"
#include "wifi.h"
#include "aliot.h"
uint8_t Send_wifidata[102];
char D_wen[20];
char D_shi[20];
char D_time[20];
extern float voc;
extern float ch2o;
extern float co2;
extern float hum;
extern float tem;
extern ADCARR adcData;
extern const unsigned char gImage_hengliu[153600];
uint8_t key3flag,cntt;
uint32_t sec=0;
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
SysTick_Config(72000);
Esp8266_Config();
strcpy((char*)Send_wifidata, "hello world");
Kqm_U4Config();
Su03t_U5Config();
DHT11_Config();
Adc_Config();
RTC_Configuration();
Wifi_ConnectIP();
Ali_SendConnect();
while(1)
{
if(ledcnt[0]>=ledcnt[1]){//过去500ms
ledcnt[0]=0;
Get_Smoke_Light_MidValue();//烟雾光照中位数
DHT11_ReadData();//温湿度
KQM_DealData();//空气质量
Get_Smoke_Light_MidValue();
Delay_ms(5000);
MQTT_Send_Publish();
}
}
return 0;
}
aliot.c
#include "aliot.h"
//发送连接报文,链接阿里云
void Ali_SendConnect()
{
uint8_t buf[300]={0};//存放连接报文
uint16_t buflen=0;//报文长度
MQTTPacket_connectData options = MQTTPacket_connectData_initializer;
options.clientID.cstring = clientId;
options.password.cstring = passwd;
options.username.cstring = Username;
//拼接连接报文,返回值是;连接报文的长度
buflen = MQTTSerialize_connect(buf,300,&options);
if(buflen==0)
{
printf("连接报文拼接失败\r\n");
return ;
}
printf("连接报文拼接成功\r\n");
//发送连接
U3_Sendarr(buf,buflen);
}
void MQTT_Send_Publish()
{
char baowen[900]={0};
int baowenlen;
MQTTString topicName;
topicName.cstring =TOPICNAME;
char Load_Massage[500]={0};
sprintf(Load_Massage,"{\
\"method\":\"thing.event.property.post\",\
\"id\":\"1820044024\",\
\"params\":{\
\"TVOC\":%.2f,\
\"HCHO\":%.2f,\
\"co2\":%.2f,\
\"Humidity\":%.2f,\
\"temperature\":%.2f,\
\"Smoke\":%d,\
\"lightp\":1\
},\
\"version\":\"1.0.0\"\
}",voc,ch2o,co2,hum,tem,adcData.mq2);
baowenlen=MQTTSerialize_publish(baowen,900,0,0,0,0,topicName,Load_Massage,strlen(Load_Massage));
U3_Sendarr(baowen,baowenlen);
}
aliot.h
#ifndef _ALIOT_H_
#define _ALIOT_H_
#include "stm32f10x.h"
#include "stdio.h"
#include "wifi.h"
#include "MQTTPacket.h"
#include "string.h"
#include "su03t.h"
#include "adc.h"
extern float voc;
extern float ch2o;
extern float co2;
extern float hum;
extern float tem;
extern ADCARR adcData;
void MQTT_Send_Publish();
void Ali_SendConnect();
#define clientId "a1QAarSERXA.20240704zzz|securemode=2,signmethod=hmacsha256,timestamp=1720146161015|"
#define Username "20240704zzz&a1QAarSERXA"
#define passwd "8b5d1527f058aeddf2b9606bd13bf68b425fc658fba79d1f8064fddf455ca368"
#define IP "a1QAarSERXA.iot-as-mqtt.cn-shanghai.aliyuncs.com"
#define port "1883"
#define TOPICNAME "/sys/a1QAarSERXA/20240704zzz/thing/event/property/post"
#endif
wifi.c
#include "wifi.h"
WIFIDATA wifidata={0};
//配置串口3 8数据位,0校验位,1停止位,波特率115200
//PB10(TX) PB11(RX)
void Esp8266_Config()
{
//开时钟:GPIOB,USART3
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
//配置对应的IO口 PB10(tx):复用推挽 PB11(RX):浮空输入
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//PE6
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE,&GPIO_InitStruct);
//配置串口3 8数据位,0校验位,1停止位,波特率115200
USART_InitTypeDef USART_InitStruct = {0};//可以通过结构体类型跳转
USART_InitStruct.USART_BaudRate = 115200;//波特率
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件控制流不开
USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;//打开发送和接收
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART3,&USART_InitStruct);
USART_Cmd(USART3,ENABLE);
//配置串口3的中断
USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);//USART1->CR1 |= 0x1<<5;//使能串口1的接收非空中断
NVIC_SetPriority(USART3_IRQn,7);//设置优先级0~15
NVIC_EnableIRQ(USART3_IRQn);//使能中断通道
GPIO_SetBits(GPIOE,GPIO_Pin_6);
Delay_nms(500);
}
void USART3_IRQHandler(void)
{
uint8_t data=0;
if((USART3->SR&0x1<<5)!=0)
{//执行该中断函数的原因有很多,所以判断一下是不是接收导致的
data = USART_ReceiveData(USART3);//读操作,同时也是清空中断标志位
wifidata.recvbuf[wifidata.recvcnt] = data;
wifidata.recvcnt++;
wifidata.recvcnt%=1024;
USART_SendData(USART1, data);
}
}
//串口3发送单字节函数
void Usart3Senddata(uint8_t data)
{
//等待发送完成
while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==0);
//如果上次发送完成,就发送
USART_SendData(USART3,data);
}
//串口3发送数组函数
void U3_Sendarr(uint8_t * data,uint32_t len)
{
uint32_t i=0;
for(i=0;i<len;i++){
Usart3Senddata(*data);
data++;
}
}
void U3_SendStr(uint8_t * data)
{
uint32_t i=0;
while(*data!='\0')
{
Usart3Senddata(*data);
data++;
}
}
uint8_t Wifi_Send_Cmd(char * cmd,char * recv,uint32_t timeout)
{
uint32_t timecnt=0;
memset(&wifidata,0,sizeof(wifidata));
U3_SendStr((uint8_t *)cmd);
while(strstr((char *)wifidata.recvbuf,recv)==NULL){
timecnt++;
Delay_nms(1);
if(timecnt>=timeout){
printf("发送超时失败%s",cmd);
return 1;
}
}
printf(" 发送成功 ");
return 0;
}
uint8_t Wifi_ConnectIP(void)
{
uint8_t buf[100] = {0};
if(Wifi_Send_Cmd("AT\r\n","OK",1000) != 0){//测试
return 1;
}
if(Wifi_Send_Cmd("AT+CWMODE=1\r\n","OK",2000) != 0){//设置为STA
return 1;
}
if(Wifi_Send_Cmd("AT+CWJAP=\"LEGION-5169\",\"88888888\"\r\n","OK",10000)!= 0){//连接热点
return 1;
}
sprintf((char*)buf,"AT+CIPSTART=\"TCP\",\"%s\",%s\r\n",IP,port);
if(Wifi_Send_Cmd((char*)buf,"OK",10000)!= 0){//连接服务器
return 1;
}
if(Wifi_Send_Cmd("AT+CIPMODE=1\r\n","OK",1000)!= 0){//开启透传
return 1;
}
if(Wifi_Send_Cmd("AT+CIPSEND\r\n","OK",1000)!= 0){//启动发送功能
return 1;
}
return 0;
}
wifi.h
#ifndef _WIFI_H_
#define _WIFI_H_
#include "stm32f10x.h"
#include "aliot.h"
#include "delay.h"
#include "stdio.h"
#include "string.h"
typedef struct{
uint8_t recvbuf[1024];
uint16_t recvcnt;
}WIFIDATA;
void Esp8266_Config();
void U3_SendStr(uint8_t * data);
uint8_t Wifi_Send_Cmd(char * cmd,char * recv,uint32_t timeout);
uint8_t Wifi_ConnectIP(void);
void U3_SendStr(uint8_t * data);
void U3_Sendarr(uint8_t * data,uint32_t len);
#endif
总结
1.当代码将报文发送给串口3时,由于串口3连接的是wifi模块,此时就相当于将报文通过wifi模块传送到云端。
2.将数据传输到串口1时,由于串口1连接的是电脑上,所以相当于将数据打印。