Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ The bootloader enables safe over-the-air firmware updates by maintaining separat
cargo xtask build

# Build individual components
cargo xtask build --bootloader # Build bootloader only
cargo xtask build --application # Build main PDU controller application
cargo xtask build --bootloader # Build bootloader only
cargo xtask build --application # Build main PDU controller application
cargo xtask build --application --w6100 # Build main PDU controller application for WIZnet W6100

# Produce combined UF2 (build + combine)
cargo xtask dist
# Produce combined UF2 (build + combine) for WIZnet W6100 variant
cargo xtask dist --w6100

# Flash to device
cargo xtask flash # Flash combined UF2 via BOOTSEL drag-and-drop
cargo xtask flash --w6100 # Flash combined UF2 via BOOTSEL drag-and-drop (for WIZnet W6100)
cargo xtask flash --bootloader # Flash bootloader only
cargo xtask flash --application # Flash application only
cargo xtask flash --probe # Flash via probe-rs with RTT logging (recommended for dev)
Expand Down Expand Up @@ -67,7 +71,7 @@ All build outputs are placed in the `build/` directory:

## Hardware Configuration

### W5500 Ethernet Module (SPI)
### W5500/W6100 Ethernet Module (SPI)
- MISO: GPIO 16
- MOSI: GPIO 19
- CLK: GPIO 18
Expand Down Expand Up @@ -165,7 +169,7 @@ Hard faults trigger system reset to retry boot.
### Application Structure

The application uses Embassy async runtime with multiple concurrent tasks:
- **ethernet_task**: Manages W5500 hardware and link layer
- **ethernet_task**: Manages W5500/W6100 hardware and link layer
- **net_task**: Runs the embassy-net network stack (TCP/IP, DHCP)
- **gpio_task**: Handles GPIO control commands via Signal primitive (8 pins)
- **sensor_task**: Reads RP2040 internal ADC temperature every 5s
Expand Down Expand Up @@ -206,7 +210,7 @@ The `archive/` directory contains the original PIC18-based firmware tooling (Mak
Application dependencies:
- `embassy-executor`: Async task executor
- `embassy-net`: Network stack with DHCP support
- `embassy-net-wiznet`: Driver for W5500 Ethernet chip
- `embassy-net-wiznet`: Driver for W5500/W6100 Ethernet chip
- `embassy-rp`: RP2040 HAL with flash support
- `embassy-boot-rp`: Bootloader and firmware updater
- `embassy-embedded-hal`: Async adapters for blocking peripherals
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Raspberry Pi Pico (RP2040), replacing the original PIC18-based firmware.
## Features

- 8 relay-controlled power outlets (GPIO 0-7)
- W5500 Ethernet with DHCP
- W5500 or W6100 Ethernet with DHCP
- HTTP server with single-page web UI
- REST API for GPIO control, sensors, user management
- HTTP Basic Auth with multi-user support and per-port ACL
Expand Down
2 changes: 2 additions & 0 deletions application/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ heapless = "0.8"
default = ["panic-reset"]
debug = ["panic-probe"]
skip-include = []
# Select the WIZnet W6100, defaults to W5500 if not enabled
w6100 = []

# 'panic-reset' and 'panic-probe' are mutually exclusive.
# Build with --no-default-features --features debug to use panic-probe.
Expand Down
28 changes: 21 additions & 7 deletions application/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ use defmt::*;
use defmt_rtt as _;
use embassy_executor::Spawner;
use embassy_net::StackResources;
#[cfg(not(feature = "w6100"))]
use embassy_net_wiznet::chip::W5500;
#[cfg(feature = "w6100")]
use embassy_net_wiznet::chip::W6100;
use embassy_net_wiznet::*;
use embassy_rp::adc::{Adc, Channel, Config as AdcConfig};
use embassy_rp::bind_interrupts;
Expand Down Expand Up @@ -67,6 +70,7 @@ bind_interrupts!(struct Irqs {
// ── Ethernet task ──────────────────────────────────────────────────────────────

/// Type alias for the W5500 runner to avoid repeating the full generic signature.
#[cfg(not(feature = "w6100"))]
type EthernetRunner = Runner<
'static,
W5500,
Expand All @@ -75,6 +79,16 @@ type EthernetRunner = Runner<
Output<'static>,
>;

/// Type alias for the W6100 runner to avoid repeating the full generic signature.
#[cfg(feature = "w6100")]
type EthernetRunner = Runner<
'static,
W6100,
ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static>, embassy_time::Delay>,
Input<'static>,
Output<'static>,
>;

#[embassy_executor::task]
async fn ethernet_task(runner: EthernetRunner) -> ! {
runner.run().await
Expand Down Expand Up @@ -135,7 +149,7 @@ async fn main(spawner: Spawner) {
// 6. Spawn GPIO task
spawner.spawn(gpio_task(relay_pins).unwrap());

// 7. Init W5500 Ethernet (SPI0, DMA_CH0 TX, DMA_CH1 RX)
// 7. Init Ethernet (SPI0, DMA_CH0 TX, DMA_CH1 RX)
let mut spi_cfg = SpiConfig::default();
spi_cfg.frequency = SPI_FREQ_HZ;
let spi = Spi::new(
Expand All @@ -145,18 +159,18 @@ async fn main(spawner: Spawner) {
p.DMA_CH0, p.DMA_CH1, Irqs, spi_cfg,
);
let cs = Output::new(p.PIN_17, Level::High);
let w5500_int = Input::new(p.PIN_21, Pull::Up);
let w5500_reset = Output::new(p.PIN_20, Level::High);
let eth_int = Input::new(p.PIN_21, Pull::Up);
let eth_reset = Output::new(p.PIN_20, Level::High);

let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
static W5500_STATE: StaticCell<embassy_net_wiznet::State<8, 8>> = StaticCell::new();
let state = W5500_STATE.init(embassy_net_wiznet::State::<8, 8>::new());
static ETH_STATE: StaticCell<embassy_net_wiznet::State<8, 8>> = StaticCell::new();
let state = ETH_STATE.init(embassy_net_wiznet::State::<8, 8>::new());
let (device, runner) = embassy_net_wiznet::new(
mac_addr,
state,
ExclusiveDevice::new(spi, cs, embassy_time::Delay),
w5500_int,
w5500_reset,
eth_int,
eth_reset,
)
.await
.unwrap();
Expand Down
58 changes: 41 additions & 17 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ enum Command {
/// Build only the application
#[arg(long)]
application: bool,
/// Build the application for the WIZnet W6100 instead of W5500
#[arg(long)]
w6100: bool,
},
/// Combine bootloader + application ELFs into build/combined.uf2
Combine,
/// Build everything and produce the combined UF2 (build + combine)
Dist,
Dist {
/// Build the application for the WIZnet W6100 instead of W5500
#[arg(long)]
w6100: bool,
},
/// Flash firmware to the device
Flash {
/// Flash only the bootloader
Expand All @@ -36,6 +43,9 @@ enum Command {
/// Flash only the application
#[arg(long)]
application: bool,
/// Build the application for the WIZnet W6100 instead of W5500
#[arg(long)]
w6100: bool,
/// Use probe-rs instead of UF2 drag-and-drop (attaches RTT for live logging)
#[arg(long)]
probe: bool,
Expand Down Expand Up @@ -112,13 +122,17 @@ fn build_bootloader(sh: &Shell, root: &Path) -> Result<()> {
Ok(())
}

fn build_application(sh: &Shell, root: &Path) -> Result<()> {
fn build_application(sh: &Shell, root: &Path, w6100: bool) -> Result<()> {
eprintln!("→ Building application…");
let build_dir = build_dir(root);
std::fs::create_dir_all(&build_dir)?;

let _dir = sh.push_dir(root.join("application"));
cmd!(sh, "cargo build --release").run()?;
if w6100 {
cmd!(sh, "cargo build --release --features w6100").run()?;
} else {
cmd!(sh, "cargo build --release").run()?;
}

let src = root.join("target/thumbv6m-none-eabi/release/pdu-rp-application");
let dst = application_elf(root);
Expand All @@ -135,16 +149,24 @@ fn build_application(sh: &Shell, root: &Path) -> Result<()> {
/// Build the application with the `debug` feature (panic-probe + full defmt RTT logging).
/// The resulting ELF is stored separately as `build/application-debug.elf` so it
/// doesn't overwrite the production UF2 artefacts.
fn build_application_debug(sh: &Shell, root: &Path) -> Result<()> {
fn build_application_debug(sh: &Shell, root: &Path, w6100: bool) -> Result<()> {
eprintln!("→ Building application (debug / probe-rs)…");
std::fs::create_dir_all(build_dir(root))?;

let _dir = sh.push_dir(root.join("application"));
cmd!(
sh,
"cargo build --release --no-default-features --features debug"
)
.run()?;
if w6100 {
cmd!(
sh,
"cargo build --release --no-default-features --features debug,w6100"
)
.run()?;
} else {
cmd!(
sh,
"cargo build --release --no-default-features --features debug"
)
.run()?;
}

let src = root.join("target/thumbv6m-none-eabi/release/pdu-rp-application");
let dst = application_debug_elf(root);
Expand Down Expand Up @@ -559,29 +581,31 @@ fn main() -> Result<()> {
Command::Build {
bootloader,
application,
w6100,
} => {
let both = !bootloader && !application;
if both || bootloader {
build_bootloader(&sh, &root)?;
}
if both || application {
build_application(&sh, &root)?;
build_application(&sh, &root, w6100)?;
}
}

Command::Combine => {
combine(&sh, &root)?;
}

Command::Dist => {
Command::Dist { w6100 } => {
build_bootloader(&sh, &root)?;
build_application(&sh, &root)?;
build_application(&sh, &root, w6100)?;
combine(&sh, &root)?;
}

Command::Flash {
bootloader,
application,
w6100,
probe,
debug,
ota,
Expand All @@ -595,7 +619,7 @@ fn main() -> Result<()> {
let uf2 = application_uf2(&root);
if !uf2.exists() {
eprintln!("Application UF2 not found — building first…");
build_application(&sh, &root)?;
build_application(&sh, &root, w6100)?;
}
flash_ota(&uf2, ip)?;
} else if probe {
Expand All @@ -610,9 +634,9 @@ fn main() -> Result<()> {
};
let build_app = |sh: &Shell, root: &Path| {
if use_debug_build {
build_application_debug(sh, root)
build_application_debug(sh, root, w6100)
} else {
build_application(sh, root)
build_application(sh, root, w6100)
}
};

Expand Down Expand Up @@ -677,7 +701,7 @@ fn main() -> Result<()> {
let uf2 = combined_uf2(&root);
if !uf2.exists() {
build_bootloader(&sh, &root)?;
build_application(&sh, &root)?;
build_application(&sh, &root, w6100)?;
combine(&sh, &root)?;
}
flash_uf2(&uf2)?;
Expand All @@ -692,7 +716,7 @@ fn main() -> Result<()> {
if application {
let uf2 = application_uf2(&root);
if !uf2.exists() {
build_application(&sh, &root)?;
build_application(&sh, &root, w6100)?;
}
flash_uf2(&uf2)?;
}
Expand Down
Loading