STM32教程第十七讲:实现数据传输与云平台连接

目录

  • 需求
  • 一、云平台项目创建
  • 二、代码编写
  • 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连接的是电脑上,所以相当于将数据打印。

    作者:小白橘颂

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32教程第十七讲:实现数据传输与云平台连接

    发表回复