一、环境介绍
MCU: 采用意法半导体低功耗芯片 STM32L431RCT6
编译软件: Keil5 + CubeMX
云平台: 采用阿里云物联网云平台
完整项目源代码下载地址(不懂可以私信问): https://download.csdn.net/download/xiaolong1126626497/19272620
二、功能与硬件介绍
2.1 功能介绍
前面的一篇文章是同样的环境,云平台采用的是腾讯物联网云平台(https://xiaolong.blog.csdn.net/article/details/117407900)
这篇文章将云平台换成了阿里云物联网平台,其他硬件功能都是一样的。
再次介绍一下功能:
这是采用STM32L431 + ES8266设计的云端绿化管理系统,可以通过ESP8266 WIFI连接阿里云物联网平台,使用网页和阿里云的APP远程进行绿化管理,比如:实时获取光照强度、温度、湿度、远程控制水泵进行浇水灌溉,在任何地方都可以给自己种的花花草草浇水,了解周边环境情况。
2.2 硬件介绍
开发板采用的是小熊开发板,包括完成绿化管理系统的所有功能都是采用小熊派开发板的配套套件完成。
小熊开发板板载了一个stlink调试器(就是STM32F103C8T6实现的),程序下载非常方便。串口1用来调试打印数据,ESP8266是接在串口LPUART1上的。
小熊派开发板本身自带的例子程序也比较丰富,自带例子里采用的云平台是华为的物联网云平台,工程比较庞大使用了LiteOS操作系统。本文里的工程是重新编写的代码,使用裸机完成项目功能,没有跑操作系统,云平台采用阿里云平台服务器,MQTT协议和ESP8266驱动代码都是重新编写,框架、逻辑比较清晰,代码量也较少,适合初学者入门学习。
相关传感器模块型号: (采用的是小熊开发板配套的E53_IA1扩展板)
WIFI采用:ESP8266
温湿度检测传感器采用:SHT30
光照强度检测传感器采用:BH1750
电机采用:微型直流电机
三、阿里云物联网云平台
关于阿里云物联网平台的创建与使用之前也介绍过一篇,只不过MCU采用的是STM32F103C8T6,这篇文章MCU采用的是STM32L431RCT6,属于低功耗系列,更加适合物联网领域;如果之前没有使用过阿里云物联网云平台,先参考这里学习了解一下:https://xiaolong.blog.csdn.net/article/details/107311897
3.1 在阿里云物联网平台创建产品
官网地址: https://iot.console.aliyun.com/lk/summary/new
创建产品:
配置产品模型参数:页面最后加密方式这些选择默认
添加设备:
设备添加之后,可以一键将设备证书复制下来保存到记事本,方便后面使用;不复制也没关系,后面也可以设备信息中查看的:
{ "ProductKey": "a1ukQj2EnEJ", "DeviceName": "GreeningManagement", "DeviceSecret": "a5268d71d363f1bd68e708c9097fa3d2" }
设备添加完成:
在设备信息的页面也可以查看设备证书:
添加功能属性字段:
根据自己产品交互使用的数据类型进行定义:(绿化管理系统使用了温度、湿度、电机、光照强度一共4个数据字段。其中电机是读写类型,其他都是只读类型)
自定义功能属性添加完毕之后就发布上线:
查看物模型数据格式:后面通过MQTT协议向服务器上报数据就是这个格式
可以选择导出模型文件,导出是一个json格式文件,方便设备端开发参考。
3.2 通过IoT Studio创建web可视化界面
地址: https://iot.console.aliyun.com/lk/related-services
之前旧版本的IoT Studio 选项是在产品页面里,现在移到控制台首页了。
新建项目:
新建web应用:
设计WEB页面之前 先关联产品和设备:
选择对应的产品进行关联:
选择对应的设备进行关联:
关联成功:
可以更改页面名称:
添加组件,设计页面: 阿里云的web页面控件非常丰富,可以根据自己需求设计好看的页面。
接下来就要给每个控件配置数据源:
调整仪表盘的属性:刻度字号
配置完毕:
有域名的可以绑定到域名:
这里可以预览页面:
四、登录阿里云平台测试
4.1 MQTT协议登录的域名与端口号
关于MQTT协议登录所需要的参数官方说明文档: https://help.aliyun.com/document_detail/140507.html?spm=a2c4g.11186623.6.571.1e417544OGPj2y
MQTT登录域名的格式:
${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com
其中:
${YourProductKey}:请替换为设备所属产品的ProductKey
${YourRegionId}:请替换为物联网平台设备所在地域代码。
下面是阿里云国内的服务器地域和可用区详情:
地域名称 所在城市 Region ID 可用区数量 华北 1 青岛 cn-qingdao 2 华北 2 北京 cn-beijing 10 华北 3 张家口 cn-zhangjiakou 3 华北 5 呼和浩特 cn-huhehaote 2 华北 6 乌兰察布 cn-wulanchabu 3 华东 1 杭州 cn-hangzhou 8 华东 2 上海 cn-shanghai 8 华南 1 深圳 cn-shenzhen 6 华南 2 河源 cn-heyuan 2 华南 3 广州 cn-guangzhou 2 西南 1 成都 cn-chengdu 2
端口号是:1883
经过上面的格式解释,我的阿里云服务器登录的域名就是(选择的是上海服务器):a1ukQj2EnEJ.iot-as-mqtt.cn-shanghai.aliyuncs.com
域名对应的IP地址(动态解析出来的): 106.14.207.159
在线解析域名网站:https://site.ip138.com/8O76VHCU7Y.iotcloud.tencentdevices.com/
4.2 MQTT协议登录的ID、用户名、密码
4.2.1 MQTT_ClientID
固定格式:${ClientID}|securemode=${Mode},signmethod=${SignMethod}|。
参数说明:
${ClientId}: 设备ID,一般填设备的硬件编号。我这里就直接填当前的设备名称,后面的密码里也要填这个ID,必须一样就行。(设备名称就是创建设备的时候复制出来3个参数里的设备名称)
securemode=3:TCP直连模式,无需设置SSL/TLS信息。
securemode=2:TLS直连模式,需要设置SSL/TLS信息。
${SignMethod}:算法类型,支持hmacmd5和hmacsha1。
示例:
当前我的绿化管理系统设备名称是:GreeningManagement ,选择TCP直连模式,选择hmacsha1算法类型。
那么我的ClientID就是:
GreeningManagement|securemode=3,signmethod=hmacsha1|
4.2.2 MQTT_UserName
固定格式:${DeviceName}&${ProductKey}
参数解释:
${DeviceName} 是设备的名称(就是创建设备的时候复制出来3个参数里的设备名称)
${ProductKey} 是设备的ProductKey(就是创建设备的时候复制出来3个参数里的ProductKey)
示例:
当前我的绿化管理系统设备名称是:GreeningManagement ,我的ProductKey是:a1ukQj2EnEJ
那么我的UserName就是:
GreeningManagement&a1ukQj2EnEJ
4.2.3 MQTT_PassWord
下载工具,运行:
根据说明填充参数:
说明:productKey、deviceName、deviceSecret:是设备证书信息,可在控制台设备详情页查看。clientID在4.2.1小节里已经说过了。时间戳可以省略不填。
点击Generate生成密码。
经过小工具生成后的密码是:
9E580B36EE7E001980AF61EA09EAF85F0211C146
4.3 使用MQTT客户端工具登录阿里云服务器
MQTT客户端工具下载地址:https://blog.csdn.net/xiaolong1126626497/article/details/116779490
根据前面获取的参数填入,登录测试: (为了保证不会断开连接,可以勾选MQTT客户端右下角的心跳包选项,保活)
如果登录成功,在阿里云控制台页面上可以看到设备已经在线:
如果设备能成功上线,那么就说明MQTT所需要的参数都已经填正确了,接下来就可以正常订阅、发布主题了。
属性上报主题与属性设置主题格式:
发布主题:
/sys/a1ukQj2EnEJ/GreeningManagement/thing/event/property/post
上报属性消息的格式(精简格式):
{"method":"thing.event.property.post","params":{"temperature":11.1,"humidity":12.1,"illumination":13,"machine":1}}
上报属性消息的格式详细格式(可以带上ID和版本号):
{"method":"thing.event.property.post","id":"1234567890","params":{"temperature":66.1,"humidity":22.1,"illumination":88,,"machine":1},"version":"1.1.1"}
订阅主题:
/sys/a1ukQj2EnEJ/GreeningManagement/thing/service/property/set
通过MQTT客户端订阅主题、上报属性数据:
把相关的参数填正确,然后登陆,订阅、发布测试:
阿里云物联网平台云端收到的数据:
地址:https://studio.iot.aliyun.com/
点击页面上的的按钮,MQTT客户端可以收到下发的消息(要先订阅才能收到消息):
注意: 阿里云按钮点击下发消息之后,客户端收到后要重新上报一次按钮的状态回去,不然阿里云按钮会恢复之前的状态。
五、STM32代码测试
STM32的代码主要分为以下几个部分:
1. ESP8266底层驱动代码: 完成ESP8266模式配置、数据发送,应答检测等底层网络接口。
2. MQTT协议代码:这是参考标准MQTT编写C语言版本MQTT协议框架代码,实现了重要的几个接口(主题订阅、主题发布、心跳包、登录MQTT服务器),底层采用ESP8266发送数据。 这个MQTT协议不是使用ESP8266本身的SDK,是根据MQTT协议自己实现的,所以如果使用其他的网卡,移植也很方便,不挑网卡设备。
3. 传感器初始化代码: 完成温湿度传感器、光照强度传感器的驱动代码编写。
4. LCD屏代码: LCD是SPI接口的,可以显示温湿度、光照强度数据。
5. main函数: 完成整个逻辑代码编写,检测阿里云平台是否有下发的指令,进行分析,完成水泵的开关控制;当温室和湿度到达某个阀值,自动控制水泵浇水,并上报给阿里云平台;主程序里1秒检测一次温湿度、光照强度、电机状态主动上报给阿里云平台;在设备端按下按键(模拟现场实体开关)也可以控制水泵浇水或者关闭,这些状态都会实时上报给云平台。
程序的模板是使用CubeMX生成的。
5.1 main.c代码
/** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** ** This notice applies to any and all portions of this file * that are not between comment pairs USER CODE BEGIN and * USER CODE END. Other portions of this file, whether * inserted by the user or by software development tools * are owned by their respective copyright owners. * * COPYRIGHT(c) 2019 STMicroelectronics * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of STMicroelectronics nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "stm32l4xx_hal.h" #include "i2c.h" #include "usart.h" #include "gpio.h" #include "E53_IA1.h" #include "lcd.h" #include "spi.h" #include "mqtt.h" #include "esp8266.h" /* USER CODE BEGIN Includes */ #include "stdio.h" /* USER CODE END Includes */ void SystemClock_Config(void); #define ESP8266_WIFI_AP_SSID "CMCC-Cqvn" //将要连接的路由器名称 --不要出现中文、空格等特殊字符 #define ESP8266_AP_PASSWORD "99pu58cb" //将要连接的路由器密码 //阿里云物联网服务器的设备信息 #define MQTT_ClientID "GreeningManagement|securemode=3,signmethod=hmacsha1|" #define MQTT_UserName "GreeningManagement&a1ukQj2EnEJ" #define MQTT_PassWord "9E580B36EE7E001980AF61EA09EAF85F0211C146" //订阅与发布的主题 #define SET_TOPIC "/sys/a1ukQj2EnEJ/GreeningManagement/thing/service/property/set" //订阅 #define POST_TOPIC "/sys/a1ukQj2EnEJ/GreeningManagement/thing/event/property/post" //发布 //保存温湿度、光照强度 E53_IA1_Data_TypeDef E53_IA1_Data; //显示文本 char lcd_text_str[50]; UART_HandleTypeDef at_usart; //低功耗串口初始化 int32_t at_usart_init(void) { at_usart.Instance = LPUART1; at_usart.Init.BaudRate = 115200; at_usart.Init.WordLength = UART_WORDLENGTH_8B; at_usart.Init.StopBits = UART_STOPBITS_1; at_usart.Init.Parity = UART_PARITY_NONE; at_usart.Init.HwFlowCtl = UART_HWCONTROL_NONE; at_usart.Init.Mode = UART_MODE_RX | UART_MODE_TX; if(HAL_UART_Init(&at_usart) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } // __HAL_UART_CLEAR_FLAG(usart, UART_FLAG_TC); __HAL_UART_ENABLE_IT(&at_usart, UART_IT_IDLE); __HAL_UART_ENABLE_IT(&at_usart, UART_IT_RXNE); HAL_NVIC_EnableIRQ(LPUART1_IRQn); //使能USART1中断通道 HAL_NVIC_SetPriority(LPUART1_IRQn, 3, 3); //抢占优先级3,子优先级3 return 0; } unsigned char ESP8266_RecvBuf[MAX_RECV_CNT]; unsigned int ESP8266_Recv_cnt=0; unsigned int ESP8266_Recv_flag=0; void LPUART1_IRQHandler() { //接收到数据 if(__HAL_UART_GET_FLAG(&at_usart, UART_FLAG_RXNE) != RESET) { if(ESP8266_Recv_cnt<MAX_RECV_CNT-1) { ESP8266_RecvBuf[ESP8266_Recv_cnt++] = (uint8_t)(at_usart.Instance->RDR & 0x00FF); } else { ESP8266_Recv_flag=1; } } else if (__HAL_UART_GET_FLAG(&at_usart, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(&at_usart); ESP8266_Recv_flag=1; } } void AT_SendData(unsigned char *p,unsigned int len) { int i=0; for(i=0;i<len;i++) { while((LPUART1->ISR & 0X40) == 0); //循环发送,直到发送完毕 LPUART1->TDR = p[i]; } } char mqtt_message[200]; int main(void) { int i=0; int cnt=0; int motor_state=0; HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_SPI2_Init(); MX_USART1_UART_Init(); at_usart_init(); //初始化硬件 STM32L431RC_BearPiBH1750_I2C1\STM32L431RC_BearPiBH1750_I2C1.axf: Error: L6218E: Undefined symbol printf (referred from main.o). Init_E53_IA1(); LCD_Init(); LCD_Clear(BLACK);//清屏为黑色 LCD_ShowString(20, 00, 240, 32, 32, "Init ESP8266");//显示字符串,字体大小32*32 if(ESP8266_Init()) { printf("ESP8266硬件检测错误.\n"); LCD_Clear(BLACK);//清屏为黑色 LCD_ShowString(0, 00, 240, 32, 32, "ESP8266 ERROR");//显示字符串,字体大小32*32 } else { LCD_Clear(BLACK);//清屏为黑色 LCD_ShowString(20, 00, 240, 32, 32, "ESP8266 OK");//显示字符串,字体大小32*32 printf("准备连接到指定的服务器.\n"); //非加密端口 printf("WIFI:%d\r\n",ESP8266_STA_TCP_Client_Mode(ESP8266_WIFI_AP_SSID,ESP8266_AP_PASSWORD,"a1ukQj2EnEJ.iot-as-mqtt.cn-shanghai.aliyuncs.com",1883,1)); } //2. MQTT协议初始化 MQTT_Init(); //3. 连接阿里云IOT服务器 while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord)) { printf("服务器连接失败,正在重试...\n"); HAL_Delay(500); } printf("服务器连接成功.\n"); //3. 订阅主题 if(MQTT_SubscribeTopic(SET_TOPIC,0,1)) { printf("主题订阅失败.\n"); } else { printf("主题订阅成功.\n"); } while (1) { if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET)//查询按键KEY1低电平 { HAL_Delay(10);//消抖 if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET)//查询按键KEY1低电平 { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);//亮 //补光灯亮 HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_SET); //电机转 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_SET); motor_state=1; } } if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET)//查询按键KEY2低电平 { HAL_Delay(10);//消抖 if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET)//查询按键KEY2低电平 { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);//灭 //补光灯灭 HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_RESET); //电机停 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_RESET); motor_state=0; } } cnt++; HAL_Delay(10); if(cnt>=100) { cnt=0; E53_IA1_Read_Data(); printf("光照强度:%.1f %%\r\n", E53_IA1_Data.Lux); printf("湿度:%.1f %%\r\n",E53_IA1_Data.Humidity); printf("温度:%.1f ℃\r\n", E53_IA1_Data.Temperature); sprintf(lcd_text_str,"L: %0.1f %%",E53_IA1_Data.Lux); LCD_ShowString(40, 50+10+32*1, 240, 32, 32,lcd_text_str); sprintf(lcd_text_str,"H: %.1f %%",E53_IA1_Data.Humidity); LCD_ShowString(40, 50+10+32*2, 240, 32, 32,lcd_text_str); sprintf(lcd_text_str,"T: %.1f C",E53_IA1_Data.Temperature); LCD_ShowString(40, 50+10+32*3, 240, 32, 32,lcd_text_str); //切换引脚的状态 HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin); //上传数据 sprintf(mqtt_message,"{\"method\":\"thing.event.property.post\",\"id\":\"1234567890\",\"params\":{\"temperature\":%f,\"humidity\":%f,\"illumination\":%f,\"machine\":%d},\"version\":\"1.1.1\"}", E53_IA1_Data.Temperature,E53_IA1_Data.Humidity,E53_IA1_Data.Lux,motor_state); MQTT_PublishData(POST_TOPIC,mqtt_message,0); //根据湿度自动灌溉 if((int)E53_IA1_Data.Humidity<50) //小于50自动灌溉 { printf("自动灌溉....\n"); motor_state=1; //电机状态更新 //电机转 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_SET); } } //接收到数据 if(ESP8266_Recv_flag) { //如果是下发了属性,判断是开锁还是关锁 if(ESP8266_Recv_cnt>5) { ESP8266_RecvBuf[ESP8266_Recv_cnt]='\0'; //使用字符串查找函数 if(strstr((char*)&ESP8266_RecvBuf[5],"\"machine\":1")) { motor_state=1; //电机状态更新 //电机转 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_SET); printf("开启电机...\n"); } else if(strstr((char*)&ESP8266_RecvBuf[5],"\"machine\":0")) { //电机停 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_RESET); motor_state=0; printf("关闭电机...\n"); } for(i=0;i<ESP8266_Recv_cnt;i++)printf("%c",ESP8266_RecvBuf[i]); ESP8266_Recv_cnt=0; } ESP8266_Recv_flag=0; } } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_PeriphCLKInitTypeDef PeriphClkInit; /**Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_MSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = 16; RCC_OscInitStruct.MSIState = RCC_MSI_ON; RCC_OscInitStruct.MSICalibrationValue = 0; RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI; RCC_OscInitStruct.PLL.PLLM = 1; RCC_OscInitStruct.PLL.PLLN = 40; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_I2C1; PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_HSI; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Configure the main internal regulator output voltage */ if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Configure the Systick interrupt time */ HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000); /**Configure the Systick */ HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); /* SysTick_IRQn interrupt configuration */ HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @param file: The file name as string. * @param line: The line in file as a number. * @retval None */ void _Error_Handler(char *file, int line) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ while(1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /** * @} */ /** * @} */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
5.2 mqtt.c代码
#include "mqtt.h" u8 *mqtt_rxbuf; u8 *mqtt_txbuf; u16 mqtt_rxlen; u16 mqtt_txlen; u8 _mqtt_txbuf[256];//发送数据缓存区 u8 _mqtt_rxbuf[256];//接收数据缓存区 typedef enum { //名字 值 报文流动方向 描述 M_RESERVED1 =0 , // 禁止 保留 M_CONNECT , // 客户端到服务端 客户端请求连接服务端 M_CONNACK , // 服务端到客户端 连接报文确认 M_PUBLISH , // 两个方向都允许 发布消息 M_PUBACK , // 两个方向都允许 QoS 1消息发布收到确认 M_PUBREC , // 两个方向都允许 发布收到(保证交付第一步) M_PUBREL , // 两个方向都允许 发布释放(保证交付第二步) M_PUBCOMP , // 两个方向都允许 QoS 2消息发布完成(保证交互第三步) M_SUBSCRIBE , // 客户端到服务端 客户端订阅请求 M_SUBACK , // 服务端到客户端 订阅请求报文确认 M_UNSUBSCRIBE , // 客户端到服务端 客户端取消订阅请求 M_UNSUBACK , // 服务端到客户端 取消订阅报文确认 M_PINGREQ , // 客户端到服务端 心跳请求 M_PINGRESP , // 服务端到客户端 心跳响应 M_DISCONNECT , // 客户端到服务端 客户端断开连接 M_RESERVED2 , // 禁止 保留 }_typdef_mqtt_message; //连接成功服务器回应 20 02 00 00 //客户端主动断开连接 e0 00 const u8 parket_connetAck[] = {0x20,0x02,0x00,0x00}; const u8 parket_disconnet[] = {0xe0,0x00}; const u8 parket_heart[] = {0xc0,0x00}; const u8 parket_heart_reply[] = {0xc0,0x00}; const u8 parket_subAck[] = {0x90,0x03}; void MQTT_Init(void) { //缓冲区赋值 mqtt_rxbuf = _mqtt_rxbuf; mqtt_rxlen = sizeof(_mqtt_rxbuf); mqtt_txbuf = _mqtt_txbuf; mqtt_txlen = sizeof(_mqtt_txbuf); memset(mqtt_rxbuf,0,mqtt_rxlen); memset(mqtt_txbuf,0,mqtt_txlen); // //无条件先主动断开 // MQTT_Disconnect(); // HAL_Delay(100); // MQTT_Disconnect(); // HAL_Delay(100); } /* 函数功能: 登录服务器 函数返回值: 0表示成功 1表示失败 */ u8 MQTT_Connect(char *ClientID,char *Username,char *Password) { u8 i,j; 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); //固定报头 //控制报文类型 mqtt_txbuf[mqtt_txlen++] = 0x10; //MQTT Message Type CONNECT //剩余长度(不包括固定头部) do { u8 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; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); //可变报头 //协议名 mqtt_txbuf[mqtt_txlen++] = 0; // Protocol Name Length MSB mqtt_txbuf[mqtt_txlen++] = 4; // Protocol Name Length LSB mqtt_txbuf[mqtt_txlen++] = 'M'; // ASCII Code for M mqtt_txbuf[mqtt_txlen++] = 'Q'; // ASCII Code for Q mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T //协议级别 mqtt_txbuf[mqtt_txlen++] = 4; // MQTT Protocol version = 4 对于 3.1.1 版协议,协议级别字段的值是 4(0x04) //连接标志 mqtt_txbuf[mqtt_txlen++] = 0xc2; // conn flags mqtt_txbuf[mqtt_txlen++] = 0; // Keep-alive Time Length MSB mqtt_txbuf[mqtt_txlen++] = 100; // Keep-alive Time Length LSB 100S心跳包 保活时间 mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen); mqtt_txlen += ClientIDLen; if(UsernameLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen); //username length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen); //username length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen); mqtt_txlen += UsernameLen; } if(PasswordLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen); //password length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen); //password length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen); mqtt_txlen += PasswordLen; } memset(mqtt_rxbuf,0,mqtt_rxlen); ESP8266_Recv_flag=0; ESP8266_Recv_cnt=0; MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); HAL_Delay(200); memcpy((char *)mqtt_rxbuf,ESP8266_RecvBuf,ESP8266_Recv_cnt); for(i=0;i<ESP8266_Recv_cnt;i++)printf("%#x ",ESP8266_RecvBuf[i]); //CONNECT if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //连接成功 { return 0;//连接成功 } return 1; } /* 函数功能: MQTT订阅/取消订阅数据打包函数 函数参数: topic 主题 qos 消息等级 0:最多分发一次 1: 至少分发一次 2: 仅分发一次 whether 订阅/取消订阅请求包 (1表示订阅,0表示取消订阅) 返回值: 0表示成功 1表示失败 */ u8 MQTT_SubscribeTopic(char *topic,u8 qos,u8 whether) { u8 i,j; mqtt_txlen=0; int topiclen = strlen(topic); int DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度 //固定报头 //控制报文类型 if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //消息类型和标志订阅 else mqtt_txbuf[mqtt_txlen++] = 0xA2; //取消订阅 //剩余长度 do { u8 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; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); //可变报头 mqtt_txbuf[mqtt_txlen++] = 0; //消息标识符 MSB mqtt_txbuf[mqtt_txlen++] = 0x0A; //消息标识符 LSB //有效载荷 mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主题长度 MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主题长度 LSB memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen); mqtt_txlen += topiclen; if(whether) { mqtt_txbuf[mqtt_txlen++] = qos;//QoS级别 } ESP8266_Recv_flag=0; ESP8266_Recv_cnt=0; MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); HAL_Delay(200); memcpy((char *)mqtt_rxbuf,ESP8266_RecvBuf,ESP8266_Recv_cnt); for(i=0;i<ESP8266_Recv_cnt;i++)printf("%#x ",ESP8266_RecvBuf[i]); if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //订阅成功 { return 0;//订阅成功 } return 1; //失败 } //MQTT发布数据打包函数 //topic 主题 //message 消息 //qos 消息等级 u8 MQTT_PublishData(char *topic, char *message, u8 qos) { int topicLength = strlen(topic); int messageLength = strlen(message); static u16 id=0; int DataLen; mqtt_txlen=0; //有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度 //QOS为0时没有标识符 //数据长度 主题名 报文标识符 有效载荷 if(qos) DataLen = (2+topicLength) + 2 + messageLength; else DataLen = (2+topicLength) + messageLength; //固定报头 //控制报文类型 mqtt_txbuf[mqtt_txlen++] = 0x30; // MQTT Message Type PUBLISH //剩余长度 do { u8 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; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); mqtt_txbuf[mqtt_txlen++] = BYTE1(topicLength);//主题长度MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主题长度LSB memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷贝主题 mqtt_txlen += topicLength; //报文标识符 if(qos) { mqtt_txbuf[mqtt_txlen++] = BYTE1(id); mqtt_txbuf[mqtt_txlen++] = BYTE0(id); id++; } memcpy(&mqtt_txbuf[mqtt_txlen],message,messageLength); mqtt_txlen += messageLength; MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); return mqtt_txlen; } void MQTT_SentHeart(void) { MQTT_SendBuf((u8 *)parket_heart,sizeof(parket_heart)); } void MQTT_Disconnect(void) { MQTT_SendBuf((u8 *)parket_disconnet,sizeof(parket_disconnet)); } void MQTT_SendBuf(u8 *buf,u16 len) { AT_SendData(buf,len); }
5.3 设备运行效果
串口打印调试数据: 连接成功
六、阿里云生活物联网平台
官网首页:https://help.aliyun.com/product/123207.html?spm=a2c4g.11186623.6.540.5f956897WfxgIa
生活物联网平台是阿里云IoT针对生活领域推出的物联网平台,以解决家电智能化的问题。
生活物联网平台提供了设备接入能力,有公版APP可以直接开发使用;下篇文章再讲解生活物联网平台使用示例。