本文说明如何把本仓库的 examples/main_examples/ping_pong_example 工程配置为:
内容包括工程结构、收发方法、主机/从机配置、编译烧录、串口日志和常见问题。
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 容量不同。
| 角色 | CMake 参数 | 启动行为 |
|---|---|---|
| 主机 master | -DPING_PONG_START_AS_SLAVE=OFF(默认) |
上电立即发送 252 字节 0x55 |
| 从机 slave | -DPING_PONG_START_AS_SLAVE=ON |
上电只接收,收到合法 0x55 后回复 252 字节 0x88 |
角色在编译时固定,运行中不能切换;两块板必须分别编译、分别烧录。
使用 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。
| 项目 | 说明 |
|---|---|
| 模组 | 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,共地 |
| 天线 | 两块板都必须接好对应频段天线 |
引脚定义来自 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 天线切换引脚。
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,然后重新编译两块板。
BOARD 是 STM32F103CB这是本文最容易踩坑的地方。
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 板级选项
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。
两者都是 LQFP48 封装,SPI1、USART1、GPIO 引脚完全相同,区别只在 Flash 容量。
本工程实测 Flash 约 43 KB,RAM 约 7.8 KB,C8T6 可以正常运行。
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、看门狗 |
main_ping_pong.cvoid 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 状态机
}
}
关键点:
smtc_rac_run_engine() 要尽可能频繁地调用,否则 TX/RX 调度会延迟,甚至触发看门狗。RADIO_DIOX 电平补一次回调。periodic_uplink_init() 虽然被调用,但没有定义 ENABLE_PERIODIC_UPLINK,所以周期上行是关闭的,不会和 ping-pong 事务抢射频。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 );
然后填充事务上下文:
ping_pong.payload[252];SMTC_RAC_MODULATION_LORA;callback_pre_radio_transaction → pre_ping_pong_callback():根据 TX/RX 点亮对应 LED;callback_post_radio_transaction → post_ping_pong_callback():处理 TXDONE / RXDONE / 超时 / CRC 错误。每次收发通过 smtc_rac_submit_radio_transaction() 提交,RAC 和 radio_planner 负责:
start_time_ms 调度;post_ping_pong_callback();RP_STATUS_* 状态。调度模式:
| 模式 | 含义 |
|---|---|
SMTC_RAC_ASAP_TRANSACTION |
到了计划时间就开始;错过了就尽快开始 |
SMTC_RAC_SCHEDULED_TRANSACTION |
必须按计划时间开始;错过就中止 |
上电
└─ 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(正常情况下不会出现)
上电
└─ restart_ping_pong()
└─ RX(连续接收 + 255 符号前导码超时)
├─ RXDONE:校验 252 × 0x55
│ └─ 等待 100ms(SLAVE_PROCESSING_TIME_MS)
│ └─ TX 252 × 0x88
│ └─ TXDONE
│ └─ 继续 RX
├─ RX_TIMEOUT
│ └─ 继续 RX(slave 不计数重试)
└─ 非法载荷
└─ 继续 RX,不回复
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 是安全的。
252 字节、SF9、BW125、CR4/5、前导码 12、CRC 开启:
2^9 / 125000 = 4.096 ms因此日志大约每 2~3 秒出现一组 TXDONE / RXDONE,这是正常的,不是卡死。
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
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 不使用该参数,可以忽略。
主机:
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
| 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 缓存可能仍保留旧值。
编译:
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
行为:
0x55;0x88;编译:
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...
行为:
0x55 并校验通过后,等待 100 ms 回复 252 字节 0x88;建议:
receiving...;如果先给主机上电,主机会先发几包、超时几次,然后 restart;从机上电后仍能恢复正常通信,
但日志里会先出现一些 retrying / timeout,属于正常现象。
以下参数两块板必须完全相同,否则无法通信:
RF_FREQ_IN_HZLORA_SPREADING_FACTORLORA_BANDWIDTHLORA_CODING_RATELORA_PREAMBLE_LENGTH0x12)只允许“角色”不同(PING_PONG_START_AS_SLAVE)。
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
任意串口工具,参数 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后重新编译。
以下 RSSI/SNR 为示例值,实际会随距离、天线和环境变化。只要 size=252 且包号持续递增,
就说明主从通信成功。
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
...
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)
...
| 时刻 | 主机日志 | 从机日志 | 说明 |
|---|---|---|---|
| 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 后发下一包 |
满足以下条件即可判定主从通信成功:
work mode: master 和 work mode: slave;TXDONE 后跟 RXDONE,从机持续出现 RXDONE 后跟 TXDONE;size=252,内容分别是 0x55(主机发/从机收)和 0x88(从机发/主机收);ERROR: unexpected payload;retrying、max retry count reached;| 日志 | 含义 | 是否正常 |
|---|---|---|
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。
st-flash 是否报错)。compile time / work mode,之后没有任何收发日志-DRAC_RADIO=lr1121,不是 lr2012 / lr2021 / sx1262。BOARD=STM32F103CB,不是 NUCLEO_L476。TXDONE,没有 RXDONEreceiving...。work mode:)。SLAVE_PROCESSING_TIME_MS 是否过大(应小于约 1 s)。receiving...两块板都编译成了 slave。检查两边的 work mode:,至少把其中一块重新编译为 master
(PING_PONG_START_AS_SLAVE=OFF)。
两块板都编译成了 master。两块 master 会互相抢发,日志表现为大量 TXDONE 和timeout。把其中一块重新编译为 slave。
ERROR: unexpected payload收到 252 字节但内容不对,常见原因:
retrying / max retry count reachedENABLE_PERIODIC_UPLINK(会与 ping-pong 抢事务)。如果日志出现:
INFO: usp/rac: new event: ERROR: RX_CRC_ERROR
说明收到空口数据但 CRC 校验失败。优先检查两端的 SF/BW/CR/同步字是否一致,
并排查同频干扰、天线匹配和供电噪声。代码会重新提交上一次接收事务。
smtc_rac_run_engine()。HAL_Delay。st-info --flash 确认实际容量。0x08000000。[0;32m 之类的字符这是日志颜色控制码,不是乱码。使用支持 ANSI 的串口终端,或在smtc_hal_dbg_trace.h 中关闭 HAL_DBG_TRACE_COLOR 后重新编译。
-DBOARD=STM32F103CB(不是 STM32F103C8)-DRAC_RADIO=lr1121PING_PONG_START_AS_SLAVE=OFFPING_PONG_START_AS_SLAVE=ONwork mode: master 和 work mode: slaveTXDONE / RXDONEsize=252,RSSI/SNR 合理retrying / max retry count reachedexamples/main_examples/ping_pong_example/readme.mdexamples/CMakeLists.txt(ping_pong 相关段落)examples/modem_pinout.hexamples/smtc_hal_f1/examples/radio_hal/ral_lr11xx_bsp.csmtc_rac_lib/smtc_rac_api/smtc_rac_api.hsmtc_rac_lib/radio_planner/FAQ_ra11_cn.md文档版本:2026-10-29
适用工程:examples/main_examples/ping_pong_example
目标配置:BOARD=STM32F103CB + RAC_RADIO=lr1121