This document explains how to set up a CMake cross-compilation environment for USP / RAC example projects for the Ai-Thinker Ra-11 / Ra-11H (RF chip: lr1121). It covers both Linux and Windows, and uses examples/main_examples/ping_pong_example as an example to illustrate the build and firmware outputs, and explains how to create a custom example project in this repository.
Before development, please clone this project from GitHub (Ai-Thinker Ra-11 / Ra-11H / Ra-20 / Ra-20H USP / RAC SDK):
| Method | URL |
|---|---|
| HTTPS | https://github.com/Ai-Thinker-AIoT/ra11-ra20.git |
# Choose any directory, for example ~/project
mkdir -p ~/project && cd ~/project
# Or use HTTPS
git clone https://github.com/Ai-Thinker-AIoT/ra11-ra20.git
cd ra11-ra20
All subsequent cmake / ninja commands in this document are run from the repository root (the directory containing examples/ and smtc_rac_lib/).
Tip: If Git is not installed yet, install the dependencies described in Section 3 (Linux) or Section 4 (Windows) first, then return here to perform the clone operation.
This project uses the CMake parameter -DBOARD= to select the MCU and the associated HAL / toolchain. Recommended for Ra-11 development:
-DBOARD= value |
MCU / Development Board | HAL Directory | Notes |
|---|---|---|---|
STM32F103CB |
STM32F103CBT6 (commonly used by Ai-Thinker Ra-11 / Ra-11H) | examples/smtc_hal_f1 |
Default recommendation for Ra-11 |
NUCLEO_L476 |
ST NUCLEO-L476RG | examples/smtc_hal_l4 |
Frequently used for Semtech official validation (not the default for Ai-Thinker boards) |
NUCLEO_L073 |
ST NUCLEO-L073RZ | examples/smtc_hal_l0_LL |
Available, but less thoroughly validated |
Note: You must reconfigure CMake after changing
BOARD(clearbuild/or use a new build directory). Otherwise, an old toolchain cache may be reused.
The Ra-11 / Ra-11H use the LR11xx family. Select the following fixed radio option during compilation:
-DRAC_RADIO= |
Chip Family | Typical Modules |
|---|---|---|
lr1121 |
LR11xx | Ai-Thinker Ra-11 / Ra-11H |
The relevant drivers and abstraction layers are located in:
smtc_rac_lib/radio_drivers/ — chip register-level driverssmtc_rac_lib/smtc_ral/ — Radio Abstraction Layer (RAL)smtc_rac_lib/smtc_rac/ — Radio Access Controller (RAC / USP)Some examples are enabled only for specific chips. (For example, cad_side_detector supports lr1121). After CMake configuration, the terminal prints Available examples; use that actual list as the source of truth.
Whether on Linux or Windows, the CMake cross-compilation flow is fundamentally the same:
Source code (C/ASM)
→ CMake configuration (select BOARD / RAC_RADIO / toolchain)
→ Ninja (or Make) compilation
→ arm-none-eabi-gcc links an ELF file
→ arm-none-eabi-objcopy generates .hex / .bin
→ (Optional) st-flash / STM32CubeProgrammer programs the target
| Tool | Purpose |
|---|---|
ARM GNU Toolchain (arm-none-eabi-gcc, etc.) |
Cross-compiler, assembler, and linker for Cortex-M |
| arm-none-eabi-objcopy | Converts ELF files to Intel HEX (.hex) or raw binary (.bin) |
| CMake (≥ 3.25 required by this project) | Generates the build system (Ninja / Makefile) and manages dependencies, board options, and chip options |
| Ninja (recommended) or Make | Runs the actual compilation commands. Ninja is faster and provides clearer logs |
| Git | Retrieves and manages source code |
stlink-tools (st-flash) or STM32CubeProgrammer |
Writes firmware to MCU Flash through ST-Link |
| Serial terminal (minicom / screen / PuTTY / serial assistant) | Views logs and verifies examples such as ping-pong |
Optional tools:
| Tool | Purpose |
|---|---|
| ccache | Speeds up repeated builds |
| OpenOCD / gdb-multiarch | On-target debugging (not required for compilation) |
| Python3 + pyserial | Serial scripts and helper programming/logging tools |
The official README broadly recommends gcc ≥ 13.3 (the distribution-provided 10.x version usually also works), CMake ≥ 3.28 (this project requires at least 3.25), and Ninja ≥ 1.11.
The following examples use Ubuntu / Debian / WSL2.
sudo apt update
sudo apt install -y \
gcc-arm-none-eabi \
binutils-arm-none-eabi \
gdb-multiarch \
cmake \
ninja-build \
git \
stlink-tools \
python3 python3-pip
# Optional
sudo apt install -y ccache
pip3 install --user pyserial
| Package | Purpose |
|---|---|
gcc-arm-none-eabi |
Provides arm-none-eabi-gcc / g++ / as to compile STM32 firmware |
binutils-arm-none-eabi |
Provides arm-none-eabi-objcopy, objdump, size, and similar tools to generate hex/bin files and inspect sections |
cmake |
Reads examples/CMakeLists.txt, configures BOARD/RAC_RADIO, and generates build.ninja |
ninja-build |
Builds targets such as ping_pong in parallel according to dependencies |
stlink-tools |
Provides st-flash / st-info for programming .bin files through ST-Link |
gdb-multiarch |
Multi-architecture GDB for debugging with OpenOCD/ST-Link (optional) |
git |
Version control |
python3 + pyserial |
Helper scripts such as serial monitoring (optional) |
arm-none-eabi-gcc --version
arm-none-eabi-objcopy --version
cmake --version # Recommended: ≥ 3.25
ninja --version
st-info --version # When an ST-Link is connected
If the toolchain is not in the default PATH, set it as follows:
export PATH=/path/to/gcc-arm-none-eabi/bin:$PATH
# Assume the repository was cloned to ~/project/ra11-ra20 as described above:
cd ~/project/ra11-ra20
# Clear the old cache when configuring for the first time or changing BOARD / RAC_RADIO
rm -rf build
# Configure (Ra-11: STM32F103CB + lr1121)
cmake -S examples -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DBOARD=STM32F103CB \
-DRAC_RADIO=lr1121
# Build the target
cmake --build build --target ping_pong
The output is placed in the build/ by default (see Section 5).
There are two common Windows paths: native Windows, or WSL2 (recommended; commands are identical to Linux).
wsl --install).~/project/...) rather than /mnt/c/... to avoid slower builds.st-flash in WSL (requires USB passthrough), or use STM32CubeProgrammer in Windows to program the .bin / .hex generated by WSL (accessible via the \\wsl$\... path).The build commands are identical to Linux; use the CMake commands in Sections 3.4 and 5 directly.
| Software | Purpose | Installation Notes |
|---|---|---|
| ARM GNU Toolchain (arm-none-eabi) | Cross-compiles STM32 firmware | Download the Windows version from ARM Developer; then add its bin directory to the system PATH |
| CMake | Project configuration | Select Add CMake to the system PATH during installation; version ≥ 3.25 is recommended |
| Ninja | Build execution | Download ninja.exe and place it on PATH, or use winget install Ninja-build.Ninja, Scoop, or Chocolatey |
| Git for Windows | Retrieves source code | Git Bash may be included |
| STM32CubeProgrammer or ST-Link Utility / stlink | Programs firmware | CubeProgrammer GUI / CLI is the most reliable option |
| Serial tool (PuTTY, MobaXterm, serial assistant, etc.) | Views logs | Choose the correct COM port and baud rate |
| (Optional) Python 3 | Helper scripts | Select Add to PATH, then run pip install pyserial |
Optional IDEs:
arm-none-eabi-gcc --version
cmake --version
ninja --version
# Assume the repository was cloned to D:\project\ra11-ra20 as described above:
cd D:\project\ra11-ra20
# Clear old cache on first configuration or after changing chip/board options
Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue
cmake -S examples -B build `
-G Ninja `
-DCMAKE_BUILD_TYPE=MinSizeRel `
-DBOARD=STM32F103CB `
-DRAC_RADIO=lr1121
cmake --build build --target ping_pong
STM32_Programmer_CLI -c port=SWD -w build\ping_pong.bin 0x08000000 -v -rst
Alternatively, open build\ping_pong.hex / .bin in the CubeProgrammer GUI, select the ST-Link SWD interface, and use address 0x08000000.
Tip: If CMake cannot find the compiler, set the PATH first:
$env:Path = "C:\Program Files (x86)\Arm GNU Toolchain arm-none-eabi\...\bin;" + $env:Path
This project generally configuresarm-none-eabi-gccautomatically through its toolchain file.
You can also install the build tools in MSYS2:
pacman -S mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja
# The official arm-none-eabi toolchain is still recommended; do not use the host GCC to build STM32 firmware
The cross-compiler must be arm-none-eabi-*. Do not use an x86_64 GCC directly to build firmware.
Example source directory: examples/main_examples/ping_pong_example/
CMake target name: ping_pong
Ping-Pong requires two boards with different roles:
| Device | Role | Behavior |
|---|---|---|
| Device A | Manager (default) | Sends PING after power-up and waits for PONG |
| Device B | Subordinate | Listens after power-up and replies with PONG after receiving PING |
RF parameter settings must match on both sides (frequency, SF, BW, coding rate, sync word, etc.), see main_ping_pong.h.
Do not program both boards with the default Manager firmware. Otherwise, both devices send
PINGand wait forPONG, so they cannot communicate normally.
Step 1 — Build device A (Manager, default)
Ra-11 (lr1121) + STM32F103CB:
rm -rf build
cmake -S examples -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DBOARD=STM32F103CB \
-DRAC_RADIO=lr1121
cmake --build build --target ping_pong
Outputs: build/ping_pong.bin and build/ping_pong.hex. Save or program these to device A first, for example:
cp build/ping_pong.bin build/ping_pong_manager.bin
cp build/ping_pong.hex build/ping_pong_manager.hex
Step 2 — Build Device B (Subordinate)
Enable -DPING_PONG_START_AS_SUBORDINATE=ON in the configuration and rebuild (CMake must be reconfigured):
rm -rf build
cmake -S examples -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DBOARD=STM32F103CB \
-DRAC_RADIO=lr1121 \
-DPING_PONG_START_AS_SUBORDINATE=ON
cmake --build build --target ping_pong
The outputs remain build/ping_pong.bin / .hex; program it to Device B.
Do not use -DBOARD=NUCLEO_L476 to test an Ai-Thinker Ra-11 board. NUCLEO_L476 targets the ST NUCLEO-L476RG. With the current pin configuration in this repository, building ping_pong for that board may fail because HW_MODEM_* macros are undefined.
For Ra-11, consistently use -DBOARD=STM32F103CB and -DRAC_RADIO=lr1121.
List targets available in the current configuration:
cmake --build build --target help
After ping_pong builds successfully, build/ typically contains:
| File | Format | Description |
|---|---|---|
build/ping_pong (or ping_pong.elf, depending on the generator) |
ELF | Direct linker output, containing debug symbols, used by GDB |
build/ping_pong.bin |
Raw binary | A contiguous image from the Flash base address; commonly used by st-flash |
build/ping_pong.hex |
Intel HEX | A text format with address records; commonly used by CubeProgrammer and some other programming tools |
By default, this project generates both .hex and .bin during the POST_BUILD step in add_example(). It does not generate only one format.
The relevant logic (from examples/CMakeLists.txt):
# HEX
COMMAND ${CMAKE_OBJCOPY} -O ihex
$<TARGET_FILE:${TARGET_NAME}>
${CMAKE_BINARY_DIR}/${TARGET_NAME}.hex
# BIN
COMMAND ${CMAKE_OBJCOPY} -O binary -S
$<TARGET_FILE:${TARGET_NAME}>
${CMAKE_BINARY_DIR}/${TARGET_NAME}.bin
Therefore:
flash_<target> target uses st-flash with .bin, while many GUI tools prefer .hex. Both are generated, so select the one appropriate for your tool.Use a standard project target (recommended)
Targets registered using add_example (like ping_pong), already configure a POST_BUILD action that outputs both hex and bin by default.
Run objcopy manually (for debugging or custom targets)
# Assume the ELF file is build/ping_pong
arm-none-eabi-objcopy -O ihex build/ping_pong build/ping_pong.hex
arm-none-eabi-objcopy -O binary -S build/ping_pong build/ping_pong.bin
If you see only an ELF and no hex/bin
add_example target such as ping_pong, rather than a temporary target without a POST_BUILD hook.arm-none-eabi-objcopy is on PATH (that is, available as CMake's CMAKE_OBJCOPY).Creating ping_pong.hex... / Creating ping_pong.bin....# Linux: program the BIN at address 0x08000000
cmake --build build --target flash_ping_pong
# Or
st-flash --connect-under-reset write build/ping_pong.bin 0x8000000
Recommended procedure:
PING_PONG_START_AS_SUBORDINATE).-DPING_PONG_START_AS_SUBORDINATE=ON).Always clean and rebuild after changing BOARD, or reconfiguring parameters:
rm -rf build
cmake -S examples -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DBOARD=STM32F103CB \
-DRAC_RADIO=lr1121
cmake --build build --target ping_pong
In this repository, a “project” is an example directory under examples/main_examples/, registered as an independently buildable target through add_example() in examples/CMakeLists.txt. The recommended approach is to copy and rename the closest existing example, then modify its application logic rather than building HAL and toolchain setup from scratch.
Assume the new example is named hello_lora (you can choose another name; this name is used below):
examples/
├── main.c # Common entry point; dispatches to examples using MAKEFILE_APP
├── main.h # Application IDs and main_xxx() declarations for examples
├── CMakeLists.txt # Registers targets using add_example()
└── main_examples/
└── hello_lora_example/ # New directory
├── main_hello_lora.h
├── main_hello_lora.c # Initializes HAL / RAC and contains the main loop
├── app_hello_lora.h
└── app_hello_lora.c # Radio application logic (TX/RX/CAD, etc.)
Reference templates:
| Requirement | Recommended Reference |
|---|---|
| LoRa TX/RX and state machine | ping_pong_example |
| Continuous wave transmission / basic TX | tx_cw_example |
Step 1 — Copy and rename
cd examples/main_examples
cp -r ping_pong_example hello_lora_example
cd hello_lora_example
# Rename files as needed, for example:
# main_ping_pong.c -> main_hello_lora.c
# app_ping_pong.c -> app_hello_lora.c
# Then globally replace function names, header names, and log prefixes.
Step 2 — Implement the entry function
Provide a public entry point in main_hello_lora.c. (Its name must match main.h and main.c.) A typical structure is:
void main_hello_lora( void )
{
hal_mcu_init( );
smtc_rac_init( );
/* Initialize LEDs, buttons, and other peripherals (optional). */
hello_lora_init( ); /* Implemented in app_hello_lora.c */
while( true )
{
hal_watchdog_reload( );
smtc_rac_run_engine( ); /* Must be called periodically to drive RAC / radio_planner. */
}
}
On the application side, use APIs such as smtc_rac_open_radio(), smtc_rac_get_context(), and smtc_rac_submit_radio_transaction() to configure LoRa parameters and transmit or receive. (Follow the style in app_ping_pong.c.)
Step 3 — Register an application ID in examples/main.h
At the end of the existing #define list, add an unused numeric ID and declare the entry function:
#define HELLO_LORA 19 /* The value must be unique; do not conflict with existing applications */
void main_hello_lora( void );
Step 4 — Add a branch to examples/main.c
add_example() defines MAKEFILE_APP=HELLO_LORA for this target. The common main() uses it to call your entry point:
#elif MAKEFILE_APP == HELLO_LORA
main_hello_lora( );
Place it in the existing #elif chain before the #else containing #error.
Step 5 — Register the target in examples/CMakeLists.txt
Add the following in the “Define all examples” section (adjust paths according to your directory structure):
add_example(hello_lora "HELLO_LORA"
"${CMAKE_CURRENT_SOURCE_DIR}/main_examples/hello_lora_example/main_hello_lora.c;${CMAKE_CURRENT_SOURCE_DIR}/main_examples/hello_lora_example/app_hello_lora.c"
)
target_include_directories(hello_lora PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/main_examples/hello_lora_example
)
Notes:
hello_lora, is the Ninja/CMake target name. (Use it for builds and flash_hello_lora.)"HELLO_LORA", must match the macro name in main.h. (CMake treats the name case-insensitively and converts it with TOUPPER.).c source file list belonging to this example (semicolon-separated); main.c, HAL, RAC, and related dependencies are added automatically by add_example().Optional additions (for all_examples and log output):
# Add hello_lora to the ALL_EXAMPLES list
list(APPEND ALL_EXAMPLES hello_lora)
message(STATUS " - hello_lora : custom hello LoRa example")
If the example supports only Ra-11 (lr1121), wrap it in a conditional, for example:
if(${RAC_RADIO} MATCHES "^lr1121$")
add_example(hello_lora "HELLO_LORA" "...")
target_include_directories(hello_lora PRIVATE ...)
endif()
Step 6 — Reconfigure and build
After modifying CMakeLists.txt, main.h, or main.c, reconfigure CMake:
rm -rf build
cmake -S examples -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DBOARD=STM32F103CB \
-DRAC_RADIO=lr1121
cmake --build build --target hello_lora
Outputs:
build/hello_lora (ELF)build/hello_lora.binbuild/hello_lora.hexProgram:
cmake --build build --target flash_hello_lora
# Or
st-flash --connect-under-reset write build/hello_lora.bin 0x8000000
| Item | Description |
|---|---|
Unique APP ID in main.h |
Must not duplicate another #define |
Declaration in main.h and branch in main.c |
Names must match HELLO_LORA / main_hello_lora |
Second add_example argument |
Must correspond to the macro in main.h (like "HELLO_LORA") |
target_include_directories |
Must make headers in this directory discoverable |
smtc_rac_run_engine() in main loop |
Without it, transmit/receive callbacks do not run |
Clean build/ after changing BOARD/RAC_RADIO |
Prevents cache-related errors |
BOARD / HAL rather than bypassing the existing add_example workflow.)-DAPP= in the aggregate example target and forget to register an independent target through add_example. (Independent targets such as ping_pong are preferred for normal development.)examples/main.c; put it in main_examples/<your>_example/.| Parameter | Meaning | Example |
|---|---|---|
-DBOARD= |
MCU board type | STM32F103CB (recommended for Ra-11) |
-DRAC_RADIO= |
LoRa chip | lr1121 |
-DCMAKE_BUILD_TYPE= |
Optimization level | MinSizeRel (recommended), Debug, Release |
-G Ninja |
Generator | Unix Makefiles can also be used |
-DPING_PONG_START_AS_SUBORDINATE= |
ping_pong role |
OFF = Manager (default), ON = Subordinate |
Q: CMake reports “You have to define a -DBOARD=…”
A: Explicitly pass -DBOARD=STM32F103CB (or another supported board type).
Q: CMake reports “You must select the radio with -DRAC_RADIO=”
A: Ra-11 requires -DRAC_RADIO=lr1121.
Q: After changing configuration, the old driver is still linked / behavior is abnormal.
A: Delete build/ and reconfigure. CMakeCache retains old values for RAC_RADIO and BOARD.
Q: Why does configuration/building fail with -DBOARD=NUCLEO_L476?
A: Use -DBOARD=STM32F103CB for Ra-11. NUCLEO_L476 uses a different HAL. When HW_MODEM_* is commented out in the current modem_pinout.h, the L4 UART build may fail. This is a board-type and pin-configuration mismatch, not an error in the ping_pong command syntax.
Q: CMake reports that it cannot find axk_cmd_example/....
A: This optional example directory is not present in the source tree. CMakeLists.txt skips that target when its source is missing; run rm -rf build and configure again.
Q: Both boards continuously report unexpected payload in ping-pong.
A: Usually both boards were programmed with the default Manager firmware. Build one with the default configuration, then reconfigure and build the other with -DPING_PONG_START_AS_SUBORDINATE=ON. In logs, when prefix_received='80' is printed as decimal, it corresponds to ASCII 'P' (the first byte of PING/PONG).
Q: After creating a new project, why does linking fail with undefined reference to main_xxx?
A: Check that main.c contains a #elif MAKEFILE_APP == YOUR_APP branch calling main_xxx(), and that the APP string in add_example matches the macro in main.h.
Q: My new project builds successfully, but the radio does not respond.
A: Confirm that the main loop calls smtc_rac_run_engine(), and verify RAC_RADIO=lr1121, frequency/SF/BW settings, and antenna compatibility with the hardware.
Q: How do I build on Windows?
A: WSL2 is recommended because its commands are identical to Linux. Alternatively, install the native Windows toolchain as described in Section 4.2, then use cmake with ninja directly.
| Document | Contents |
|---|---|
| Root-level README_cn.md | USP overview and official board/example list |
| Development Environment Setup Guide | CMake development environment setup |
| examples/main_examples/ping_pong_example/readme.md | Detailed Chinese Ping-Pong guide |
| examples/main_examples/ping_pong_example/FAQ.md | Ping-Pong FAQ |
| doc/usp_porting_guide.md | Porting guidance and PA / frequency-band notes |
Project for this document: USP/RAC examples for Ai-Thinker Ra-11 / Ra-11H (-DBOARD=STM32F103CB -DRAC_RADIO=lr1121).