news 2026/10/22 10:32:35

基于LoRaWAN的环境监测系统:触摸屏配置与掉电存储实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于LoRaWAN的环境监测系统:触摸屏配置与掉电存储实现

简介:这份资源面向嵌入式开发与物联网方向的学习者,提供一套基于LoRaWAN技术的环境监测系统完整工程源码,可用于温湿度、光照等环境数据的采集与远程监控场景。系统支持触摸屏参数配置、数据可视化展示,并具备掉电存储与上电恢复能力,适合作为课程设计、毕业设计或LoRaWAN入门实战的参考方案。压缩包共299个文件,约23.07MB,以C语言源文件与头文件为核心,配合STM32L4系列HAL库驱动、Keil工程文件、链接脚本及少量说明文档,覆盖传感器采集、无线通信、LCD显示与数据存储等模块。资源中已包含STM32L4xx HAL底层驱动与Lcd_App等应用层代码,便于读者理解从硬件驱动到业务逻辑的完整组织方式。目前已有64人学习下载,适合具备一定嵌入式基础、希望快速搭建LoRaWAN环境监测原型的开发者参考借鉴。

1. 从一块触摸屏和一根天线说起:LoRaWAN 环境监测到底在做什么

去年帮一个做农业大棚的团队调一套环境监测设备,现场情况很典型:棚区跨度接近 800 米,中间隔着两排钢结构厂房,Wi-Fi 信号出了办公室就断,4G 模块在棚里信号只有一格,数据传着传着就丢。他们最后选的路子就是 LoRaWAN——终端节点采集温湿度、光照,通过网关汇聚,再落到本地触摸屏和后台做可视化。这套「基于 LoRaWAN 物联网技术的环境监测系统」要解决的核心问题其实就三件事:数据怎么稳定采上来、参数怎么在现场改、掉电之后数据怎么不丢。

标题里几个关键词拆开看,指向的是完整的一条链路:LoRaWAN 负责低功耗远距离传输,温湿度光照是采集对象,触摸屏是本地人机交互入口,掉电存储与上电恢复是数据可靠性兜底,利尔达 LoRaWAN 模组则是常见的国产选型方案。适合谁看?做工业现场监测、农业环境采集、仓储温湿度记录的嵌入式开发者,尤其是那些被无线距离和断电丢数据折腾过的团队。下面按「先讲清选型逻辑,再落到能跑的代码和参数」的顺序展开,中间会穿插我踩过的坑。

2. 硬件选型与 LoRaWAN 入网:模组、传感器、网关怎么配

2.1 为什么是 LoRaWAN 而不是 Wi-Fi 或纯 LoRa 点对点

现场无线方案无非几种:Wi-Fi 覆盖短、功耗高,电池供电节点撑不过一周;ZigBee 要组 mesh,节点多了路由维护成本高;纯 LoRa 点对点简单,但节点一多就冲突,且没有标准入网机制。LoRaWAN 的价值在于它把「物理层 LoRa 调制」和「MAC 层网络管理」分开了,终端只管发,网关负责汇聚和转发,入网有 OTAA/ABP 两种激活方式,节点容量和功耗都可控。

选利尔达这类模组的原因也直接:它把 LoRaWAN 协议栈和 AT 指令封装好了,主控通过 UART 发指令就能入网和收发,不用自己啃协议栈。常见做法是主控用 STM32 或 ESP32,模组走 UART,传感器走 I2C 或单总线。这里要注意,模组供电必须干净,LoRa 发射瞬间电流能到 120mA 左右,如果和传感器共用一路 LDO 且滤波不足,采集值会跳变——这个坑后面避坑章节细说。

2.2 传感器接线与采集最小代码

温湿度用 SHT30(I2C 地址 0x44),光照用 BH1750(I2C 地址 0x23),两者都挂同一条 I2C 总线,上拉电阻 4.7k 到 3.3V。下面是一段基于 STM32 HAL 的采集代码骨架,重点看时序和单位换算。

// SHT30 单次采集,高重复性,无时钟拉伸 // 命令 0x2C06:高重复性测量,主机读 6 字节 uint8_t cmd[2] = {0x2C, 0x06}; HAL_I2C_Master_Transmit(&hi2c1, 0x44 << 1, cmd, 2, 100); HAL_Delay(15); // 高重复性测量典型耗时 12.5ms,留余量 uint8_t buf[6]; HAL_I2C_Master_Receive(&hi2c1, 0x44 << 1, buf, 6, 100); // 温度换算:-45 + 175 * raw / 65535 float temp = -45.0f + 175.0f * ((buf[0] << 8) | buf[1]) / 65535.0f; // 湿度换算:100 * raw / 65535 float humi = 100.0f * ((buf[3] << 8) | buf[4]) / 65535.0f; // BH1750 连续高分辨率模式,命令 0x10 uint8_t mode = 0x10; HAL_I2C_Master_Transmit(&hi2c1, 0x23 << 1, &mode, 1, 100); HAL_Delay(180); // 高分辨率模式转换时间约 120ms,留余量 uint8_t luxBuf[2]; HAL_I2C_Master_Receive(&hi2c1, 0x23 << 1, luxBuf, 2, 100); float lux = ((luxBuf[0] << 8) | luxBuf[1]) / 1.2f; // 除以 1.2 得 lx

逻辑说明:SHT30 发送测量命令后必须等转换完成再读,读到的 6 字节里前 3 字节是温度(含 CRC),后 3 字节是湿度,这里为了简洁跳过了 CRC 校验,实际产品里建议校验。BH1750 的 1.2 是数据手册给的换算系数,不同模式系数不同,连续高分辨率模式就是 1.2。参数上,I2C 速率建议 100kHz,太快了长线容易出错;HAL_Delay 的余量不能省,省了会读到上一次的旧值。

2.3 LoRaWAN 入网参数与 AT 指令流程

模组出厂默认可能是 ABP,但现场部署我更推荐 OTAA,因为 OTAA 每次入网会协商新的会话密钥,安全性更好,且网关侧管理清晰。需要配置的核心参数就四个:DevEUI、AppEUI(有些叫 JoinEUI)、AppKey、频段。频段国内常用 CN470,具体子频段要跟网关对齐。

# 通过 UART 发 AT 指令配置 OTAA 参数(示例,具体指令集以模组手册为准) AT+ID=DevEui,"0011223344556677" AT+ID=AppEui,"0011223344556677" AT+KEY=APPKEY,"00112233445566778899AABBCCDDEEFF" AT+DR=0 # 数据速率,CN470 下 0 对应 SF12,距离远但速率低 AT+CH=0,470.3 # 设置信道频率,需与网关一致 AT+JOIN=1 # 发起 OTAA 入网

逻辑说明:DevEUI 和 AppEUI 各 8 字节,AppKey 16 字节,都是十六进制字符串。AT+DR 设的是数据速率,SF12 灵敏度最高但单包空中时间能到 1.5 秒以上,如果节点多、上报频繁,要往上调 DR 换速率。AT+JOIN 之后模组会回 +JOIN:1 表示成功,失败会回 +JOIN:0 并带错误码,常见错误码 5 是密钥不匹配,6 是网关没响应。参数改完建议 AT+SAVE 存一下,否则断电就回默认值——这个和后面掉电存储是两码事,别混。

3. 触摸屏参数配置与数据可视化:本地交互怎么不卡

3.1 触摸屏选型与串口屏 vs RGB 屏的取舍

现场触摸屏一般两种:一种是串口屏(自带 UI 组态软件,主控发指令切页面、改数值),一种是 RGB/MIPI 屏配 LVGL 自己画。串口屏开发快,但灵活性差,复杂图表做不了;LVGL 灵活,但要占主控资源,刷新率和触摸响应要调。我的经验是:如果只是显示几个数值、几个配置项,串口屏够用且省事;如果要画历史曲线、多页面联动,上 LVGL。

这里以 LVGL 为例,因为它更能体现「参数配置 + 可视化」的完整逻辑。主控建议至少 8MB PSRAM 或外扩 SDRAM,否则双缓冲刷不起来。

3.2 参数配置页面的数据绑定与写入

配置页面要改的参数通常有:上报周期、温度上限报警值、湿度下限报警值、LoRa 数据速率。这些值不能只存在内存里,改完要写进 Flash 或 EEPROM,否则掉电就丢。下面是一段 LVGL 事件回调,把滑块值写进配置结构体并触发存储。

// 配置结构体,固定长度,方便整体读写 typedef struct { uint32_t reportInterval; // 上报周期,秒 int16_t tempHigh; // 温度上限,0.1℃ 为单位 int16_t humiLow; // 湿度下限,0.1% 为单位 uint8_t dataRate; // LoRa DR uint32_t crc; // 结构体校验 } sys_config_t; static sys_config_t g_cfg; // 滑块事件回调:拖动结束才写,避免频繁擦写 Flash static void slider_event_cb(lv_event_t *e) { lv_obj_t *slider = lv_event_get_target(e); if (lv_event_get_code(e) == LV_EVENT_RELEASED) { g_cfg.reportInterval = lv_slider_get_value(slider); g_cfg.crc = calc_crc32(&g_cfg, sizeof(g_cfg) - 4); config_save(&g_cfg); // 写入 Flash } }

逻辑说明:关键点在 LV_EVENT_RELEASED,只在手指松开时写一次,如果监听 VALUE_CHANGED,拖动过程中会触发几十次写操作,Flash 寿命很快耗尽。crc 字段放在结构体末尾,保存前算一次,读取时校验,校验不过就加载默认值。参数单位统一用整数(0.1℃、0.1%),避免浮点存储的对齐和精度问题。

3.3 数据可视化:实时曲线怎么画不卡顿

LVGL 画曲线用 lv_chart,数据点不要无限追加,固定窗口比如 60 个点,新数据来了左移覆盖。刷新周期和采集周期解耦:采集可以 1 秒一次,图表刷新 500ms 一次就够,刷太快 CPU 全耗在重绘上。

// 初始化图表:60 个点,两条曲线 lv_obj_t *chart = lv_chart_create(lv_scr_act()); lv_chart_set_type(chart, LV_CHART_TYPE_LINE); lv_chart_set_point_count(chart, 60); lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100); lv_chart_series_t *ser_temp = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y); lv_chart_series_t *ser_humi = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y); // 定时器里刷新,500ms 一次 void chart_timer_cb(lv_timer_t *t) { lv_chart_set_next_value(chart, ser_temp, (int)current_temp); lv_chart_set_next_value(chart, ser_humi, (int)current_humi); lv_chart_refresh(chart); }

逻辑说明:lv_chart_set_next_value 会自动滚动,不用手动移数组。范围设 0 到 100 是因为温湿度都落在这个区间,光照如果也要画,得单独开一个 Y 轴或者归一化。刷新定时器周期别低于 300ms,低于这个值人眼也看不出差别,纯浪费 CPU。如果发现触摸响应变慢,先查图表刷新是不是太频繁,这是最常见的卡顿来源。

4. 掉电存储与上电恢复:数据不丢的完整实现

4.1 存储介质选型:内部 Flash、EEPROM 还是外部 Flash

三种都用过,说下边界。内部 Flash 擦写寿命一般 1 万次左右,适合存配置参数,不适合存高频采集数据;EEPROM(如 AT24C02)寿命 100 万次,容量小,适合存配置和少量历史;外部 SPI Flash(如 W25Q64)容量大、单位成本低,适合存历史数据,但擦除是按扇区的,要自己做磨损均衡。

我的方案是:配置参数存内部 Flash 最后一页,历史数据存外部 SPI Flash 的环形缓冲区。这样配置读写少,历史数据写入频繁也不怕。

4.2 环形缓冲区与掉电保护

历史数据用环形缓冲区,写指针走到头就回绕。关键问题是掉电瞬间正在写的那条记录可能只写了一半,上电后要能识别并丢弃。做法是每条记录加一个「有效标志」,写完数据再写标志,标志写成功才算这条有效。

#define RECORD_SIZE 16 #define RECORD_MAGIC 0xA5 typedef struct { uint32_t timestamp; int16_t temp; int16_t humi; uint16_t lux; uint8_t magic; // 有效标志,最后写 uint8_t reserved; } record_t; // 写入一条记录:先写数据区,再写 magic void record_write(uint32_t addr, record_t *rec) { rec->magic = 0x00; spi_flash_write(addr, (uint8_t *)rec, RECORD_SIZE); // 先写无效状态 rec->magic = RECORD_MAGIC; spi_flash_write(addr + offsetof(record_t, magic), &rec->magic, 1); // 再写标志 } // 上电扫描:magic 不对的记录直接跳过 bool record_valid(record_t *rec) { return rec->magic == RECORD_MAGIC; }

逻辑说明:offsetof 拿到 magic 字段在结构体里的偏移,只写这一个字节,避免整条重写。上电恢复时从缓冲区头部扫描,遇到 magic 不对的就认为这条无效,从下一条继续。参数上,RECORD_SIZE 取 16 是为了对齐 Flash 页,实际字段只用了 13 字节,留 3 字节余量方便以后加字段。

4.3 上电恢复流程与配置加载

上电后的顺序很重要:先初始化存储,再加载配置,配置校验不过就用默认值,最后恢复历史数据指针。顺序错了会出现用未初始化配置去读数据的情况。

void system_boot(void) { flash_init(); if (!config_load(&g_cfg) || !config_check_crc(&g_cfg)) { config_set_default(&g_cfg); // 校验失败,加载默认 config_save(&g_cfg); } history_init(); // 扫描环形缓冲区,定位有效记录范围 lvgl_init(); lora_init(&g_cfg); // 用配置里的 DR 初始化模组 }

逻辑说明:config_load 返回 false 表示读失败(比如首次上电 Flash 是空的),config_check_crc 返回 false 表示数据损坏,两种情况都走默认值。history_init 里做的是扫描 magic,确定写指针位置。lora_init 放在最后,因为它依赖配置里的 dataRate,顺序不能提前。

5. 避坑与排查:那些让系统「看起来正常」的隐性故障

5.1 采集值随机跳变,重启就好

现象:温湿度偶尔跳到 85℃ 或 0%,重启后恢复。原因:LoRa 发射瞬间电流拉低电源,传感器供电纹波变大,I2C 通信出错读到异常值。解决:传感器和模组分开 LDO 供电,模组电源脚就近放 100uF 加 0.1uF,采集前先判断数值是否在物理合理范围(温度 -40 到 80,湿度 0 到 100),超范围直接丢弃重采。

5.2 OTAA 入网一直失败,错误码 6

现象:AT+JOIN 返回 +JOIN:0,6。原因:网关没收到入网请求,或者频段/子频段不匹配。解决:先用 AT+CH 确认信道频率和网关一致,再看网关日志有没有收到 Join Request。如果网关日志空的,检查天线接好没有、模组发射功率是不是被设成了最低。这个错误码 6 我遇到过三次,两次是天线没拧紧,一次是网关频段设错。

5.3 触摸屏拖动滑块后配置没保存

现象:改完参数当时生效,断电重启又回旧值。原因:事件回调监听的是 VALUE_CHANGED,拖动过程中写了几十次,最后一次可能因为 Flash 忙没写进去;或者 config_save 里没等写完成就返回。解决:改成 LV_EVENT_RELEASED 触发,config_save 里加写完成等待或状态查询,确认写入成功再返回。

5.4 历史数据上电后全部丢失

现象:断电前明明存了数据,上电后一条都读不出来。原因:环形缓冲区的写指针没有持久化,上电后从 0 开始扫描,而数据是从中间某地址开始写的,前面全是 0xFF,magic 不匹配就全跳过了。解决:写指针也要定期存到 Flash 固定位置,上电先读指针再扫描;或者扫描整个缓冲区找 magic,但这样上电慢。我一般用前者,指针每写 100 条存一次,最多丢 100 条。

5.5 图表刷新导致触摸失灵

现象:数据量大了以后,触摸屏点不动或者响应延迟明显。原因:图表刷新和触摸扫描在同一个任务里,刷新占用时间过长,触摸事件被阻塞。解决:把图表刷新放到独立定时器或低优先级任务,触摸扫描保持高优先级;或者降低刷新频率到 500ms 以上。如果还不行,检查是不是开了抗锯齿,关了能省不少 CPU。

6. 进阶技巧:把上报周期和功耗压到电池能撑一年

前面讲的都是功能跑通,这一章说下怎么把整套系统做到电池供电能撑一年以上。核心就一句话:让节点尽可能长时间睡觉,醒来的时间尽可能短。

先算一笔账。假设用 2000mAh 的锂亚电池,目标一年,平均电流要控制在 0.23mA 以内。LoRa 模组发射时电流约 120mA,接收约 10mA,睡眠约 1uA。如果每 5 分钟上报一次,每次发射空中时间按 SF12 算约 1.5 秒,那一天发射 288 次,总发射时间 432 秒,发射平均电流贡献是 120mA × 432s / 86400s ≈ 0.6mA,已经超了。所以要么降低上报频率,要么提高数据速率缩短空中时间。

我的做法是:上报周期设 15 分钟,数据速率从 SF12 提到 SF9,空中时间降到约 0.3 秒。这样一天发射 96 次,总发射时间 28.8 秒,发射平均电流约 0.04mA。传感器采集每次约 200ms,平均电流可忽略。主控在两次上报之间进 STOP 模式,RTC 唤醒,STOP 模式电流约 2uA。这样算下来平均电流远低于 0.23mA,电池撑一年有余。

// 主循环:采集、上报、然后进 STOP 模式等 RTC 唤醒 void main_loop(void) { sensor_read(&temp, &humi, &lux); record_write(next_addr(), &rec); // 存历史 lora_send(&temp, &humi, &lux); // 上报 enter_stop_mode(REPORT_INTERVAL_SEC); // RTC 定时唤醒 } // 进 STOP 前要做的三件事 void enter_stop_mode(uint32_t sec) { lora_sleep(); // 模组进睡眠 sensor_power_off(); // 传感器断电,别让它漏电 rtc_set_wakeup(sec); // 设 RTC 唤醒 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); system_clock_reconfig(); // 唤醒后重配时钟 }

逻辑说明:enter_stop_mode 里三件事的顺序有讲究。先让模组睡,再断传感器电,最后设 RTC。如果先设 RTC 再断传感器,中间那点时间传感器还在耗电。唤醒后必须重配时钟,因为 STOP 模式会切到内部低速时钟,不重配的话串口和 I2C 速率全乱。这个坑我踩过,唤醒后串口打印乱码,查了半天才发现是时钟没恢复。

还有一个技巧是数据聚合上报。如果现场对实时性要求不高,可以把 15 分钟内的采集数据攒起来,一次上报多条,减少发射次数。但要注意 LoRaWAN 单包载荷有限,CN470 下 DR0 最大 51 字节,DR5 能到 242 字节,攒数据前先确认当前 DR 的载荷上限。我一般攒 4 条报一次,载荷控制在 64 字节以内,兼容性最好。

最后说个验证方法:别只看理论算的电流,实际用高精度电流表或者带电流记录的电源测一整天。我见过理论算 0.1mA、实测 0.8mA 的情况,原因是传感器断电不彻底,或者模组睡眠指令没生效。测的时候把万用表串在电池正极,记录 24 小时,看平均电流和峰值,峰值超标就查发射时序,平均超标就查睡眠漏电。

这套东西做下来,最深的教训是:别信「应该没问题」,每个环节都要实测。天线要测驻波,电源要测纹波,睡眠要测电流,存储要测掉电。我现在的习惯是每改一版硬件,先跑 72 小时老化,中间人为断电 10 次,看数据丢不丢、配置乱不乱。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 10:32:01

Hinton RSI论文、陶哲轩转向与ChatGPT签名伪造:AI治理三大信号

1. 三条新闻背后的技术信号拆解2026年10月6日这一天&#xff0c;AI圈子里集中爆出了三条值得细品的消息&#xff1a;Hinton首发了一篇关于RSI的论文、陶哲轩公开转向AI减速派、ChatGPT被曝出伪造漫画家签名。这三件事单独看各有各的讨论角度&#xff0c;但放在同一天来看&#…

作者头像 李华
网站建设 2026/10/22 10:31:25

自动驾驶多视角传感器融合:从BEV空间到工程落地的关键路径

1. 这不是“加个摄像头就完事”的融合——自动驾驶多视角传感器数据融合到底在解决什么问题&#xff1f;你有没有注意过&#xff0c;一辆自动驾驶测试车的车顶像长满了触角&#xff1f;激光雷达、毫米波雷达、前视/环视/后视多个摄像头&#xff0c;甚至还有超声波探头。但很多人…

作者头像 李华
网站建设 2026/10/22 10:30:41

3D建模怎么选?NURBS与Polygon、CAD与DCC底层逻辑全解析

前几天有个朋友来问我&#xff1a;“想学3D建模&#xff0c;NURBS和Polygon到底哪个好&#xff1f;CAD和DCC又是什么意思&#xff1f;”这个问题问得特别典型&#xff0c;几乎每个刚入坑3D的人都会卡在这一步。我在这个行当里待了十几年&#xff0c;带过不少新人&#xff0c;见…

作者头像 李华
网站建设 2026/10/22 10:30:31

MATLAB FHSS仿真:两个文件搞定跳频扩频与频谱分析

简介&#xff1a;这份资源面向无线通信学习者与MATLAB仿真入门者&#xff0c;围绕FHSS&#xff08;频率跳变扩频&#xff09;技术提供配套代码实践&#xff0c;帮助理解跳频序列生成、调制解调与频谱分析等核心环节。压缩包为rar格式&#xff0c;共2个文件&#xff0c;均为m脚本…

作者头像 李华
网站建设 2026/10/22 10:29:55

TensorFlow Lite移动端集成:从模型转换到实时推理的工程实践

1. 这不是“把模型塞进手机”那么简单&#xff1a;TensorFlow Lite集成的真实战场 “第17章&#xff1a;TensorFlow Lite集成”——光看这个标题&#xff0c;很多人第一反应是&#xff1a;“哦&#xff0c;又一个教你怎么把训练好的模型转成.tflite文件的教程”。但如果你真这么…

作者头像 李华
网站建设 2026/10/22 10:29:12

上下文工程实战:如何让大模型输出更稳定、成本更低

1. 从一个真实困境说起&#xff1a;为什么模型“变笨”了很多人应该都遇到过这种场景&#xff1a;同一个模型&#xff0c;同一套接口&#xff0c;昨天跑得好好的任务&#xff0c;今天换了个问法就答非所问&#xff1b;或者在一个多轮对话里&#xff0c;聊到第五六轮之后&#x…

作者头像 李华