Bootstrap

STM32第十七课:连接云平台进行数据传输


需求

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连接的是电脑上,所以相当于将数据打印。

;