任务介绍
赛道四:低功耗物联网
【进阶级题目:LoRa远程环境监测终端】
外接LoRa通信模块(如SX1262)及环境传感器,实现远距离环境数据采集与无线传输,空旷环境通信距离1km以上,支持远程参数配置及监测数据上传。
推荐开发板:MAX32650FTHR、MAX32690EVKIT
项目描述
该项目旨在基于MAX32650FTHR板卡,连接lora模块以及温湿度传感器,实现一个低功耗温湿度数据采集节点,并实时上报数据到上位机。并且可通过上位机对节点进行远程配置。
简短的硬件介绍
1. MAX32650FTHR 开发板
MAX32650FTHR 是 Maxim Integrated 推出的 MAX32650 微控制器评估开发板,主要用于高性能、低功耗及电池供电嵌入式系统的开发与验证。其核心 MAX32650 采用 Arm Cortex-M4 处理器并集成浮点运算单元 FPU,最高主频为 120 MHz,片内集成 3 MB Flash 和 1 MB SRAM,能够满足较复杂的数据采集、处理和控制任务。开发板支持最高 105 个 GPIO,并具备 SmartDMA 功能,可进行后台存储器数据传输。板载资源包括电池接口及充电电路、Micro-SD 卡接口、USB 2.0 Full-Speed Device 接口以及 MAX11261 六通道 24 位 ADC,同时兼容 Adafruit Feather 板卡规格。MAX32650FTHR 还提供 I²C、SPI、UART 等常用通信接口,其中板载 ADC 通过 I²C 与 MCU 相连,Micro-SD 通过 Quad-SPI 连接,并提供按键、LED 和用于调试的 UART 接口,适合作为嵌入式数据采集与通信系统的主控制平台。
2. SHT30 温湿度传感器
SHT30 是 Sensirion 推出的 SHT3x 系列数字温湿度传感器,属于该系列中的经济型产品,可同时完成环境相对湿度和温度的数字化测量。SHT30 采用 I²C 数字通信接口,能够直接与微控制器进行数据通信,适合应用于环境监测及嵌入式传感系统。根据 Sensirion 官方 SHT30-DIS-B 产品资料,其相对湿度测量范围为 0~100 %RH,典型相对湿度精度为 ±2 %RH,湿度响应时间约为 8 s;温度工作范围为 -40~125 ℃,典型温度精度为 ±0.2 ℃。器件供电电压范围为 2.15~5.5 V,平均供电电流约 1.7 μA,尺寸约为 2.5 mm × 2.5 mm × 0.9 mm。SHT30 在出厂时经过校准,采用集成式数字传感结构,可通过较少的外围电路实现温度和湿度测量,具有体积小、功耗低以及数字接口使用方便等特点。
3. E220-400T22D LoRa 模块
E220-400T22D 是成都亿佰特 E220-T 系列中的 LoRa UART 无线通信模块,基于 Semtech 高性能射频芯片设计,采用新一代 LoRa 扩频调制技术,具有通信距离远、抗干扰能力强等特点。模块使用 UART 串口与外部控制器通信,串口及 IO 通信电平为 3.3 V。E220-400T22D 工作频率范围为 410~493 MHz,最大发射功率为 22 dBm(约 158 mW),采用直插式封装,尺寸约 21 mm × 36 mm,配置 SMA-K 天线接口。官方给出的参考测试距离为 5 km,该距离是在晴朗空旷、空中速率 2.4 kbps、天线架高 2 m 条件下测得。模块空中速率可在 2.4~62.5 kbps 范围内调节,22 dBm 型号接收灵敏度可达 -126 dBm。模块支持 AT 和 HEX 指令、RSSI 信号强度指示、定点传输、广播传输、空中唤醒以及参数掉电保存等功能,并可通过 M0、M1 引脚选择不同工作模式。
方案框图 + 设计思路
在本设计中,以 MAX32650FTHR 作为整个系统的核心控制器,主要负责温湿度数据采集、无线通信以及低功耗管理。通过 I²C 接口连接 SHT30 温湿度传感器,由 MAX32650FTHR 按照设定的采样周期读取环境温度和湿度数据。采集完成后,将温湿度及系统状态等信息进行封装,并通过 UART 发送给 LoRa 模块,再由 LoRa 模块将数据无线传输到接收端。
为了实现低功耗,在程序中加入了 RTC 定时唤醒机制。当一次数据采集和无线发送完成后,MAX32650FTHR 进入低功耗状态,同时使 E220 进入休眠模式;到达设定时间后,再由 RTC 唤醒系统,重新执行采集和发送流程。
通信方式上,节点向上位机上传温湿度和运行状态外,上位机还可以向节点下发采样周期、采集开关等参数,节点接收后进行更新并返回 ACK。接收端通过另一只 E220 接收数据,再通过串口与 PC 相连,最终由网页上位机完成数据查看、节点状态监测以及参数配置。

调试软件说明
CodeFusion Studio(CFS) 是 Analog Devices(ADI)推出的一套嵌入式软件开发平台,基于 Visual Studio Code 构建,主要面向 ADI 的微控制器、处理器及异构多核器件开发。它将代码编辑、工程配置、编译、下载、调试等功能集中到统一的开发环境中,并支持命令行工作流程。
CodeFusion Studio 支持 Zephyr RTOS,同时提供引脚、时钟、外设和系统资源配置工具,以及寄存器查看、ELF 文件分析和多核调试等功能,可以减少嵌入式开发过程中手动配置工具链和外设的工作量。对于复杂的多核系统,还能够在同一开发环境中进行资源分配和联合调试。
目前 CodeFusion Studio 已支持 MAX32650 等 ADI 微控制器,因此可以用于 MAX32650FTHR 的程序开发。在本项目中,CodeFusion Studio 主要用于创建和管理 Zephyr 工程、配置 I²C、UART、USB 和低功耗相关功能,并完成程序的编译、烧录和调试。


软件流程图
关键代码说明
- lora模块
本次是基于zephyr进行开发的,所以依旧是根据数据手册对于e220模块进行设备注册并编写对饮的驱动代码,sht30的话可以直接在iic中进行设备注册即可,可以像调用espidf组件库类似的方式直接直接调用官方接口进行数据读取
e220: lora {
compatible = "ebyte,e220-t";
m0-gpios = <&gpio1 23 GPIO_ACTIVE_HIGH>; /* E220 pin 1: M0 */
m1-gpios = <&gpio1 24 GPIO_ACTIVE_HIGH>; /* E220 pin 2: M1 */
status = "okay";
};
description: EBYTE E220-T UART LoRa module
compatible: "ebyte,e220-t"
include: base.yaml
on-bus: uart
properties:
m0-gpios:
type: phandle-array
required: true
description: E220 M0 mode selection pin
m1-gpios:
type: phandle-array
required: true
description: E220 M1 mode selection pin
sht30: sht30@44 {
compatible = "sensirion,sht3xd";
reg = <0x44>;
status = "okay";
};
这里仅贴出实现的几个接口
uint16_t app_crc16_ccitt(const uint8_t *data, size_t len); // 计算数据的 CRC16-CCITT 校验值
int app_frame_encode(const struct app_frame *frame, uint8_t *out, size_t out_size, size_t *out_len); // 将应用层数据帧编码为字节流
int app_frame_decode(const uint8_t *buf, size_t len, struct app_frame *frame, size_t *consumed); // 从字节流中解析应用层数据帧
int app_encode_config_request_payload(const struct app_config_request *cfg, uint8_t *out, size_t out_size, uint8_t *out_len); // 编码配置请求数据载荷
int app_decode_config_request_payload(const uint8_t *payload, size_t len, struct app_config_request *cfg); // 解码配置请求数据载荷
int app_encode_telemetry_payload(const struct app_telemetry *telemetry, uint8_t *out, size_t out_size, uint8_t *out_len); // 编码遥测数据载荷
int app_decode_telemetry_payload(const uint8_t *payload, size_t len, struct app_telemetry *telemetry); // 解码遥测数据载荷
int app_encode_config_ack_payload(const struct app_config_ack *ack, uint8_t *out, size_t out_size, uint8_t *out_len); // 编码配置应答数据载荷
int app_decode_config_ack_payload(const uint8_t *payload, size_t len, struct app_config_ack *ack); // 解码配置应答数据载荷
把 驱动树里定义好的 E220 模块节点,自动注册成 Zephyr 的 device 设备实例,
/*
* 为每一个 compatible="ebyte,e220-t" 且 status="okay" 的 Devicetree 节点
* 自动生成 Zephyr device 实例。
*
* cfg 保存静态硬件资源,data 保存运行时状态。
*/
#define E220_DEFINE(inst) \
static struct e220_dev_data e220_data_##inst; \
static const struct e220_dev_config e220_cfg_##inst = { \
.uart = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
.m0 = GPIO_DT_SPEC_INST_GET(inst, m0_gpios), \
.m1 = GPIO_DT_SPEC_INST_GET(inst, m1_gpios), \
}; \
DEVICE_DT_INST_DEFINE(inst, e220_init, NULL, &e220_data_##inst, &e220_cfg_##inst, \
POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY, NULL);
/* 对所有启用的 E220 Devicetree 实例执行 E220_DEFINE。 */
DT_INST_FOREACH_STATUS_OKAY(E220_DEFINE)
- 主程序
当前程序采用 Zephyr 多线程方式组织系统功能,主要将温湿度采集和 LoRa 无线通信分离处理。主函数完成 GPIO、I²C、SHT30、UART、E220 以及 RTC 等硬件资源的初始化,并创建传感器采集线程和无线通信线程。采集线程按照设定的采样周期读取 SHT30 的温度和湿度数据,并将最新采样结果保存到共享数据区;无线通信线程负责 E220 的工作模式切换、数据帧封装、CRC16 校验、遥测数据和心跳数据发送,同时处理上位机下发的配置命令及配置应答。通过线程划分,使传感器采集和无线通信相互独立,减少阻塞影响,也便于系统在任务空闲时进入低功耗状态。
K_THREAD_DEFINE(sensor_tid, SENSOR_THREAD_STACK_SIZE, sensor_thread, NULL, NULL, NULL,
SENSOR_THREAD_PRIORITY, 0, 0);
K_THREAD_DEFINE(radio_tid, RADIO_THREAD_STACK_SIZE, radio_thread, NULL, NULL, NULL,
RADIO_THREAD_PRIORITY, 0, 0);
static void radio_thread(void *p1, void *p2, void *p3)
{
while (1) {
bool heartbeat;
int64_t now;
/* 等待新采样;超时说明采样周期比心跳周期还长,或者采集已经关闭。 */
if (k_sem_take(&sample_ready, K_SECONDS(HEARTBEAT_PERIOD_S)) != 0) {
LOG_DBG("no sample in %u s, send keep-alive heartbeat", HEARTBEAT_PERIOD_S);
}
now = k_uptime_get();
heartbeat = (now - last_heartbeat_ms) >= (int64_t)(HEARTBEAT_PERIOD_S * 1000);
pm_busy_lock();
/* 通信开始:E220 从模式3(休眠)切到模式0(透明传输)。 */
if (e220_set_mode(e220_dev, E220_MODE_TRANSPARENT) == 0) {
(void)e220_flush_rx(e220_dev);
int send_ret = radio_send_telemetry(heartbeat); /* 采样数据;到点就与心跳合并成同一包 */
if (send_ret < 0) {
LOG_ERR("telemetry send failed: %d", send_ret);
} else if (heartbeat) {
/* 只有心跳真正写入 UART 并完成发送等待后,才推进心跳周期。 */
last_heartbeat_ms = now;
radio_config_window(); /* 配置窗口:处理上位机配置并回 ACK */
}
}
/* 通信结束:E220 回到模式3,线程阻塞等待下一次采样。 */
(void)e220_set_mode(e220_dev, E220_MODE_SLEEP);
pm_busy_unlock();
}
}
static void sensor_thread(void *p1, void *p2, void *p3)
{
while (1) {
bool enabled;
uint32_t interval_s;
k_mutex_lock(&state_lock, K_FOREVER);
enabled = sampling_enabled;
interval_s = sampling_interval_s;
k_mutex_unlock(&state_lock);
if (!enabled) {
/* 采集已关闭:阻塞等待配置重新使能。 */
(void)k_sem_take(&sensor_wake, K_FOREVER);
continue;
}
sensor_sample_once();
/* 采样周期到期返回 -EAGAIN,配置变化被提前唤醒才返回 0。 */
if (k_sem_take(&sensor_wake, K_SECONDS(interval_s)) == 0) {
continue;
}
}
}
int main(void)
{
if (!device_is_ready(e220_dev) || !device_is_ready(sht30_dev)) {
LOG_ERR("device not ready");
return -1;
}
k_mutex_init(&state_lock);
/* 平时状态:E220 停在模式3,射频关闭。 */
(void)e220_set_mode(e220_dev, E220_MODE_SLEEP);
LOG_INF("low power sensor node started");
while (1) {
k_sleep(K_FOREVER);
}
return 0;
}
- 网页上位机

- 低功耗策略
系统低功耗设计主要由 MAX32650 的 Zephyr 电源管理、E220 休眠控制以及无线数据发送策略三部分组成。程序启用 Zephyr 的电源管理机制,当采集线程和无线通信线程均处于等待状态、系统没有待处理任务时,内核可以自动进入适合的低功耗状态;RTC 或其他中断事件到来后,再唤醒处理器继续执行任务。、
对于 E220 模块,系统在需要进行无线通信时将其切换到正常透明传输模式,完成数据发送和配置接收后重新进入休眠模式。这样可以避免 LoRa 模块长期保持接收或发送状态,进一步降低节点的平均功耗。整个节点形成“休眠 → 定时唤醒 → 采集 → 无线通信 → 再次休眠”的周期运行过程。
- 系统既需要周期上传温湿度数据,也需要通过心跳包维持节点与上位机之间的在线状态。如果采集包和心跳包分别独立发送,会增加 E220 唤醒次数和无线发送次数,因此程序采用合并发送的思路,只有在较长时间没有产生新的采样数据时,系统才单独发送心跳包。不过这样也会造成一个弊端,心跳包可能不再是固定60s发送一下,不过影响不大
- 心跳包与采集包的合并发送
- 由于节点大部分时间处于低功耗状态,上位机不能在任意时刻直接向节点发送配置数据,否则节点可能正在休眠而无法接收。因此利用心跳包或遥测包建立配置接收窗口。节点发送心跳包或采集包后,不会立即让 E220 进入休眠,而是保持一段时间的接收状态。上位机收到节点数据后即可确认节点当前已经唤醒,立即发送配置包,将新的配置下发。节点收到配置包后对帧格式和 CRC16 进行校验,配置有效时更新运行参数,并返回配置 ACK;配置处理完成或接收窗口超时后,E220 再重新进入休眠状态。
- 2.2 心跳包与上位机配置包机制
在实际程序中,需要特别避免总线通信尚未结束,系统或外设就提前进入休眠。例如 SHT30 正在通过 I²C 读取数据,或者 E220 正在通过 UART 发送数据时,如果此时 Zephyr 判断系统空闲并进入较深的低功耗状态,就可能导致通信中断、数据丢失或外设状态异常。
因此需要以下代码对部分地方进行上锁
/**
* @brief 进入不允许深度睡眠的临界区
*
* 必须与 pm_busy_unlock() 成对使用。状态锁是按状态引用计数的,
* 两个线程各自的加解锁互不干扰。
*/
static void pm_busy_lock(void)
{
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
}
/**
* @brief 退出临界区,重新允许 MCU 进入深度睡眠
*/
static void pm_busy_unlock(void)
{
pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
}
- 工程文件目录
m4/
├─ boards/max32650fthr.overlay 板级设备树:uart0/uart1、i2c1、gpio1、rtc_counter、分区
├─ dts/bindings/lora/
│ └─ ebyte,e220-t.yaml E220 设备树绑定(m0-gpios / m1-gpios)
├─ include/
│ ├─ app_protocol.h 应用层协议:帧格式、类型、负载结构
│ └─ e220.h E220 驱动对外接口
├─ src/
│ ├─ main.c 应用:两个线程、配置回应窗口、低功耗状态锁
│ ├─ e220.c E220 驱动实现
│ └─ app_protocol.c 帧编解码 + CRC16-CCITT
├─ e220_web/index.html 上位机网页端(Web Serial 单文件)
└─ prj.conf Zephyr Kconfig:外设、日志、低功耗
实物演示及说明
以下是最后的成品,将其接到电脑上并连接上位机

可以看到温湿度以心跳包都是正常在更新的

静态电流呢可以看到大概在9ma左右

遇到的难点及解决方法
其一:最前期调试过程中,使用CodeFusion Studio插件的图形化配置功能,尝试打开iic设备树,配置了时钟io和外设,保存后发现实际上没起到效果,排查了半天,发现是配置并没有落实到max32650fthr.overlay和max32650fthr.conf文件中
解决方法:手动去将需要的配置在这俩文件中打开。后续才发现其实是因为没有Generate Code(哭),实际上应该对设备树配置好后,点击Generate Code进行同步才能生效
其二:使能CONFIG_PM=y进行低功耗调试后,发现编译后的代码进行烧录每次都会报错,并且后续再次烧录会烧录失败,一度怀疑设备不会变砖了吧
解决方案:第一次烧录时,OpenOCD 成功连接到 MAX32650,并且程序实际上已经成功写入 Flash,但在烧录完成执行复位运行后,程序很快再次进入低功耗状态,使 SWD 接口失去响应,因此随后出现 DP initialisation failed(图1),这种情况无视就可以。第二次烧录时程序已经处于低功耗状态,导致 SWD 调试接口无法正常响应,OpenOCD 因无法读取 Debug Port 的 IDR 而直接报错,表现为 cannot read IDR(图2)。针对这种情况,解决方法是:按住开发板复位键,使 MCU 保持在复位状态,然后点击擦除芯片;待擦除操作开始后松开复位键,完成整片擦除,再重新进行程序烧录。这样可以避免原有低功耗程序立即运行,从而恢复 SWD 的正常连接和烧录(图3)。

(1)

(2)

(3)
其三,实际写完全部功能的调试过程中,发现预想的每心跳会打开一个固定的窗口接收上位机下发的配置包的设想,实际执行起来及其不稳定,排查了窗口时间太短,延长到10s依旧无法解决,并且少数能接收到配置包的情况,650的回包经常也没有被上位机正确的识别出来,一度成为玄学问题。
解决方法:经过各个方面排查发现,事实上,其实是因为2个lora模块离的太近,导致数据异常,将2者稍微拉远点就行了
其四,低功耗的唤醒,根据手册,一开始设想的是使用aux引脚+e220的空中唤醒来实现上位机配置包下发,但实测发现并不好用,一个是无法达到理想的最低功耗,还有就是再wor模式下,只能是收或者是发,然而在当前的设计下,节点和上位机均需要承担收和发2个工作,也就是全双工,这极大的加大了2者的复杂度,需要反复对于收发模式的切换,并且对于上位机来说,由于使用的是ch340这种普通的串口转usb模块,硬件切换需要手动换线,软件切换又太慢
解决方法:重构收发流程,从e220的手册可以看出,事实上,进入休眠模式才是最省电,于是流程就改成了节点侧每隔一段时间唤醒mcu进行采集,并每60s发送一个心跳包,向上位机说明当前节点的存在,与此同时阻塞休眠一段时间作为接收配置包窗口,上位机接收到心跳包后直接下发配置包,节点侧接收数据包并进行对比,然后返回ack,随后mcu和e200一同进入休眠,这样的配置下上位机可以通过循环发包,如果配置包没有正常下发下去也没关系,自动再下一个心跳包到来时再次发送,即能达到降低功耗的效果,也能避免配置包因为意外没有正确下发到节点的情况。
心得体会
经过本次活动,对lora通信,低功耗设计,节点与上位机之间的数据同步之间有了新的认识,收获颇丰。