你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32WLE55 无线收发功能调试记录

[复制链接]
短笛君 发布时间:2026-9-6 16:46

简介

看到ST新出了一款低功耗无线MCU STM32WL3R很感兴趣,于是果断下单样品准备尝鲜,先前有购买过WLE55的模组 准备先熟悉下sub-Ghz的软件框架。

在此之前还是先对比这两款MCU

STM32WL3R && STM32WLE55

特性 STM32WL3R STM32WLE55
核心架构 单核 Arm® Cortex®-M0+[] 双核 Arm® Cortex®-M4 + Cortex®-M0+
主频 最高 64 MHz 最高 48 MHz
Flash 最大容量 128 KB 256 KB
SRAM 最大容量 16 KB 64 KB
主要无线协议 2/4(G)FSK, OOK, ASK, D-BPSK, DSSS[] LoRa®
TX 最大输出功率 +20 dBm +22 dBm
10dBm TX 电流 8 mA 15 mA
超深度停止模式 450 nA (RAM 数据保持) 1.07 µA (Stop2 模式)
关断模式电流 14 nA (6个唤醒引脚) 31 nA

WL3R更偏向的是低功耗终端 WLE55偏向高性能复杂物联网设计 最主要的是WLE55支持LoRa

代码框架设计

cubemx配置

tiao调试串口 方便后续跟踪logimage.png

image.png

使能sub-Ghz收发器,以及收发中断

image.png

重点

image.png

射频参数按照如上配置 使能串口log模块 禁用低功耗模式 调制使用LoRa 频段暂定868Mhz 后续可以更改

带宽125Khz

image.png

设定好平台参数 指定调试串口image.png

然后就生成代码

框架实现思路与功能

代码参考了官方的pingpong例程实现 抽象了发送函数,实现一行调用即可发送,另外实现了功率调试 最高22dbm输出,软件层面实现了CRC16校验+ACK应答+自动重传极致,大大提高了稳定性

项目结构

├─ Core/                      # CubeMX 生成的内核层
│  ├─ Inc/
│  └─ Src/
│     ├─ main.c               #   入口:时钟/外设初始化 +
│     ├─ subghz.c             #   SUBGHZ外设初始化
│     ├─ usart.c / usart_if.c #   调试串口
│     ├─ sys_app.c            #   APP_LOG / 日志系统
│     └─ stm32wlxx_it.c       #
├─ SubGHz_Phy/
│  ├─ App/
│  │  ├─ subghz_phy_app.c     # 收发状态机 +传输协议
│  │  ├─ subghz_phy_app.h     #  频率/功率/调制参数
│  │  └─ app_subghz_phy.c     #   MX_SubGHz_Phy_Init/Process
│  └─ Target/
│     ├─ radio_conf.h         #   射频中间件配置
│     └─ radio_board_if.c     #   射频开关/PA 等板级适配
├─ Middlewares/Third_Party/SubGHz_Phy/
│  ├─ radio.h                 #   Radio 驱动 API(Radio.Send/Rx/Standby...)
│  └─ stm32_radio_driver/     #  STM32WL 底层驱动
├─ Utilities/                 #   sequencer(任务调度)、timer(软定时器)、trace ...
├─ Drivers/                   #   CMSIS + STM32WLxx HAL
├─ BSP/STM32WLxx_LM401/       #   板级支持(LED / 按键 / 射频开关)
└─ MDK-ARM/

射频事件(收完/发完/超时)在 中断上下文 回调里只做两件事 :更新状态、用 UTIL_SEQ_SetTask() 排一个后台任务。射频操作(发送、重进接收、协议处理)全部放在后台任务 SubghzApp_Process() 里,在主循环上下文执行。这样中断里不操作SPI接口 。

│  仅:State = ... ; UTIL_SEQ_SetTask(...)
▼
[主循环] UTIL_SEQ_Run() → SubghzApp_Process()
│  按 State 分发:
│   RX_DONE     → Proto_HandleRxFrame()  解析/校验CRC/回ACK或NACK/上报
│   TX_DONE     → 若刚发的是 DATA,启动 ACK 等待定时器
│   RX_ERROR    → 记录
│   ACK_TIMEOUT → Proto_Retransmit()     重传或判失败
└─ 收尾:有待发帧则 Radio.Send(),否则(且不在发送中)Radio.Rx() 继续监听

校验机制和自动重传触发

收发校验和自动重传参考以下流程

发送端 A                            发送端 B
────────                           ────────
SubghzApp_Send()
TX DATA(seq=N) ─────────────────── 收到 → 计算CRC并与帧内CRC比较
启动 ACK 定时器(1s)                  ├─ 一致 → 上报数据, 回 ACK(seq=N)
                                    └─ 不一致 → 回 NACK(seq=N)
收到 ACK(seq=N) ───────────────────┘
→ transfer OK, seq++── 若收到 NACK 或 ACK 超时 ──
重传 DATA(seq=N)  (最多 MAX_RETRIES=3 次)
仍失败 → transfer FAILED

依靠这个机制我们就可以实现数据可靠的发送与接收 防止对面接收到坏帧的情况

抽像发送函数

为了方便收发 我们直接将发送函数进行二次封装

bool SubghzApp_Send(const uint8_t *data, uint8_t size)
{
  if (!data || size == 0 || size > MAX_PAYLOAD_LEN) return false;  /* 参数检查 */
  if (awaitingAck) return false;                     /* 停等:同一时刻仅一帧在途 */
  lastFrameLen = Proto_BuildFrame(lastFrame, FRAME_TYPE_DATA, txSeq, data, size);
  retryCount = 0; awaitingAck = 1;
  Proto_QueueTx(lastFrame, lastFrameLen, FRAME_TYPE_DATA);  /* 入队 + 唤醒任务 */
  return true;
}

使用队列确保同一时刻只有1个帧在使用硬件,组好 DATA 帧存入 lastFrame(供重传),置 awaitingAck,交给任务发送;真正的 Radio.Send 在任务里完成

后台主任务subghzApp_Process

static void SubghzApp_Process(void)
{
  switch (State) {                       /* 1) 按事件分发 */
    case RX_DONE:  BSP_LED_Toggle(LED_GREEN); Proto_HandleRxFrame(); break;
    case TX_DONE:  BSP_LED_Toggle(LED_RED);
                   if (txFrameType == FRAME_TYPE_DATA && awaitingAck)
                     UTIL_TIMER_Start(&ackTimer);   /* 刚发的是 DATA → 开始等 ACK */
                   break;
    case RX_ERROR: APP_LOG(...,"RxError\n\r"); break;
    case ACK_TIMEOUT: if (awaitingAck) { APP_LOG(...,"ACK timeout..."); Proto_Retransmit(); } break;
    default: break;
  }
  if (TxPending != 0) {                   /* 2) 有待发帧:先发 */
    TxPending = 0; State = TX;
    Radio.Standby();                      /* 发送前置于干净状态 */
    Radio.Send(BufferTx, TxSize);
  } else if (State != TX) {               /* 否则继续监听 —— */
    State = RX;                           /*  "State!=TX" 守卫见 §9.2:*/
    Radio.Rx(RX_TIMEOUT_VALUE);           /*  防止重入时打断在途发送 */
  }
}

简单实现了一个状态机 依靠不同的状态来实现相应行为

实际操作演示

image.png

如图 上电后会自动打印PHY状态与初始化参数 按下按键后 会触发发送函数,对端接收到数据后 会计算CRC数据并进行校验 校验通过后会回ACK包 这是一帧完整的收发流程,如果发生撞包或者坏包 就会触发自动重传流程

功率测试

c4c78d0f9365760a289e0af933e273d5.jpg

上图为降功率发送的效果 因为频谱仪只有15dbm最大输入 没有配置衰减器 实测22dbm可以稳定输出 电流120ma@5V

赞 收藏 评论0 发布时间:2026-9-6 16:46

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版