#夏日挑战赛# 基于小熊派设计的云端绿化管理系统(华为云IoT) 原创 精华
1. 设计需求、硬件环境介绍
1.1 项目背景
绿化管理系统在现实生活、生产中的应用十分广泛,它是集环境感知、规划决策、自动管理等功能于一体的综合系统。目前,在城市道路两旁的区域绿化主要为花、草、树、灌木等,其中较多种植物对水的敏感性较高,如果供水不足够或过多,会造成枯萎,甚至死亡。而且采用人工的方式进行管理,不能够较好的了解种植区域的数据,而造成水资源极大地浪费,为响应国家“十四五”规划提出的生态文明建设实现新进步,能源资源配置更加合理、利用效率大幅提高,生态环境持续改善;因此发展先进的智能灌溉技术对绿化管理至关重要。
传统的手动绿化管理控制多采用人工方式通过观察地表潮湿度来进行灌溉,这样不仅造成水资源浪费和人力资源的浪费,而且灌溉不精确。本文就利用华为云IOT物联网平台设计云端绿化管理系统,设备平台采用小熊开发板-CPU是意法半导体的STM32L431芯片,这是意法半导体推出的低功耗芯片;配合外部的一些专业传感器,能够获取空气中的温湿度数据,光照度数据等,根据种植区的空气温湿度数据,判断是否进行灌溉。因此,研究一种具有检测到绿化情况以及价格低廉、应用范围广、性能可靠的绿化管理系统对绿化水平的提高以及节能减排都具有重要的意义。
1.2 实现功能
本项目是利用意法半导体的STM32L431+ESP8266 WIFI ,配合华为云物联网平台服务器,组建一个微小型的绿化管理系统,结合外部传感器采集的数据,并利用这些数据判断是否进行灌溉。
考虑到以学习为目,当前项目采用了ESP8266无线WIFI网卡作为联网设备,ESP8266价钱便宜,支持串口编程,有标准的一套AT资料,资料多,作为学习而言,非常适合。可以通过对ESP8266的编程实验,了解TCP、MQTT网络编程相关知识点。
当前项目主要分为六个功能模块,分别是:基础系统模块、温度采集模块、湿度采集模块、光照采集模块、无线传感器网络模块、OLED显示屏模块。 (1)基础系统模块:进行各个数据的接收与转发,控制扫水作业是否进行,浇水作业是采用板载的电机模拟 (2)温度采集模块:采集监测区域的温度数据,传输到微控制器 (3)湿度采集模块:采集监测区域的湿度数据,传输到微控制器 (4)光照采集模块:采集监测区域的光照数据,传输到微控制器 (5)无线传感器网络模块:数据上传至云平台,数据下发交互等 (6)LCD显示屏模块:实时显示所监测到的各项数据
小熊开发板的扩展板上自带了光敏传感器、温湿度传感器、直流电机模块,可以很方便的实现上面的这些功能需求。
本项目的源代码没有采用官方的案例工程,官方自大工程非常庞大,为了兼容各种设备平台,代码较多,不适合初学者理解代码,本文工程代码全部针对本项目编写,没有多余代码,采用寄存器风格编写,代码简洁,非常适合初学者学习MQTT协议,学习各种传感器的通信协议等等;连接华为云的MQTT协议也是按照MQTT的官方中文手册自行编写的,不依赖任何外部SDK,不依赖ESP8266设备,只要能联网的设备都可以连接华为云IOT,非常适合移植到其他单片机平台;不管是采用51,STM32F1系列,都可以直接参考代码移植。
1.3 设备实物图
小熊开发板的设备相关实物图如下:
2. 创建IOT服务器端产品
2.1 创建产品
直接打开物联网产品页面: https://www.huaweicloud.com/product/iothub.html
打开产品页面,选择右上角创建产品。
根据自己情况填写信息。
创建成功后打开产品详情页面,拉到最下面,点击创建自定义模型文件。
这里创建模型文件主要就是为了MQTT客户端能够正确的上传传感器数据上来,每个传感器设置一个属性,这个属性就是表示了传感器的数据值类型。
比如: 先添加一个电机,这个电机就是浇水电机,能上报开关状态,云端也能下发命令控制电机,所以需要添加属性和下发的命令。
添加属性:
添加命令: 因为电机需要云端远程控制。
接下来就创建温度、湿度、光照度传感器的属性,这些传感器只是向云端上传数据,不需要下发指令控制,所以只创建属性就行了。
创建完毕效果,一共有4个属性,电机、温度、湿度、光强度:
2.2 创建设备
选择设备页面,注册设备。
创建后保持设备密匙等信息,接下来登录服务器时,生成MQTT账号密匙需要用到这些参数。
当前创建的设备信息如下:
{
"device_id": "61cd1d97078a93029b84e7b6_1126626497",
"secret": "1126626497"
}
2.3 生成MQTT登录账号信息
官微提供的在线小工具: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
按照提示填入数据,生成,非常方便。
当前生成的信息如下:
ClientId 61cd1d97078a93029b84e7b6_1126626497_0_0_2021123003
Username 61cd1d97078a93029b84e7b6_1126626497
Password b219f3a0099fa0284a2671a5c699b67a7cf6d5f7355d9ee8190011f3b64f71b5
3. 使用MQTT客户端模拟测试
为了验证服务器配置是否OK,先使用MQTT客户端软件进行连接测试。
3.1 华为云IOT服务器地址与端口
端口: 1883
域名: a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com
IP地址: 121.36.42.100
3.2 订阅主题
在产品页面,可以看到主题管理页面,能看到当前设备可以订阅的主题有哪些。
一般订阅下发的数据:
格式: $oc/devices/{device_id}/sys/messages/down
//订阅主题: 平台下发消息给设备
$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/messages/down
3.3 上报主题数据
官方文档介绍: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html
服务ID,属性ID在产品页面查看,2.1小节创建产品里就讲了这个属性的作用。
每次可以单个属性上报,也可以一起上报。
格式: $oc/devices/{device_id}/sys/properties/report
//设备上报主题请求
$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/properties/report
//上报的数据格式如下
//电机开状态反馈
{"services": [{"service_id": "motor","properties":{"motor":1}}]}
//电机关状态反馈
{"services": [{"service_id": "motor","properties":{"motor":0}}]}
//温度上报
{"services": [{"service_id": "motor","properties":{"SHT30_H":14}}]}
//湿度上报
{"services": [{"service_id": "motor","properties":{"SHT30_L":70}}]}
//光照强度上报
{"services": [{"service_id": "motor","properties":{"BH1750":80}}]}
//也可以一起上报
{"services": [{"service_id": "motor","properties":{"motor":1}},{"service_id": "motor","properties":{"SHT30_H":15}},{"service_id": "motor","properties":{"SHT30_L":70}},{"service_id": "motor","properties":{"BH1750":80}}]}
3.4 登录服务器
按照软件提示,填入相关数据即可。
如需要也需要使用和我一样的同款软件,打开百度搜索MQTT客户端_v2.4(协议3.1.1).exe
即可找到下载地址。
发送数据后查看云端,已经登录成功,数据已经上传成功。
3.5 下发命令
电机设备支持读写,支持下发命令,在设备页面测试。
点击确定之后,参看MQTT客户端软件,已经收到了下发的数据。
len:174,Data:l$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/commands/request_id=390ce15d-6e69-4021-b83a-5e953eea874c{"paras":{"motor":1},"service_id":"motor","command_name":"motor"}
4. 设备端上华为云IOT
工程代码:
工程代码较多,这里就贴出main.c全部代码:
#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" //将要连接的路由器
//华为云IOT物联网服务器的设备信息
#define MQTT_ClientID "61cd1d97078a93029b84e7b6_1126626497_0_0_2021123003"
#define MQTT_UserName "61cd1d97078a93029b84e7b6_1126626497"
#define MQTT_PassWord "b219f3a0099fa0284a2671a5c699b67a7cf6d5f7355d9ee8190011f3b64f71b5"
//订阅与发布的主题
#define SET_TOPIC "$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/messages/down" //订阅
#define POST_TOPIC "$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/properties/report" //发布
//保存温湿度、光照强度
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();
//初始化硬件
Init_E53_IA1();
LCD_Init();
LCD_Clear(BLACK);//清屏为黑色
LCD_ShowString(0, 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(0, 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,"106.55.124.154",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("光照强度:%d %%\r\n", (int)E53_IA1_Data.Lux);
printf("湿度:%d %%\r\n",(int)E53_IA1_Data.Humidity);
printf("温度:%d ℃\r\n", (int)E53_IA1_Data.Temperature);
sprintf(lcd_text_str,"L: %d %%",(int)E53_IA1_Data.Lux);
LCD_ShowString(40, 50+10+32*1, 240, 32, 32,lcd_text_str);
sprintf(lcd_text_str,"H: %d %%",(int)E53_IA1_Data.Humidity);
LCD_ShowString(40, 50+10+32*2, 240, 32, 32,lcd_text_str);
sprintf(lcd_text_str,"T: %d C",(int)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,"{\"services\": [{\"service_id\": \"motor\",\"properties\":{\"motor\":%d}},"
"{\"service_id\": \"motor\",\"properties\":{\"SHT30_H\":%d}},{\"service_id\": \"motor\",\"properties\":"
"{\"SHT30_L\":%d}},{\"service_id\": \"motor\",\"properties\":{\"BH1750\":%d}}]}",
motor_state,(int)E53_IA1_Data.Humidity,(int)E53_IA1_Data.Temperature,(int)E53_IA1_Data.Lux);
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 */
如果需要整个工程源码的话,评论区留下邮箱即可。
17616522363@163.com 跪谢大佬