Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
LittlevGL_LVGL
onenet
[RT-Thread x 大学生夏令营] 物联网室内环境检测系统
发布于 2023-07-25 22:20:14 浏览:756
订阅该版
[tocm] # 1. 系统设计 ## 功能: 采集当前环境的光强、温度、湿度的数值在LCD上进行显示并上传至onenet云端进行存储和分析,同时LED的亮度随着环境光强的减弱而增强,当温度上升时,按上升梯度逐渐点亮LED矩阵,温度达到阈值时LED矩阵中的所有LED被点亮且蜂鸣器开始报警,超出阈值越多则蜂鸣器的报警频率越高。 ## 线程: 1. 传感器采集 2. WiFi扫描及连接 3. 上传数据至onenet 4. LED亮度调节以及蜂鸣器的报警 5. LCD显示 ## 开发环境: RT-spark星火一号开发板,RT-Thread Studio,(SquareLine Studio) # 2. 各模块设计 ## 传感器采集模块 线程优先级设为25,入口函数如下,先调用函数初始化传感器,再读取传感器的值赋给全局变量即可,注意在初始化传感器之后需要加入一点延时使得传感器正常工作。 ```python // ./applications/project_aht10.c static void aht10_entry(void *parameter) { rt_thread_mdelay(18000); float humidity,temperature,brightness; aht10_device_t aht10; ap3216c_device_t ap3216c; const char *ap3216c_i2c_name = "i2c2"; const char *aht10_i2c_name = "i2c3"; int brightness_send; int temperature_send; int humidity_send; int temperature_send_led; aht10 = aht10_init(aht10_i2c_name); ap3216c = ap3216c_init(ap3216c_i2c_name); if(aht10 == RT_NULL || ap3216c == RT_NULL) { LOG_E("The sensor initializes failture"); } rt_thread_mdelay(2000); while (1) { humidity = aht10_read_humidity(aht10); LOG_D("humidity : %d.%d %%",(int)humidity,(int)(humidity*10)%10); temperature = aht10_read_temperature(aht10); LOG_D("temperature : %d.%d",(int)temperature, (int)(temperature*10)%10); brightness = ap3216c_read_ambient_light(ap3216c); LOG_D("brightness : %d.%d(lux)",(int)brightness, (int)(brightness*10)%10); tem=temperature; hum=humidity; inte=brightness; rt_thread_delay(rt_tick_from_millisecond(5 * 1000)); } } ``` ## wifi模块 wifi模块线程的优先级设为20,入口函数如下所示,先初始化,然后开始扫描热点,再根据预设的两个宏定义WLAN_SSID和WLAN_PASSWORD来自动连接对应的热点,在短暂延时后把onenet的初始化也加入了wifi线程中。 ``` // ./applications/project_wifi.c #define WLAN_SSID "Microelectronics" #define WLAN_PASSWORD "12345678" static void wifi_entry(void *parameter) { static int i = 0; int result = RT_EOK; struct rt_wlan_info info; /* 等待 500 ms 以便 wifi 完成初始化 */ rt_thread_mdelay(500); /* 扫描热点 */ LOG_D("start to scan ap ..."); /* 执行扫描 */ rt_sem_init(&scan_done,"scan_done",0,RT_IPC_FLAG_FIFO); rt_wlan_register_event_handler(RT_WLAN_EVT_SCAN_REPORT, wlan_scan_report_hander,&i); rt_wlan_register_event_handler(RT_WLAN_EVT_SCAN_DONE, wlan_scan_done_hander,RT_NULL); if(rt_wlan_scan() == RT_EOK) { LOG_D("the scan is started... "); }else { LOG_E("scan failed"); } /*等待扫描完毕 */ rt_sem_take(&scan_done,RT_WAITING_FOREVER); /* 热点连接 */ LOG_D("start to connect ap ..."); rt_sem_init(&net_ready, "net_ready", 0, RT_IPC_FLAG_FIFO); /* 注册 wlan ready 回调函数 */ rt_wlan_register_event_handler(RT_WLAN_EVT_READY, wlan_ready_handler, RT_NULL); /* 注册 wlan 断开回调函数 */ rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED, wlan_station_disconnect_handler, RT_NULL); /* 同步连接热点 */ result = rt_wlan_connect(WLAN_SSID, WLAN_PASSWORD); if (result == RT_EOK) { rt_memset(&info, 0, sizeof(struct rt_wlan_info)); /* 获取当前连接热点信息 */ rt_wlan_get_info(&info); LOG_D("station information:"); print_wlan_information(&info,0); /* 等待成功获取 IP */ result = rt_sem_take(&net_ready, NET_READY_TIME_OUT); if (result == RT_EOK) { LOG_D("networking ready!"); msh_exec("ifconfig", rt_strlen("ifconfig")); } else { LOG_D("wait ip got timeout!"); } /* 回收资源 */ rt_wlan_unregister_event_handler(RT_WLAN_EVT_READY); rt_sem_detach(&net_ready); } else { LOG_E("The AP(%s) is connect failed!", WLAN_SSID); } rt_thread_mdelay(5000); onenet_mqtt_init(); } ``` ## 数据上传模块 数据上传到onenet的线程优先级设为25,简单地通过将全局变量的值赋值给当前的局部变量后通过函数去上传,需要注意的是该线程需要等待wifi线程运行完成后成功连接互联网后再运行才不会上传出错,由于在wifi线程中提前加入了onenet初始化函数,此处直接上传数据即可。 注意两个数据的上传直接要插入一定的延时。在结尾加入4ms的延时是为了匹配传感器每5ms采集一次信号。 ``` // ./applications/onenet.c static void onenet_upload_entry(void *parameter) { rt_thread_mdelay(40000); printf("send thread\n"); // int brightness,humidity,temperature; float brightness,humidity,temperature; while (1) { brightness = inte; humidity = hum; temperature = tem; printf("send thread while\n"); if (onenet_mqtt_upload_digit("brightness", (int)brightness) < 0) { LOG_E("upload has an error, stop uploading"); break; } rt_thread_mdelay(500); if (onenet_mqtt_upload_digit("humidity", (int)humidity) < 0) { LOG_E("upload has an error, stop uploading"); break; } rt_thread_mdelay(500); if (onenet_mqtt_upload_digit("temperature", (int)temperature) < 0) { LOG_E("upload has an error, stop uploading"); break; } rt_thread_delay(rt_tick_from_millisecond(4 * 1000)); } } ``` ## 蜂鸣器、LED矩阵模块 功能一:由温度传感器采集的数据控制LED矩阵 该功能基于温度传感器所采集的温度,当温度位于指定区间时,LED显示指定的数量并且蜂鸣器响三声,并且LED的亮起速度和蜂鸣器的鸣叫速度基于温度而定,温度越高,LED亮起的速度和蜂鸣器鸣叫的速度越快。超过指定温度时,蜂鸣器会一直响。并且该功能由按键UP控制,另一个照明功能由按键DoWN决定。 功能二:由亮度传感器采集的数据控制LED矩阵 根据光强传感器采集到的光亮控制LED矩阵的亮度,该功能同样可以由功能一通过按键DOWN切换到该功能。 按键控制写在./applications/BUTTOM.c文件的中断函数。 ``` void irq_callback1(void *args) { rt_uint32_t sign = (rt_uint32_t) args; switch (sign) { case PIN_WK_UP : mode = 0; jud1 = 0; break; case PIN_WK_DOWN : mode = 1; jud2 = 0; break; default: break; } } ``` 主要响应算法写在./applications/Matrx_LED.c,为了防止由于传感器采样噪声引起响应的抖动,设置响应不仅与当前时刻的量有关,还取决于前几个时刻的量,这样的后果必然是响应时间的延长,但是可以带来更稳定可靠的响应结果。 ## lvgl显示模块 ### 方案一 根据lvgl开发文档及其example,选择相应的widget进行图形化界面设计。 使用table组件进行温度、湿度和光强的显示,达到整齐美观的显示效果。 ``` /*create table*/ table = lv_table_create(lv_scr_act()); /*Fill the first column*/ lv_table_set_cell_value(table, 0, 0, "Temperature"); lv_table_set_cell_value(table, 1, 0, "Humidity"); lv_table_set_cell_value(table, 2, 0, "Intensity"); /*Fill the second column*/ lv_table_set_cell_value_fmt(table, 0, 1,"%d.%.2d`C",(int)tem,(int)((tem-(int)tem)*100)); lv_table_set_cell_value_fmt(table, 1, 1, "%d.%.2d%",(int)hum,(int)((hum-(int)hum)*100)); lv_table_set_cell_value_fmt(table, 2, 1, "%d.%.2dlux",(int)inte,(int)((inte-(int)inte)*100)); /*Set the height and the position of the table.*/ lv_obj_set_height(table, 240); lv_obj_align(table, LV_ALIGN_TOP_MID, 0, 0); ``` 为了更加直观展示内容,加入自定义symbol。参照lvgl用户手册,进行符号的字库转换及定义。由于table不支持对单元格style的单独设置,因此使用一个单独的label进行符号的显示,需要先创建一个新的style使用自定义symbol作为字体,然后令该label使用该style。 ``` /*create the style*/ LV_FONT_DECLARE(my_symbol); static lv_style_t style; void lv_style_custom(void) { lv_style_init(&style); lv_style_set_text_font(&style, &my_symbol); lv_style_set_text_line_space(&style, 28); } /*create the label*/ void lv_label(void) { label = lv_label_create(lv_scr_act()); lv_obj_add_style(label,&style,0); lv_label_set_text(label, LV_SYMBOL_TEMPERATURE"\n"LV_SYMBOL_HUMIDITY"\n"LV_SYMBOL_INTENSITY); lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 14); } ``` 再通过NTP将网络时间同步到RTC中,通过调用RTC中的系统时钟进行实时时间显示,并使用meter进行表盘化实现。其中NTP同步RTC时钟由netutils软件包实现,再使用time函数每秒从RTC获取时间,并使用localtime转换为tm结构,再分离出时和分以进行表盘显示。 ``` void get_hour_min(void) { cur_time=time(NULL); struct tm* cur_time_tm=localtime(&cur_time); min=cur_time_tm->tm_min; hour=cur_time_tm->tm_hour; } ``` 使用lv_meter软件包自带函数进行表盘个性化设计。 ``` void lv_clock_init(void) { meter = lv_meter_create(lv_scr_act()); lv_obj_set_size(meter, 110, 110); lv_obj_align(meter, LV_ALIGN_TOP_LEFT, 0, 130); lv_obj_set_style_pad_all(meter, 0, 0); /*Create a scale for the minutes*/ /*61 ticks in a 360 degrees range (the last and the first line overlaps)*/ lv_meter_scale_t * scale_min = lv_meter_add_scale(meter); lv_meter_set_scale_ticks(meter, scale_min, 61, 1, 10, lv_palette_main(LV_PALETTE_GREY)); lv_meter_set_scale_range(meter, scale_min, 0, 60, 360, 270); /*Create another scale for the hours. It's only visual and contains only major ticks*/ lv_meter_scale_t * scale_hour = lv_meter_add_scale(meter); lv_meter_set_scale_ticks(meter, scale_hour, 12, 0, 0, lv_palette_main(LV_PALETTE_GREY)); /*12 ticks*/ lv_meter_set_scale_major_ticks(meter, scale_hour, 1, 2, 20, lv_color_black(), 1); /*Every tick is major*/ lv_meter_set_scale_range(meter, scale_hour, 1, 12, 330, 300); /*[1..12] values in an almost full circle*/ /*Add indicators*/ indic_min = lv_meter_add_needle_line(meter, scale_min,5, lv_color_black(), -5); indic_hour = lv_meter_add_needle_line(meter, scale_min, 5,lv_palette_main(LV_PALETTE_GREY) ,-20); int hour_12=(hour>12)?hour-12:hour; lv_meter_set_indicator_value(meter, indic_min, min); lv_meter_set_indicator_value(meter, indic_hour, (hour_12*5)+(min/12)); } ``` 为提高运行速度,减少冗余的CPU占用,将LCD显示程序分为不变的显示和需要循环刷新数据的显示两部分,分别封装为lv_demo_display_init和lv_demo_display。lv_demo_display_init在线程中只执行一次,而lv_demo_display在线程中循环执行以达到刷新的效果。 ``` void lv_demo_display_init(void) { lv_style_custom(); lv_table_init(); lv_label(); lv_clock_init(); lv_label_time_init(); } void lv_demo_display(void) { get_hour_min(); lv_table(); lv_clock(); lv_label_time(); } ``` ### 方案二 采用SquareLine Studio软件,根据需求设计导航栏信息、主页面传感器信息展示,添加相应的字库和插图
设置项目的分辨率240·240、颜色深度16(根据lvgl驱动程序设置)、LVGL版本(不高于lvgl驱动程序的版本,这里选择8.3.3),设置lvgl.h路径,最后导出ui文件夹
将ui文件夹添加到工程的package目录下。在lvgl入口函数中调用ui_init()函数。
注意工程需要提前成功配置好LCD驱动以及LVGL驱动程序,在lv_conf.h中开启使用的字体。 这里一般可以使用Cmake编译,如果无法导入识别文件夹,需要修改SConscript脚本,将所有文件夹内的文件包括进来。例如: ``` src += Glob('images/*.c') path += [cwd + '/images'] ``` 调整打印输出的固定数字文本为所需的变量 将lv_label_set_text函数调整为lv_label_set_text_fmt如 ``` lv_label_set_text_fmt(ui_Number_SYS, "%d.%.2d",(int)temp,(int)((temp-(int)temp)*100)); ``` 最终显示结果如下
## 可视化数据显示 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/bf5cac8607c95204cb5ea24a34806c29.png.webp) 基于Onenet View的模版工程进行修改。 ### 曲线模块 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/c50a18b466c26208694ff6db6a9c1232.png.webp) 显示近五个数据的变化,横轴为时间,纵轴为数值,过滤器如下 ``` return [ { "x": data[data.length - 7].at, "y1": data[data.length - 7].value, "y2": "1" }, { "x": data[data.length - 6].at, "y1": data[data.length - 6].value, "y2": "2" }, { "x": data[data.length - 5].at, "y1": data[data.length - 5].value, "y2": "1" }, { "x": data[data.length - 4].at, "y1": data[data.length - 4].value, "y2": "2" }, { "x": data[data.length - 3].at, "y1": data[data.length - 3].value, "y2": "1" }, { "x": data[data.length - 2].at, "y1": data[data.length - 2].value, "y2": "2" }, { "x": data[data.length - 1].at, "y1": data[data.length - 1].value, "y2": "1" } ] ``` ### 当前数据 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/d46e9ded9e56b9621cb18b7c43472898.png) 显示最新的当前数据,过滤器如下 ``` function last(arr) { var len = arr ? arr.length : 0; if (len) return arr[len - 1]; } return [{ value: parseInt(last(data).value) + (parseInt(last(data).value * 100) % 100) * 0.01 }] ``` ### 图像控件 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/2b136be66c0d3987a69a8cf621218e03.png) 光强控件采用简单的柱状图,过滤器如下,控件显示最大光强设置为300 ``` function last(arr) { var len = arr ? arr.length : 0; if (len) return arr[len - 1]; } return [{ x: "lightness", y1: last(data), max:300 }] ``` 湿度空间采用水滴型,最大输出值为1,因此需要归一化,如下所示 ``` function last(arr) { var len = arr ? arr.length : 0; if (len) return arr[len - 1]; } return [{ wave: [last(data).value / 100] }] ``` 温度控件使用温度计,最大输出值也为1,归一化过滤器如下,最高显示温度设置为 ``` function last(arr) { var len = arr ? arr.length : 0; if (len) return arr[len - 1]; } return [{ wave: [last(data).value / 50] }] ``` ###数据处理 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/c739ef8fa80e6a90d15526282ef82d83.png.webp) 对显示的几组数据做简单的处理得到最大值、最小值和平均值,其过滤器分别如下所示,最后一行可将输出转化为整数部分加两位小数部分 最大值 ``` var result = [ data[data.length - 7].value, data[data.length - 6].value, data[data.length - 5].value, data[data.length - 4].value, data[data.length - 3].value, data[data.length - 2].value, data[data.length - 1].value, ] function maxArr(arr) { return Math.max.apply(null, arr); } return [{ value: parseInt( maxArr(result))+( parseInt(maxArr(result) * 100) % 100)*0.01 }] ``` 最小值 ``` var result = [ data[data.length - 7].value, data[data.length - 6].value, data[data.length - 5].value, data[data.length - 4].value, data[data.length - 3].value, data[data.length - 2].value, data[data.length - 1].value, ] function minArr(arr) { return Math.min.apply(null, arr); } return [{ value: parseInt(minArr(result)) + (parseInt(minArr(result) * 100) % 100) * 0.01 }] ``` 平均值 ``` var result = [ data[data.length - 7].value, data[data.length - 6].value, data[data.length - 5].value, data[data.length - 4].value, data[data.length - 3].value, data[data.length - 2].value, data[data.length - 1].value, ] // 这一块的封装,主要是针对数字类型的数组 // 求和 function sumArr(arr) { return arr.reduce(function (pre, cur) { return pre + cur; }); } // 数组平均值,小数点可能会有很多位,这里不做处理 function covArr(arr) { return this.sumArr(arr) / arr.length; } return [{ value: parseInt(sumArr(result) / 7) + (parseInt(sumArr(result) / 7 * 100) % 100) * 0.01 }] ``` # 3. 测试 ## 成功连接wifi ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/6f91dbcb9107ebd606e70a507fa3b81a.png.webp) ## 可视化显示采集
![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/cf7c783ec6acbff52c835bdd155c4f6e.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/54a9e9deecd2e061f60c90e2ae6bb4cd.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/7abcf2818e60459b53349b52bfdcf419.png.webp)
## 常温时依据温度亮灯 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/1ee8ebbbaa4816a7bd3aed04ff194e97.png.webp) ## 温度过高时红灯报警并蜂鸣器响,温度下降后解除警报 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/2f505d86802104c4799ab487712c5b8b.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/bbda9d1e3679bfee16a8153f3fef22d1.png.webp) ## 外界光强较亮时关闭led,外界光强变暗时打开led白灯 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/e80be44888cb8c891817e774a72027cd.png.webp) ## lcd用精美的ui界面显示当前环境以及当前时间 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230725/507790b295eb7d0dcc8259b883c057fc.png.webp)
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
fdq15305978703
这家伙很懒,什么也没写!
文章
2
回答
0
被采纳
0
关注TA
发私信
相关文章
1
studio中onenet包使能自动注册设备功能,编译缺少代码
2
rt-thread如何上传GPS的经纬度到onenet?
3
MQTT 在“ read 0:1, break “后断开重连
4
ONENET+esp8266连接报错
5
有哪位实现了ONENET包获取onenet数据源数据吗
6
请问一下使用BC26对接ONENET的步骤是什么呀
7
AT Client receive failed???
8
onenet示例程序运行异常
9
如何使用OneNet软件包上传2个以上数据流
10
OneNET 浮点数据上传,但小数部分却被截掉
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
出出啊
1518
个答案
343
次被采纳
小小李sunny
1444
个答案
290
次被采纳
张世争
813
个答案
177
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
149
次被采纳
本月文章贡献
出出啊
1
篇文章
5
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
3
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部