BL702
Bouffalo Lab’s BL702 is a RISC-V SoC with built-in IEEE 802.15.4 and BLE 5.0
radio. The zigbee-rs BL702 backend uses FFI bindings to Bouffalo’s lmac154
C library for radio access, combined with Embassy for async operation.
Hardware Overview
| Spec | Value |
|---|---|
| Core | RISC-V 32-bit (RV32IMAF), 144 MHz |
| Flash | 512 KB (XIP) |
| SRAM | 128 KB (112 KB usable after cache) |
| Radio | BLE 5.0 + IEEE 802.15.4 |
| Target | riscv32imac-unknown-none-elf |
| I/O | UART ×2, SPI, I2C, ADC, DAC, USB 2.0 FS |
Common Modules and Boards
- XT-ZB1 — Zigbee module based on BL702
- DT-BL10 — compact BL702 breakout
- Pine64 Pinenut — BL602/BL702 module
- BL706 IoT DevBoard (Sipeed) — devkit with USB and headers
Memory Map
FLASH : ORIGIN = 0x23000000, LENGTH = 512K
RAM : ORIGIN = 0x42014000, LENGTH = 112K
Prerequisites
Rust Toolchain
rustup default nightly
rustup update nightly
# Add the RISC-V target
rustup target add riscv32imac-unknown-none-elf
# rust-src for build-std
rustup component add rust-src
Vendor Libraries (for real radio operation)
The BL702 backend uses FFI bindings to two pre-compiled C libraries from Bouffalo’s BL IoT SDK:
liblmac154.a— 802.15.4 MAC/PHY librarylibbl702_rf.a— RF calibration and configuration
These libraries are compiled with rv32imfc/ilp32f (hardware float ABI),
while Rust targets riscv32imac/ilp32 (soft-float). The .a files must
have their ELF float-ABI flag stripped before linking.
Flash Tool
cargo install blflash # Community flash tool for BL702
Building
With Stubs (CI mode — no vendor SDK)
cd examples/bl702-sensor
cargo build --release --features stubs -Z build-std=core,alloc
The stubs feature provides no-op implementations of all FFI symbols, allowing
the full Rust code to compile and link without the vendor libraries. This is how
CI verifies the BL702 code compiles on every push.
With Real Vendor Libraries
Three options for linking vendor libraries (in priority order):
Option 1: Full SDK path
BL_IOT_SDK_DIR=/path/to/bl_iot_sdk cargo build --release -Z build-std=core,alloc
Option 2: Explicit library directories
LMAC154_LIB_DIR=/path/to/lib BL702_RF_LIB_DIR=/path/to/lib cargo build --release -Z build-std=core,alloc
Option 3: Local vendor_libs/ directory
Place ABI-patched copies of liblmac154.a and libbl702_rf.a in
examples/bl702-sensor/vendor_libs/:
cargo build --release -Z build-std=core,alloc
CI Build Command
From .github/workflows/ci.yml:
cd examples/bl702-sensor
cargo build --release --features stubs -Z build-std=core,alloc
# Firmware artifact extraction
OBJCOPY=$(find $(rustc --print sysroot) -name llvm-objcopy | head -1)
$OBJCOPY -O binary $ELF ${ELF}.bin
$OBJCOPY -O ihex $ELF ${ELF}.hex
Build Script (build.rs)
The build.rs handles vendor library discovery:
#![allow(unused)]
fn main() {
// Skip vendor libs when `stubs` feature is active (CI mode)
let use_stubs = env::var("CARGO_FEATURE_STUBS").is_ok();
if use_stubs { return; }
// Priority: BL_IOT_SDK_DIR → LMAC154_LIB_DIR → vendor_libs/
}
.cargo/config.toml
[build]
target = "riscv32imac-unknown-none-elf"
[unstable]
build-std = ["core", "alloc"]
Flashing
blflash
blflash flash target/riscv32imac-unknown-none-elf/release/bl702-sensor.bin \
--port /dev/ttyUSB0
Bouffalo Dev Cube (GUI)
- Download BLDevCube from Bouffalo
- Select BL702 chip
- Load the
.binfile - Connect via UART and flash
Entering Boot Mode
Hold the BOOT pin low during power-on or reset to enter the UART bootloader.
MAC Backend Notes
The BL702 MAC backend lives in zigbee-mac/src/bl702/:
zigbee-mac/src/bl702/
├── mod.rs # Bl702Mac struct, MacDriver trait impl, PIB management
└── driver.rs # Bl702Driver — FFI bindings to lmac154.a
Feature Flag
zigbee-mac = { features = ["bl702"] }
Architecture
MacDriver trait methods
│
▼
Bl702Mac (mod.rs)
├── PIB state (addresses, channel, config)
├── Frame construction (beacon req, assoc req, data)
└── Bl702Driver (driver.rs)
├── FFI → liblmac154.a (Bouffalo C library)
│ ├── lmac154_setChannel / setPanId / setShortAddr
│ ├── lmac154_triggerTx / enableRx / readRxData
│ └── lmac154_runCCA / getRSSI / getLQI
├── TX completion: lmac154_txDoneEvent callback → TX_SIGNAL
└── RX completion: lmac154_rxDoneEvent callback → RX_SIGNAL
FFI Callbacks
The vendor library calls back into Rust through these functions:
#![allow(unused)]
fn main() {
#[no_mangle]
extern "C" fn lmac154_txDoneEvent(/* ... */) {
TX_SIGNAL.signal(tx_status);
}
#[no_mangle]
extern "C" fn lmac154_rxDoneEvent(/* ... */) {
// Copy RX data to static buffer
RX_SIGNAL.signal(());
}
}
Interrupt Registration
After creating the MAC, register the M154 interrupt handler:
#![allow(unused)]
fn main() {
// bl_irq_register(M154_IRQn, lmac154_getInterruptHandler());
// bl_irq_enable(M154_IRQn);
}
Radio Features
- Hardware CRC generation and checking
- Configurable TX power: 0 dBm to +14 dBm
- RSSI / LQI measurement
- Hardware auto-ACK with configurable retransmission
- Hardware address filtering (PAN ID, short addr, long addr)
- CSMA-CA support
- AES-128 CCM hardware acceleration
Example Walkthrough
The bl702-sensor example implements a Zigbee 3.0 temperature & humidity
end device with button control and UART logging.
Custom Embassy Time Driver
Since there is no embassy-bl702 HAL yet, the example provides a minimal
Embassy time driver using the BL702 TIMER_CH0:
#![allow(unused)]
fn main() {
// 32-bit match-compare timer at 1 MHz (FCLK/32 from 32 MHz)
const TIMER_BASE: usize = 0x4000_A500;
pub fn init() {
// Prescaler: FCLK(32 MHz) / 32 = 1 MHz tick
write_volatile(TCCR, 0x1F);
// Free-running mode
write_volatile(TMR_0, 0xFFFF_FFFF);
// Enable counter
write_volatile(TCER, 0x01);
}
}
UART Logging
Debug output goes through the BL702 UART peripheral:
#![allow(unused)]
fn main() {
impl log::Log for Bl702Logger {
fn log(&self, record: &log::Record) {
let mut w = UartWriter;
write!(w, "[{}] {}\r\n", record.level(), record.args());
}
}
}
GPIO Button
Direct register-based GPIO for the join/leave button:
#![allow(unused)]
fn main() {
const BUTTON_PIN: u8 = 8; // GPIO8 on most BL702 modules
gpio::configure_input_pullup(BUTTON_PIN);
}
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Linker error: undefined lmac154_* | Vendor libraries not linked | Set BL_IOT_SDK_DIR or use --features stubs |
| Float ABI mismatch | Vendor .a uses hard-float | Strip ELF float-ABI flag from .a files |
blflash can’t connect | Not in boot mode | Hold BOOT pin low during reset |
| No UART output | Wrong UART pins or baud | Check board schematic; default 115200 baud |
| Timer not working | TIMER_CH0 not initialized | Ensure time_driver::init() runs before Embassy |
Build fails without stubs | Missing vendor lib env vars | Set LMAC154_LIB_DIR or BL_IOT_SDK_DIR |