From 9c664290300ea27d74909d5c65186ed96d4fb46f Mon Sep 17 00:00:00 2001 From: wieerwill Date: Mon, 25 May 2026 19:27:51 +0200 Subject: [PATCH] add rgb check and example --- AGENTS.md | 3 + Cargo.lock | 28 +++++ Cargo.toml | 5 + README.md | 10 +- docs/hardware/blue-pill.md | 9 +- examples/README.md | 8 ++ examples/embassy-blinky/Cargo.toml | 2 +- examples/embassy-blinky/src/main.rs | 2 +- examples/embassy-button/Cargo.toml | 2 +- examples/embassy-button/src/main.rs | 2 +- examples/embassy-rgb-check/Cargo.toml | 16 +++ examples/embassy-rgb-check/build.rs | 12 +++ examples/embassy-rgb-check/src/main.rs | 70 ++++++++++++ examples/embassy-rgb/Cargo.toml | 16 +++ examples/embassy-rgb/build.rs | 9 ++ examples/embassy-rgb/src/main.rs | 143 +++++++++++++++++++++++++ justfile | 22 ++++ 17 files changed, 347 insertions(+), 12 deletions(-) create mode 100644 examples/embassy-rgb-check/Cargo.toml create mode 100644 examples/embassy-rgb-check/build.rs create mode 100644 examples/embassy-rgb-check/src/main.rs create mode 100644 examples/embassy-rgb/Cargo.toml create mode 100644 examples/embassy-rgb/build.rs create mode 100644 examples/embassy-rgb/src/main.rs diff --git a/AGENTS.md b/AGENTS.md index 5aab2f8..1403840 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,7 @@ # AGENTS +ALWAYS COMPACT YOUR CONTEXT FIRST! + ## Map - Root has workspace files. @@ -43,6 +45,7 @@ - Short names. - Short comments. - Explain embedded idea. +- Every Rust function we write except the main must have a short description header comment following rust best practice - Do not explain obvious Rust syntax. - No heap. - No dynamic dispatch unless teaching needs it. diff --git a/Cargo.lock b/Cargo.lock index fb17937..147c891 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -408,6 +408,34 @@ dependencies = [ "defmt 0.3.100", ] +[[package]] +name = "embassy-rgb" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt 1.1.0", + "defmt-rtt", + "embassy-executor", + "embassy-stm32", + "embassy-time", + "panic-probe", +] + +[[package]] +name = "embassy-rgb-check" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt 1.1.0", + "defmt-rtt", + "embassy-executor", + "embassy-stm32", + "embassy-time", + "panic-probe", +] + [[package]] name = "embassy-stm32" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 4a192ff..95e177b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ members = [ "examples/button-input", "examples/embassy-blinky", "examples/embassy-button", + "examples/embassy-rgb-check", + "examples/embassy-rgb", ] resolver = "2" @@ -22,3 +24,6 @@ embassy-time = "=0.5.1" nb = "=1.1.0" panic-probe = { version = "=1.0.0", features = ["print-defmt"] } stm32f1xx-hal = "=0.11.0" + +[profile.dev] +opt-level = "s" diff --git a/README.md b/README.md index 492a5da..e13deb9 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ See [blue-pill.md](docs/hardware/blue-pill.md) for wiring notes. - internal LED: `PC13` - RGB LED: - - `PA5` = red - - `PA6` = green - - `PA7` = blue - - `PA4` = drive low for the LED return path + - `PA0` = GND, drive low + - `PA1` = red + - `PA2` = green + - `PA3` = blue - 5-way button board: - `PB12` = GND, drive low - `PA10` = VCC, drive high @@ -97,6 +97,8 @@ probe-rs chip list | rg STM32F103 - `button-input` - poll the center button on `PA8`, mirror state on the LED, log transitions - `embassy-blinky` - minimal async blink with Embassy - `embassy-button` - minimal Embassy polling loop for the center button on `PA8` +- `embassy-rgb-check` - simple RGB on/off check on `PA1`/`PA2`/`PA3` +- `embassy-rgb` - async RGB PWM animation on `PA1`/`PA2`/`PA3` The button examples assume the 5-way board is wired like this: diff --git a/docs/hardware/blue-pill.md b/docs/hardware/blue-pill.md index a3b7cd0..3534a5c 100644 --- a/docs/hardware/blue-pill.md +++ b/docs/hardware/blue-pill.md @@ -15,10 +15,11 @@ ## RGB LED -- `PA5` = red -- `PA6` = green -- `PA7` = blue -- `PA4` = drive low for the LED return path +- `PA0` = GND, drive low +- `PA1` = red +- `PA2` = green +- `PA3` = blue +- The RGB fade example uses software PWM on `PA1`, `PA2`, and `PA3` ## 5-Way Button Board diff --git a/examples/README.md b/examples/README.md index f1d2edf..c69c6c8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,6 +9,8 @@ Each example is its own Cargo package. - `button-input` - poll the center button on `PA8`, mirror state to `PC13`, log transitions - `embassy-blinky` - minimal Embassy executor and async blink - `embassy-button` - minimal Embassy polling loop for the center button on `PA8` +- `embassy-rgb-check` - simple RGB on/off check on `PA1`/`PA2`/`PA3` +- `embassy-rgb` - async RGB PWM animation on `PA1`/`PA2`/`PA3` ## Build One Example @@ -18,6 +20,8 @@ just build-timer just build-button just build-embassy-blinky just build-embassy-button +just build-embassy-rgb +just build-embassy-rgb-check ``` ## Flash One Example @@ -28,6 +32,8 @@ just flash-timer just flash-button just flash-embassy-blinky just flash-embassy-button +just flash-embassy-rgb +just flash-embassy-rgb-check ``` ## Run With Logs @@ -35,4 +41,6 @@ just flash-embassy-button ```bash just run-blinky just run-embassy-blinky +just run-embassy-rgb +just run-embassy-rgb-check ``` diff --git a/examples/embassy-blinky/Cargo.toml b/examples/embassy-blinky/Cargo.toml index a12f1ff..36f5640 100644 --- a/examples/embassy-blinky/Cargo.toml +++ b/examples/embassy-blinky/Cargo.toml @@ -11,6 +11,6 @@ cortex-m-rt.workspace = true defmt.workspace = true defmt-rtt.workspace = true embassy-executor = { workspace = true, features = ["defmt", "executor-thread", "platform-cortex-m"] } -embassy-stm32 = { workspace = true, features = ["defmt", "stm32f103c8", "time-driver-tim2"] } +embassy-stm32 = { workspace = true, features = ["defmt", "stm32f103c8", "time-driver-tim3"] } embassy-time.workspace = true panic-probe.workspace = true diff --git a/examples/embassy-blinky/src/main.rs b/examples/embassy-blinky/src/main.rs index b7fcaa4..c4b5bc4 100644 --- a/examples/embassy-blinky/src/main.rs +++ b/examples/embassy-blinky/src/main.rs @@ -2,9 +2,9 @@ #![no_std] #![no_main] +use cortex_m as _; use defmt::info; use defmt_rtt as _; -use cortex_m as _; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; use embassy_time::Timer; diff --git a/examples/embassy-button/Cargo.toml b/examples/embassy-button/Cargo.toml index 9cfdc3c..082a4fc 100644 --- a/examples/embassy-button/Cargo.toml +++ b/examples/embassy-button/Cargo.toml @@ -11,6 +11,6 @@ cortex-m-rt.workspace = true defmt.workspace = true defmt-rtt.workspace = true embassy-executor = { workspace = true, features = ["defmt", "executor-thread", "platform-cortex-m"] } -embassy-stm32 = { workspace = true, features = ["defmt", "stm32f103c8", "time-driver-tim2"] } +embassy-stm32 = { workspace = true, features = ["defmt", "stm32f103c8", "time-driver-tim3"] } embassy-time.workspace = true panic-probe.workspace = true diff --git a/examples/embassy-button/src/main.rs b/examples/embassy-button/src/main.rs index e3b6dcf..99b4fb9 100644 --- a/examples/embassy-button/src/main.rs +++ b/examples/embassy-button/src/main.rs @@ -2,9 +2,9 @@ #![no_std] #![no_main] +use cortex_m as _; use defmt::info; use defmt_rtt as _; -use cortex_m as _; use embassy_executor::Spawner; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_time::Timer; diff --git a/examples/embassy-rgb-check/Cargo.toml b/examples/embassy-rgb-check/Cargo.toml new file mode 100644 index 0000000..02600a4 --- /dev/null +++ b/examples/embassy-rgb-check/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "embassy-rgb-check" +version = "0.1.0" +edition = "2024" +publish = false +build = "build.rs" + +[dependencies] +cortex-m.workspace = true +cortex-m-rt.workspace = true +defmt.workspace = true +defmt-rtt.workspace = true +embassy-executor = { workspace = true, features = ["defmt", "executor-thread", "platform-cortex-m"] } +embassy-stm32 = { workspace = true, features = ["defmt", "stm32f103c8", "time-driver-tim3"] } +embassy-time.workspace = true +panic-probe.workspace = true diff --git a/examples/embassy-rgb-check/build.rs b/examples/embassy-rgb-check/build.rs new file mode 100644 index 0000000..a6fb5b5 --- /dev/null +++ b/examples/embassy-rgb-check/build.rs @@ -0,0 +1,12 @@ +// main: tell Cargo to rerun when memory.x changes and add the shared link path. +// Input: none. +// Output: build script side effects only. +fn main() { + println!("cargo:rerun-if-changed=../../memory.x"); + println!( + "cargo:rustc-link-search={}", + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("../..") + .display() + ); +} diff --git a/examples/embassy-rgb-check/src/main.rs b/examples/embassy-rgb-check/src/main.rs new file mode 100644 index 0000000..c9df066 --- /dev/null +++ b/examples/embassy-rgb-check/src/main.rs @@ -0,0 +1,70 @@ +#![deny(unsafe_code)] +#![no_std] +#![no_main] + +use cortex_m as _; +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_probe as _; + +const HOLD_MS: u64 = 1000; + +// main: cycle each RGB pin high and low in order so the LED wiring can be checked. +// Input: the Embassy spawner, which this example does not use. +// Output: never returns. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let _gnd = Output::new(p.PA0, Level::Low, Speed::Low); + let mut red = Output::new(p.PA1, Level::Low, Speed::Low); + let mut green = Output::new(p.PA2, Level::Low, Speed::Low); + let mut blue = Output::new(p.PA3, Level::Low, Speed::Low); + let mut led = Output::new(p.PC13, Level::High, Speed::Low); + + loop { + info!("expected: red high, green low, blue low"); + red.set_high(); + green.set_low(); + blue.set_low(); + led.set_low(); + Timer::after_millis(HOLD_MS).await; + + info!("expected: red low, green low, blue low"); + red.set_low(); + green.set_low(); + blue.set_low(); + led.set_high(); + Timer::after_millis(HOLD_MS).await; + + info!("expected: red low, green high, blue low"); + red.set_low(); + green.set_high(); + blue.set_low(); + led.set_low(); + Timer::after_millis(HOLD_MS).await; + + info!("expected: red low, green low, blue low"); + red.set_low(); + green.set_low(); + blue.set_low(); + led.set_high(); + Timer::after_millis(HOLD_MS).await; + + info!("expected: red low, green low, blue high"); + red.set_low(); + green.set_low(); + blue.set_high(); + led.set_low(); + Timer::after_millis(HOLD_MS).await; + + info!("expected: red low, green low, blue low"); + red.set_low(); + green.set_low(); + blue.set_low(); + led.set_high(); + Timer::after_millis(HOLD_MS).await; + } +} diff --git a/examples/embassy-rgb/Cargo.toml b/examples/embassy-rgb/Cargo.toml new file mode 100644 index 0000000..27e1208 --- /dev/null +++ b/examples/embassy-rgb/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "embassy-rgb" +version = "0.1.0" +edition = "2024" +publish = false +build = "build.rs" + +[dependencies] +cortex-m.workspace = true +cortex-m-rt.workspace = true +defmt.workspace = true +defmt-rtt.workspace = true +embassy-executor = { workspace = true, features = ["defmt", "executor-thread", "platform-cortex-m"] } +embassy-stm32 = { workspace = true, features = ["defmt", "stm32f103c8", "time-driver-tim3"] } +embassy-time.workspace = true +panic-probe.workspace = true diff --git a/examples/embassy-rgb/build.rs b/examples/embassy-rgb/build.rs new file mode 100644 index 0000000..1295cd7 --- /dev/null +++ b/examples/embassy-rgb/build.rs @@ -0,0 +1,9 @@ +fn main() { + println!("cargo:rerun-if-changed=../../memory.x"); + println!( + "cargo:rustc-link-search={}", + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("../..") + .display() + ); +} diff --git a/examples/embassy-rgb/src/main.rs b/examples/embassy-rgb/src/main.rs new file mode 100644 index 0000000..bb6a87f --- /dev/null +++ b/examples/embassy-rgb/src/main.rs @@ -0,0 +1,143 @@ +#![deny(unsafe_code)] +#![no_std] +#![no_main] + +use cortex_m as _; +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_probe as _; + +const SMOOTH_STEPS: u16 = 64; +const SMOOTH_STEP_MS: u64 = 30; +const HARD_HOLD_MS: u64 = 1000; +const PWM_FRAME_US: u64 = 10_000; +const PWM_SLICES: u16 = 100; +const PWM_SLICE_US: u64 = PWM_FRAME_US / PWM_SLICES as u64; + +// mix two 8-bit values across a fixed number of steps. +fn blend(start_value: u8, end_value: u8, step: u16, steps: u16) -> u8 { + let start_value = u16::from(start_value); + let end_value = u16::from(end_value); + let step = step.min(steps); + let left = steps - step; + (((start_value * left) + (end_value * step)) / steps) as u8 +} + +// enerate one software PWM frame on the RGB GPIO pins +async fn pwm_frame( + red: &mut Output<'_>, + green: &mut Output<'_>, + blue: &mut Output<'_>, + red_pct: u8, + green_pct: u8, + blue_pct: u8, +) { + for slice in 0..PWM_SLICES { + if slice < u16::from(red_pct) { + red.set_high(); + } else { + red.set_low(); + } + + if slice < u16::from(green_pct) { + green.set_high(); + } else { + green.set_low(); + } + + if slice < u16::from(blue_pct) { + blue.set_high(); + } else { + blue.set_low(); + } + + Timer::after_micros(PWM_SLICE_US).await; + } +} + +// hold one RGB color for a fixed amount of time +async fn show_color( + red: &mut Output<'_>, + green: &mut Output<'_>, + blue: &mut Output<'_>, + red_pct: u8, + green_pct: u8, + blue_pct: u8, + hold_ms: u64, +) { + let frames = hold_ms / (PWM_FRAME_US / 1000); + + for _ in 0..frames { + pwm_frame(red, green, blue, red_pct, green_pct, blue_pct).await; + } +} + +// fade the RGB output through a soft color loop +async fn smooth_cycle(red: &mut Output<'_>, green: &mut Output<'_>, blue: &mut Output<'_>) { + const COLORS: [(u8, u8, u8); 4] = [(100, 0, 0), (0, 100, 0), (0, 0, 100), (100, 0, 0)]; + + for pair in COLORS.windows(2) { + let (r0, g0, b0) = pair[0]; + let (r1, g1, b1) = pair[1]; + + info!( + "fade step: {}% {}% {}% -> {}% {}% {}%", + r0, g0, b0, r1, g1, b1 + ); + + for step in 0..=SMOOTH_STEPS { + show_color( + red, + green, + blue, + blend(r0, r1, step, SMOOTH_STEPS), + blend(g0, g1, step, SMOOTH_STEPS), + blend(b0, b1, step, SMOOTH_STEPS), + SMOOTH_STEP_MS, + ) + .await; + } + } +} + +// switch the RGB output through fixed hard colors. +async fn hard_cycle(red: &mut Output<'_>, green: &mut Output<'_>, blue: &mut Output<'_>) { + const COLORS: [((u8, u8, u8), &str); 8] = [ + ((100, 0, 0), "red"), + ((0, 100, 0), "green"), + ((0, 0, 100), "blue"), + ((100, 100, 0), "yellow"), + ((0, 100, 100), "cyan"), + ((100, 0, 100), "magenta"), + ((100, 100, 100), "white"), + ((0, 0, 0), "off"), + ]; + + for &((r, g, b), name) in &COLORS { + info!("hard step: {}", name); + show_color(red, green, blue, r, g, b, HARD_HOLD_MS).await; + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let _gnd = Output::new(p.PA0, Level::Low, Speed::Low); + let mut led = Output::new(p.PC13, Level::High, Speed::Low); + let mut red = Output::new(p.PA1, Level::Low, Speed::Low); + let mut green = Output::new(p.PA2, Level::Low, Speed::Low); + let mut blue = Output::new(p.PA3, Level::Low, Speed::Low); + + loop { + led.toggle(); + info!("phase: smooth"); + smooth_cycle(&mut red, &mut green, &mut blue).await; + + led.toggle(); + info!("phase: hard"); + hard_cycle(&mut red, &mut green, &mut blue).await; + } +} diff --git a/justfile b/justfile index 5e02e49..ebd97e7 100644 --- a/justfile +++ b/justfile @@ -21,6 +21,12 @@ build-embassy-blinky: build-embassy-button: cargo build -p embassy-button +build-embassy-rgb: + cargo build -p embassy-rgb + +build-embassy-rgb-check: + cargo build -p embassy-rgb-check + flash-blinky: cargo build -p blinky-basic probe-rs download --chip {{chip}} target/{{target}}/debug/blinky-basic @@ -41,6 +47,14 @@ flash-embassy-button: cargo build -p embassy-button probe-rs download --chip {{chip}} target/{{target}}/debug/embassy-button +flash-embassy-rgb: + cargo build -p embassy-rgb + probe-rs download --chip {{chip}} target/{{target}}/debug/embassy-rgb + +flash-embassy-rgb-check: + cargo build -p embassy-rgb-check + probe-rs download --chip {{chip}} target/{{target}}/debug/embassy-rgb-check + run-blinky: cargo build -p blinky-basic probe-rs run --chip {{chip}} target/{{target}}/debug/blinky-basic @@ -61,6 +75,14 @@ run-embassy-button: cargo build -p embassy-button probe-rs run --chip {{chip}} target/{{target}}/debug/embassy-button +run-embassy-rgb: + cargo build -p embassy-rgb + probe-rs run --chip {{chip}} target/{{target}}/debug/embassy-rgb + +run-embassy-rgb-check: + cargo build -p embassy-rgb-check + probe-rs run --chip {{chip}} target/{{target}}/debug/embassy-rgb-check + fmt: cargo fmt --all