diff --git a/.zed/tasks.json b/.zed/tasks.json new file mode 100644 index 0000000..1c59260 --- /dev/null +++ b/.zed/tasks.json @@ -0,0 +1,15 @@ +// Project tasks configuration. See https://zed.dev/docs/tasks for documentation. +// +// Example: +[ + { + "label": "cargo run --release [roleplay-microcontroller]", + "command": "cargo", + "args": ["run", "--release"], + "use_new_terminal": false, + "allow_concurrent_runs": false, + "reveal": "always", + "hide": "never", + "shell": "system" + } +] diff --git a/Cargo.lock b/Cargo.lock index fc43ff7..3f06027 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,7 +107,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f377753756ec12e76b52d2dd657437be0448cc9736402ffadd0b8b8b9602c8a1" dependencies = [ - "embassy-sync", + "embassy-sync 0.6.2", "embedded-io", "embedded-io-async", "futures-intrusive", @@ -215,7 +215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fea5ef5bed4d3468dfd44f5c9fa4cda8f54c86d4fb4ae683eacf9d39e2ea12" dependencies = [ "embassy-futures", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-time", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -262,7 +262,7 @@ checksum = "ed041cc19a603d657124fddefdcbe5ef8bd60e77d972793ebb57de93394f5949" dependencies = [ "document-features", "embassy-net-driver", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-time", "embedded-io-async", "embedded-nal-async", @@ -291,6 +291,20 @@ dependencies = [ "heapless", ] +[[package]] +name = "embassy-sync" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef1a8a1ea892f9b656de0295532ac5d8067e9830d49ec75076291fd6066b136" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless", +] + [[package]] name = "embassy-time" version = "0.4.0" @@ -499,7 +513,7 @@ dependencies = [ "document-features", "embassy-embedded-hal", "embassy-futures", - "embassy-sync", + "embassy-sync 0.6.2", "embedded-can", "embedded-hal 1.0.0", "embedded-hal-async", @@ -537,7 +551,7 @@ dependencies = [ "critical-section", "document-features", "embassy-executor", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-time", "embassy-time-driver", "embassy-time-queue-utils", @@ -612,7 +626,7 @@ dependencies = [ "critical-section", "document-features", "embassy-net-driver", - "embassy-sync", + "embassy-sync 0.6.2", "embedded-io", "embedded-io-async", "enumset", @@ -874,7 +888,9 @@ dependencies = [ "bleps", "critical-section", "embassy-executor", + "embassy-futures", "embassy-net", + "embassy-sync 0.7.0", "embassy-time", "embedded-io", "embedded-io-async", @@ -885,8 +901,10 @@ dependencies = [ "esp-wifi", "heapless", "log", + "smart-leds", "smoltcp", "static_cell", + "ws2812-spi", ] [[package]] @@ -1081,6 +1099,15 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + [[package]] name = "riscv" version = "0.12.1" @@ -1163,6 +1190,24 @@ dependencies = [ "serde", ] +[[package]] +name = "smart-leds" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66df34e571fa9993fa6f99131a374d58ca3d694b75f9baac93458fe0d6057bf0" +dependencies = [ + "smart-leds-trait", +] + +[[package]] +name = "smart-leds-trait" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edeb89c73244414bb0568611690dd095b2358b3fda5bae65ad784806cca00157" +dependencies = [ + "rgb", +] + [[package]] name = "smoltcp" version = "0.12.0" @@ -1502,6 +1547,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ws2812-spi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2fd98e2b649252eced2ec3aa8d5048e7d2ac294276b0567939bbf47741f9934" +dependencies = [ + "embedded-hal 1.0.0", + "smart-leds-trait", +] + [[package]] name = "xtensa-lx" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index 76f046d..f5b1b41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,18 @@ version = "0.1.0" edition = "2024" [[bin]] -name = "uwu" +name = "m5stamp_c3_ws2812" path = "./src/bin/main.rs" +test = false +doctest = false +bench = false + +[lib] +name = "m5stamp_c3_ws2812" +path = "./src/lib.rs" +test = false +doctest = false +bench = false [dependencies] embassy-net = { version = "0.6.0", features = [ @@ -52,6 +62,10 @@ esp-wifi = { version = "0.13.0", features = [ ] } heapless = { version = "0.8.0", default-features = false } static_cell = { version = "2.1.0", features = ["nightly"] } +smart-leds = "0.4.0" +ws2812-spi = "0.5.0" +embassy-futures = "0.1.1" +embassy-sync = "0.7.0" [profile.dev] # Rust debug is too slow. diff --git a/src/bin/main.rs b/src/bin/main.rs index 8f72a47..5d9e5ec 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,12 +1,25 @@ #![no_std] #![no_main] +use core::pin::Pin; + use embassy_executor::Spawner; -use embassy_time::{Duration, Timer}; +use embassy_futures::select::{Either, select}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::channel::{Channel, Sender}; +use embassy_time::{Duration, Instant, Timer}; use esp_hal::clock::CpuClock; +use esp_hal::gpio::{Input, InputConfig, Pull}; +use esp_hal::peripherals::GPIO; +use esp_hal::spi::master::{Config, Spi}; +use esp_hal::time::Rate; use esp_hal::timer::systimer::SystemTimer; use esp_hal::timer::timg::TimerGroup; use log::info; +use smart_leds::RGB8; +use smart_leds::SmartLedsWrite; +use static_cell::StaticCell; +use ws2812_spi::Ws2812; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { @@ -15,8 +28,11 @@ fn panic(_: &core::panic::PanicInfo) -> ! { extern crate alloc; +static CHANNEL: StaticCell> = StaticCell::new(); + #[esp_hal_embassy::main] async fn main(spawner: Spawner) { + let channel = CHANNEL.init(Channel::new()); // generator version: 0.3.1 esp_println::logger::init_logger_from_env(); @@ -39,13 +55,176 @@ async fn main(spawner: Spawner) { ) .unwrap(); - // TODO: Spawn some tasks - let _ = spawner; + spawner + .spawn(listen_rotary_encoder( + RotaryEncoderConfig { + clk: Input::new( + peripherals.GPIO8, + InputConfig::default().with_pull(Pull::Up), + ), + ot: Input::new( + peripherals.GPIO10, + InputConfig::default().with_pull(Pull::Up), + ), + }, + channel.sender(), + )) + .unwrap(); + + let mut led_strip = { + let spi = Spi::new( + peripherals.SPI2, + Config::default().with_frequency(Rate::from_mhz(3)), + ) + .unwrap() + .with_mosi(peripherals.GPIO4); + Ws2812::new(spi) + }; + + let colors = [ + RGB8 { r: 0, g: 255, b: 0 }, + RGB8 { r: 255, g: 0, b: 0 }, + RGB8 { r: 255, g: 0, b: 0 }, + RGB8 { + r: 0, + g: 255, + b: 255, + }, + RGB8 { + r: 255, + g: 0, + b: 255, + }, + RGB8 { + r: 255, + g: 255, + b: 0, + }, + ]; + let mut color_idx = 0; loop { - info!("Hello world!"); - Timer::after(Duration::from_secs(1)).await; + let delta = channel.receive().await; + color_idx += delta; + // led_strip + // .write([ + // colors[color_idx % colors.len()], + // colors[(color_idx + 1) % colors.len()], + // colors[(color_idx + 2) % colors.len()], + // colors[(color_idx + 3) % colors.len()], + // ]) + // .unwrap(); + // + + let cnt = 4; + + led_strip + .write((0..cnt).map(|it| { + if it <= (color_idx % cnt) { + RGB8 { + r: 255, + g: 50, + b: 00, + } + } else { + RGB8 { r: 0, g: 0, b: 0 } + } + })) + .unwrap(); } // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-beta.0/examples/src/bin } + +pub struct Throttler<'a> { + input: Input<'a>, + throttle: Duration, + last_fire: Option, +} + +impl<'a> Throttler<'a> { + pub fn new(input: Input<'a>, throttle: Duration) -> Self { + Self { + input, + throttle, + last_fire: None, + } + } + + pub async fn wait_for(&mut self) { + if let Some(allow_next_fire) = self + .last_fire + .and_then(|last_fire| last_fire.checked_add(self.throttle)) + { + Timer::at(allow_next_fire).await; + } + self.input.wait_for_falling_edge().await; + self.last_fire = Some(Instant::now()); + } +} + +struct RotaryEncoderConfig<'a, 'b> { + clk: Input<'a>, + ot: Input<'b>, +} + +#[derive(Clone, Copy, Debug)] +enum RotaryState { + Clk, + Ot, +} +#[embassy_executor::task] +async fn listen_rotary_encoder( + mut config: RotaryEncoderConfig<'static, 'static>, + channel: Sender<'static, NoopRawMutex, i32, 2>, +) { + info!("Waiting for rotary."); + + let mut state: Option = None; + + let mut clk_input = Throttler::new(config.clk, Duration::from_millis(50)); + let mut ot_input = Throttler::new(config.ot, Duration::from_millis(50)); + + loop { + let clk = async { + clk_input.wait_for().await; + }; + + let ot = async { + ot_input.wait_for().await; + }; + + let next_state = match select(clk, ot).await { + Either::First(_) => (RotaryState::Clk), + Either::Second(_) => (RotaryState::Ot), + }; + + //info!("State: {next_state:?}"); + + match (state, next_state) { + (None, RotaryState::Ot) => { + if clk_input.input.is_high() { + state = Some(RotaryState::Ot); + } + } + (None, RotaryState::Clk) => { + if ot_input.input.is_high() { + state = Some(RotaryState::Clk); + } + } + (Some(RotaryState::Clk), RotaryState::Ot) => { + info!("Clk -> Ot"); + state = None; + channel.send(1).await; + } + (Some(RotaryState::Ot), RotaryState::Clk) => { + info!("Ot -> Clk"); + state = None; + channel.send(-1).await; + } + + (Some(RotaryState::Ot), RotaryState::Ot) + | (Some(RotaryState::Clk), RotaryState::Clk) => {} + } + } +}