RCP 802.15.4 Firmware¶
Radio Co-Processor (RCP) firmware for the EFR32MG1B232F256GM48 chip found in the Lidl Silvercrest Smart Home Gateway.
This firmware transforms the gateway's Zigbee chip into a Radio Co-Processor
that handles only the 802.15.4 PHY/MAC layer. The Zigbee stack runs host-side
in zigbeed, which lets us pair a Series 1 EFR32MG1B radio with a modern
EmberZNet 8.2.2 stack — the gateway then exposes EZSP v18 to Z2M / ZHA,
even though Silabs froze on-chip Series 1 support at EmberZNet 7.5.1.
| Host stack | Exposes | When to use |
|---|---|---|
cpcd + zigbeed 8.2.2 (recommended) |
EZSP v18 | Default — modern stack, latest Z2M/ZHA features |
cpcd + zigbeed 7.5.1 (legacy) |
EZSP v13 | Only if you need bit-for-bit parity with the NCP-UART-HW path |
Single-stack only. This RCP firmware does not support running Zigbee and Thread concurrently on this gateway — the EFR32MG1B is Silabs Series 1 and supports only Dynamic Multiprotocol (BLE + one 15.4 stack), not the Concurrent Multiprotocol needed for Zigbee+Thread. See
docker/cpcd-zigbeed-otbr/README.mdfor the full post-mortem. For Thread / Matter, reflash with the OT-RCP firmware in26-OT-RCP/.
About RCP Architecture¶
Unlike standalone firmware (like the Router), an RCP delegates the entire Zigbee protocol stack to a host computer. The EFR32 only handles the radio PHY/MAC layer and communicates with the host via the CPC Protocol (Co-Processor Communication).
+-------------------+ UART +-------------------+ Ethernet +---------------------+
| EFR32MG1B (RCP) | 460800 | RTL8196E | TCP/IP | Host (x86/ARM) |
| | baud | (Gateway SoC) | | |
| 802.15.4 PHY/MAC |<----------->| in-kernel |<------------>| cpcd |
| + CPC Protocol | ttyS1 | UART<->TCP bridge| port 8888 | | |
| | | (rtl8196e-uart- | | v |
| | | bridge) | | |
| CPC Protocol v5 | | | | zigbeed |
| HW Flow Control | | | | (Zigbee stack) |
| | | | | | |
+-------------------+ +-------------------+ | v |
| Zigbee2MQTT |
+---------------------+
Why use RCP instead of NCP?
| Aspect | NCP (24-NCP-UART-HW) | RCP (this firmware) |
|---|---|---|
| Stack location | On EFR32 (limited RAM) | On host (unlimited resources) |
| Protocol | EZSP (binary) | CPC (multiplexed) |
| Stack version | Locked to 7.5.1 (EZSP v13) | 7.5.1 or 8.2.2 (EZSP v13 / v18) |
| Stack updates | Requires reflashing the EFR32 | Swap zigbeed on the host, EFR32 untouched |
| Network size | Limited by EFR32 RAM | Host memory is the limit |
Hardware¶
| Component | Specification |
|---|---|
| Zigbee SoC | EFR32MG1B232F256GM48 |
| Flash | 256KB |
| RAM | 32KB |
| Radio | 2.4GHz IEEE 802.15.4 |
| UART | PA0 (TX), PA1 (RX), PA4 (RTS), PA5 (CTS) @ 460800 baud |
Option 1: Flash Pre-built Firmware (Recommended)¶
Pre-built firmware is available in the firmware/ directory. From the
repository root:
./flash_efr32.sh -y rcp # default baud 460800, default IP 192.168.1.88
./flash_efr32.sh -y rcp 230400 # 230400 baud (cpcd POSIX-supported only)
./flash_efr32.sh -y -g 10.0.0.5 rcp # custom gateway IP
./flash_efr32.sh --help # full CLI reference
The script handles everything: pulse nRST for a clean chip state, switch
the in-kernel UART bridge to flash mode, run the Xmodem upload, write the
matching FIRMWARE_BAUD=<baud> to /userdata/etc/radio.conf so the bridge
arms at the right speed on next boot, then reboot.
Supported RCP bauds (pre-built GBLs): 115200, 230400, 460800.
cpcd rejects non-POSIX bauds (691200, 892857), so RCP is capped at
460800. For a custom baud (POSIX values only), see
Option 2 below.
Legacy env-var interface (deprecated, kept for v3.0.x compat):
FW_CHOICE=3 BAUD_CHOICE=460800 CONFIRM=y ./flash_efr32.shstill works with a deprecation warning. Prefer the flag form above.
Gateway state after flash¶
flash_efr32.sh writes the matching baud to /userdata/etc/radio.conf
so the gateway-side init scripts arm the bridge correctly on next boot.
For RCP at baud <B>:
FIRMWARE=rcp # what's in the EFR32 application slot
FIRMWARE_BAUD=<B> # chip-side UART baud — S50uart_bridge reads this and
# arms TCP:8888 at <B> (no MODE= line; otbr-agent off)
(No FIRMWARE_VERSION for RCP — the meaningful EmberZNet version is
host-side in zigbeed, not in the chip firmware.) See
3-Main-SoC-Realtek-RTL8196E/34-Userdata/README.md
for the full key reference.
The init script S50uart_bridge reads this on boot. cpcd on the host
then connects to tcp://<gw>:8888 (see Host Software Setup
or docker/docker-compose-zigbee.yml).
Then continue to Host Software Setup to configure cpcd and zigbeed on your host machine.
Option 2: Build from Source¶
For users who want to modify the CPC configuration, change baudrate, or use a different SDK version.
Prerequisites¶
Install Silicon Labs tools (see 1-Build-Environment/12-silabs-toolchain/):
This installs:
- slc-cli - Silicon Labs Configurator
- arm-none-eabi-gcc - ARM GCC toolchain
- commander - Simplicity Commander
- Gecko SDK with EmberZNet
Build¶
cd 2-Zigbee-Radio-Silabs-EFR32/25-RCP-UART-HW
./build_rcp.sh # default baud 460800
./build_rcp.sh 230400 # POSIX baud only — cpcd rejects 691200/892857
./build_rcp.sh --help # show baud options + defaults
Output¶
The output filename embeds the chosen baud:
firmware/
├── rcp-uart-802154-115200.gbl
├── rcp-uart-802154-230400.gbl
└── rcp-uart-802154-460800.gbl # default
flash_efr32.sh resolves the right file via a glob.
Customization¶
Pin layout (PA0/PA1/PA4/PA5) is in patches/sl_cpc_drv_uart_usart_vcom_config.h.
The build script edits the BAUDRATE define automatically based on the
positional BAUD argument — no manual file editing for baud changes.
Flash¶
Via network (same as Option 1):
Via J-Link/SWD (if you have physical access to the SWD pads):
For a detailed explanation of how
universal-silabs-flasherworks (firmware detection, bootloader entry, the-fflag, troubleshooting), see 22-Backup-Flash-Restore.
Host Software Setup¶
After flashing the RCP firmware, you need to configure the host software chain.
Required Components¶
| Component | Version | Source | Description |
|---|---|---|---|
| cpcd | v4.5.3 | SiliconLabs/cpc-daemon | CPC daemon |
| zigbeed | EmberZNet 8.2.2 | Simplicity SDK 2025.6.3 | Zigbee stack daemon (recommended) |
| zigbeed | EmberZNet 7.5.1 | Gecko SDK 4.5.0 | Zigbee stack daemon (legacy) |
CPC transport — native TCP bus.
cpcdhere is built with a nativebus_type: TCP(carried ascpcd/tcp-bus.patchand applied automatically bybuild_cpcd.sh). cpcd connects straight to the gateway's in-kernel UART↔TCP bridge onTCP:8888and owns its own reconnection, so the host stack no longer needs asocatPTY shim in front of cpcd. The Docker stack config in this tree (docker/cpcd-zigbeed/) is wired for it (bus_type: TCP,tcp_server_address/tcp_server_port); rebuild the image from that directory to ship the patched cpcd. Setbus_type: UARTwithuart_device_fileto fall back to the classic socat-PTY path.
Build Instructions¶
See subdirectories for detailed build instructions:
- cpcd/ - CPC daemon (for host)
- zigbeed-8.2.2/ - zigbeed EmberZNet 8.2.2 (recommended)
- zigbeed-7.5.1/ - zigbeed EmberZNet 7.5.1 (legacy)
- rcp-stack/ - Systemd service manager for the complete chain
- cpcd-rtl8196e/ - Cross-compile cpcd for gateway (experimental)
Quick Start with Docker (Recommended)¶
A pre-built Docker image is available for PC (amd64) and Raspberry Pi (arm64):
# Pull the image
docker pull ghcr.io/jnilo1/cpcd-zigbeed:latest
# Or use the full stack with Zigbee2MQTT
cd docker/
# Edit docker-compose-zigbee.yml: set RCP_HOST to your gateway's IP
docker compose -f docker-compose-zigbee.yml up -d
See docker/README.md for detailed instructions.
| Image | cpcd | EmberZNet | EZSP | Architectures |
|---|---|---|---|---|
ghcr.io/jnilo1/cpcd-zigbeed:latest |
4.5.3 | 8.2.2 | v18 | amd64, arm64 |
Quick Start with rcp-stack (Native)¶
The rcp-stack tool manages the entire cpcd + zigbeed chain natively (without Docker):
# Start the stack
rcp-stack up
# Check status
rcp-stack status
# Stop the stack
rcp-stack down
# Troubleshoot
rcp-stack doctor
Zigbee2MQTT Configuration¶
With rcp-stack (recommended):
Baudrate and Network Considerations¶
Baudrate Options¶
| Baudrate | Status | Notes |
|---|---|---|
| 115200 | Supported | Conservative |
| 230400 | Supported | |
| 460800 | Default | Max supported by cpcd (POSIX baud limit) |
| 692857 | Not usable | Non-standard — cpcd rejects it |
| 892857 | Not usable | Non-standard — cpcd rejects it |
cpcd validates baud rates against the POSIX standard list and rejects non-standard values like 691200 or 892857 (even over a TCP/PTY bridge where the baud is irrelevant). 460800 is the practical maximum for RCP mode. Higher bauds (up to 892857) are available for NCP and OT-RCP, which don't use cpcd.
All bauds run through the in-kernel rtl8196e-uart-bridge driver on
kernel 6.18 — change via
echo <baud> > /sys/module/rtl8196e_uart_bridge/parameters/baud.
Network Size vs Baudrate¶
| Liaison | Throughput |
|---|---|
| 802.15.4 radio | ~25 KB/s |
| UART 115200 | ~11 KB/s |
| UART 460800 | ~46 KB/s |
| UART 892857 | ~89 KB/s |
At 115200, the UART is ~2x slower than the Zigbee radio. At 460800+ it is no longer the bottleneck.
| Network size | 115200 | 230400 | 460800+ |
|---|---|---|---|
| < 50 devices | OK | OK | OK |
| 50-100 devices | OK* | OK | Recommended |
| > 100 devices | May bottleneck | OK | Recommended |
*OK for normal use; possible latency during traffic spikes (OTA updates, large groups).
For most home installations, 115200 is sufficient.
Maximum Baud: Why 892857 and Not 921600¶
The RTL8196E UART has a fixed 16× oversampling with integer-only
divisors and a 200 MHz bus clock. The achievable baud is
200000000 / (16 × N) for integer N. For 921600 the divisor falls at
13.56 — neither 13 nor 14 gives acceptable error (−3.1% / +4.3%).
892857 = 200000000 / (16 × 14) hits an exact integer divisor, giving 0% baud error on the RTL side. The EFR32 reaches 893023 with its fractional divider (0.02% mismatch). This is 7.7× the original 115200 and within 3% of 921600.
See 3-Main-SoC-Realtek-RTL8196E/32-Kernel/POST-MORTEM-6.18.md for
the full N+1 divisor investigation.
Check UART errors on the gateway:
Changing Baudrate¶
# 1. Build the GBL at the desired baud (POSIX values only for RCP)
cd 2-Zigbee-Radio-Silabs-EFR32/25-RCP-UART-HW && ./build_rcp.sh 230400
# 2. Flash — radio.conf FIRMWARE_BAUD is updated automatically
./flash_efr32.sh -y rcp 230400
# 3. Update UART_BAUDRATE in docker-compose-zigbee.yml (or cpcd.conf)
# so cpcd opens the TCP socket at the matching speed.
The baud must be a standard POSIX value (115200, 230400, 460800) — cpcd
rejects anything else (the build script and the flash script will warn
but not block, in case you plan to use a non-cpcd consumer).
TCP Stability Requirements¶
The CPC protocol is sensitive to network conditions. For reliable operation:
| Requirement | Why |
|---|---|
| Hardware flow control | Prevents buffer overruns |
| Direct Ethernet | Minimizes latency and jitter |
| No WiFi bridges | WiFi adds unpredictable latency |
| Avoid congested switches | Packet delays cause CPC timeouts |
Recommended: Connect the gateway directly to the host with an Ethernet cable.
Troubleshooting¶
Flashing Issues¶
| Problem | Solution |
|---|---|
| No response from RCP | Verify TCP: nc -zv <gateway-ip> 8888 |
| Xmodem timeout | Close all SSH sessions, use -f flag |
| Wrong firmware flashed | Reflash - the bootloader is always preserved |
cpcd Connection Issues¶
| Problem | Solution |
|---|---|
| cpcd won't connect | With the native TCP bus, check tcp_server_address/tcp_server_port in cpcd.conf point at the gateway bridge; verify nc -zv <gateway-ip> 8888 |
| CPC version mismatch | Use GSDK 4.5.0 for CPC Protocol v5 |
| Frequent disconnects | Use direct Ethernet, check for WiFi bridges |
zigbeed Issues¶
| Problem | Solution |
|---|---|
| zigbeed won't start | Check cpcd is running: rcp-stack status |
| Stack version mismatch | Rebuild zigbeed with matching SDK version |
Memory Usage¶
| Resource | Used | Available |
|---|---|---|
| Flash | ~116 KB | 256 KB |
| RAM | ~22 KB | 32 KB |
Features¶
- RTL8196E Boot Delay: 1-second delay for host UART initialization
- Hardware Flow Control: RTS/CTS required for reliable TCP operation
- CPC Security Disabled: Saves ~45KB flash (not needed for local network)
- Stack flexibility: Same
.gblworks withzigbeed7.5.1 or 8.2.2 — no EFR32 reflash needed to change EZSP version - Native TCP bus in cpcd: cpcd dials the gateway's UART↔TCP bridge directly (
bus_type: TCP) and reconnects on its own — nosocatPTY shim, no stale-PTY restart cascade
Project Structure¶
25-RCP-UART-HW/
├── build_rcp.sh # RCP firmware build script
├── README.md # This file
├── patches/ # RCP firmware patches
│ ├── rcp-uart-802154.slcp # Project config
│ ├── main.c # Entry point (1s delay)
│ ├── sl_cpc_drv_uart_usart_vcom_config.h # UART pins
│ └── sl_cpc_security_config.h # CPC security disabled
├── firmware/ # Output (rcp-uart-802154.gbl)
├── cpcd/ # CPC daemon build scripts
├── zigbeed-7.5.1/ # zigbeed EmberZNet 7.5.1 (legacy)
├── zigbeed-8.2.2/ # zigbeed EmberZNet 8.2.2 (recommended)
├── docker/ # Docker stack (cpcd + zigbeed + Z2M)
└── rcp-stack/ # Systemd service manager
├── bin/rcp-stack # Main script
├── scripts/ # Helper scripts
├── systemd/user/ # Service units
└── examples/ # Config examples
Related Projects¶
24-NCP-UART-HW/- NCP firmware (simpler, stack on EFR32)27-Router/- Router firmware (autonomous, no host needed)
References¶
- CPC Daemon
EMBERZNET-8.x-GUIDE.md— how a Series 1 EFR32MG1B ends up exposing EZSP v18 via host-sidezigbeed- rtl8196e-uart-bridge
License¶
Educational and personal use. Silicon Labs SDK components under their respective licenses.