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

【STM32WL3R】使用STM32WL3R实现LoRa收发通信

[复制链接]
短笛君 发布时间:2026-9-28 23:51

这次使用自制的STM32WL3R板卡来实现LoRa的收发。

首先参考 STM32Cube FW_WL3_V1.5.0 中的 MRSUBG_BasicGeneric_Tx、MRSUBG_BasicGeneric_Rx 和 802.15.4 Tx 示例,将工程改为 Basic Packet 模式。

优先启用外部48Mhz高速晶振 使用内部RC会出现hardfault的情况

内部 HSI RC 可以让 CPU 正常运行,但在实际测试中 MRSUBG 发射序列无法完成

参数 最终采用值 说明
频率 868 MHz 最终正常发射验证使用的频段
调制 2-GFSK 0.5 MOD_2GFSK05
速率 1 kbit/s 255 字节包便于功率计捕获
频偏 20 kHz 与接收端一致
带宽 100 kHz 与接收端一致
前导码 16 Basic Packet
同步字 0x88888888,31
bit 收发两端必须一致
CRC 8 bit 接收只接受 CRC 正确的数据
数据白化 开启 收发两端一致

配置代码参考如下

MRSUBG_RadioInitStruct.lFrequencyBase = 868000000U;
MRSUBG_RadioInitStruct.xModulationSelect = MOD_2GFSK05;
MRSUBG_RadioInitStruct.lDatarate = 1000U;
MRSUBG_RadioInitStruct.lFreqDev = 20000U;
MRSUBG_RadioInitStruct.lBandwidth = 100000U;
MRSUBG_RadioInitStruct.outputPower = 20;
MRSUBG_RadioInitStruct.PADrvMode = PA_DRV_TX_TX_HP;

在这个项目中 我们采用AB乒乓通信的方式进行收发 也就是按下A的按键时 B进行动作 按下B的按键时 A进行动作,两端通信类似半双工,这样就有一个收发状态机切换的问题

按键发送前射频仍处于 RX 状态,代码发送 CMD_SABORT 后立即切换 TX,会导致无法响应的问题,我们新增一个中间态 STATE_IDLE,在 RX 到 TX、TX 到 RX 的切换前增加状态机等待:

static void RadioAbortAndWaitIdle(void)
{
  uint32_t abortTick;

  __HAL_MRSUBG_STROBE_CMD(CMD_SABORT);
  abortTick = HAL_GetTick();
  while ((MR_SUBG_GLOB_STATUS->RADIO_FSM_INFO & 0x1FU) != STATE_IDLE)
  {
    if ((uint32_t)(HAL_GetTick() - abortTick) >= 20U)
    {
      break;
    }
  }
}

按下按键发送后,等PHY返回IDLE状态再进行状态切换

发送代码我们使用Basic Packet

配置部分如下代码

MRSUBG_PacketSettingsStruct.PreambleLength = 16U;
MRSUBG_PacketSettingsStruct.PostambleLength = 0U;
MRSUBG_PacketSettingsStruct.SyncLength = 31U;
MRSUBG_PacketSettingsStruct.SyncWord = 0x88888888U;
MRSUBG_PacketSettingsStruct.FixVarLength = VARIABLE;
MRSUBG_PacketSettingsStruct.PreambleSequence = PRE_SEQ_0101;
MRSUBG_PacketSettingsStruct.PostambleSequence = POST_SEQ_0101;
MRSUBG_PacketSettingsStruct.CrcMode = PKT_CRC_MODE_8BITS;
MRSUBG_PacketSettingsStruct.Coding = CODING_NONE;
MRSUBG_PacketSettingsStruct.DataWhitening = ENABLE;
MRSUBG_PacketSettingsStruct.LengthWidth = BYTE_LEN_1;
MRSUBG_PacketSettingsStruct.SyncPresent = ENABLE;

发送部分代码建立了一块四字节对齐的RAM

__attribute__((aligned(4))) static uint8_t TxPayload[TX_PAYLOAD_SIZE];   // 32 字节
__attribute__((aligned(4))) volatile uint8_t RxPayload[TX_PAYLOAD_SIZE]; // 32 字节

发送代码实现

static void RadioPrepareTx(void)
{
  RadioAbortAndWaitIdle();                                       // 停掉 RX 并等状态机回到 IDLE
  __HAL_MRSUBG_SET_TX_MODE(TX_NORMAL);                           // 只提供 payload,前导/同步/长度/CRC 由硬件生成
  HAL_MRSubG_PktBasicSetPayloadLength(TX_PAYLOAD_SIZE);          //写 DATABUFFER_SIZE(容量) 和 PCKTLEN(包长) = 32
  __HAL_MRSUBG_SET_DATABUFFER0_POINTER((uint32_t)TxPayload);     // 告诉硬件数据在 RAM 哪里
}

发送状态机是很重要的 否则就会导致发送一次后TX卡在某个状态无法恢复



static uint8_t RadioSendPacket(void)
{
  uint32_t txStartTick = HAL_GetTick();
  uint32_t irq;

  RadioPrepareTx();
  __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(__HAL_MRSUBG_GET_RFSEQ_IRQ_STATUS()); // 清旧标志
  __DMB();                                                     // 确保上面的寄存器/缓冲区写在命令前生效
  __HAL_MRSUBG_STROBE_CMD(CMD_TX);                             //  打开发射序列

  do {                                                         // 轮询等待硬件发完
    irq = __HAL_MRSUBG_GET_RFSEQ_IRQ_STATUS();
    RadioLastIrq = irq;
    if ((irq & (MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_TX_DONE_F | TX_ERROR_FLAGS)) != 0U)
      break;
  } while ((uint32_t)(HAL_GetTick() - txStartTick) < TX_TIMEOUT_MS);   // 500ms 超时

  RadioLastDurationMs = HAL_GetTick() - txStartTick;
  RadioLastFsm = MR_SUBG_GLOB_STATUS->RADIO_FSM_INFO;

  if ((irq & TX_DONE_F) && ((irq & TX_ERROR_FLAGS) == 0U)) {
    RadioTxCount++;  RadioLastTxOk = 1U;
    TxPayload[TX_PAYLOAD_SIZE - 1U]++;                         // 载荷末字节自增,当包序号
    __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(irq);                    // 标志是写 1 清零,把读到的值写回即可
    return 1U;
  }
  RadioTxErrorCount++;  RadioLastTxOk = 0U;
  __HAL_MRSUBG_STROBE_CMD(CMD_SABORT);                         // 异常则中止
  return 0U;
}

收到 CMD_TX 后 FSM 走 SYNTH_SETUP → CALIB_VCO → LOCKONTX → EN_PA → TX,通过自己的 AHB 主机按 DATABUFFER0_PTR 读走 32 字节 payload,自动加前导码/同步字/长度字段/8bit CRC/白化,发完后置 TX_DONE_F。若 AHB 取数出错会置 `AHB_ACCESS_ERROR_F

接收函数的实现和发送一样都是阻塞式轮询的

RadioLastRxIrq = __HAL_MRSUBG_GET_RFSEQ_IRQ_STATUS();

if ((RadioLastRxIrq & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_OK_F) != 0U)   // 收到且 CRC 正确
{
  RadioRxCount++;
  RadioLastRxLength = __HAL_MRSUBG_GET_DATABUFFER_COUNT();   // 硬件实际写入 RxPayload 的字节数
  __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(RadioLastRxIrq);

  SEGGER_RTT_printf(0, "Hello STM32, timestamp=%lu ms, rx_count=%lu, length=%lu\r\n", ...);

  { /* 读链路质量:RSSI_SYNC / RSSI_RUN / AGC / PQI / SQI / AFC */
    uint32_t rxIndicator = __HAL_MRSUBG_GET_RX_INDICATOR();   // bit0-8=RSSI_ON_SYNC, bit12-20=RSSI_RUN, bit24-27=AGC
    uint32_t qiInfo      = MR_SUBG_GLOB_STATUS->QI_INFO;      // bit0-7=PQI, bit8-13=SQI, bit16-23=AFC
    int32_t  rssiDbm     = HAL_MRSubG_GetRssidBm();
    int32_t  rssiOnSyncDbm = __HAL_MRSUBG_CONVERT_RSSI_TO_DBM(rssiOnSyncRaw);
    int32_t  rssiRunDbm    = __HAL_MRSUBG_CONVERT_RSSI_TO_DBM(rssiRunRaw);
    SEGGER_RTT_printf(0, "RF RX: RSSI_SYNC=%ld dBm, ... QI=0x%08lX\r\n", ...);
  }

  /* PB0 拉高 200ms 表示收到一个 CRC 正确包 */
  HAL_GPIO_WritePin(RX_ACTIVITY_GPIO_Port, RX_ACTIVITY_Pin, GPIO_PIN_SET);
  rxIndicatorOffTick = HAL_GetTick() + RX_INDICATE_MS;
  rxIndicatorActive = 1U;

  RadioStartRx();                                            // 等待下一包
}
else if ((RadioLastRxIrq & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_CRC_ERROR_F) != 0U)
{
  RadioRxCrcErrorCount++;
  __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(RadioLastRxIrq);
  RadioStartRx();                                            // CRC 错误
}

接收处理函数,在进入接收后 会停止正在进行的TXRX动作 保证每个包都会有单独的接收窗口 防止粘包

static void RadioAbortAndWaitIdle(void)          // 
{
  uint32_t abortTick;
  __HAL_MRSUBG_STROBE_CMD(CMD_SABORT);           // 停掉正在进行的 RX/TX
  abortTick = HAL_GetTick();
  while ((MR_SUBG_GLOB_STATUS->RADIO_FSM_INFO & 0x1FU) != STATE_IDLE) {  // 必须等 FSM 真回到 IDLE(0x00)
    if ((uint32_t)(HAL_GetTick() - abortTick) >= 20U) break;             // 最多等 20ms
  }
}

static void RadioPrepareRx(void)
{
  RadioAbortAndWaitIdle();
  __HAL_MRSUBG_SET_RX_MODE(RX_NORMAL);                          // payload 存进 RAM,CRC/长度在状态寄存器
  HAL_MRSubG_PktBasicSetPayloadLength(TX_PAYLOAD_SIZE);         // 缓冲区容量/包长 32(与发送端一致)
  __HAL_MRSUBG_SET_DATABUFFER0_POINTER((uint32_t)RxPayload);    // 接收数据落地点
}

static void RadioStartRx(void)
{
  RadioPrepareRx();
  __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(__HAL_MRSUBG_GET_RFSEQ_IRQ_STATUS());
  __DMB();
  __HAL_MRSUBG_STROBE_CMD(CMD_RX);                              // 打开接收序列
}

这里一直轮询PHY的状态 必须等phy到IDLE状态才能开始下一轮接收 否则就会导致异常出现

在这套工程中我们也实现了SeggerRTT的组件 可以用RTT的printf来实现高效的log输出

9_8CSOG9X6VBBKKF5@@U_R.png

如图就是每次按下按键后 A板发送Hello STM32 B板接收并打印出字符串 同时将RF信息也一起打印出来 方便调试人员调参,按键初始化这些过于简单就不在继续讲述了

<iframe src="https://player.bilibili.com/player.html?isOutside=true&aid=117349126703249&bvid=BV1mfaG6PExZ&cid=42282452013&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

赞 收藏 评论2 发布时间:2026-9-28 23:51

举报

2个回答
短笛君 回答时间:昨天 23:51

<iframe src="https://player.bilibili.com/player.html?isOutside=true&aid=117349126703249&bvid=BV1mfaG6PExZ&cid=42282452013&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

短笛君 回答时间:昨天 23:51

<iframe src="https://player.bilibili.com/player.html?isOutside=true&aid=117349126703249&bvid=BV1mfaG6PExZ&cid=42282452013&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

所属标签

相似技术帖

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