add rgb check and example
This commit is contained in:
@@ -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.
|
||||
|
||||
28
Cargo.lock
generated
28
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
10
README.md
10
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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
16
examples/embassy-rgb-check/Cargo.toml
Normal file
16
examples/embassy-rgb-check/Cargo.toml
Normal file
@@ -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
|
||||
12
examples/embassy-rgb-check/build.rs
Normal file
12
examples/embassy-rgb-check/build.rs
Normal file
@@ -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()
|
||||
);
|
||||
}
|
||||
70
examples/embassy-rgb-check/src/main.rs
Normal file
70
examples/embassy-rgb-check/src/main.rs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
16
examples/embassy-rgb/Cargo.toml
Normal file
16
examples/embassy-rgb/Cargo.toml
Normal file
@@ -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
|
||||
9
examples/embassy-rgb/build.rs
Normal file
9
examples/embassy-rgb/build.rs
Normal file
@@ -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()
|
||||
);
|
||||
}
|
||||
143
examples/embassy-rgb/src/main.rs
Normal file
143
examples/embassy-rgb/src/main.rs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
22
justfile
22
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user