项目分享:把亿佰特 E22-433M30S LoRa 评估板演示固件从裸机移植到 Zephyr 4.4, 并在其上实现两块开发板的温湿度(SHT30)实时互联。
1. 项目简介
本项目在两块 STM32F103C8T6 + E22-433M30S(SX1268,433MHz LoRa) 开发板上实现:
图形化菜单(SSD1306 OLED,u8g2 图形库 + MultMenu 菜单框架): LoRa 参数设置(SF/BW/CR/频率/功率/包数)、Tx/Rx 收发测试(含 RSSI、丢包统计)
双机温湿度互联:每块板通过 SHT30 采集本地温湿度,经 LoRa 周期性互发; OLED 上实时显示互联状态(Online/Offline)、本地/对端温湿度、对端信号强度 RSSI、 收发包计数
USB CDC 虚拟串口控制台(诊断日志)、LED 收发指示、蜂鸣器按键音
整个系统运行在 Zephyr RTOS 4.4 上,由 AI 代理辅助完成移植与调试。
2. 硬件平台与接线
2.1 开发板资源(板载,无需接线)
| LoRa 模组 SPI1 | PA5/PA6/PA7 + CS PA4 |
| 模组 RESET / BUSY / DIO1 | PB0 / PB1 / PA3 |
| 模组射频开关 TXEN / RXEN | PB12 / PB13 |
| OLED(SSD1306,I2C2) | PB10/PB11 |
| 按键 上/下/确认 | PB4 / PB9 / PB7 |
| LED_TX / LED_RX | PA15 / PB6 |
| 蜂鸣器(TIM2_CH2) | PB3 |
| USB CDC | PA11/PA12 |
| SWD 调试 | PA13/PA14 |
2.2 外接 SHT30(至少一块板)
板载硬件 I2C 引脚均被占用或未引出,SHT30 使用软件模拟 I2C:
| VCC | 3.3V(针 2) |
| GND | GND(针 5) |
| SCL | PB15(针 9) |
| SDA | PB14(针 10) |
模块 ADDR 脚接法不限(0x44/0x45 固件自动探测)
只靠芯片内部上拉即可工作;若读数不稳定,SCL/SDA 各加 4.7kΩ 上拉到 3.3V
3. 功能演示指南
两块板上电 → OLED 显示 logo → 进入菜单首页
菜单操作:上/下/确认三键(PB4/PB9/PB7),按键有蜂鸣提示音
TH Link 页(双机互联主界面):
Link : Online ← 双机互联状态(6s 内收到对端包)
Local: 25.3C 48.2% ← 本机 SHT30
Peer : 24.9C 50.1% ← 对端温湿度(无传感器显示 --)
RSSI : -45dBm 2s ago ← 对端最近一包信号强度/距今时间
TX/RX: 123/45 ← 本板已发包数 / 已收对端包数
实物图:

4. 系统架构
4.1 线程模型
main 线程:设备检查 → key/led/e22/th_link 初始化 → 创建菜单线程
菜单线程(1536B 栈):key_scan_tick() + Menu_Task(),2ms 节拍
互联线程(1536B 栈):每 2.0~2.8s(周期由芯片 UID 决定)读 SHT30 → LoRa 发包 → 挂接收
系统工作队列(2048B 栈):LoRa 接收回调(只做校验+数据更新)
4.2 代码结构(D:\luglZephyrproject\app\e22_433m30s_demo)
src/
main.c 入口
e22_demo.c LoRa 应用层(Zephyr lora API:lora_config/send/recv_async)
th_link.c 双机互联:SHT30 采集、数据包协议、收发调度、状态判定
sw_i2c.c 自写软件 I2C 主机(GPIO bit-bang,方向切换式)
key.c/led.c/buzzer.c/u8g2_hal.c 平台移植层
MultMenu/ 菜单框架 + 应用回调(原工程代码,最小改动)
u8g2Lib/ u8g2 图形库(原工程裁剪版,零改动)
boards/stm32_min_dev_stm32f103xb.overlay 全部硬件描述(设备树)
4.3 互联协议
数据包(8 字节,__packed):'T','H' | seq | temp×100(int16) | hum×100(int16) | CRC8
CRC-8 多项式 0x31 初值 0xFF(与 SHT30 相同),覆盖 crc8 之前所有字段
传感器无效时发哨兵值 INT16_MAX:互联状态与传感器解耦,Link 照样 Online
接收回调校验 magic + CRC,通过才更新对端数据与 RSSI
5. 关键技术点
Zephyr LoRa 子系统:内核自带 SX1268 驱动(LBM 后端),TCXO 电压/射频开关/ 复位时序全部在设备树里声明,应用层只需 lora_config/lora_send/lora_recv_async
u8g2 + MultMenu 整体复用:只重写约 100 行 I2C 移植层,几千行界面代码零改动
软件 I2C(方向切换式 bit-bang):STM32F1 硬件 I2C 全部被占用, PB15/PB14 上 GPIO 模拟,开漏语义用"输入带上拉=释放 / 输出低=拉低"实现
双机免同步错频:发包周期用 STM32 96 位唯一芯片 ID 派生(每片不同), 两台设备的发送窗口持续滑动,无需任何握手即可避免持续对撞
射频仲裁:进菜单 Tx/Rx 测试时后台互联自动挂起,退出自动恢复
6. 调试历程(踩坑记录)
这次开发中最有价值的部分——四个真实踩到的坑:
坑 1:J-Link 拒绝烧录(器件型号 64KB 限制)
Zephyr 按板型给 J-Link 传 STM32F103C8(标称 64KB),86KB 固件越界被拒。 解决:手动指定 STM32F103CB(128KB 兼容型号)烧录。C8 芯片实际多有 128KB, 设备树里把 flash0 扩到 128KB 即可。
坑 2:F1 内部上拉在输出模式无效(SHT30 读不到)
软 I2C 初版用"开漏输出 + 内部上拉",结果 SHT30 完全无应答。 根因:STM32F1 的内部上拉电阻只在输入模式生效(RM0008), 开漏输出模式下释放总线 = 引脚悬空。 解决:改为方向切换式——释放总线切成"输入+上拉",拉低切成"输出低"。
坑 3:双机持续对撞(偶尔收到一包)
两板都是固定 2s 发包周期,节拍接近时每轮都在同一瞬间发射, 发射期间收不到对方,只有晶振误差让相位慢慢漂开才偶尔收到一包 (现象:RSSI 有值但 "111s ago")。 第一版修复(失败):(启动时刻 + 序号×397) % 800 抖动——两板序号同步递增, 若上电时刻接近,抖动同步变化,依然对撞。 最终修复:用芯片唯一 ID 派生不同的基础周期(如 2.1s vs 2.45s), 发送窗口持续滑动,必然频繁错开。
坑 4:结构体填充字节毁掉 CRC(RX 恒为 0)
数据包结构体没加 __packed,编译器在 uint8 seq 和 int16 temp 之间插了 1 字节填充。发送方算 CRC 时把未初始化的填充字节和 crc 字段本身的垃圾值 算了进去,接收方用收到的真实 crc 值重算——必然不匹配,几乎每包都被丢弃 (约 1/256 概率碰巧对上,正好解释了"偶尔能收到")。 解决:__packed + 零初始化 + CRC 长度用 offsetof(crc8)。 教训:跨设备协议结构体必须 packed,CRC 覆盖范围要用 offsetof 显式表达, 不要依赖 sizeof 的巧合。
调试方法论
固件里主动埋诊断:串口日志(每周期一条:发送计数/传感器错误码/ 收到对端包)+ OLED 上直接显示 TX/RX 包计数,不用串口也能分诊
解耦排查:传感器无效也照常发包(哨兵值),把"射频通不通"和 "传感器通不通"拆成两个独立问题
计数器分诊:TX 不涨=没发出、TX 涨 RX 不涨=收不到、RX 涨=通了
7. 构建与烧录
# 构建(Git Bash)
cd /d/luglZephyrproject
ZEPHYR_TOOLCHAIN_VARIANT=zephyr \
ZEPHYR_SDK_INSTALL_DIR='D:\luglZephyrproject\zephyr-sdk-1.0.1\zephyr-sdk-1.0.1' \
west build -b stm32_min_dev/stm32f103xb app/e22_433m30s_demo -p always
# 烧录(J-Link,注意用 STM32F103CB 型号)
"C:\Program Files\SEGGER\JLink\JLink.exe" -nogui 1 -if swd -speed 4000 \
-device STM32F103CB -CommanderScript jlink_flash.jlink
# jlink_flash.jlink 内容:r / h / loadfile ...zephyr.hex / r / g / q
USB 串口(115200 8N1)可查看诊断日志:TH: SHT30 found at 0x44、 TH: local=25.3C/48.2% sent#12、TH: peer packet rssi=-45 等。
8. 资源占用
| FLASH | 89916 B | 128 KB | 68.6% |
| SRAM | 20160 B | 20 KB | 98.4% |
SRAM 余量仅约 300B,为塞进 20KB 做的裁剪:主栈/中断栈各 1024、菜单与互联线程 各 1536、系统工作队列 2048(LoRa 接收回调需要)、CDC FIFO 256/256、USB 缓冲池 256。
9. 已知限制
Zephyr LoRa 驱动只支持标准同步字(私有 0x1424),与原 Keil 固件(0x14)不互通; 两块 Zephyr 板之间互通
菜单 Tx/Rx 测试与后台互联共用射频,进入测试页时互联暂停(TH Link 显示 Offline 属正常),退出后自动恢复
软 I2C 约 50kHz,只服务 SHT30(每 2 秒约 1ms 事务),不影响显示与射频
SRAM 余量小,如需加功能优先考虑砍 USB CDC(可省约 7.5KB RAM)
10. 总结
移植:裸机 → Zephyr,换来驱动生态(LoRa/USB/PWM 现成)、硬件描述与代码解耦、 安全的并发模型;代价是资源开销与底层控制力下降
互联:8 字节小包 + CRC + UID 错频 + 状态解耦,用最简单的机制实现了 免配置的双机数据互通
调试:四个坑(烧录型号、F1 上拉、双机对撞、结构体填充)覆盖了嵌入式开发中
我要赚赏金
