WiFi & Bluetooth on Pico 2 W

The Pico 2 W includes the CYW43439 wireless chip, adding WiFi and Bluetooth capabilities to your embedded projects. This opens up possibilities for IoT applications, web servers, network communication, and wireless sensor networks.

Understanding the CYW43439 Wireless Chip

The CYW43439 is a complete WiFi and Bluetooth solution that communicates with the RP2350 via SPI. The chip manages all wireless operations independently, and your Rust code communicates with it through the cyw43 driver.

Key Wireless Features

WiFi 802.11n

2.4GHz wireless networking with support for WPA2/WPA3 security. Connect to existing networks or create your own access point.

Bluetooth 5.2

Low-energy Bluetooth support for connecting to phones, sensors, and other Bluetooth devices.

Onboard PCB Antenna

Built-in antenna eliminates the need for external components, though range may be limited compared to external antennas.

Important

Reserved GPIO Pins: The CYW43439 wireless chip uses GPIO 23, 24, 25, and 29 for communication with the RP2350. These pins are NOT available for general GPIO use on the Pico 2 W.

  • GPIO 23: Power control for the wireless chip
  • GPIO 24: SPI data line (MOSI/MISO)
  • GPIO 25: SPI chip select (also controls onboard LED)
  • GPIO 29: SPI clock line

Setting Up WiFi with Embassy

Embassy provides excellent support for the Pico 2 W through the embassy-rp and cyw43 crates. Here's how to initialize WiFi and connect to a network.

Required Dependencies

Add these to your Cargo.toml:

toml
[dependencies]
embassy-executor = { version = "0.6.4", features = ["arch-cortex-m", "executor-thread"] }
embassy-rp = { version = "0.3.0", features = ["rp2350"] }
embassy-time = { version = "0.3.2", features = ["defmt"] }
embassy-net = { version = "0.5.0", features = ["tcp", "udp", "dhcpv4", "medium-ethernet"] }

cyw43 = { version = "0.2.0", features = ["defmt", "firmware-logs"] }
cyw43-pio = { version = "0.2.0", features = ["overclock"] }

static_cell = "2.1.0"
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }

Complete WiFi Connection Example

This example shows how to initialize the WiFi chip and connect to your home network:

rust
#![no_std]
#![no_main]

use cyw43_pio::PioSpi;
use defmt::*;
use embassy_executor::Spawner;
use embassy_net::{Stack, StackResources};
use embassy_rp::bind_interrupts;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::peripherals::{DMA_CH0, PIO0};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_time::{Duration, Timer};
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};

bind_interrupts!(struct Irqs {
    PIO0_IRQ_0 => InterruptHandler<PIO0>;
});

// Task to run the CYW43 network stack
#[embassy_executor::task]
async fn wifi_task(
    runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>
) -> ! {
    runner.run().await
}

// Task to run the network stack
#[embassy_executor::task]
async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! {
    stack.run().await
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let p = embassy_rp::init(Default::default());
    
    // Initialize PIO for CYW43 communication
    let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
    
    // Set up CYW43 pins
    let pwr = Output::new(p.PIN_23, Level::Low);
    let cs = Output::new(p.PIN_25, Level::High);
    let mut pio_spi = PioSpi::new(
        &mut common,
        sm0,
        p.DMA_CH0,
        p.PIN_24,
        p.PIN_29,
        p.PIN_25,
    );
    
    // Load CYW43 firmware
    static STATE: StaticCell<cyw43::State> = StaticCell::new();
    let state = STATE.init(cyw43::State::new());
    let fw = include_bytes!("../../../firmware/43439A0.bin");
    let clm = include_bytes!("../../../firmware/43439A0_clm.bin");
    
    // Initialize CYW43 driver
    let (net_device, mut control, runner) = cyw43::new(state, pwr, pio_spi, fw).await;
    unwrap!(spawner.spawn(wifi_task(runner)));
    
    // Initialize network stack
    static STACK: StaticCell<Stack<cyw43::NetDriver<'static>>> = StaticCell::new();
    static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
    
    let config = embassy_net::Config::dhcpv4(Default::default());
    let seed = 0x1234_5678; // Random seed for network stack
    
    let stack = &*STACK.init(Stack::new(
        net_device,
        config,
        RESOURCES.init(StackResources::new()),
        seed,
    ));
    
    unwrap!(spawner.spawn(net_task(stack)));
    
    // Connect to WiFi
    control.init(clm).await;
    control.set_power_management(cyw43::PowerManagementMode::PowerSave).await;
    
    let wifi_network = "YOUR_WIFI_SSID";
    let wifi_password = "YOUR_WIFI_PASSWORD";
    
    info!("Connecting to WiFi...");
    loop {
        match control.join_wpa2(wifi_network, wifi_password).await {
            Ok(_) => {
                info!("WiFi connected!");
                break;
            }
            Err(err) => {
                warn!("Failed to connect to WiFi: {:?}", err);
                Timer::after(Duration::from_secs(1)).await;
            }
        }
    }
    
    // Wait for DHCP
    info!("Waiting for DHCP...");
    while !stack.is_config_up() {
        Timer::after_millis(100).await;
    }
    info!("DHCP configured!");
    
    // Print IP address
    if let Some(config) = stack.config_v4() {
        info!("IP address: {:?}", config.address);
    }
    
    // Blink LED to show we're connected
    loop {
        control.gpio_set(0, true).await;
        Timer::after_millis(100).await;
        control.gpio_set(0, false).await;
        Timer::after_millis(900).await;
    }
}
Tip

Firmware Files Required: The CYW43439 chip requires two firmware files to operate:

  • 43439A0.bin - Main firmware
  • 43439A0_clm.bin - Country locale matcher (regulatory compliance)

These files are included in the Embassy repository under cyw43-firmware/.

Creating a Simple HTTP Server

Once WiFi is connected, you can create network applications like web servers. Here's a simple HTTP server example:

rust
use embassy_net::tcp::TcpSocket;
use embassy_net::{Stack, StackResources};

#[embassy_executor::task]
async fn http_server_task(stack: &'static Stack<cyw43::NetDriver<'static>>) {
    let mut rx_buffer = [0; 4096];
    let mut tx_buffer = [0; 4096];
    
    loop {
        let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
        socket.set_timeout(Some(Duration::from_secs(10)));
        
        info!("Listening on TCP:80...");
        if let Err(e) = socket.accept(80).await {
            warn!("Accept error: {:?}", e);
            continue;
        }
        
        info!("Received connection from {:?}", socket.remote_endpoint());
        
        let mut buf = [0; 1024];
        loop {
            let n = match socket.read(&mut buf).await {
                Ok(0) => {
                    warn!("Connection closed");
                    break;
                }
                Ok(n) => n,
                Err(e) => {
                    warn!("Read error: {:?}", e);
                    break;
                }
            };
            
            // Simple HTTP response
            let response = b"HTTP/1.1 200 OK\r\n\
                Content-Type: text/html\r\n\
                Connection: close\r\n\
                \r\n\
                <html><body><h1>Hello from Pico 2 W!</h1></body></html>";
            
            if let Err(e) = socket.write_all(response).await {
                warn!("Write error: {:?}", e);
            }
            break;
        }
    }
}

Bluetooth Support

The CYW43439 chip includes Bluetooth 5.2 support, but as of writing, full Bluetooth functionality in Embassy is still under development. WiFi support is mature and production-ready, while Bluetooth support is improving with each release.

Note
Check the latest Embassy repository and documentation for the most up-to-date Bluetooth examples and support status for the Pico 2 W.

WiFi Power Management

The CYW43 driver supports different power management modes to balance power consumption and responsiveness:

Power Management Modes

PowerSave (Recommended)

rust
control.set_power_management(cyw43::PowerManagementMode::PowerSave).await;

Balances power consumption with network performance. WiFi chip sleeps when idle.

Performance

rust
control.set_power_management(cyw43::PowerManagementMode::Performance).await;

WiFi chip stays active for lowest latency. Higher power consumption.

Aggressive

rust
control.set_power_management(cyw43::PowerManagementMode::Aggressive).await;

Maximum power savings. May affect connection stability.

Common WiFi Operations

Scanning for Networks

rust
let scan_options = cyw43::ScanOptions::default();
let mut networks = [cyw43::ScanResult::default(); 10];

let count = control.scan(&scan_options, &mut networks).await;
for network in &networks[..count] {
    info!("SSID: {:?}, RSSI: {}", network.ssid, network.rssi);
}

Checking Connection Status

rust
let status = control.status();
match status {
    cyw43::Status::Connected => info!("WiFi connected"),
    cyw43::Status::Disconnected => warn!("WiFi disconnected"),
    _ => info!("WiFi status: {:?}", status),
}

Disconnecting from Network

rust
control.leave().await;
info!("Disconnected from WiFi");

Troubleshooting WiFi

Common Issues

Connection Failures

  • Verify WiFi credentials (SSID and password)
  • Check that your network uses WPA2 or WPA3 (WEP is not supported)
  • Ensure you're within range of the access point

No IP Address (DHCP Issues)

  • Wait longer for DHCP to complete (can take 5-10 seconds)
  • Check that your router has DHCP enabled
  • Try using a static IP configuration instead

Firmware Loading Errors

  • Ensure firmware files are in the correct location
  • Check that PIO pins (23, 24, 25, 29) are correctly configured
  • Verify the Pico 2 W is properly powered (WiFi requires stable 3.3V)

Further Resources

You now have the foundation to build WiFi-enabled IoT projects with your Pico 2 W!