Getting Started: Linux Host

September 4, 2026 · View on GitHub

Home · Getting Started: Linux · Getting Started: MCU · Troubleshooting

Use this guide when your host is a Linux board (demonstrated on a Raspberry Pi) and an Espressif chip is the Wi-Fi + Bluetooth co-processor. Understand the two goals, pick a bus, then follow that bus's section top to bottom.

Tip

No Raspberry Pi? It is just the demonstrated board — most Linux platforms can host ESP-Hosted, with some board bring-up (kernel headers, device-tree/overlay, GPIO mapping, building the module). To use another board:

  • Map the connection tables below to your board's GPIOs
  • Handle the board-specific bits via Porting: Linux host: the reset GPIO, enabling the bus (device-tree / overlay), and building the kernel module.

The concepts, buses, and software flow are the same; only the host pins and the device-tree/overlay mechanism differ.

Example hardware used

RoleChip
HostRaspberry Pi (or any Linux platform)
Co-processorESP32-C5 or ESP32-C6 (also ESP32, C2, C3, C61, S2, S3)
Wi-Fi busSDIO (fastest) or SPI Full-Duplex (easiest)
Bluetoothover the same bus, or over a dedicated UART
Demo exampleWi-Fi Station

Supported bus combinations

SDIO 👍SDIO + UARTSPI 👍SPI + UART
Wi-Fi busSDIOSDIOSPI Full-DuplexSPI Full-Duplex
BluetoothMultiplexed on SDIOdedicated UARTMultiplexed on SPIdedicated UART
Extra BT pinsNo+2 / +4No+2 / +4
Wi-Fi throughputHighestHighGoodGood
WiringPCB + pull-upsPCB + pull-upsjumpers OKjumpers OK
Best forMax Wi-Fi speedMax Wi-Fi + BTEasiest bring-upEasy bring-up + BT

Note

  1. Dedicated UART doesn't necessarily mean better Bluetooth.
  2. ESP has one radio, so be it multiplexed or dedicated, the radio would be serialised.
  3. Dedicated UART is only preferred, when you need standard HCI packets on the bus directly.
  4. In case of SDIO and SPI buses (without UART), we multiplex all traffic types including Bluetooth, on the same bus.
  5. SDIO and SPI (without dedicated UART) give reliable and better performance than UART combined.

Supported ESP co-processors

Co-processorSDIOSDIO + UARTSPISPI + UART
ESP32
ESP32-C2
ESP32-C3
ESP32-C5
ESP32-C6
ESP32-C61
ESP32-S2
ESP32-S3

Note

  • SDIO is available on ESP32, ESP32-C5, ESP32-C6, and ESP32-C61 (fixed SDIO pins). A classic ESP32 may need an eFuse burn — see Specific considerations.
  • The + UART set-ups add Bluetooth over a dedicated UART. ESP32-S2 has no Bluetooth radio, so its + UART set-ups are unavailable. Bluetooth can also ride the Wi-Fi bus (SDIO/SPI) instead of UART.

Goals

Two things run over the bus once set-up is done — a control path and a data path. Here is what each does.

Goal 1 — Control path

Carries commands that configure the radio: scan, connect, start a SoftAP, and every other feature call. Your app makes a normal call; ESP-Hosted turns it into an RPC message, sends it over the bus (via /dev/esps0) to the co-processor, which runs the real esp_wifi call and returns the result.

flowchart LR
    APP["Host app<br/>c_app / py_app"] -->|"command (RPC)"| KMOD["Kernel module<br/>/dev/esps0"]
    KMOD <-->|"SDIO / SPI"| CP["Co-processor"]
    CP --> DRV["esp_wifi / feature"]

Goal 2 — Data path

Carries actual network traffic. After the host associates to an access point, the co-processor bridges Wi-Fi frames to a standard Linux network interface, ethsta0. Your standard TCP/IP stack (DHCP, sockets, ping) runs over it — no ESP-Hosted API needed for data.

flowchart LR
    STACK["Standard TCP/IP stack"] <--> ETH["ethsta0"]
    ETH <--> KMOD["Kernel module"]
    KMOD <-->|"SDIO / SPI"| CP["Co-processor"]
    CP <-->|"Wi-Fi"| AIR(("AP / air"))

Mental model

Bring the stack up in this order, and stop at the first arrow whose signal does not appear:

flowchart TD
    S1["Hardware setup<br/>connection verified"]:::c1
    S2["Choose the bus<br/>co-processor and host"]:::c2
    S3["Co-processor<br/>configure and flash"]:::c3
    S4["Raspberry Pi<br/>enable bus (device-tree) + reboot"]:::c4
    S5["Kmod<br/>build and load for the bus"]:::c5
    S6["User-space app<br/>build and run"]:::c6

    S1 --> S2 --> S3 --> S4
    S4 -->|"bus allowed & free"| S5
    S5 -->|"first communication successful"| S6

    classDef c1 fill:#FDEBD0,stroke:#E59866,color:#111
    classDef c2 fill:#FCF3CF,stroke:#B7950B,color:#111
    classDef c3 fill:#D6EAF8,stroke:#5DADE2,color:#111
    classDef c4 fill:#D5F5E3,stroke:#58D68D,color:#111
    classDef c5 fill:#E8DAEF,stroke:#AF7AC5,color:#111
    classDef c6 fill:#D1F2EB,stroke:#48C9B0,color:#111

Choosing the bus (co-processor and host)

On Linux, Wi-Fi rides SDIO or SPI Full-Duplex; Bluetooth rides the same bus or a dedicated UART.

BusPins (signals + Reset + GND)ThroughputProsCons
SDIOCLK, CMD, DAT0–DAT3, Reset, GND
= 8 (+ external pull-ups)
HighestBest Wi-Fi data-plane speedNeeds a PCB + pull-ups; fixed pins
SPI Full-DuplexSCLK, MOSI, MISO, CS, Handshake, Data Ready, Reset, GND
= 8
GoodEasiest, most robust; jumpers OKLower throughput than SDIO
+ UART (Bluetooth)Tx, Rx (+ CTS, RTS)
= +2 (2-wire) / +4 (4-wire)
LowDedicated BT/BLE linkBT only; extra pins

Pick a set-up and jump straight to its steps:

1. SDIO2. SPI Full-Duplex3. SDIO + UART4. SPI + UART
1. Hardware
2. Flash CP
3. Enable bus
4. Load kmod
5. Run & verify
1. Hardware
2. Flash CP
3. Enable bus
4. Load kmod
5. Run & verify
1. Hardware
2. Flash CP
3. Enable bus
4. Load kmod
5. Run & verify
1. Hardware
2. Flash CP
3. Enable bus
4. Load kmod
5. Run & verify

Set up the tools

eh.py is the one command that builds, configures, flashes, and runs ESP-Hosted. Install it once per checkout, and source the environment once per shell:

cd /path/to/esp_hosted
./install.sh     # or ./install.fish   (fish shell)
. ./export.sh    # or . ./export.fish  (fish shell)

install.sh sets up the toolchain and dependencies; export.sh puts eh.py on your PATH. See Tools: eh.py for the full command reference. Work from the Wi-Fi Station example, then follow one of the four set-ups below.


1. SDIO

Raspberry Pi — SDIO — ESP co-processor. Highest throughput; strictest hardware requirements.

1.1 Hardware considerations and connections

Hardware considerations — SDIO:

  • Fixed pins. SDIO uses fixed pins on both ends — the Raspberry Pi SDIO controller and the co-processor (ESP32 / ESP32-C5 / ESP32-C6 / ESP32-C61 have fixed SDIO GPIOs). Wire exactly as shown; the pins are not reassignable.
  • Reset signal. Host output to the co-processor EN/RST pin (configurable GPIO), asserted at start-up to sync host and co-processor state. Co-processor menuconfig: Example configuration → SDIO Configuration → Host SDIO GPIOs → Slave GPIO pin to reset itself.
  • Pull-up resistors (mandatory). External 51 kΩ pull-ups on CMD, DAT0DAT3 — on all of them (also marked in the table). Select your co-processor in the linked page's chip selector.
  • Clock. SDIO max is 50 MHz. Raspberry Pi 3/4 cap the actual SDIO clock at ~41.467 MHz even when 50 MHz is requested; Raspberry Pi 5 reaches 50 MHz. Check the achieved clock with sudo cat /sys/kernel/debug/mmc0/ios.
  • Voltage levels. All signals are 3.3 V; if using a level shifter, set its output to 3.3 V.
  • Power. Insufficient power is a leading, frequently-overlooked cause of non-deterministic crashes and suboptimal performance. Get all three right:
    1. Power adapter — use only the official Raspberry Pi adapter, matched to the board's exact input rating.
    2. Power cable — rated to carry the expected current, for both the Raspberry Pi and the ESP; thin or long cables cause brown-outs.
    3. ESP supply — power the ESP from a reliable supply of adequate rating.
  • PCB design (production). Length-match all SDIO signals (CLK, CMD, DAT0–3); if not perfect, prioritise matching CLK to the data lines. Use controlled-impedance traces, bypass capacitors near the power pins, optional series termination, and a 4-layer board with power/ground planes for high speed.

Specific considerations — SDIO:

  • SDIO 1-bit mode. Full 4-bit SDIO needs a proper PCB carrying the mandatory pull-ups — jumpers are not suitable. Only SDIO 1-bit mode may be prototyped on 'wire wrap' wires: all leads equal length, each ≤ 5 cm; the pull-ups remain mandatory.
  • Classic ESP32 eFuse. A classic ESP32 co-processor will likely need a one-time, irreversible eFuse burn (bootstrapping-pin / DAT2 conflict) — follow the pull-up requirements procedure; an incorrect burn can brick the chip. Applies to the classic ESP32 only, not ESP32-C2/C3/C5/C6/C61/S2/S3.

Connections — SDIO (Raspberry Pi ↔ co-processor)

RPi pin (BCM)ESP32ESP32-C6ESP32-C61ESP32-C5Function
15 (GPIO22)IO14IO19IO26IO9CLK
16 (GPIO23) (pull-up)IO15IO18IO25IO10CMD
18 (GPIO24) (pull-up)IO2IO20IO27IO8DAT0
22 (GPIO25) (pull-up)IO4IO21IO28IO7DAT1
37 (GPIO26) (pull-up)IO12IO22IO22IO14DAT2
13 (GPIO27) (pull-up)IO13IO23IO23IO13DAT3
31 (GPIO6)ENRSTRSTRSTReset
39 (GND)GNDGNDGNDGNDGround

1.2 Flash the co-processor

cd examples/wifi/sta/cp
eh.py set-target esp32c6
eh.py menuconfig          # Transport -> SDIO
eh.py -p <PORT> flash monitor

1.3 Enable the SDIO bus on Raspberry Pi

Enable SDIO in /boot/firmware/config.txt:

dtoverlay=sdio,poll_once=off

Warning

Reboot the Raspberry Pi after editing config.txt — the change only takes effect after a reboot. (Bus allowed & free.)

1.4 Build and load the kmod

Build and load in one command:

cd ../linux_802_3_host/kmod
./build.sh --bus sdio --reload --reset-gpio 518 --clock-mhz 50
  • --reload — build, then unload and load the module.
  • --reset-gpio 518 — host GPIO wired to the co-processor reset (sets the resetpin module parameter; RPi GPIO6 = 518, pin 31).
  • --clock-mhz 50 — SDIO bus clock (sets clockspeed); 50 MHz max. See Clock in the hardware considerations for the Raspberry Pi 3/4 cap and how to check the achieved clock.
  • Non-Raspberry-Pi board: prefix with PORT=<board>. Unload with ./unload.sh.

Check the bus — first communication successful. dmesg should show the co-processor announce itself:

$ dmesg | tail
esp_hosted: process_capabilities: ESP peripheral capabilities: 0x3d
esp_hosted: print_capabilities: Features supported are:
esp_hosted: print_capabilities:  * WLAN
esp_hosted: slave fw version: 0x00020200
esp_hosted: Slave up event processed

If the capabilities / Slave up event lines never appear, stop and fix the transport/bus before continuing.

1.5 Run and verify

cd ../py_app        # or ../c_app
eh.py set-target linux
eh.py build
eh.py run

The driver creates ethsta0 (network interface) and /dev/esps0 (control device). The app only associates to the AP over the control path — it does not assign an IP. To use the data path, bring the interface up, obtain an IP via DHCP, and ping:

sudo ip link set ethsta0 up
sudo dhclient ethsta0        # or: sudo udhcpc -i ethsta0
ping -I ethsta0 1.1.1.1

The MAC on ethsta0 is populated only if the example has the Wi-Fi feature enabled.


2. SPI Full-Duplex

Raspberry Pi — SPI Full-Duplex — ESP co-processor. Easiest, most robust bring-up.

2.1 Hardware considerations and connections

Hardware considerations — SPI Full-Duplex:

  • Pins. The Raspberry Pi uses its fixed SPI0 pins (SCLK/MOSI/MISO/CS0); Handshake and Data Ready are on the GPIOs shown (set via the load flags). On the co-processor, SPI pins are configurable — prefer IO_MUX pins for best performance.
  • Extra signals. Handshake and Data Ready (co-processor → host), plus Reset (host → co-processor EN/RST, configurable GPIO).
  • Jumper wires. Suitable for prototyping: high-quality, low-capacitance, ≤ 10 cm, equal length; minimise crosstalk (especially clock/data); consider twisted pairs for clock/data; run a ground between every signal; connect as many grounds as possible.
  • Clock. Start at a low clock (~5 MHz), then raise toward the co-processor's practical max SPI-slave frequency. Intermittent errors? Lower CLK first — if they vanish, it is signal integrity. The Raspberry Pi rounds the SPI clock down to the nearest achievable divider, so the effective clock may be lower than requested.
  • Voltage levels. Verify voltage compatibility between host and co-processor; use level shifters if they differ, and keep a common ground.
  • Power. Insufficient power is a leading, frequently-overlooked cause of non-deterministic crashes and suboptimal performance. Get all three right:
    1. Power adapter — use only the official Raspberry Pi adapter, matched to the board's exact input rating.
    2. Power cable — rated to carry the expected current, for both the Raspberry Pi and the ESP; thin or long cables cause brown-outs.
    3. ESP supply — power the ESP from a reliable supply of adequate rating.
  • PCB design (production). Length-match all SPI signals (CLK, MOSI, MISO, CS); if not perfect, prioritise matching CLK to the data lines. Use controlled-impedance traces, bypass capacitors near the power pins, optional series termination, and a 4-layer board for high speed.
  • Debugging tips. Use an oscilloscope/logic analyzer to verify signal integrity and timing; start at a lower clock and raise gradually; ensure solid grounding.

Connections — SPI (Raspberry Pi ↔ co-processor)

RPi pin (BCM)ESP32ESP32-S2/S3ESP32-C2/C3/C5/C6Function
23 (GPIO11)IO14IO12IO6SCLK
21 (GPIO9)IO12IO13IO2MISO
19 (GPIO10)IO13IO11IO7MOSI
24 (GPIO8)IO15IO10IO10CS0
15 (GPIO22)IO2IO17IO3Handshake
13 (GPIO27)IO4IO4IO4Data Ready
31 (GPIO6)ENRSTRSTReset
25 (GND)GNDGNDGNDGround

Tip

An optional 10 kΩ pull-up on CS prevents the line floating.

2.2 Flash the co-processor

cd examples/wifi/sta/cp
eh.py set-target esp32c6
eh.py menuconfig          # Transport -> SPI Full-Duplex
eh.py -p <PORT> flash monitor

2.3 Enable the SPI bus on Raspberry Pi

Enable SPI in /boot/firmware/config.txt:

dtparam=spi=on

For best throughput, pin the CPU governor: add CPU_DEFAULT_GOVERNOR="performance" to /etc/default/cpu_governor.

Warning

Reboot the Raspberry Pi after editing config.txt — the change only takes effect after a reboot. (Bus allowed & free.)

2.4 Build and load the kmod

Build and load in one command:

cd ../linux_802_3_host/kmod
./build.sh --bus spi --reload --reset-gpio 518 --clock-mhz 10 \
           --spi-bus 0 --spi-cs 0 --spi-mode 3 --spi-handshake 534 --spi-dataready 539
  • --reload — build, then unload and load the module.
  • --reset-gpio 518 — host GPIO wired to the co-processor reset (sets resetpin; RPi GPIO6 = 518, pin 31).
  • --clock-mhz 10 — SPI clock (sets clockspeed). Start at 10 MHz; once the link is stable, raise it in steps up to the co-processor's max SPI clock — see Performance Optimization. (The Pi rounds down to the nearest divider.)
  • --spi-bus 0 --spi-cs 0 — the Raspberry Pi SPI bus and chip-select to use.
  • --spi-mode 3 — SPI mode: 3 for most chips, 2 for a classic ESP32.
  • --spi-handshake 534 --spi-dataready 539 — the Handshake and Data Ready GPIOs (RPi GPIO22 = 534 on pin 15; GPIO27 = 539 on pin 13).
  • Non-Raspberry-Pi board: prefix with PORT=<board>. Unload with ./unload.sh.

Check the bus — first communication successful. dmesg should show the co-processor announce itself:

$ dmesg | tail
esp_hosted: process_capabilities: ESP peripheral capabilities: 0x3d
esp_hosted: print_capabilities: Features supported are:
esp_hosted: print_capabilities:  * WLAN
esp_hosted: slave fw version: 0x00020200
esp_hosted: Slave up event processed

If the capabilities / Slave up event lines never appear, stop and fix the transport before continuing.

2.5 Run and verify

cd ../py_app        # or ../c_app
eh.py set-target linux
eh.py run # would build and run the app

The driver creates ethsta0 and /dev/esps0. The app only associates to the AP over the control path — it does not assign an IP. To use the data path, bring the interface up, obtain an IP via DHCP, and ping:

sudo ip link set ethsta0 up
sudo dhclient ethsta0        # or: sudo udhcpc -i ethsta0
ping -I ethsta0 1.1.1.1

The MAC on ethsta0 is populated only if the example has the Wi-Fi feature enabled.


3. Base SDIO + dedicated UART for Bluetooth

Raspberry Pi — SDIO (Wi-Fi) + UART (Bluetooth) — ESP co-processor.

3.1 Hardware considerations and connections

Hardware considerations — SDIO:

  • Fixed pins. SDIO uses fixed pins on both ends — the Raspberry Pi SDIO controller and the co-processor (ESP32 / ESP32-C5 / ESP32-C6 / ESP32-C61 have fixed SDIO GPIOs). Wire exactly as shown; the pins are not reassignable.
  • Reset signal. Host output to the co-processor EN/RST pin (configurable GPIO), asserted at start-up to sync host and co-processor state. Co-processor menuconfig: Example configuration → SDIO Configuration → Host SDIO GPIOs → Slave GPIO pin to reset itself.
  • Pull-up resistors (mandatory). External 51 kΩ pull-ups on CMD, DAT0DAT3 — on all of them (also marked in the table). Select your co-processor in the linked page's chip selector.
  • Clock. SDIO max is 50 MHz. Raspberry Pi 3/4 cap the actual SDIO clock at ~41.467 MHz even when 50 MHz is requested; Raspberry Pi 5 reaches 50 MHz. Check the achieved clock with sudo cat /sys/kernel/debug/mmc0/ios.
  • Voltage levels. All signals are 3.3 V; if using a level shifter, set its output to 3.3 V.
  • Power. Insufficient power is a leading, frequently-overlooked cause of non-deterministic crashes and suboptimal performance. Get all three right:
    1. Power adapter — use only the official Raspberry Pi adapter, matched to the board's exact input rating.
    2. Power cable — rated to carry the expected current, for both the Raspberry Pi and the ESP; thin or long cables cause brown-outs.
    3. ESP supply — power the ESP from a reliable supply of adequate rating.
  • PCB design (production). Length-match all SDIO signals (CLK, CMD, DAT0–3); if not perfect, prioritise matching CLK to the data lines. Use controlled-impedance traces, bypass capacitors near the power pins, optional series termination, and a 4-layer board with power/ground planes for high speed.

Specific considerations — SDIO:

  • SDIO 1-bit mode. Full 4-bit SDIO needs a proper PCB carrying the mandatory pull-ups — jumpers are not suitable. Only SDIO 1-bit mode may be prototyped on jumper wires: all leads equal length, each ≤ 5 cm; the pull-ups remain mandatory.
  • Classic ESP32 eFuse. A classic ESP32 co-processor will likely need a one-time, irreversible eFuse burn (bootstrapping-pin / DAT2 conflict) — follow the pull-up requirements procedure; an incorrect burn can brick the chip. Applies to the classic ESP32 only, not ESP32-C5/C6/C61.

Hardware considerations — UART (Bluetooth):

  • GPIOs. UART can use almost any GPIO; prefer IO_MUX pins. Any free GPIOs work for Rx/Tx, but avoid the ESP debug UART0 (Tx0/Rx0) — ESP-Hosted uses a separate UART controller.
  • Reset. The same host→co-processor reset covers both buses.
  • General. UART is low-speed, so signal integrity is less critical, but keep wires short (< 10 cm) and equal length, with a ground between signals. Start at 115200 baud and raise once verified.
  • Flow control. Four-line UART (with CTS/RTS) for ESP32/S3/C3; two-line UART (no flow control) for ESP32-C2/C5/C6.

Connections — SDIO (Raspberry Pi ↔ co-processor)

RPi pin (BCM)ESP32ESP32-C6ESP32-C61ESP32-C5Function
15 (GPIO22)IO14IO19IO26IO9CLK
16 (GPIO23) (pull-up)IO15IO18IO25IO10CMD
18 (GPIO24) (pull-up)IO2IO20IO27IO8DAT0
22 (GPIO25) (pull-up)IO4IO21IO28IO7DAT1
37 (GPIO26) (pull-up)IO12IO22IO22IO14DAT2
13 (GPIO27) (pull-up)IO13IO23IO23IO13DAT3
31 (GPIO6)ENRSTRSTRSTReset
39 (GND)GNDGNDGNDGNDGround

Connections — UART, four-line (with flow control: ESP32 / S3 / C3)

RPi pin (BCM)FunctionESP32ESP32-S3ESP32-C3
10 (GPIO15)RXIO5IO16IO5
8 (GPIO14)TXIO18IO18IO18
36 (GPIO16)CTSIO19IO19IO19
11 (GPIO17)RTSIO23IO20IO1

Connections — UART, two-line (no flow control: ESP32-C2 / C5 / C6)

RPi pin (BCM)FunctionESP32-C2ESP32-C5ESP32-C6
10 (GPIO15)RXIO5IO5IO5
8 (GPIO14)TXIO1IO23IO12

3.2 Flash the co-processor

cd examples/wifi/sta/cp
eh.py set-target esp32c6
eh.py menuconfig          # Transport -> SDIO ; Bluetooth -> on
eh.py -p <PORT> flash monitor

3.3 Enable the bus and UART on Raspberry Pi

Enable SDIO, disable native Bluetooth (to free the Pi's UART), and enable the UART in /boot/firmware/config.txt:

dtoverlay=sdio,poll_once=off
dtoverlay=disable-bt
enable_uart=1

Also remove console=serial0,115200 from /boot/cmdline.txt and free the port with sudo systemctl disable hciuart.

Warning

Reboot the Raspberry Pi after editing config.txt / cmdline.txt. (Bus allowed & free.)

3.4 Build and load the kmod

Wi-Fi on SDIO, Bluetooth on UART (uart4 for ESP32/S3/C3; uart2 for ESP32-C2/C5/C6):

cd ../linux_802_3_host/kmod
./build.sh --bus sdio --bt-bus uart4 --reload --reset-gpio 518 --clock-mhz 50
  • --bt-bus uart4 — route Bluetooth over the four-line UART (use uart2 for two-line chips).
  • --reload — build, then unload and load.
  • --reset-gpio 518 — co-processor reset GPIO (sets resetpin; RPi GPIO6 = 518, pin 31).
  • --clock-mhz 50 — SDIO bus clock (sets clockspeed); 50 MHz max. See Clock in the hardware considerations for the Raspberry Pi 3/4 cap and how to check the achieved clock.
  • Non-Raspberry-Pi board: prefix with PORT=<board>. Unload with ./unload.sh.

Check the bus — first communication successful. dmesg should show the co-processor announce itself:

$ dmesg | tail
esp_hosted: process_capabilities: ESP peripheral capabilities: 0x3d
esp_hosted: print_capabilities: Features supported are:
esp_hosted: print_capabilities:  * WLAN
esp_hosted: print_capabilities:  * BT/BLE
esp_hosted: Slave up event processed

If the lines never appear, stop and fix the transport before continuing.

3.5 Run and verify

Wi-Fi:

cd ../py_app        # or ../c_app
eh.py set-target linux
eh.py build
eh.py run

The driver creates ethsta0 and /dev/esps0. The app only associates — it does not assign an IP. Bring the interface up, obtain an IP via DHCP, and ping:

sudo ip link set ethsta0 up
sudo dhclient ethsta0        # or: sudo udhcpc -i ethsta0
ping -I ethsta0 1.1.1.1

Bluetooth: the controller is now visible to the Linux BT stack — manage it with BlueZ:

hciconfig hci0 up
bluetoothctl                 # scan, pair, connect

See Bluetooth for details.


4. Base SPI + dedicated UART for Bluetooth

Raspberry Pi — SPI (Wi-Fi) + UART (Bluetooth) — ESP co-processor.

4.1 Hardware considerations and connections

Hardware considerations — SPI Full-Duplex:

  • Pins. The Raspberry Pi uses its fixed SPI0 pins (SCLK/MOSI/MISO/CS0); Handshake and Data Ready are on the GPIOs shown (set via the load flags). On the co-processor, SPI pins are configurable — prefer IO_MUX pins for best performance.
  • Extra signals. Handshake and Data Ready (co-processor → host), plus Reset (host → co-processor EN/RST, configurable GPIO).
  • Jumper wires. Suitable for prototyping: high-quality, low-capacitance, ≤ 10 cm, equal length; minimise crosstalk (especially clock/data); consider twisted pairs for clock/data; run a ground between every signal; connect as many grounds as possible.
  • Clock. Start at a low clock (~5 MHz), then raise toward the co-processor's practical max SPI-slave frequency. Intermittent errors? Lower CLK first — if they vanish, it is signal integrity. The Raspberry Pi rounds the SPI clock down to the nearest achievable divider, so the effective clock may be lower than requested.
  • Voltage levels. Verify voltage compatibility between host and co-processor; use level shifters if they differ, and keep a common ground.
  • Power. Insufficient power is a leading, frequently-overlooked cause of non-deterministic crashes and suboptimal performance. Get all three right:
    1. Power adapter — use only the official Raspberry Pi adapter, matched to the board's exact input rating.
    2. Power cable — rated to carry the expected current, for both the Raspberry Pi and the ESP; thin or long cables cause brown-outs.
    3. ESP supply — power the ESP from a reliable supply of adequate rating.
  • PCB design (production). Length-match all SPI signals (CLK, MOSI, MISO, CS); if not perfect, prioritise matching CLK to the data lines. Use controlled-impedance traces, bypass capacitors near the power pins, optional series termination, and a 4-layer board for high speed.
  • Debugging tips. Use an oscilloscope/logic analyzer to verify signal integrity and timing; start at a lower clock and raise gradually; ensure solid grounding.

Hardware considerations — UART (Bluetooth):

  • GPIOs. UART can use almost any GPIO; prefer IO_MUX pins. Any free GPIOs work for Rx/Tx, but avoid the ESP debug UART0 (Tx0/Rx0) — ESP-Hosted uses a separate UART controller.
  • Reset. The same host→co-processor reset covers both buses.
  • General. UART is low-speed, so signal integrity is less critical, but keep wires short (< 10 cm) and equal length, with a ground between signals. Start at 115200 baud and raise once verified.
  • Flow control. Four-line UART (with CTS/RTS) for ESP32/S3/C3; two-line UART (no flow control) for ESP32-C2/C5/C6.

Connections — SPI (Raspberry Pi ↔ co-processor)

RPi pin (BCM)ESP32ESP32-S2/S3ESP32-C2/C3/C5/C6Function
23 (GPIO11)IO14IO12IO6SCLK
21 (GPIO9)IO12IO13IO2MISO
19 (GPIO10)IO13IO11IO7MOSI
24 (GPIO8)IO15IO10IO10CS0
15 (GPIO22)IO2IO17IO3Handshake
13 (GPIO27)IO4IO4IO4Data Ready
31 (GPIO6)ENRSTRSTReset
25 (GND)GNDGNDGNDGround

Note

An optional 10 kΩ pull-up on CS prevents the line floating.

Connections — UART, four-line (with flow control: ESP32 / S3 / C3)

RPi pin (BCM)FunctionESP32ESP32-S3ESP32-C3
10 (GPIO15)RXIO5IO16IO5
8 (GPIO14)TXIO18IO18IO18
36 (GPIO16)CTSIO19IO19IO19
11 (GPIO17)RTSIO23IO20IO1

Connections — UART, two-line (no flow control: ESP32-C2 / C5 / C6)

RPi pin (BCM)FunctionESP32-C2ESP32-C5ESP32-C6
10 (GPIO15)RXIO5IO5IO5
8 (GPIO14)TXIO1IO23IO12

4.2 Flash the co-processor

cd examples/wifi/sta/cp
eh.py set-target esp32c6
eh.py menuconfig          # Transport -> SPI Full-Duplex ; Bluetooth -> on
eh.py -p <PORT> flash monitor

4.3 Enable the bus and UART on Raspberry Pi

Enable SPI, disable native Bluetooth (to free the Pi's UART), and enable the UART in /boot/firmware/config.txt:

dtparam=spi=on
dtoverlay=disable-bt
enable_uart=1

For best throughput, add CPU_DEFAULT_GOVERNOR="performance" to /etc/default/cpu_governor. Remove console=serial0,115200 from /boot/cmdline.txt and run sudo systemctl disable hciuart.

Warning

Reboot the Raspberry Pi after editing config.txt / cmdline.txt. (Bus allowed & free.)

4.4 Build and load the kmod

Wi-Fi on SPI, Bluetooth on UART (uart4 for ESP32/S3/C3; uart2 for ESP32-C2/C5/C6):

cd ../linux_802_3_host/kmod
./build.sh --bus spi --bt-bus uart4 --reload --reset-gpio 518 --clock-mhz 10 \
           --spi-bus 0 --spi-cs 0 --spi-mode 3 --spi-handshake 534 --spi-dataready 539
  • --bt-bus uart4 — route Bluetooth over the four-line UART (use uart2 for two-line chips).
  • --reload — build, then unload and load.
  • --reset-gpio 518 — co-processor reset GPIO (sets resetpin; RPi GPIO6 = 518, pin 31).
  • --clock-mhz 10 — SPI clock (sets clockspeed). Start at 10 MHz; once stable, raise it in steps up to the co-processor's max SPI clock — see Performance Optimization. (The Pi rounds down to the nearest divider.)
  • --spi-bus 0 --spi-cs 0 — Raspberry Pi SPI bus and chip-select.
  • --spi-mode 3 — SPI mode: 3 for most chips, 2 for a classic ESP32.
  • --spi-handshake 534 --spi-dataready 539 — Handshake / Data Ready GPIOs (RPi GPIO22 = 534 on pin 15; GPIO27 = 539 on pin 13).
  • Non-Raspberry-Pi board: prefix with PORT=<board>. Unload with ./unload.sh.

Check the bus — first communication successful. dmesg should show the co-processor announce itself:

$ dmesg | tail
esp_hosted: process_capabilities: ESP peripheral capabilities: 0x3d
esp_hosted: print_capabilities: Features supported are:
esp_hosted: print_capabilities:  * WLAN
esp_hosted: print_capabilities:  * BT/BLE
esp_hosted: Slave up event processed

If the lines never appear, stop and fix the transport before continuing.

4.5 Run and verify

Wi-Fi:

cd ../py_app        # or ../c_app
eh.py set-target linux
eh.py build
eh.py run

The driver creates ethsta0 and /dev/esps0. The app only associates — it does not assign an IP. Bring the interface up, obtain an IP via DHCP, and ping:

sudo ip link set ethsta0 up
sudo dhclient ethsta0        # or: sudo udhcpc -i ethsta0
ping -I ethsta0 1.1.1.1

Bluetooth: the controller is now visible to the Linux BT stack — manage it with BlueZ:

hciconfig hci0 up
bluetoothctl

See Bluetooth for details.


Next steps

Once Wi-Fi Station works: Hosted Events · GPIO Expander · Network Split · Feature Overview.

Report an issue

Hit a problem? See Troubleshooting, or open an issue at github.com/espressif/esp-hosted/issues.