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

📅 发布时间:2026/10/22 10:32:35
基于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 传感器接线与采集最小代码温湿度用 SHT30I2C 地址 0x44光照用 BH1750I2C 地址 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 参数示例具体指令集以模组手册为准 ATIDDevEui,0011223344556677 ATIDAppEui,0011223344556677 ATKEYAPPKEY,00112233445566778899AABBCCDDEEFF ATDR0 # 数据速率CN470 下 0 对应 SF12距离远但速率低 ATCH0,470.3 # 设置信道频率需与网关一致 ATJOIN1 # 发起 OTAA 入网逻辑说明DevEUI 和 AppEUI 各 8 字节AppKey 16 字节都是十六进制字符串。ATDR 设的是数据速率SF12 灵敏度最高但单包空中时间能到 1.5 秒以上如果节点多、上报频繁要往上调 DR 换速率。ATJOIN 之后模组会回 JOIN:1 表示成功失败会回 JOIN:0 并带错误码常见错误码 5 是密钥不匹配6 是网关没响应。参数改完建议 ATSAVE 存一下否则断电就回默认值——这个和后面掉电存储是两码事别混。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现象ATJOIN 返回 JOIN:0,6。原因网关没收到入网请求或者频段/子频段不匹配。解决先用 ATCH 确认信道频率和网关一致再看网关日志有没有收到 Join Request。如果网关日志空的检查天线接好没有、模组发射功率是不是被设成了最低。这个错误码 6 我遇到过三次两次是天线没拧紧一次是网关频段设错。5.3 触摸屏拖动滑块后配置没保存现象改完参数当时生效断电重启又回旧值。原因事件回调监听的是 VALUE_CHANGED拖动过程中写了几十次最后一次可能因为 Flash 忙没写进去或者 config_save 里没等写完成就返回。解决改成 LV_EVENT_RELEASED 触发config_save 里加写完成等待或状态查询确认写入成功再返回。5.4 历史数据上电后全部丢失现象断电前明明存了数据上电后一条都读不出来。原因环形缓冲区的写指针没有持久化上电后从 0 开始扫描而数据是从中间某地址开始写的前面全是 0xFFmagic 不匹配就全跳过了。解决写指针也要定期存到 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 次看数据丢不丢、配置乱不乱。希望帮到你。本文还有配套的精品资源点击获取