commit 6fe6f2f74aa6566f1f656fb754cec9b8262fc4e2 Author: Victoria Fischer Date: Sun Oct 20 17:22:27 2024 +0200 Initial Commit diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..6065d79 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,18 @@ +[build] +target = "xtensa-esp32-espidf" + +[target.xtensa-esp32-espidf] +linker = "ldproxy" +runner = "espflash flash --monitor" # Select this runner for espflash v3.x.x +rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 + +[unstable] +build-std = ["std", "panic_abort"] + +[env] +MCU="esp32" +# Note: this variable is not used by the pio builder (`cargo build --features pio`) +ESP_IDF_VERSION = "v5.2.2" + +# Workaround for https://github.com/esp-rs/esp-idf-template/issues/174 +CRATE_CC_NO_DEFAULTS = "1" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73a638b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.vscode +/.embuild +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..be291f5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "renderbug" +version = "0.1.0" +authors = ["tdfischer"] +edition = "2021" +resolver = "2" +rust-version = "1.77" + +[[bin]] +name = "renderbug" +harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors + +[profile.release] +opt-level = "s" + +[profile.dev] +debug = true # Symbols are nice and they don't increase the size on Flash +opt-level = "z" + +[features] +default = ["std", "embassy", "esp-idf-svc/native"] + +pio = ["esp-idf-svc/pio"] +std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"] +alloc = ["esp-idf-svc/alloc"] +nightly = ["esp-idf-svc/nightly"] +experimental = ["esp-idf-svc/experimental"] +embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"] + +[dependencies] +log = { version = "0.4", default-features = false } +esp-idf-svc = { version = "0.49", default-features = false } +ws2812-esp32-rmt-driver = { version = "*", features = ["embedded-graphics-core"]} +embedded-graphics = "0.8.1" +hsv = "0.1.1" +palette = { version = "0.7.6" } + +[build-dependencies] +embuild = "0.32.0" +anyhow = "1" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..112ec3f --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + embuild::espidf::sysenv::output(); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a2f5ab5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "esp" diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..d905496 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,11 @@ +# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 + +# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). +# This allows to use 1 ms granularity for thread sleeps (10 ms by default). +#CONFIG_FREERTOS_HZ=1000 + +# Workaround for https://github.com/espressif/esp-idf/issues/7631 +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n +CONFIG_ESP32_XTAL_FREQ_26=y diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ddee6d9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,126 @@ +#![allow(arithmetic_overflow)] +use esp_idf_svc::hal::prelude::Peripherals; +use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelStrip, Ws2812DrawTarget}; +use embedded_graphics::{ + prelude::*, + pixelcolor::{Rgb888, RgbColor}, +}; +use std::thread::sleep; +use std::time::Duration; +use palette::{Hsv, Srgb}; +use palette::convert::{IntoColor, IntoColorUnclamped, FromColorUnclamped}; + +fn colorToMW(color : impl RgbColor) -> u32 { + const gRed_mW : u32 = 16 * 5; //< 16mA @ 5v = 80mW + const gGreen_mW : u32 = 11 * 5; //< 11mA @ 5v = 55mW + const gBlue_mW : u32 = 15 * 5; //< 15mA @ 5v = 75mW + const gDark_mW : u32 = 1 * 5; //< 1mA @ 5v = 5mW + + let redMW = (color.r() as u32 * gRed_mW).wrapping_shr(8); + let greenMW = (color.g() as u32 * gGreen_mW).wrapping_shr(8); + let blueMW = (color.b() as u32 * gBlue_mW).wrapping_shr(8); + + return redMW + greenMW + blueMW + gDark_mW; +} + +fn brightnessForMW(totalMW : u32, target : u8, maxPower: u32) -> u8 { + let target32 = target as u32; + let requestedMW = (totalMW * target32) / 256; + if requestedMW > maxPower { + return ((target32 * maxPower) / requestedMW).try_into().unwrap(); + } + return target; +} + +#[derive(PartialEq, Debug)] +struct RGB8 { + red: u8, + green: u8, + blue: u8 +} + +impl RGB8 { + fn new(red : u8, green : u8, blue : u8) -> Self { + Self { + red: red, + green: green, + blue: blue + } + } +} + +impl FromColorUnclamped> for RGB8 { + fn from_color_unclamped(hsv: Hsv) -> RGB8 { + if hsv.saturation == 0 { + return RGB8::new(hsv.value, hsv.value, hsv.value); + } + + let region = hsv.hue.into_inner() / 43; + let remainder = (hsv.hue.into_inner() - (region * 43)) * 6; + + let p = (hsv.value.wrapping_mul(255 - hsv.saturation).wrapping_shr(8)); + let q = (hsv.value.wrapping_mul(255 - ((hsv.saturation.wrapping_mul(remainder)).wrapping_shr(8)))).wrapping_shr(8); + let t = (hsv.value.wrapping_mul(255 - ((hsv.saturation.wrapping_mul(255 - remainder)).wrapping_shr(8)))).wrapping_shr(8); + + match region { + 0 => RGB8::new(hsv.value, t, p), + 1 => RGB8::new(q, hsv.value, p), + 2 => RGB8::new(p, hsv.value, t), + 3 => RGB8::new(p, q, hsv.value), + 4 => RGB8::new(t, p, hsv.value), + _ => RGB8::new(hsv.value, p, q) + } + } +} + +fn main() { + // It is necessary to call this function once. Otherwise some patches to the runtime + // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 + esp_idf_svc::sys::link_patches(); + + // Bind the log crate to the ESP Logging facilities + esp_idf_svc::log::EspLogger::initialize_default(); + + log::info!("Hello, world!"); + + let peripherals = Peripherals::take().unwrap(); + let led_pin = peripherals.pins.gpio14; + let channel = peripherals.rmt.channel0; + + const NUM_PIXELS : usize = 255; + const MAX_POWER_MW : u32 = 1000; + + let mut draw = Ws2812DrawTarget::>::new(channel, led_pin).unwrap(); + let mut hue : u8 = 0; + let mut length : usize = NUM_PIXELS; + let mut forwards = false; + loop { + let mut totalMW = 0; + draw.clear(Rgb888::BLACK); + for i in 0..length { + //let color = HSV::new(hue.wrapping_add(i as u8), 255, 255); + let hsvColor = Hsv::new_srgb(hue.wrapping_add(i as u8), 255, 255); + let rgbColor : RGB8 = hsvColor.into_color_unclamped(); + //let rgbColor : Srgb = hsvColor.into_color(); + let color = Rgb888::new(rgbColor.red, rgbColor.green, rgbColor.blue); + + totalMW += colorToMW(color); + Pixel(Point::new(i as i32, 0), color).draw(&mut draw).unwrap(); + } + let brightness = brightnessForMW(totalMW, 255, MAX_POWER_MW); + draw.set_brightness(brightness); + draw.flush().unwrap(); + log::info!("Frame hue={} power={} brightness={}", hue, totalMW, brightness); + hue = hue.wrapping_add(1); + if forwards { + length += 1 + } else { + length -= 1 + } + if length <= 1 { + forwards = true; + } else if length >= NUM_PIXELS { + forwards = false; + } + } +}