Ra-11 / Ra-11H(LR1121)+ STM32F103C8T6 Ping-Pong 应用说明

本文说明如何把本仓库的 examples/main_examples/ping_pong_example 工程配置为:

内容包括工程结构、收发方法、主机/从机配置、编译烧录、串口日志和常见问题。


1. 结论速查

1.1 正确的编译参数

BOARD      = STM32F103CB
RAC_RADIO  = lr1121

注意:MCU 是 STM32F103C8T6,但 BOARD 必须写 STM32F103CB,不能写 STM32F103C8。

原因见第 3 节:本仓库的 F103 板级选项只有 STM32F103CB,它使用的链接脚本
STM32F103XB_FLASH.ld 已经按 64 KB Flash / 20 KB RAM 配置,正好对应 C8T6;
C8T6 与 CBT6 同为 LQFP48、引脚完全一致,只是 Flash 容量不同。

1.2 主机 / 从机一句话区分

角色 CMake 参数 启动行为
主机 master -DPING_PONG_START_AS_SLAVE=OFF(默认) 上电立即发送 252 字节 0x55
从机 slave -DPING_PONG_START_AS_SLAVE=ON 上电只接收,收到合法 0x55 后回复 252 字节 0x88

角色在编译时固定,运行中不能切换;两块板必须分别编译、分别烧录。

1.3 本机实测编译结果

使用 arm-none-eabi-gcc 10.3.1、-DCMAKE_BUILD_TYPE=MinSizeRel 实测:

角色 FLASH RAM 结果
master 43396 B / 64 KB(66.22%) 7832 B / 20 KB(38.24%) 编译通过
slave 43320 B / 64 KB(66.10%) 7832 B / 20 KB(38.24%) 编译通过

两个角色都能放进 STM32F103C8T6 的 64 KB Flash / 20 KB RAM。


2. 硬件准备

2.1 模组与 MCU

项目 说明
模组 Ra-11 / Ra-11H,射频芯片 LR1121
MCU STM32F103C8T6,LQFP48,64 KB Flash / 20 KB RAM
调试串口 USART1,PA9(TX) / PA10(RX),115200 8N1
供电 MCU 与射频均 3.3 V,共地
天线 两块板都必须接好对应频段天线

2.2 接线(F103C8T6 ↔ LR1121)

引脚定义来自 examples/modem_pinout.h 与 examples/smtc_hal_f1/:

STM32F103C8T6 射频侧 功能 备注
PA4 NSS / CS SPI 片选 软件片选,空闲为高
PA5 SCK SPI 时钟 模式 0
PA6 MISO SPI MISO —
PA7 MOSI SPI MOSI —
PB14 NRST 射频复位 低电平有效
PB1 DIO1 / DIOX 射频中断 上升沿触发
PA0 BUSY 射频忙信号 必须接
PA8 — LED_TX 发射时点亮
PB15 — LED_RX 接收时点亮
PA9 — 调试串口 TX 115200 8N1
PA10 — 调试串口 RX —

LR1121 的天线开关由芯片内部 RF switch 配置完成(ral_lr11xx_bsp.c 中配置 RFSW0~RFSW3),
本 F103 示例不需要额外的 MCU 天线切换引脚。

2.3 频点与法规

main_ping_pong.h 默认:

#define RF_FREQ_IN_HZ        470100000   /* 470.1 MHz */
#define TX_OUTPUT_POWER_DBM  14          /* 14 dBm */

修改方式:编辑 examples/main_examples/ping_pong_example/main_ping_pong.h,然后重新编译两块板。

2.4 电源与射频注意事项


3. 为什么 BOARD 是 STM32F103CB

这是本文最容易踩坑的地方。

3.1 CMake 只接受三种板型

examples/CMakeLists.txt 开头:

if(BOARD STREQUAL NUCLEO_L073)
    ...
elseif(BOARD STREQUAL NUCLEO_L476)
    ...
elseif(BOARD STREQUAL STM32F103CB)
    ...
else()
    message(FATAL_ERROR "You have to define a -DBOARD=NUCLEO_L476 or NUCLEO_L073 or STM32F103CB")
endif()

因此:

-DBOARD=STM32F103C8     # 错误,CMake 直接 FATAL_ERROR
-DBOARD=STM32F103CB     # 正确,F103 板级选项

3.2 链接脚本就是 C8T6 的 64 KB / 20 KB

STM32F103CB 使用 examples/smtc_hal_f1/cmake_stm32f1_toolchain.cmake,其中:

set(BOARD_LDSCRIPT ${MCU_DRIVERS_DIR}/core/STM32F1xx/STM32F103XB_FLASH.ld)
...
-DSTM32F103xB -DUSE_HAL_DRIVER

STM32F103XB_FLASH.ld 文件头写得很明确:

64KByte FLASH (STM32F103C8), 20KByte RAM

所以这个“CB”板级选项实际链接的是 C8T6 的 64 KB / 20 KB 资源,不是 128 KB 的 CBT6。

3.3 C8T6 与 CBT6 的引脚一致

两者都是 LQFP48 封装,SPI1、USART1、GPIO 引脚完全相同,区别只在 Flash 容量。
本工程实测 Flash 约 43 KB,RAM 约 7.8 KB,C8T6 可以正常运行。


4. 工程结构与实现方法

4.1 目录与文件

examples/main_examples/ping_pong_example/
├── main_ping_pong.c       # 程序入口:MCU/RAC 初始化、主循环
├── main_ping_pong.h       # 射频参数宏(频率、功率、SF、BW、CR 等)
├── app_ping_pong.c        # Ping-Pong 状态机、收发调度、日志
├── app_ping_pong.h
├── app_periodic_uplink.c  # 周期上行(ping_pong 默认关闭)
├── app_periodic_uplink.h
└── readme.md              # 原始示例说明

相关的底层模块:

模块 作用
smtc_rac_lib/smtc_rac/ RAC(Radio Access Controller):事务提交、LoRa 参数、回调
smtc_rac_lib/radio_planner/ Radio Planner:TX/RX 时序调度、超时、失败保护
smtc_rac_lib/radio_drivers/lr11xx_driver/ LR1121 寄存器级驱动
examples/radio_hal/ral_lr11xx_bsp.c LR11xx BSP:射频开关、功率配置、PA 选择
examples/smtc_hal_f1/ STM32F103 HAL:SPI、UART、GPIO、RTC、看门狗

4.2 程序入口 main_ping_pong.c

void main_ping_pong( void )
{
    hal_mcu_init( );        // 时钟、UART、SPI、GPIO、RTC、看门狗
    smtc_rac_init( );       // 初始化 RAC 与 radio_planner

    hal_gpio_init_out( SMTC_LED_TX, 0 );
    hal_gpio_init_out( SMTC_LED_RX, 0 );

    ping_pong_init( );      // 打开 radio、填事务上下文、启动主/从流程
    periodic_uplink_init( );// ping_pong 目标未定义 ENABLE_PERIODIC_UPLINK,实际不启用

    while( true )
    {
        hal_watchdog_reload( );

        /* DIO1 漏边沿兜底:电平仍为高时补一次 IRQ 回调 */
        if( ( smtc_rac_is_irq_flag_pending( ) == false ) &&
            ( rp_radio_is_free( smtc_rac_get_rp( ) ) == false ) &&
            ( hal_gpio_get_value( RADIO_DIOX ) != 0 ) )
        {
            rp_radio_irq_callback( smtc_rac_get_rp( ) );
        }

        smtc_rac_run_engine( );   // 必须持续调用,驱动 RAC/planner 状态机
    }
}

关键点:

  1. 主循环不能长时间阻塞。smtc_rac_run_engine() 要尽可能频繁地调用,否则 TX/RX 调度会延迟,甚至触发看门狗。
  2. DIO1 既有中断也有电平兜底。即使上升沿丢失,主循环也会通过 RADIO_DIOX 电平补一次回调。
  3. periodic_uplink_init() 虽然被调用,但没有定义 ENABLE_PERIODIC_UPLINK,所以周期上行是关闭的,不会和 ping-pong 事务抢射频。

4.3 RAC 的事务模型

app_ping_pong.c 的 ping_pong_init() 做了这些事:

ping_pong.radio_access_id = SMTC_SW_PLATFORM( smtc_rac_open_radio( RAC_HIGH_PRIORITY ) );
ping_pong.transaction     = smtc_rac_get_context( ping_pong.radio_access_id );

然后填充事务上下文:

每次收发通过 smtc_rac_submit_radio_transaction() 提交,RAC 和 radio_planner 负责:

调度模式:

模式 含义
SMTC_RAC_ASAP_TRANSACTION 到了计划时间就开始;错过了就尽快开始
SMTC_RAC_SCHEDULED_TRANSACTION 必须按计划时间开始;错过就中止

4.4 Ping-Pong 状态机

主机 master

上电
  └─ ping_pong_start_as_master()
        └─ TX 252 × 0x55(delay=0ms)
              └─ TXDONE
                    └─ RX(10ms 后进入接收,255 符号前导码超时)
                          ├─ RXDONE:校验 252 × 0x88
                          │     └─ counter++
                          │           ├─ counter < 25:250ms 后再 TX 252 × 0x55
                          │           └─ counter >= 25:restart,重新从 TX 开始
                          ├─ RX_TIMEOUT / 非法载荷
                          │     └─ 重试,最多 5 次;超过后 restart(仍是 master)
                          ├─ RX_CRC_ERROR / TASK_ABORTED
                          │     └─ 重新提交上一次事务(代码未做 5 次上限)
                          └─ 其它错误:panic(正常情况下不会出现)

从机 slave

上电
  └─ restart_ping_pong()
        └─ RX(连续接收 + 255 符号前导码超时)
              ├─ RXDONE:校验 252 × 0x55
              │     └─ 等待 100ms(SLAVE_PROCESSING_TIME_MS)
              │           └─ TX 252 × 0x88
              │                 └─ TXDONE
              │                       └─ 继续 RX
              ├─ RX_TIMEOUT
              │     └─ 继续 RX(slave 不计数重试)
              └─ 非法载荷
                    └─ 继续 RX,不回复

4.5 关键参数

射频参数(main_ping_pong.h)

宏 默认值 说明
RF_FREQ_IN_HZ 470100000 470.1 MHz
TX_OUTPUT_POWER_DBM 14 14 dBm
LORA_SPREADING_FACTOR RAL_LORA_SF9 SF9
LORA_BANDWIDTH RAL_LORA_BW_125_KHZ 125 kHz
LORA_CODING_RATE RAL_LORA_CR_4_5 4/5
LORA_PREAMBLE_LENGTH 12 12 个符号
LORA_PKT_LEN_MODE RAL_LORA_PKT_EXPLICIT 显式包头
LORA_IQ false 标准 IQ
LORA_CRC true 开启 CRC

同步字在 app_ping_pong.c 中固定为:

static const smtc_rac_lora_syncword_t SYNC_WORD = LORA_PRIVATE_NETWORK_SYNCWORD;

即私有网络同步字 0x12(寄存器值 0x1424)。

应用参数(app_ping_pong.c)

参数 默认值 说明
PAYLOAD_SIZE 252 每包字节数
MASTER_TX_PATTERN 0x55 主机发送内容
SLAVE_TX_PATTERN 0x88 从机回复内容
DELAY 250 ms 主机两轮之间的间隔
RX_PROCESSING_TIME 10 ms 主机 TXDONE 后进入 RX 的延迟
SLAVE_PROCESSING_TIME_MS 100 ms 从机收到后回复前的延迟
RX_SYMBOL_TIMEOUT 255 符号 前导码搜索超时(SF9/125k 约 1 s)
MAX_RETRY_COUNT 5 主机最大连续失败次数
MAX_EXCHANGE_COUNT 25 主机完成多少轮后 restart

SLAVE_PROCESSING_TIME_MS 要小于前导码超时(255 符号,SF9/125k 约 1 s),
否则主机可能在从机回复前就超时。默认 100 ms 是安全的。

4.6 时序估算

252 字节、SF9、BW125、CR4/5、前导码 12、CRC 开启:

因此日志大约每 2~3 秒出现一组 TXDONE / RXDONE,这是正常的,不是卡死。


5. 编译

5.1 环境要求

arm-none-eabi-gcc --version    # 建议 10.x 或 12.x
cmake --version                # 要求 >= 3.25
ninja --version
st-flash --version             # 或 st-info --version

安装(Ubuntu / Debian / WSL2):

sudo apt update
sudo apt install -y gcc-arm-none-eabi binutils-arm-none-eabi \
                    cmake ninja-build stlink-tools git

5.2 推荐:使用 build.sh

主机:

cd /path/to/Ra-20_Ra-11_aithinker

BUILD_DIR=build-lr1121-master \
  ./build.sh lr1121 ping_pong

从机:

BUILD_DIR=build-lr1121-slave \
PING_PONG_START_AS_SLAVE=ON \
  ./build.sh lr1121 ping_pong

可选:调整从机回复延迟(单位 ms):

BUILD_DIR=build-lr1121-slave \
PING_PONG_START_AS_SLAVE=ON \
SLAVE_PROCESSING_TIME_MS=100 \
  ./build.sh lr1121 ping_pong

生成文件:

build-lr1121-master/ping_pong.bin
build-lr1121-master/ping_pong.hex
build-lr1121-slave/ping_pong.bin
build-lr1121-slave/ping_pong.hex

build.sh 的 BOARD 默认就是 STM32F103CB,所以上面的命令不需要再显式指定
BOARD=STM32F103CB;如果环境里已经导出过别的 BOARD,请先 unset BOARD
或在命令前显式写 BOARD=STM32F103CB。

编译时如果看到:

CMake Warning:
  Manually-specified variables were not used by the project:
    LEGACY_EVK_LR20XX

这是正常的。LEGACY_EVK_LR20XX 只对 LR20xx(LR2012/LR2021/LR2022)有效,
LR1121 不使用该参数,可以忽略。

5.3 手动 CMake 方式

主机:

rm -rf build-lr1121-master

cmake -S examples -B build-lr1121-master -G Ninja \
  -DCMAKE_BUILD_TYPE=MinSizeRel \
  -DBOARD=STM32F103CB \
  -DRAC_RADIO=lr1121 \
  -DPING_PONG_START_AS_SLAVE=OFF

cmake --build build-lr1121-master --target ping_pong

从机:

rm -rf build-lr1121-slave

cmake -S examples -B build-lr1121-slave -G Ninja \
  -DCMAKE_BUILD_TYPE=MinSizeRel \
  -DBOARD=STM32F103CB \
  -DRAC_RADIO=lr1121 \
  -DPING_PONG_START_AS_SLAVE=ON \
  -DSLAVE_PROCESSING_TIME_MS=100

cmake --build build-lr1121-slave --target ping_pong

5.4 编译选项说明

CMake 变量 推荐值 作用
BOARD STM32F103CB F103/C8T6 板级;不要写 STM32F103C8
RAC_RADIO lr1121 选择 LR11xx 驱动族,定义 LR1121
CMAKE_BUILD_TYPE MinSizeRel 体积优化,适合 64 KB Flash
PING_PONG_START_AS_SLAVE OFF / ON 主机 / 从机
SLAVE_PROCESSING_TIME_MS 100 从机回复前延迟
STLINK_SERIAL 可选 多块 ST-Link 时指定探针序列号
RAC_LOG_PROFILE DEFAULT RAC 内部日志等级(默认即可)

改了 BOARD、RAC_RADIO、角色或 SLAVE_PROCESSING_TIME_MS 后,必须重新
configure,不能复用上一次的构建目录,否则 CMake 缓存可能仍保留旧值。


6. 主机 / 从机配置步骤

6.1 主机(master)

编译:

BUILD_DIR=build-lr1121-master ./build.sh lr1121 ping_pong

烧录:

st-flash --connect-under-reset write build-lr1121-master/ping_pong.bin 0x08000000

或使用 CMake 目标:

cmake --build build-lr1121-master --target flash_ping_pong

上电后串口应首先看到:

INFO: work mode: master
INFO: starting ping-pong as master

行为:

6.2 从机(slave)

编译:

BUILD_DIR=build-lr1121-slave \
PING_PONG_START_AS_SLAVE=ON \
  ./build.sh lr1121 ping_pong

烧录:

st-flash --connect-under-reset write build-lr1121-slave/ping_pong.bin 0x08000000

或:

cmake --build build-lr1121-slave --target flash_ping_pong

上电后串口应首先看到:

INFO: work mode: slave
INFO: this device is still slave
INFO: receiving...

行为:

6.3 上电顺序

建议:

  1. 先给从机上电,确认它已经进入 receiving...;
  2. 再给主机上电。

如果先给主机上电,主机会先发几包、超时几次,然后 restart;从机上电后仍能恢复正常通信,
但日志里会先出现一些 retrying / timeout,属于正常现象。

6.4 两块板必须一致的项目

以下参数两块板必须完全相同,否则无法通信:

只允许“角色”不同(PING_PONG_START_AS_SLAVE)。


7. 烧录与串口监视

7.1 烧录

st-flash --connect-under-reset write build-lr1121-master/ping_pong.bin 0x08000000
st-flash --connect-under-reset write build-lr1121-slave/ping_pong.bin  0x08000000

多块 ST-Link 同时连接时,用 --serial <SN> 指定探针:

st-flash --serial <ST-LINK-SN> --connect-under-reset write build-lr1121-master/ping_pong.bin 0x08000000

7.2 串口监视

任意串口工具,参数 115200 8N1:

picocom -b 115200 /dev/ttyUSB0
# 或
minicom -D /dev/ttyUSB0 -b 115200

Windows 下可用任意串口助手,选择对应的 COM 口,115200 8N1。

固件日志默认带 ANSI 颜色控制码(ESC[0;32m 等)。大多数终端会正常显示为绿色
INFO:;如果看到乱码前缀,请使用支持 ANSI 的终端,或在 smtc_hal_dbg_trace.h
中把 HAL_DBG_TRACE_COLOR 设为 HAL_FEATURE_OFF 后重新编译。


8. 通信成功的日志

以下 RSSI/SNR 为示例值,实际会随距离、天线和环境变化。只要 size=252 且包号持续递增,
就说明主从通信成功。

8.1 主机 master 日志

INFO: compile time: Oct 29 2026 15:00:00
INFO: work mode: master
INFO: starting ping-pong as master
INFO: sending 252 bytes of 0x55 (delay=0ms)
INFO: TXDONE: packet #1 sent (252 bytes of 0x55)
INFO: receiving...
INFO: RXDONE: packet #1 received, size=252, RSSI=-42 dBm, SNR=9 dB
INFO: sending 252 bytes of 0x55 (delay=250ms)
INFO: TXDONE: packet #2 sent (252 bytes of 0x55)
INFO: receiving...
INFO: RXDONE: packet #2 received, size=252, RSSI=-41 dBm, SNR=9 dB
INFO: sending 252 bytes of 0x55 (delay=250ms)
INFO: TXDONE: packet #3 sent (252 bytes of 0x55)
INFO: receiving...
INFO: RXDONE: packet #3 received, size=252, RSSI=-43 dBm, SNR=8 dB
...

8.2 从机 slave 日志

INFO: compile time: Oct 29 2026 15:00:00
INFO: work mode: slave
INFO: this device is still slave
INFO: receiving...
INFO: RXDONE: packet #1 received, size=252, RSSI=-40 dBm, SNR=8 dB
INFO: sending 252 bytes of 0x88 (delay=100ms)
INFO: TXDONE: packet #1 sent (252 bytes of 0x88)
INFO: receiving...
INFO: RXDONE: packet #2 received, size=252, RSSI=-40 dBm, SNR=8 dB
INFO: sending 252 bytes of 0x88 (delay=100ms)
INFO: TXDONE: packet #2 sent (252 bytes of 0x88)
INFO: receiving...
INFO: RXDONE: packet #3 received, size=252, RSSI=-41 dBm, SNR=8 dB
INFO: sending 252 bytes of 0x88 (delay=100ms)
INFO: TXDONE: packet #3 sent (252 bytes of 0x88)
...

8.3 主从日志对应关系

时刻 主机日志 从机日志 说明
T0 sending ... 0x55 receiving... 主机开始发 Ping
T0+1.3s TXDONE: packet #N sent RXDONE: packet #N received 从机收到 0x55
T0+1.4s receiving... sending ... 0x88 从机延迟 100ms 后回复
T0+2.7s RXDONE: packet #N received TXDONE: packet #N sent 主机收到 0x88
T0+2.95s sending ... (delay=250ms) receiving... 主机 250ms 后发下一包

8.4 成功判据

满足以下条件即可判定主从通信成功:

  1. 两端第一行分别是 work mode: master 和 work mode: slave;
  2. 主机持续出现 TXDONE 后跟 RXDONE,从机持续出现 RXDONE 后跟 TXDONE;
  3. size=252,内容分别是 0x55(主机发/从机收)和 0x88(从机发/主机收);
  4. 包序号持续递增;
  5. 没有反复出现 ERROR: unexpected payload;
  6. 没有反复出现 retrying、max retry count reached;
  7. 没有看门狗复位或 HardFault;
  8. PA8(TX LED)和 PB15(RX LED)随收发交替点亮。

8.5 正常运行中会看到的“非错误”日志

日志 含义 是否正常
max exchange count reached, restarting 主机完成 25 轮,自动重新开始 正常
this device is still master restart 时确认角色仍是主机 正常
starting ping-pong as master restart 后重新开始发送 正常
usp/rac: new event: transaction (reception) has ended (timeout) 接收窗口内没有收到前导码 从机常态 / 主机偶发
device is slave, continuing 从机超时后继续监听 正常
retrying (fails=1, max=5) 主机某次没收好,正在重试 偶发正常,连续出现需排查
max retry count reached, restarting 主机连续失败 5 次后重启流程 偶发正常,频繁出现需排查

注意:restart 只重置 counter,不会重置 tx_packet_count / rx_packet_count,
所以日志里的 packet # 会继续递增,不会回到 1。


9. 常见问题排查

9.1 串口完全没有输出

9.2 只有 compile time / work mode,之后没有任何收发日志

9.3 主机只有 TXDONE,没有 RXDONE

9.4 两端都在 receiving...

两块板都编译成了 slave。检查两边的 work mode:,至少把其中一块重新编译为 master
(PING_PONG_START_AS_SLAVE=OFF)。

9.5 两端都在发送,互相收不到

两块板都编译成了 master。两块 master 会互相抢发,日志表现为大量 TXDONE 和
timeout。把其中一块重新编译为 slave。

9.6 出现 ERROR: unexpected payload

收到 252 字节但内容不对,常见原因:

9.7 反复 retrying / max retry count reached

如果日志出现:

INFO: usp/rac: new event: ERROR: RX_CRC_ERROR

说明收到空口数据但 CRC 校验失败。优先检查两端的 SF/BW/CR/同步字是否一致,
并排查同频干扰、天线匹配和供电噪声。代码会重新提交上一次接收事务。

9.8 看门狗复位 / HardFault

9.9 编译通过但烧录后不运行

9.10 终端里出现 [0;32m 之类的字符

这是日志颜色控制码,不是乱码。使用支持 ANSI 的串口终端,或在
smtc_hal_dbg_trace.h 中关闭 HAL_DBG_TRACE_COLOR 后重新编译。


10. 快速检查清单


11. 参考资料


文档版本:2026-10-29
适用工程:examples/main_examples/ping_pong_example
目标配置:BOARD=STM32F103CB + RAC_RADIO=lr1121