
简介面向嵌入式物联网开发者这套方案围绕STM32F103与SX1278 LoRa芯片展开核心是利用SX1278的CAD信道活动检测实现低功耗空中唤醒并配合小型RTOS完成任务调度与低功耗状态管理。资源包约5.96MB内容涵盖STM32底层驱动、SX1278收发与CAD唤醒逻辑、RTOS集成示例以及低功耗无线通信的配置说明可指导读者完成从芯片初始化、无线数据收发到唤醒触发流程的完整搭建。CAD机制通过周期监听信道上的特定前导码来唤醒休眠设备相比传统轮询或持续接收可显著降低功耗整个设计特别适合电池供电的远程监测、传感器网络等场景能明显延长设备待机时间。已有118人学习对想掌握STM32LoRa低功耗通信、或希望借鉴CAD唤醒机制与RTOS低功耗管理思路的开发者具有直接参考价值。1. 一个工程名拆出来的三条线STM32F103、SX12xx 射频和 LowPwr 的账标题长但信息密度高。02_STM32F103_system_radio_LowPwr_CAD_cad_shirtgv6_STM32F103_sx12这类文件夹命名在嵌入式团队里很常见拆开看就三件事STM32F103 最小系统、SX12xxSX1276/SX1278LoRa 无线、LowPwr 低功耗。标题里出现两次 CAD在硬件侧指原理图工具在射频侧指 SX12xx 的信道活动检测Channel Activity Detection两条线在低功耗节点里其实是同一件事的两端CAD 让射频不用一直开 RX原理图决定休眠电流的下限。真正难的不是把 LoRa 调通而是把整机休眠电流压到微安级的同时射频唤醒不掉链子。下文按我做这类电池供电节点的顺序从原理图、固件、功耗预算到三个验证手段完整过一遍。2. 从 CAD 原理图开始STM32F103 最小系统与 SX12xx 射频前端的画法2.1 最小系统先别急着照抄电源和 LSE 决定了 LowPwr 的下限STM32F103 最小系统的原理图网上一抓一大把但绝大多数是“能跑”版本不是“能睡”版本。LowPwr 的下限在原理图阶段就定死了一是电源二是 32.768kHz 晶振。先说电源。给 F103 供电很多人习惯用 AMS1117-3.3静态电流 5mA 级别电池供电的板子上它一天就能吃掉 120mAh直接淘汰。我一般选静态电流在 1~2μA 以内的低压差 LDO或者用带轻载省电模式的 DC-DC比如 TPS62740 这类输出纹波控制在 30mV 以内再喂给 SX12xx避免射频 VCO 被电源噪声拉偏。DC-DC 方案效率高但空载静态电流和电感选型要提前看手册反馈分压电阻用 100k 级别别用 10k 级别否则分压电阻自己就吃掉十几 μA。再说 LSE。F103 的 Stop 模式想靠 RTC 定时唤醒LSE 必须起振。晶振选 32.768kHz 加 6.8pF 负载电容这是常规值但真正影响起振的是 PCB 上焊盘底下打过孔、板厂工艺导致板材漏电。这些问题在原理图上体现为 OSC32_IN/OSC32_OUT 各放一个 4.7pF 到地的电容、预留不焊的位置。LSE 起不来时Stop 模式就没法唤醒只能退回 Standby连 SRAM 都保不住SX12xx 配置每次都要重来一遍明显不划算。VBAT 接法也顺手说明不用备份电池时 VBAT 直接接 VDD并在旁边放 100nF 去耦使用备份寄存器存校准参数时VBAT 供电必须干净否则 LSE 漂移。最小系统的几个引脚按表格里这样接基本不会漏引脚/功能F103 管脚低功耗项目里的注意点BOOT0默认接 10k 下拉到地预留跳线DAP 下载失败时排查第一站BOOT1/PB2默认接 10k 下拉到地别空置复位后这个脚电平影响启动位置NRST100nF 对地 10k 上拉上拉电阻别小于 10k复位置位电流会被放大VDDA磁珠隔离后接 3.3VVDDA 与 VDD 之间放 1Ω 10nF模拟供电更稳VBAT直接接 VDD不装电池必须接 VDD不能悬空提示低功耗电路里所有下拉/上拉电阻优先选 100k 量级。10k 是数字电路习惯功耗上代价很大3.3V、10k 上拉常驻电流 330μA等于把休眠电流预算直接打穿。2.2 SX12xx 射频前端SPI 引脚分配与 SX1276/SX1278 的选型口径标题里的 sx12 指的就是 Semtech 的 SX1276/SX1278 系列。选型口径很直接433/470MHz 用 SX1278868/915MHz 用 SX1276。两者 LoRa 核心一样差别在前端匹配、频率范围和部分寄存器默认值代码可以共用只要把频段寄存器按各自平台重写。型号频段范围典型应用频段SX1276137~1020MHz868/915MHz欧洲/北美 ISM 频段SX1278137~525MHz433/470MHz国内常用原理图上射频部分的器件不多但每个都不能省。SPI 用 F103 的 SPI1PA5/PA6/PA7MISO/MOSI/SCK 直接连NSS 用普通 GPIO 控制别用 SPI 硬 NSS。SX12xx 的 SPI 最高支持 10MHzF103 的 SPI1 挂在 APB2 上可以跑到 18MHz频率留足余量。RST 引脚单独接一个 GPIO上电后由 MCU 做一次硬件复位确保 SX12xx 从干净状态启动。DIO0 必接接 F103 的 EXTI 引脚比如 PA0 或 PC13用于 RxDone、TxDone、CAD Detected 中断。DIO1 在 CAD 模式下做检测中断也建议接出来。注意 F103 的 EXTI 线不能多路共用选引脚前先查一遍冲突。射频匹配网络参考 SX1278 手册的参考设计电感电容值照抄后有条件就用网络分析仪微调。TCXO 对温漂友好但会增加 1~2μA 静态电流电池节点上看场景决定要不要上。射频部分的原理图顺序是天线座IPEX/U.FL→ π 型匹配 → SX12xx RFI/RFO → 去耦电容 → 数字接口。去耦电容按手册放两个10pF 100nF 靠近 VBAT_RF 脚。这个位置电容太小会直接导致发射杂散超标太大影响匹配。2.3 画原理图时顺手留好的 4 个测试点和一个坐标精度坑原理图 CAD 工具用 KiCad 还是 Altium Designer个人习惯关键是留测试点。至少留这四个VBAT 测试点量电池实际电压VDD_MCU 测试点量 MCU 供电VDD_RF 测试点量射频前端电压排查 DC-DC 纹波GND电流测量回路的基准。每个测试点串联一个 0Ω 电阻或磁珠跳线量休眠电流时把跳线摘掉、串电流表进去。这个习惯价值很大不用每次飞线也不会因为表笔压降让 MCU 复位。注意做外壳和结构导入时机械 CAD 与原理图 CAD 不是一套数据。曾经接到一张机械图直线长度标注显示成 2.1616e就是典型的单位精度/线性比例没设置好导出后结构对不上焊盘和外壳螺柱错位。低功耗节点的天线位置、电池仓与 PCB 的干涉关系必须在原理图和结构图之间确认一次距离单位。原理图导出 BOM 后我习惯用脚本把功耗相关的关键器件筛一遍防止选型阶段漏掉漏电大户。下面这个脚本检查 BOM 里静态电流超标的 LDO# 检查 BOM 里是否有静态电流超标的线性稳压器 import csv with open(bom.csv, newline, encodingutf-8) as f: for row in csv.DictReader(f): if LDO in row[类别]: iq float(row[静态电流(uA)]) if iq 10: print(f告警: {row[位号]} 静态电流 {iq}uA 超标)逻辑是读原理图工具导出的 BOM CSV筛选类别为 LDO 的行把静态电流大于 10μA 的器件打印出来。10μA 这个阈值来自低功耗预算一颗 LDO 吃掉 10μA整机休眠电流基本就告别个位数微安了。脚本跑完就能在投板前把明显不合适的电源器件换掉。3. 固件侧的 LowPwr 骨架SX12xx 驱动、睡眠状态机与 RTC 唤醒3.1 标准外设库还是 HALLowPwr 项目我选标准库F103 的开发路径有两条标准外设库 V3.5 和 CubeMX 生成的 HAL。LoRa 驱动本身跟 MCU 耦合度低SPI 读写函数写好后SX12xx 驱动层完全通用真正的分歧在低功耗管理上。HAL 用起来快但 Stop 模式唤醒后系统时钟源、PLL、Flash 等待周期这些状态要重新铺一遍CubeMX 生成的代码把时钟恢复逻辑包在 HAL_RCC_ClockConfig 里出了问题不容易定位。标准外设库网上常搜的“stm32f103库v3.50”指的就是它在时钟和功耗控制上是直接操作寄存器比如 PWR_EnterSTOPMode、RTC_Config、EXTI_StructInit 这类接口行为透明。我的选择是标准外设库做低功耗管理CubeMX 只用来在项目初期确认引脚复用和时钟树生成参考工程不直接拿 HAL 的功耗代码上产线。SPI 读写 SX12xx 的代码如果用 HAL记住 HAL_SPI_TransmitReceive 会阻塞低功耗唤醒后第一次 SPI 操作前把 SPI 外设重新初始化一遍避免时钟还没稳定就通信。3.2 SX12xx 初始化先把 LoRa 模式参数写对SX12xx 上电后默认是 FSK/OOK 模式必须切到 LoRa 模式才能用。初始化顺序是复位 → 读版本号 → 切 Sleep → 配置 LoRa 模式 → 写频率 → 写调制参数 → 开中断。下面这段是核心按序执行// 片选与SPI1初始化省略spi_write_reg/spi_read_reg为底层SPI读写 void sx12xx_reset(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_4); // RST 引脚拉低 DelayMs(10); GPIO_SetBits(GPIOA, GPIO_Pin_4); // 拉高完成复位 DelayMs(10); } void sx12xx_loRa_init(uint32_t freq_hz) { uint8_t opmode; sx12xx_reset(); if (spi_read_reg(0x42) ! 0x12) // RegVersion 固定值校验SPI通路 while(1); // 报错SPI没通或芯片没上电 spi_write_reg(0x01, 0x00); // RegOpMode先进入 Sleep 模式 DelayMs(2); opmode spi_read_reg(0x01) | 0x80; // bit7 置1切入 LoRa 模式 spi_write_reg(0x01, opmode); // 频率写入freq_hz / FSTEPFSTEP 32MHz / 2^19 ≈ 61.035Hz { uint32_t frf (uint32_t)((uint64_t)freq_hz * 524288UL / 32000000UL); spi_write_reg(0x06, (frf 16) 0xFF); // RegFrMsb spi_write_reg(0x07, (frf 8) 0xFF); // RegFrMid spi_write_reg(0x08, frf 0xFF); // RegFrLsb } spi_write_reg(0x1D, 0x72); // RegModemConfig1BW125kHz, CR4/5 spi_write_reg(0x1E, 0xB4); // RegModemConfig2SF9, CRC使能 spi_write_reg(0x26, 0x04); // RegPreambleLsb前导码 448 符号 spi_write_reg(0x09, 0x80); // RegPaConfigPA_BOOST输出约 14dBm }几个参数的权衡BW125kHz 灵敏度最好但同等 SF 下空中时间变长SF9 在 125kHz 下灵敏度约 -136dBm穿墙能力比 SF7 好不少代价是单包空中时间拉长一倍多。实际项目里链路预算紧张时优先提高 SF其次是降低 BW。前导码设为 8 个符号是为了配合后面 CAD 检测CAD 只检测前导码前导码太短接收端来不及完成检测。提示RegPaConfig 里 0x80 是 PA_BOOST 使能位。低功耗节点发射功率能不开满就不开满14dBm 到 20dBm 的发射电流能差 60mA 以上近距离场景开 14dBm链路余量就有了。3.3 状态机IDLE、Sleep、Stop 与 CAD 的关系固件主循环是典型的低功耗状态机五个状态。INIT 完成 SX12xx 初始化、RTC 初始化、Flash 参数装载IDLE 是外设空闲等待事件LISTEN 进入 SX12xx CAD 检测或直接 RX看当前时隙有没有数据TX 上报数据发完切回 LISTEN 或 SleepSLEEP/STOP 是 MCU 进 Stop 模式、SX12xx 进 Sleep 模式、由 RTC 定时唤醒。状态转换是SLEEP/STOP 下 RTC 闹钟触发 → EXTI 唤醒 MCU → 恢复时钟 → 进入 LISTENCAD 检测→ 检测到前导码则展开完整 RX → 收到数据解析 → 需要回包就 TX → 全部结束后回到 SLEEP/STOP。核心代码如下void lowpower_enter_stop(void) { // 关闭所有可能产生电流的外设再进 Stop RTC_ITConfig(RTC_IT_ALR, DISABLE); EXTI_ClearITPendingBit(EXTI_Line17); // RTC 闹钟走 EXTI17 RTC_ITConfig(RTC_IT_ALR, ENABLE); // SX12xx 进入 SleepNSS 拉高避免片选浮空漏电 sx12xx_set_mode(SX12XX_SLEEP); GPIO_SetBits(GPIOA, GPIO_Pin_4); // NSS 保持高电平 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后第一件事重建时钟 SystemInit(); while (!(RCC-CR RCC_CR_HSERDY)); // 等 HSE 稳定 // 恢复 SX12xx sx12xx_loRa_init(SX12XX_FREQ_HZ); }这里有个容易踩的坑F103 的 RTC 闹钟中断走 EXTI17进 Stop 前要把 RTC 中断重新开一遍否则闹钟到了不产生唤醒事件。唤醒后 SystemInit 是必须的Stop 模式把 PLL 关了直接继续跑外设会挂。SX12xx 建议在每次唤醒后都重新初始化一遍虽然复位引脚拉高后寄存器配置会保留但重新写一遍可以避免状态不一致。3.4 低功耗下的中断来源、GPIO 配置与看门狗Stop 模式只吃 EXTI串口、定时器都唤醒不了 MCU这是硬约束。业务上如果依赖串口输入必须把 RX 引脚接到 EXTI 引脚上或者干脆用 RTC 轮询不要试图用 USART 中断把 F103 从 Stop 里拉起来。趁这个位置把几个和 LowPwr 相关的 GPIO 坑说清楚。第一PA11/PA12 在 F103 上和 USB D-/D 复用如果板子上引了 USB 座休眠前要处理 VBUS 检测和 USB 外设禁用。不然会看到系统偶尔卡在 USB 中断里、休眠电流明显偏大——网上搜“stm32f103 pa11 bug”能踩到一堆同款经历。第二USART1 挂在 APB2 上、USART3 挂在 APB1 上休眠前把对应的 GPIO 配成模拟输入并关闭 USART 时钟否则 RX 引脚上的电平会通过 GPIO 保护二极管产生倒灌电流实测单根引脚就能漏 3~5μA。串口1和串口3的使用差异在低功耗工程里体现为时钟恢复顺序不同唤醒后先恢复 APB1 时钟再去碰 USART3不然配置写不进去。第三如果节点带 RS485 接口方向控制脚在休眠前必须锁死在接收方向且保持确定电平否则总线漂浮时 A/B 线会通过保护二极管倒灌电流。看门狗单独说一句F103 的 IWDG 一旦使能就关不掉而且它在 Stop 模式下会继续计数。如果同时开了 IWDG 和 Stop 模式就必须在 RTC 唤醒的中断里喂狗否则 MCU 在 Stop 里被看门狗复位业务上看起来像“低功耗模式偶发死机”。这个现象排查起来很费时间建议方案要么进 Stop 前确认 IWDG 关闭要么把喂狗放在 RTC 闹钟中断里而且不能用 Delay 等喂狗时机。4. 用功耗数字反推设计LowPwr 调参与 CAD 接收窗口的计算4.1 从 30uA 到 3uA休眠电流排查清单把固件跑起来后在 Stop 模式量电流新手板子通常是几十 μA 甚至几百 μA。排查顺序固定从上到下走一遍可能原因现象处理LDO 静态电流过大断开 MCU 后电流仍大换 μA 级 LDO 或低静态 DC-DCSX12xx 未进入 Sleep射频电流常驻 mA 级确认 RegOpModeSleep 且寄存器写入生效GPIO 浮空/外部上拉电流随引脚电平忽高忽低未用引脚配模拟输入必要引脚上拉改 100kPCB 焊剂残留未清洗环境湿度高时电流上涨洗板水清洗后烘干再测NSS/DIO 浮空DIO0 悬空产生开关电流加 100k 下拉或全部拉高LED/分压电阻恒流源常驻检查原理图 BOM休眠时关断或去掉量电流有个顺序问题先把 MCU 的 VDD 跳线摘掉量“板子除 MCU 外的基础电流”再把 MCU 接上量整机休眠电流。两次数值一减就能区分是射频侧还是主控侧的问题。数字万用表 μA 档的内阻会对低压系统造成压降3.3V 系统建议用电流钳或 10Ω 采样电阻差分测精度反而更高。4.2 平均电流预算占空比、唤醒时间和 CAD 时序低功耗节点不能只看单点电流要看整个周期的平均电流。公式写出来Iavg (T_sleep * I_sleep T_rx * I_rx T_tx * I_tx T_active * I_active) / T_period举一个典型场景1 小时上报一次每次上报前做一次 CAD 监听监听时间 10ms确认有下行数据时才展开完整 RX 100ms数据发送 50ms。取 I_sleep5μA、I_rx11mA、I_tx80mA14dBm、I_active8mA。每次周期内的时间是 10ms100ms50ms≈160ms平均电流就是Iavg ≈ 5μA (11mA*0.11 80mA*0.05) * (0.16/3600) ≈ 5μA 11μA ≈ 16μA这个量级的平均电流1000mAh 的锂电池理论续航已经超过 7 年实际考虑电池自放电和温度减半估计也有 3 年以上。如果改成每分钟定时接收一次平均电流会跳到几百 μA这说明低功耗节点省电的关键不是把单次 RX 做得多快而是拉长休眠占比。这也是为什么 CAD 模式在低功耗接收里比一直开 RX 实用。4.3 用 SX12xx 的 CAD 实现低功耗侦听CADChannel Activity Detection是 SX12xx 在 LoRa 模式下的信道活动检测功能它只花几个符号的时间去检测前导码是否出现不展开完整解调。功耗上和完整 RX 同量级但用时只有 RX 的几十分之一平均电流显著下降。发端前导码长度要和收端 CAD 时间匹配前导码太短CAD 还没完成检测报文已经开始发负载接收端会漏报。CAD 检测的具体时序在 125kHz、SF9 下一个符号的时间约 8.2msCAD 需要 2~3 个符号完成检测。寄存器配置如下void sx12xx_enter_cad(void) { uint8_t reg; // 把 DIO1 映射为 CAD 检测完成中断 reg spi_read_reg(0x40); // RegDioMapping1 reg (reg 0x3F) | 0x40; // DIO101CadDetected spi_write_reg(0x40, reg); // 先回 Standby再进入 CAD 模式 spi_write_reg(0x01, (spi_read_reg(0x01) 0xF8) | 0x01); DelayMs(1); spi_write_reg(0x01, (spi_read_reg(0x01) 0xF8) | 0x05); }DIO1 触发中断后读 RegIrqFlags0x12的 bit0 判断是 CadDone 还是 CadDetected。如果检测到前导码紧接着切到 RX 模式等待 DIO0 的 RxDone 中断。这里有个时序细节CAD 完成到切 RX 之间有空档如果发端报文已经开始接收会丢掉开头几个字节。解决办法是发端前导码留长一点接收端 CAD 提前完成确认后马上进 RX。SX1278 手册给出的经验值是前导码不小于 10 个符号留足 CAD 加切模式的时间窗。5. 验证 LowPwr 的三个具体手段电流波形、BOOT0/BOOT1 排查、RTC 补偿5.1 串联电阻看动态电流波形万用表只能看平均值动态电流要用示波器。方法是在 VDD 回路串一个 10Ω 的采样电阻示波器用差分探头或两通道 A-B 模式测电阻两端压差换算成电流。把时基拉到 100ms/div能看到明显的阶梯Sleep 段是平的唤醒瞬间有一个尖峰CAD 段有小台阶TX 段是最高的平台。这个波形就是功耗优化的直接依据尖峰太高查时钟恢复和 Flash 操作TX 平台太宽查空中时间和发射功率Sleep 段不平查 GPIO 漏电。5.2 DAP 下载失败先查 BOOT0 和 BOOT1低功耗板子做出来以后遇到 DAP 连不上的概率不低。第一眼看 BOOT0如果 BOOT0 被拉到高电平MCU 复位后进 ISP 模式SWD 不响应。常见原因是 BOOT0 设计成拨码开关或跳线帽装配时跳线帽插错位置。第二眼看 BOOT1PB2复位释放后 BOOT01 时BOOT1 的电平决定是从系统存储器还是内置 SRAM 启动。BOOT1 不能悬空悬空或者被周边电路带上不确定电平启动位置就不确定DAP 握手自然失败。PB3/PB4 如果被复用成 GPIO 且 JTAG 没有完全关闭同样是下载失败的高发原因。5.3 CAD 超前时间与 RTC 补偿最后落在一个具体技巧上RTC 靠 LSE 跑32768Hz 晶振的频偏通常在 ±20ppm 量级累计一天误差可能到秒级。对于 1 小时唤醒一次的节点这个误差会导致 CAD 监听窗口和发端时间对不齐。我的做法是每次收到对端时间同步包后把误差换算成补偿值写进 RTC 预分频器RTC-PRLH/PRLL同时在 CAD 窗口上做“超前唤醒”——比如 CAD 需要在 10ms 内完成就提前 2 个符号约 100ms 量级进入 CAD 监听给频偏留余量。把每次唤醒后的实际时间偏移打进串口日志连续观测 24 小时如果偏移呈线性增长就按线性值做二次补偿。这样验证一轮下来休眠电流、唤醒时序、交接窗口都有了实测数据SX12xx 的发射功率和前导码长度就可以按数据调了。本文还有配套的精品资源点击获取