1. 项目概述
本文档记录将亿佰特(Ebyte)E22P-433MBH-SC / E22-433M30S LoRa 评估板演示固件从 STM32 HAL 裸机工程(Keil MDK-ARM)移植到 Zephyr RTOS 4.4.0 的全过程与最终形态。
| MCU | STM32F103C8T6(LQFP48,72MHz) | 同左 |
| RTOS | 无(裸机 + SysTick/EXTI 中断) | Zephyr 4.4.0 |
| LoRa 驱动 | Semtech 官方 sx126x_driver v2.3.2 + 自写 HAL | Zephyr LoRa 子系统(内核 SX1268 驱动,LBM 后端) |
| OLED | u8g2 直驱 I2C2 寄存器 | u8g2(原样保留)+ Zephyr I2C API |
| 菜单 | MultMenu 框架(自研) | MultMenu 原样保留 |
| USB | ST USB Device Library(CDC) | Zephyr 新 UDC 栈 + CDC ACM |
| 构建 | Keil µVision(ARMCC 5) | west + Zephyr SDK 1.0.1(GCC 14.3) |
原工程路径:D:\EP22-433\E22P-433MBH-SC_DEMO\e22p_433mbh_e22_433m30s\e22p_433mbh_e22_433m30s\project(只读,未修改)
移植版路径:D:\luglZephyrproject\app\e22_433m30s_demo
Zephyr 工作区:D:\luglZephyrproject(内核 4.4.0,SDK 1.0.1)
2. 移植取舍(已确认的决策)
LoRa 用 Zephyr LoRa 子系统,不移植 Semtech 裸驱动。
OLED 界面完整移植 u8g2 + MultMenu,界面、动画、菜单树与原 demo 一致;不使用 Zephyr display/cfb 子系统。
保留 USB CDC 虚拟串口作为控制台。
不移植 AirPlane / DinoGame 两个小游戏(原工程即为未挂入菜单树的死代码)。
3. 目录结构
D:\luglZephyrproject\app\e22_433m30s_demo\
├── CMakeLists.txt # 编译 src/*.c + MultMenu 三个 .c + u8g2Lib/src/*.c
├── prj.conf # Kconfig 配置
├── build_stm32_min_dev.bat # 一键构建脚本
├── boards/
│ └── stm32_min_dev_stm32f103xb.overlay # 全部板级硬件描述
├── src/ # 新写的平台移植层(UTF-8,中文注释)
│ ├── main.c # 入口:设备检查 + 创建菜单线程
│ ├── e22_demo.c/.h # LoRa 应用逻辑(Zephyr lora API 重写)
│ ├── key.c/.h # 按键扫描(逻辑照搬原 key.c)
│ ├── led.c/.h # LED_TX / LED_RX
│ ├── buzzer.c/.h # PWM 蜂鸣器(非阻塞提示音)
│ └── u8g2_hal.c/.h # u8g2 移植层(I2C 字节回调 + 延时回调)
├── MultMenu/ # 从原工程拷贝(UTF-8,最小改动)
│ ├── menu/menu.c menu.h menuConfig.h
│ ├── application/application.c/.h
│ └── disp/dispDirver.c/.h
└── u8g2Lib/ # 从原工程拷贝(inc/ + src/,零改动)
4. 硬件资源映射
目标板使用 Zephyr 内核自带板型 stm32_min_dev/stm32f103xb(Blue Pill)+ 应用 overlay。 所有引脚与原工程保持一致:
| SPI1(接模组) | PA5/PA6/PA7 + PA4(CS) | &spi1 + cs-gpios = <&gpioa 4 ...>,4.5MHz |
| E22 RESET / BUSY / DIO1 | PB0 / PB1 / PA3 | sx1268 节点 reset/busy/dio1-gpios |
| E22 TXEN / RXEN | PB12 / PB13 | sx1268 节点 tx-enable-gpios / rx-enable-gpios(驱动自动跟踪收发状态) |
| I2C2(OLED) | PB10/PB11 | &i2c2 400kHz,u8g2 直驱(7 位地址 0x3C) |
| KEY_UP / DOWN / ENTER | PB4 / PB9 / PB7 | gpio-keys 节点(上拉、低有效),代码轮询 |
| LED_TX / LED_RX | PA15 / PB6 | gpio-leds 节点(低电平点亮) |
| 蜂鸣器 | PB3(TIM2_CH2 部分重映射) | &timers2 pwm 子节点,pinctrl tim2_ch2_remap1_pwm_out_pb3,3.33kHz/50% |
| USB CDC | PA11/PA12 | &zephyr_udc0 + cdc_acm_uart0,作 console |
| USB_CTRL | PB5 | &gpiob 的 gpio-hog,上电置高 |
| SWD 调试 | PA13/PA14 | 保留(见 4.1) |
4.1 JTAG 引脚释放
PB3 / PB4 / PA15 默认是 JTAG 引脚(JTDO / NJTRST / JTDI)。overlay 中设置:
&pinctrl {
swj-cfg = "jtag-disable"; /* 关闭 JTAG、保留 SWD */
};
由 zephyr/soc/st/stm32/stm32f1x/soc.c 在启动时执行 AFIO 重映射。
4.2 关闭冲突外设
板级 dts 默认打开的外设与本工程引脚冲突,overlay 中予以关闭: usart2(占 PA3=DIO1)、usart3(占 PB10/PB11=I2C2)、i2c1(占 PB6/PB7)、spi2(占 PB12/PB13=TXEN/RXEN)。
4.3 Flash 扩容
C8 标称 64KB,实际绝大多数芯片有 128KB。overlay 将 &flash0 改为 DT_SIZE_K(128) (该 SoC dtsi 无分区表,只需改 reg)。
5. LoRa 部分移植细节
5.1 Devicetree 节点(overlay 片段)
#include <zephyr/dt-bindings/lora/sx126x.h>
/ { aliases { lora0 = &lora_sx1268; }; };
&spi1 {
cs-gpios = <&gpioa 4 GPIO_ACTIVE_LOW>;
lora_sx1268: sx1268@0 {
compatible = "semtech,sx1268";
reg = <0>;
spi-max-frequency = <4500000>;
reset-gpios = <&gpiob 0 GPIO_ACTIVE_LOW>;
busy-gpios = <&gpiob 1 GPIO_ACTIVE_HIGH>;
dio1-gpios = <&gpioa 3 GPIO_ACTIVE_HIGH>;
tx-enable-gpios = <&gpiob 12 GPIO_ACTIVE_HIGH>;
rx-enable-gpios = <&gpiob 13 GPIO_ACTIVE_HIGH>;
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_3V3>; /* 有源温补晶振 3.3V */
tcxo-power-startup-delay-ms = <5>;
force-ldro; /* 与原固件一致强制 LDRO */
};
};
原工程中由 e22_demo_init() 逐条下发的底层配置(TCXO 电压、DCDC、复位/唤醒序列、 DIO2 设置等)在 Zephyr 中由驱动依据上述 binding 自动完成。
5.2 应用接口(src/e22_demo.c)
对外接口与原工程完全一致,菜单代码(application.c)零感知:
| e22_demo_init() | sx126x_reset/wakeup/SPI自检/PA/TCXO/同步字... | DEVICE_DT_GET(DT_ALIAS(lora0)) + device_is_ready |
| e22_demo_menu_config() | sx126x_set_rf_freq / set_lora_mod_params 等 | 组 struct lora_modem_config 后 lora_config() |
| e22_demo_transmit() | 写 buffer + 映射 TX_DONE + set_tx | lora_send() 阻塞发送,前后控制 LED_TX |
| e22_demo_receive() | 映射 RX_DONE + set_rx 持续接收 | lora_recv_async(dev, rx_cb, NULL) |
| e22_demo_check_rx_done() | 轮询 is_rx 标志(中断置位) | 轮询 is_rx 标志(lora_recv_async 回调置位),irq_lock 临界区保护 |
射频参数映射:
默认:SF11 / BW 500kHz / CR 4/5 / 433MHz / 前导码 8 / 显式头 / CRC 开 / IQ 不反转
菜单可配:SF 5–12、BW 125/250/500kHz、CR 4/5–4/8、频率 429–440MHz
发射功率(2 级 dBm → 芯片 1 级 dBm):30→12、27→8、24→5、21→2(与原工程一致)
私有网络:public_network = false
不支持的参数:printk + k_oops()(对应原工程的 while(1); 停机语义)
5.3 与原工程的行为差异
| 同步字 | 自定义 0x0014 | 标准私有 0x1424 | Zephyr 驱动只支持标准公有/私有同步字。新旧固件之间不互通,两块 Zephyr 板之间互通 |
| DIO1 中断处理 | EXTI3 ISR 内直接做整套 SPI 读包(最高优先级) | 驱动 IRQ 线程 + 系统工作队列回调,应用层无中断上下文 SPI | 更符合 RTOS 规范 |
| 接收重武装 | RX_DONE 中断里手动再调 e22_demo_receive() | LBM 后端持续接收,无需重武装 | 行为等价 |
6. 显示 / 菜单移植细节
u8g2:原工程裁剪版(u8g2Lib/,inc 7 个头 + src 43 个 .c)整体拷贝、零改动。 字体 u8g2_font_profont12_mf 确认存在。
u8g2 移植层(src/u8g2_hal.c):
I2C 字节回调:START/END 之间把(控制字节 + 数据)累积进 1025 字节静态缓冲, END 时一次 i2c_write() 到 u8x8_GetI2CAddress() >> 1(0x3C);
延时回调:k_msleep / k_usleep;
u8g2Init() 序列与原工程一致:u8g2_Setup_ssd1306_i2c_128x64_noname_f → u8g2_InitDisplay → u8g2_SetPowerSave(0) → u8g2_ClearBuffer。
不开 CONFIG_DISPLAY:Zephyr ssd1306 驱动与 u8g2 直驱二选一,本方案选 u8g2 (保真度最高,dispDirver.c 与菜单代码零改动)。
MultMenu:menu.c 仅删除 AirPlane/DinoGame 两行 include、替换 STM32 头文件; application.c 仅做符号映射(HAL_GetTick()→k_uptime_get_32()、 HAL_Delay()→k_msleep()、NVIC_SystemReset()→sys_reboot(SYS_REBOOT_COLD)), 菜单树与 Tx/Rx 状态机业务逻辑原样保留。
7. 按键 / 蜂鸣器 / LED 移植细节
| 按键 | SysTick 1ms 中断扫描,100ms 判定 + 可选连发 | 菜单线程每轮调 key_scan_tick(),用 k_uptime_get_32() 差值补拍(上限 50 拍);gpio_pin_get_dt() 读引脚;key_check_press() 接口不变(读后即清) |
| 蜂鸣器 | buzzer_button_push() 阻塞 50ms | TIM2_CH2 remap1 到 PB3,pwm_set() 3.33kHz/50%;提示音用 k_work_delayable 50ms 非阻塞关断 |
| LED | HAL_GPIO 直接操作 | gpio_pin_set_dt(),低电平点亮 |
8. 运行时结构
main() (主线程,1536B 栈)
├─ printk 版本横幅(USB CDC)
├─ I2C2 ready 检查(失败 k_oops)
├─ key_init() / led_init() / e22_demo_init()
└─ 创建菜单线程(2048B 栈,K_PRIO_PREEMPT(7))
└─ Menu_Init()(内含 u8g2Init + 开机 logo)
└─ while(1) { key_scan_tick(); Menu_Task(); k_msleep(2); }
系统工作队列(2048B 栈):LBM 驱动 LoRa 接收回调 rx_cb(仅 memcpy + 置标志 + LED_RX)
与原工程对比:主循环 Menu_Task() → 菜单线程;SysTick 扫键 → 线程内补拍; EXTI3 中断读包 → 驱动工作队列回调。
9. 构建与烧录
9.1 构建
双击 build_stm32_min_dev.bat,或手动:
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
注意:此 Zephyr 4.4 树中板目标为 stm32_min_dev/stm32f103xb(blue 是默认 revision), 不要加 /blue 后缀,否则报错。
9.2 烧录(J-Link)
west flash --runner jlink 会失败:Zephyr 传给 J-Link 的器件型号是 STM32F103C8 (标称 64KB),固件 86KB 越界被拒。改用 128KB 型号 STM32F103CB(与 C8 完全兼容):
# jlink_flash.jlink 内容:r / h / loadfile ...zephyr.hex / r / g / q
"C:\Program Files\SEGGER\JLink\JLink.exe" -nogui 1 -if swd -speed 4000 \
-device STM32F103CB -CommanderScript jlink_flash.jlink
9.3 资源占用(构建实测)
| FLASH | 86656 B | 128 KB | 66.1% |
| SRAM | 20200 B | 20 KB | 98.6%(余量约 263 B) |
为塞进 20KB SRAM 所做的裁剪:MAIN_STACK_SIZE=1536、ISR_STACK_SIZE=1536、 SYSTEM_WORKQUEUE_STACK_SIZE=2048(LBM 回调在栈上分配 256B 接收缓冲,1024 不够)、 CDC ACM FIFO 缩至 256/256(overlay tx-fifo-size/rx-fifo-size)、 UDC_BUF_POOL_SIZE=512、不开 SHELL/LOG。
10. 验证结果
| west build | 通过(零 error) |
| J-Link 烧录 | 通过(87040 B,校验 O.K.) |
| Rx 接收测试 | 上板验证通过(菜单 Rx Mode 可收到包并显示 RSSI/丢包统计) |
| Tx 发送 / 按键 / 蜂鸣器 / OLED 全菜单 | 以最终上板联测为准 |
11. 已知限制与风险提示
SRAM 余量仅约 263 字节。若运行中莫名重启/死机,先在 prj.conf 加 CONFIG_STACK_SENTINEL=y 重新编译排查栈溢出;进一步可压缩 CDC FIFO 或菜单线程栈。
新旧固件不互通(同步字 0x14 vs 0x1424),见 5.3。
蜂鸣器依赖 TIM2 remap1 + JTAG disable 的 AFIO 组合,属 F1 特有行为; 若个别板不响,备选方案是改 PA1(tim2_ch2_pwm_out_pa1,需飞线)。
编译残留少量无害 warning(MultMenu 既有代码的 double 提升、枚举比较等),未改动。
最终发射功率需仪器标定(2 级功率经 PA 放大,与原工程结论一致)。
12. 关键配置文件速查
prj.conf 要点:CONFIG_GPIO/HOGS/I2C/SPI/PWM、CONFIG_LORA=y + CONFIG_LORA_MODULE_BACKEND_LORA_BASICS_MODEM=y、USB CDC console 系列 (USB_DEVICE_STACK_NEXT/CDC_ACM_*/UART_CONSOLE)、CONFIG_PRINTK/REBOOT、 栈与 USB 缓冲尺寸(见 9.3)。
overlay 文件:boards/stm32_min_dev_stm32f103xb.overlay(swj-cfg、flash0 128KB、 spi1+sx1268、timers2+pwm、gpio-keys、leds、usb-ctrl hog、cdc_acm_uart0、
我要赚赏金
