├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── m5atom_embedded_graphics.rs ├── m5atom_smart_leds.rs └── smart_leds_sk6812_rgbw.rs ├── rust-toolchain.toml ├── sdkconfig.defaults └── src ├── driver ├── color.rs ├── esp32_rmt.rs └── mod.rs ├── lib.rs ├── lib_embedded_graphics.rs ├── lib_smart_leds.rs └── mock └── mod.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3) 3 | target = "xtensa-esp32-espidf" 4 | #target = "xtensa-esp32s2-espidf" 5 | #target = "xtensa-esp32s3-espidf" 6 | #target = "riscv32imc-esp-espidf" 7 | 8 | [target.xtensa-esp32-espidf] 9 | linker = "ldproxy" 10 | runner = "espflash --monitor" 11 | rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 12 | 13 | [target.riscv32imc-esp-espidf] 14 | linker = "ldproxy" 15 | runner = "espflash flash --monitor" 16 | rustflags = ["--cfg", "espidf_time64", "-C", "default-linker-libraries"] 17 | 18 | [unstable] 19 | build-std = ["std", "panic_abort"] 20 | build-std-features = ["panic_immediate_abort"] 21 | 22 | [env] 23 | # Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF stable (v5.1) 24 | ESP_IDF_VERSION = { value = "branch:release/v5.1" } 25 | # Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF master 26 | #ESP_IDF_VERSION = { value = "master" } 27 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Install Rust for Xtensa 15 | uses: esp-rs/xtensa-toolchain@v1.5 16 | with: 17 | default: true 18 | version: "1.82.0" 19 | ldproxy: true 20 | - uses: actions/checkout@v4 21 | with: 22 | path: ws2812-esp32-rmt-driver 23 | - uses: actions/cache@v4 24 | with: 25 | path: | 26 | ~/.cargo/registry 27 | ~/.cargo/git 28 | ws2812-esp32-rmt-driver/target 29 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 30 | - name: Build 31 | run: | 32 | cargo build 33 | cargo build --all-features 34 | cargo build --no-default-features --features=alloc 35 | cargo build --no-default-features --features=alloc,smart-leds-trait,embedded-graphics-core 36 | cargo build --no-default-features 37 | cargo build --no-default-features --features=smart-leds-trait,embedded-graphics-core 38 | working-directory: ./ws2812-esp32-rmt-driver 39 | - name: Build examples 40 | run: cargo build --examples --all-features --features=esp-idf-sys/binstart #--release 41 | working-directory: ./ws2812-esp32-rmt-driver 42 | - name: Test 43 | run: | 44 | cargo +stable test --target x86_64-unknown-linux-gnu --lib 45 | cargo +stable test --target x86_64-unknown-linux-gnu --lib --all-features 46 | cargo +stable test --target x86_64-unknown-linux-gnu --lib --no-default-features --features std,smart-leds-trait 47 | cargo +stable test --target x86_64-unknown-linux-gnu --lib --no-default-features --features std,embedded-graphics-core 48 | cargo +stable test --target x86_64-unknown-linux-gnu --doc --all-features 49 | working-directory: ./ws2812-esp32-rmt-driver 50 | - name: Publish (Dry run) 51 | run: cargo +stable publish --target x86_64-unknown-linux-gnu --dry-run 52 | working-directory: ./ws2812-esp32-rmt-driver 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .embuild 3 | .vscode 4 | 5 | # Created by https://www.gitignore.io/api/vim,emacs,intellij,visualstudiocode,rust 6 | # Edit at https://www.gitignore.io/?templates=vim,emacs,intellij,visualstudiocode,rust 7 | 8 | ### Emacs ### 9 | # -*- mode: gitignore; -*- 10 | *~ 11 | \#*\# 12 | /.emacs.desktop 13 | /.emacs.desktop.lock 14 | *.elc 15 | auto-save-list 16 | tramp 17 | .\#* 18 | 19 | # Org-mode 20 | .org-id-locations 21 | *_archive 22 | 23 | # flymake-mode 24 | *_flymake.* 25 | 26 | # eshell files 27 | /eshell/history 28 | /eshell/lastdir 29 | 30 | # elpa packages 31 | /elpa/ 32 | 33 | # reftex files 34 | *.rel 35 | 36 | # AUCTeX auto folder 37 | /auto/ 38 | 39 | # cask packages 40 | .cask/ 41 | dist/ 42 | 43 | # Flycheck 44 | flycheck_*.el 45 | 46 | # server auth directory 47 | /server/ 48 | 49 | # projectiles files 50 | .projectile 51 | 52 | # directory configuration 53 | .dir-locals.el 54 | 55 | # network security 56 | /network-security.data 57 | 58 | 59 | ### Intellij ### 60 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 61 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 62 | 63 | # User-specific stuff 64 | .idea/**/workspace.xml 65 | .idea/**/tasks.xml 66 | .idea/**/usage.statistics.xml 67 | .idea/**/dictionaries 68 | .idea/**/shelf 69 | 70 | # Generated files 71 | .idea/**/contentModel.xml 72 | 73 | # Sensitive or high-churn files 74 | .idea/**/dataSources/ 75 | .idea/**/dataSources.ids 76 | .idea/**/dataSources.local.xml 77 | .idea/**/sqlDataSources.xml 78 | .idea/**/dynamic.xml 79 | .idea/**/uiDesigner.xml 80 | .idea/**/dbnavigator.xml 81 | 82 | # Gradle 83 | .idea/**/gradle.xml 84 | .idea/**/libraries 85 | 86 | # Gradle and Maven with auto-import 87 | # When using Gradle or Maven with auto-import, you should exclude module files, 88 | # since they will be recreated, and may cause churn. Uncomment if using 89 | # auto-import. 90 | # .idea/modules.xml 91 | # .idea/*.iml 92 | # .idea/modules 93 | # *.iml 94 | # *.ipr 95 | 96 | # CMake 97 | cmake-build-*/ 98 | 99 | # Mongo Explorer plugin 100 | .idea/**/mongoSettings.xml 101 | 102 | # File-based project format 103 | *.iws 104 | 105 | # IntelliJ 106 | out/ 107 | 108 | # mpeltonen/sbt-idea plugin 109 | .idea_modules/ 110 | 111 | # JIRA plugin 112 | atlassian-ide-plugin.xml 113 | 114 | # Cursive Clojure plugin 115 | .idea/replstate.xml 116 | 117 | # Crashlytics plugin (for Android Studio and IntelliJ) 118 | com_crashlytics_export_strings.xml 119 | crashlytics.properties 120 | crashlytics-build.properties 121 | fabric.properties 122 | 123 | # Editor-based Rest Client 124 | .idea/httpRequests 125 | 126 | # Android studio 3.1+ serialized cache file 127 | .idea/caches/build_file_checksums.ser 128 | 129 | ### Intellij Patch ### 130 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 131 | 132 | # *.iml 133 | # modules.xml 134 | # .idea/misc.xml 135 | # *.ipr 136 | 137 | # Sonarlint plugin 138 | .idea/sonarlint 139 | 140 | ### Rust ### 141 | # Generated by Cargo 142 | # will have compiled files and executables 143 | /target/ 144 | 145 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 146 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 147 | Cargo.lock 148 | 149 | # These are backup files generated by rustfmt 150 | **/*.rs.bk 151 | 152 | ### Vim ### 153 | # Swap 154 | [._]*.s[a-v][a-z] 155 | [._]*.sw[a-p] 156 | [._]s[a-rt-v][a-z] 157 | [._]ss[a-gi-z] 158 | [._]sw[a-p] 159 | 160 | # Session 161 | Session.vim 162 | Sessionx.vim 163 | 164 | # Temporary 165 | .netrwhist 166 | # Auto-generated tag files 167 | tags 168 | # Persistent undo 169 | [._]*.un~ 170 | 171 | ### VisualStudioCode ### 172 | .vscode/* 173 | !.vscode/settings.json 174 | !.vscode/tasks.json 175 | !.vscode/launch.json 176 | !.vscode/extensions.json 177 | 178 | ### VisualStudioCode Patch ### 179 | # Ignore all local history of files 180 | .history 181 | 182 | # End of https://www.gitignore.io/api/vim,emacs,intellij,visualstudiocode,rust 183 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ws2812-esp32-rmt-driver" 3 | version = "0.12.0" 4 | license = "MIT" 5 | authors = ["@cat_in_136"] 6 | categories = ["embedded", "hardware-support"] 7 | keywords = ["embedded-graphics", "esp32", "neopixel", "smart-leds", "ws2812"] 8 | description = "WS2812 driver using ESP32 RMT" 9 | homepage = "https://github.com/cat-in-136/ws2812-esp32-rmt-smart-leds" 10 | readme = "README.md" 11 | repository = "https://github.com/cat-in-136/ws2812-esp32-rmt-smart-leds" 12 | edition = "2021" 13 | rust-version = "1.82" 14 | 15 | [dependencies] 16 | smart-leds-trait = { version = "0.3", optional = true } 17 | embedded-graphics-core = { version = "0.4", optional = true } 18 | heapless = "0.8" 19 | 20 | [target.'cfg(target_vendor = "espressif")'.dependencies] 21 | esp-idf-hal = { version = "0.45", default-features = false, features = ['rmt-legacy'] } 22 | esp-idf-sys = { version = "0.36", default-features = false } 23 | 24 | [target.'cfg(not(target_vendor = "espressif"))'.dependencies] 25 | paste = "1" 26 | 27 | [features] 28 | default = ["std"] 29 | std = [ "alloc", "esp-idf-hal/std", "esp-idf-sys/std" ] 30 | alloc = [ "esp-idf-hal/alloc" ] 31 | 32 | [dev-dependencies] 33 | smart-leds = "0.4" 34 | embedded-graphics = "0.8" 35 | 36 | [build-dependencies] 37 | embuild = "0.32" 38 | 39 | [profile.release] 40 | strip = true 41 | opt-level = "s" 42 | 43 | [profile.dev] 44 | debug = true # Symbols are nice and they don't increase the size on Flash 45 | strip = true 46 | opt-level = "z" 47 | 48 | [package.metadata.docs.rs] 49 | all-features = true 50 | rustdoc-args = ["--cfg", "docsrs"] 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 @cat_in_136 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ws2812-esp32-rmt-driver 2 | 3 | A rust driver library to control WS2812B (NeoPixel) RGB LED pixels/strips using ESP32 RMT (Remote Control) module. 4 | 5 | ![Rust](https://github.com/cat-in-136/ws2812-esp32-rmt-driver/workflows/Rust/badge.svg) 6 | [![ws2812-esp32-rmt-driver at crates.io](https://img.shields.io/crates/v/ws2812-esp32-rmt-driver.svg)](https://crates.io/crates/ws2812-esp32-rmt-driver) 7 | [![API](https://docs.rs/ws2812-esp32-rmt-driver/badge.svg)](https://docs.rs/ws2812-esp32-rmt-driver) 8 | 9 | By disabling the carrier generator of [the RMT's transmitter][rmt] 10 | , it can be used as just a PWM signal generator for [WS2812B data signal][ws2812b-datasheet]. This control way is the 11 | same as major Arduino/C++ library such as [FastLED](https://github.com/FastLED/FastLED), 12 | [Adafruit_NeoPixel](https://github.com/adafruit/Adafruit_NeoPixel). 13 | 14 | The RMT (Remote Control) module is specific to ESP32. Hence, it can be used only for ESP32 SoC. 15 | 16 | This library also support SK6812-RGBW 4-color LED pixels/strips (smart-leds API only). 17 | 18 | [rmt]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html 19 | 20 | [ws2812b-datasheet]: https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf 21 | 22 | ## Usage 23 | 24 | Install rust with Xtensa support. Refer [esp-rs/rust-build](https://github.com/esp-rs/rust-build) for the setup 25 | instruction. 26 | 27 | Add following dependency to your `Cargo.toml`. Note that version is stripped in this example but it is recommended to 28 | specify version explicitly in your project. 29 | 30 | ```toml 31 | [dependencies] 32 | esp-idf-sys = { version = "*", features = ["binstart"] } 33 | esp-idf-hal = "*" 34 | smart-leds = "*" 35 | 36 | ws2812-esp32-rmt-driver = { version = "*", features = ["smart-leds-trait"] } 37 | 38 | [build-dependencies] 39 | embuild = "*" 40 | anyhow = "1" 41 | ``` 42 | 43 | Refer `examples/` directory for the source code. 44 | 45 | Make ensure `esp` toolchain is available and `xtensa-esp32-elf-clang` is in your `$PATH`. And then, run as follows 46 | 47 | ```console 48 | $ cargo build 49 | $ cargo espflash 50 | ``` 51 | 52 | ## Features 53 | 54 | |Features |Default|Description | 55 | |------------------------|-------|----------------------------------------------------------------------| 56 | |`embedded_graphics_core`| |embedded-graphics API `ws2812_esp32_rmt_driver::lib_embedded_graphics`| 57 | |`smart-leds-trait` | |smart-leds API `ws2812_esp32_rmt_driver::lib_smart_leds` | 58 | |`std` |x |use standard library `std` | 59 | |`alloc` |x |use memory allocator (heap) | 60 | 61 | Some examples: 62 | 63 | * `features = ["embedded-graphics-core"]` to enable embedded-graphics 64 | API `ws2812_esp32_rmt_driver::lib_embedded_graphics`. 65 | * `features = ["smart-leds-trait"]` to enable smart-leds API `ws2812_esp32_rmt_driver::lib_smart_leds`. 66 | * default feature to enable just only driver API. 67 | 68 | ## no_std 69 | 70 | To use `no_std`, disable `default` feature. Then, `std` feature is disabled and this library get compatible with `no_std`. 71 | 72 | Some examples: 73 | 74 | * `default-feature = false, features = ["alloc", "embedded-graphics-core"]` to enable embedded-graphics API 75 | `ws2812_esp32_rmt_driver::lib_embedded_graphics` for `no_std` environment with memory allocator. 76 | * `default-feature = false, features = ["alloc", "smart-leds-trait"]` to enable smart-leds API 77 | `ws2812_esp32_rmt_driver::lib_smart_leds` for `no_std` environment with memory allocator. 78 | * `default-feature = false, features = ["embedded-graphics-core"]` to enable embedded-graphics API 79 | `ws2812_esp32_rmt_driver::lib_embedded_graphics` for `no_std` environment without memory allocator. 80 | * `default-feature = false, features = ["smart-leds-trait"]` to enable smart-leds API 81 | `ws2812_esp32_rmt_driver::lib_smart_leds` for `no_std` environment without memory allocator. 82 | 83 | When using the memory allocator (heap), enable the `alloc` feature. In this case, most processing works in the same way as `std`. 84 | When not using the memory allocator (heap), leave the `alloc` feature disabled. In this case, 85 | some APIs cannot be used and processing must be changed. 86 | For example, in the embedded-graphics API, the pixel data storage must be prepared by the programmer 87 | using heapless `Vec`-like struct such as `heapless::Vec`. 88 | 89 | 90 | This library is intended for use with espidf. 91 | For bare-metal environments (i.e. use with [esp-hal](https://crates.io/crates/esp-hal/)), 92 | use the espressif official crate [esp-hal-smartled](https://crates.io/crates/esp-hal-smartled). 93 | 94 | ## Development 95 | 96 | To run the test locally, specify the local toolchain (`stable`, `nightly`, etc...) and target explicitly and disable 97 | example builds (specify `--lib`) 98 | . 99 | 100 | ```console 101 | $ cargo +stable test --target x86_64-unknown-linux-gnu --lib 102 | ``` 103 | 104 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641 2 | fn main() -> Result<(), Box> { 3 | if std::env::var("CARGO_CFG_TARGET_VENDOR") == Ok("espressif".to_string()) { 4 | embuild::build::CfgArgs::output_propagated("ESP_IDF")?; 5 | embuild::build::LinkArgs::output_propagated("ESP_IDF")? 6 | } 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /examples/m5atom_embedded_graphics.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "embedded-graphics-core")] 2 | use embedded_graphics::pixelcolor::Rgb888; 3 | use embedded_graphics::prelude::*; 4 | use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle, Triangle}; 5 | use esp_idf_hal::peripherals::Peripherals; 6 | use std::thread::sleep; 7 | use std::time::Duration; 8 | use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelMatrix, Ws2812DrawTarget}; 9 | 10 | fn main() -> ! { 11 | // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once, 12 | // or else some patches to the runtime implemented by esp-idf-sys might not link properly. 13 | esp_idf_sys::link_patches(); 14 | 15 | let peripherals = Peripherals::take().unwrap(); 16 | let led_pin = peripherals.pins.gpio27; 17 | let channel = peripherals.rmt.channel0; 18 | 19 | let mut draw = Ws2812DrawTarget::>::new(channel, led_pin).unwrap(); 20 | draw.set_brightness(40); 21 | 22 | println!("Start Ws2812DrawTarget!"); 23 | 24 | let mut offset = 0; 25 | loop { 26 | draw.clear_with_black().unwrap(); 27 | 28 | let shape_width = 5 + 1; 29 | let shape_offset = |offset: i32, i: usize, count: usize| { 30 | let mut offset = offset as i32 + shape_width * i as i32; 31 | if offset < -shape_width { 32 | offset += shape_width * count as i32; 33 | } 34 | offset 35 | }; 36 | 37 | let mut translated_draw = draw.translated(Point::new(shape_offset(offset, 0, 3), 0)); 38 | Circle::new(Point::new(0, 0), 5) 39 | .into_styled(PrimitiveStyle::with_fill(Rgb888::RED)) 40 | .draw(&mut translated_draw) 41 | .unwrap(); 42 | 43 | let mut translated_draw = draw.translated(Point::new(shape_offset(offset, 1, 3), 0)); 44 | Triangle::new(Point::new(0, 0), Point::new(4, 2), Point::new(0, 4)) 45 | .into_styled(PrimitiveStyle::with_fill(Rgb888::GREEN)) 46 | .draw(&mut translated_draw) 47 | .unwrap(); 48 | 49 | let mut translated_draw = draw.translated(Point::new(shape_offset(offset, 2, 3), 0)); 50 | Rectangle::new(Point::new(0, 0), Size::new(5, 5)) 51 | .into_styled(PrimitiveStyle::with_fill(Rgb888::BLUE)) 52 | .draw(&mut translated_draw) 53 | .unwrap(); 54 | 55 | draw.flush().unwrap(); 56 | 57 | sleep(Duration::from_millis(100)); 58 | 59 | offset -= 1; 60 | if offset < -shape_width * 3 { 61 | offset += (-offset / shape_width) * shape_width; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/m5atom_smart_leds.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "smart-leds-trait")] 2 | use esp_idf_hal::peripherals::Peripherals; 3 | use esp_idf_sys::*; 4 | use smart_leds::hsv::{hsv2rgb, Hsv}; 5 | use smart_leds_trait::SmartLedsWrite; 6 | use std::thread::sleep; 7 | use std::time::Duration; 8 | use ws2812_esp32_rmt_driver::Ws2812Esp32Rmt; 9 | 10 | fn main() -> ! { 11 | // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once, 12 | // or else some patches to the runtime implemented by esp-idf-sys might not link properly. 13 | esp_idf_sys::link_patches(); 14 | 15 | let peripherals = Peripherals::take().unwrap(); 16 | let led_pin = peripherals.pins.gpio27; 17 | let channel = peripherals.rmt.channel0; 18 | let mut ws2812 = Ws2812Esp32Rmt::new(channel, led_pin).unwrap(); 19 | 20 | println!("Start NeoPixel rainbow!"); 21 | 22 | let mut hue = unsafe { esp_random() } as u8; 23 | loop { 24 | let pixels = std::iter::repeat(hsv2rgb(Hsv { 25 | hue, 26 | sat: 255, 27 | val: 8, 28 | })) 29 | .take(25); 30 | ws2812.write(pixels).unwrap(); 31 | 32 | sleep(Duration::from_millis(100)); 33 | 34 | hue = hue.wrapping_add(10); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/smart_leds_sk6812_rgbw.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "smart-leds-trait")] 2 | use esp_idf_hal::peripherals::Peripherals; 3 | use smart_leds_trait::{SmartLedsWrite, White}; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | use ws2812_esp32_rmt_driver::driver::color::LedPixelColorGrbw32; 7 | use ws2812_esp32_rmt_driver::{LedPixelEsp32Rmt, RGBW8}; 8 | 9 | fn main() -> ! { 10 | // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once, 11 | // or else some patches to the runtime implemented by esp-idf-sys might not link properly. 12 | esp_idf_sys::link_patches(); 13 | 14 | let peripherals = Peripherals::take().unwrap(); 15 | let led_pin = peripherals.pins.gpio26; 16 | let channel = peripherals.rmt.channel0; 17 | let mut ws2812 = LedPixelEsp32Rmt::::new(channel, led_pin).unwrap(); 18 | 19 | loop { 20 | let pixels = std::iter::repeat(RGBW8::new_alpha(6, 0, 0, White(0))).take(25); 21 | ws2812.write(pixels).unwrap(); 22 | sleep(Duration::from_millis(1000)); 23 | 24 | let pixels = std::iter::repeat(RGBW8::new_alpha(0, 6, 0, White(0))).take(25); 25 | ws2812.write(pixels).unwrap(); 26 | sleep(Duration::from_millis(1000)); 27 | 28 | let pixels = std::iter::repeat(RGBW8::new_alpha(0, 0, 6, White(0))).take(25); 29 | ws2812.write(pixels).unwrap(); 30 | sleep(Duration::from_millis(1000)); 31 | 32 | let pixels = std::iter::repeat(RGBW8::new_alpha(0, 0, 0, White(6))).take(25); 33 | ws2812.write(pixels).unwrap(); 34 | sleep(Duration::from_millis(1000)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | channel = "esp" 4 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) 2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 3 | 4 | # Workaround for https://github.com/espressif/esp-idf/issues/7631 5 | CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n 6 | CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n 7 | -------------------------------------------------------------------------------- /src/driver/color.rs: -------------------------------------------------------------------------------- 1 | //! device-dependant LED pixel colors 2 | 3 | /// LED pixel color trait 4 | pub trait LedPixelColor: 5 | Ord + PartialOrd + Eq + PartialEq + Clone + Sync + AsRef<[u8]> + AsMut<[u8]> 6 | { 7 | /// byte per pixel. e.g. 3 for typical RGB. 8 | const BPP: usize; 9 | /// Creates with RGB (Red-Green-Blue) value. 10 | fn new_with_rgb(r: u8, g: u8, b: u8) -> Self; 11 | /// Creates with RGBW (Red-Green-Blue, and White) value. 12 | fn new_with_rgbw(r: u8, g: u8, b: u8, w: u8) -> Self; 13 | /// Returns Red channel value 14 | fn r(&self) -> u8; 15 | /// Returns Green channel value 16 | fn g(&self) -> u8; 17 | /// Returns Blue channel value 18 | fn b(&self) -> u8; 19 | /// Returns White channel value 20 | fn w(&self) -> u8; 21 | 22 | /// Returns brightness-adjusted color. 23 | /// Each channel values of the returned shall be scaled down to `(brightness + 1) / 256`. 24 | #[inline] 25 | fn brightness(&self, brightness: u8) -> Self { 26 | Self::new_with_rgbw( 27 | ((self.r() as u16) * (brightness as u16 + 1) / 256) as u8, 28 | ((self.g() as u16) * (brightness as u16 + 1) / 256) as u8, 29 | ((self.b() as u16) * (brightness as u16 + 1) / 256) as u8, 30 | ((self.w() as u16) * (brightness as u16 + 1) / 256) as u8, 31 | ) 32 | } 33 | } 34 | 35 | /// LED pixel color struct made with an `N`-length `u8` array. 36 | /// 37 | /// * `N` - Byte per pixel. equals to [`BPP`](#associatedconstant.BPP). 38 | /// * `R_ORDER` - Index of the Red. Specify the value larger than `N - 1` if absent. 39 | /// * `G_ORDER` - Index of the Green. Specify the value larger than `N - 1` if absent. 40 | /// * `B_ORDER` - Index of the Blue. Specify the value larger than `N - 1` if absent. 41 | /// * `W_ORDER` - Index of the White. Specify the value larger than `N - 1` if absent. 42 | /// 43 | /// # Examples 44 | /// 45 | /// ``` 46 | /// use ws2812_esp32_rmt_driver::driver::color::{LedPixelColorImpl, LedPixelColor}; 47 | /// 48 | /// let color = LedPixelColorImpl::<3, 1, 0, 2, 255>::new_with_rgb(1, 2, 3); 49 | /// assert_eq!(color.as_ref(), [2, 1, 3]); 50 | /// assert_eq!((color.r(), color.g(), color.b(), color.w()), (1, 2, 3, 0)); 51 | /// ``` 52 | #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Hash)] 53 | #[repr(transparent)] 54 | pub struct LedPixelColorImpl< 55 | const N: usize, 56 | const R_ORDER: usize, 57 | const G_ORDER: usize, 58 | const B_ORDER: usize, 59 | const W_ORDER: usize, 60 | >(pub(crate) [u8; N]); 61 | 62 | impl< 63 | const N: usize, 64 | const R_ORDER: usize, 65 | const G_ORDER: usize, 66 | const B_ORDER: usize, 67 | const W_ORDER: usize, 68 | > LedPixelColor for LedPixelColorImpl 69 | { 70 | const BPP: usize = N; 71 | 72 | #[inline] 73 | fn new_with_rgb(r: u8, g: u8, b: u8) -> Self { 74 | Self::new_with_rgbw(r, g, b, 0) 75 | } 76 | 77 | #[inline] 78 | fn new_with_rgbw(r: u8, g: u8, b: u8, w: u8) -> Self { 79 | let mut array = [0; N]; 80 | if let Some(v) = array.get_mut(R_ORDER) { 81 | *v = r; 82 | } 83 | if let Some(v) = array.get_mut(G_ORDER) { 84 | *v = g; 85 | } 86 | if let Some(v) = array.get_mut(B_ORDER) { 87 | *v = b; 88 | } 89 | if let Some(v) = array.get_mut(W_ORDER) { 90 | *v = w; 91 | } 92 | Self(array) 93 | } 94 | 95 | #[inline] 96 | fn r(&self) -> u8 { 97 | self.0.get(R_ORDER).cloned().unwrap_or(0) 98 | } 99 | 100 | #[inline] 101 | fn g(&self) -> u8 { 102 | self.0.get(G_ORDER).cloned().unwrap_or(0) 103 | } 104 | 105 | #[inline] 106 | fn b(&self) -> u8 { 107 | self.0.get(B_ORDER).cloned().unwrap_or(0) 108 | } 109 | 110 | #[inline] 111 | fn w(&self) -> u8 { 112 | self.0.get(W_ORDER).cloned().unwrap_or(0) 113 | } 114 | } 115 | 116 | impl< 117 | const N: usize, 118 | const R_ORDER: usize, 119 | const G_ORDER: usize, 120 | const B_ORDER: usize, 121 | const W_ORDER: usize, 122 | > Default for LedPixelColorImpl 123 | { 124 | /// Returns the black color (All LED OFF) 125 | #[inline] 126 | fn default() -> Self { 127 | Self([0; N]) 128 | } 129 | } 130 | 131 | impl< 132 | const N: usize, 133 | const R_ORDER: usize, 134 | const G_ORDER: usize, 135 | const B_ORDER: usize, 136 | const W_ORDER: usize, 137 | > AsRef<[u8]> for LedPixelColorImpl 138 | { 139 | fn as_ref(&self) -> &[u8] { 140 | &self.0 141 | } 142 | } 143 | 144 | impl< 145 | const N: usize, 146 | const R_ORDER: usize, 147 | const G_ORDER: usize, 148 | const B_ORDER: usize, 149 | const W_ORDER: usize, 150 | > AsMut<[u8]> for LedPixelColorImpl 151 | { 152 | fn as_mut(&mut self) -> &mut [u8] { 153 | &mut self.0 154 | } 155 | } 156 | 157 | #[test] 158 | fn test_led_pixel_color_impl() { 159 | let color = LedPixelColorImpl::<3, 1, 0, 2, 255>::new_with_rgb(1, 2, 3); 160 | assert_eq!(color.0, [2, 1, 3]); 161 | assert_eq!(color.as_ref(), &color.0); 162 | assert_eq!((color.r(), color.g(), color.b(), color.w()), (1, 2, 3, 0)); 163 | 164 | let color = LedPixelColorImpl::<3, 1, 0, 2, 255>::new_with_rgbw(1, 2, 3, 4); 165 | assert_eq!(color.0, [2, 1, 3]); 166 | assert_eq!(color.as_ref(), &color.0); 167 | assert_eq!((color.r(), color.g(), color.b(), color.w()), (1, 2, 3, 0)); 168 | 169 | let color = LedPixelColorImpl::<4, 0, 1, 2, 3>::new_with_rgb(1, 2, 3); 170 | assert_eq!(color.0, [1, 2, 3, 0]); 171 | assert_eq!(color.as_ref(), &color.0); 172 | assert_eq!((color.r(), color.g(), color.b(), color.w()), (1, 2, 3, 0)); 173 | 174 | let color = LedPixelColorImpl::<4, 0, 1, 2, 3>::new_with_rgbw(1, 2, 3, 4); 175 | assert_eq!(color.0, [1, 2, 3, 4]); 176 | assert_eq!(color.as_ref(), &color.0); 177 | assert_eq!((color.r(), color.g(), color.b(), color.w()), (1, 2, 3, 4)); 178 | } 179 | 180 | #[test] 181 | fn test_led_pixel_color_brightness() { 182 | let color = LedPixelColorImpl::<4, 0, 1, 2, 3>::new_with_rgbw(255, 128, 64, 32).brightness(128); 183 | assert_eq!( 184 | (color.r(), color.g(), color.b(), color.w()), 185 | (128, 64, 32, 16) 186 | ); 187 | } 188 | 189 | /// 8-bit GRB LED pixel color (total 32-bit pixel), Typical RGB LED (WS2812B/SK6812) pixel color 190 | /// 191 | /// # Examples 192 | /// 193 | /// ``` 194 | /// use ws2812_esp32_rmt_driver::driver::color::{LedPixelColorGrb24, LedPixelColor}; 195 | /// 196 | /// let color = LedPixelColorGrb24::new_with_rgb(1, 2, 3); 197 | /// assert_eq!(color.as_ref(), [2, 1, 3]); 198 | /// ``` 199 | pub type LedPixelColorGrb24 = LedPixelColorImpl<3, 1, 0, 2, 255>; 200 | 201 | /// 8-bit RGBW LED pixel color (total 32-bit pixel) 202 | /// 203 | /// # Examples 204 | /// 205 | /// ``` 206 | /// use ws2812_esp32_rmt_driver::driver::color::{LedPixelColorRgbw32, LedPixelColor}; 207 | /// 208 | /// let color = LedPixelColorRgbw32::new_with_rgbw(1, 2, 3, 4); 209 | /// assert_eq!(color.as_ref(), [1, 2, 3, 4]); 210 | /// ``` 211 | pub type LedPixelColorRgbw32 = LedPixelColorImpl<4, 0, 1, 2, 3>; 212 | 213 | /// 8-bit GRBW LED pixel color (total 32-bit pixel) 214 | /// 215 | /// # Examples 216 | /// 217 | /// ``` 218 | /// use ws2812_esp32_rmt_driver::driver::color::{LedPixelColorGrbw32, LedPixelColor}; 219 | /// 220 | /// let color = LedPixelColorGrbw32::new_with_rgbw(1, 2, 3, 4); 221 | /// assert_eq!(color.as_ref(), [2, 1, 3, 4]); 222 | /// ``` 223 | pub type LedPixelColorGrbw32 = LedPixelColorImpl<4, 1, 0, 2, 3>; 224 | -------------------------------------------------------------------------------- /src/driver/esp32_rmt.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(target_vendor = "espressif"), allow(dead_code))] 2 | 3 | use core::convert::From; 4 | use core::error::Error; 5 | use core::fmt; 6 | use core::time::Duration; 7 | 8 | #[cfg(not(target_vendor = "espressif"))] 9 | use core::marker::PhantomData; 10 | 11 | #[cfg(not(target_vendor = "espressif"))] 12 | use crate::mock::esp_idf_hal; 13 | use esp_idf_hal::{ 14 | gpio::OutputPin, 15 | peripheral::Peripheral, 16 | rmt::{config::TransmitConfig, RmtChannel, TxRmtDriver}, 17 | }; 18 | #[cfg(target_vendor = "espressif")] 19 | use esp_idf_hal::{ 20 | rmt::{PinState, Pulse, Symbol}, 21 | units::Hertz, 22 | }; 23 | 24 | #[cfg(not(target_vendor = "espressif"))] 25 | use crate::mock::esp_idf_sys; 26 | use esp_idf_sys::EspError; 27 | 28 | /// T0H duration time (0 code, high voltage time) 29 | const WS2812_T0H_NS: Duration = Duration::from_nanos(400); 30 | /// T0L duration time (0 code, low voltage time) 31 | const WS2812_T0L_NS: Duration = Duration::from_nanos(850); 32 | /// T1H duration time (1 code, high voltage time) 33 | const WS2812_T1H_NS: Duration = Duration::from_nanos(800); 34 | /// T1L duration time (1 code, low voltage time) 35 | const WS2812_T1L_NS: Duration = Duration::from_nanos(450); 36 | 37 | /// Converter to a sequence of RMT items. 38 | #[repr(C)] 39 | #[cfg(target_vendor = "espressif")] 40 | struct Ws2812Esp32RmtItemEncoder { 41 | /// The RMT item that represents a 0 code. 42 | bit0: Symbol, 43 | /// The RMT item that represents a 1 code. 44 | bit1: Symbol, 45 | } 46 | 47 | #[cfg(target_vendor = "espressif")] 48 | impl Ws2812Esp32RmtItemEncoder { 49 | /// Creates a new encoder with the given clock frequency. 50 | /// 51 | /// # Arguments 52 | /// 53 | /// * `clock_hz` - The clock frequency. 54 | /// 55 | /// # Errors 56 | /// 57 | /// Returns an error if the clock frequency is invalid or if the RMT item encoder cannot be created. 58 | fn new(clock_hz: Hertz) -> Result { 59 | let (bit0, bit1) = ( 60 | Symbol::new( 61 | Pulse::new_with_duration(clock_hz, PinState::High, &WS2812_T0H_NS)?, 62 | Pulse::new_with_duration(clock_hz, PinState::Low, &WS2812_T0L_NS)?, 63 | ), 64 | Symbol::new( 65 | Pulse::new_with_duration(clock_hz, PinState::High, &WS2812_T1H_NS)?, 66 | Pulse::new_with_duration(clock_hz, PinState::Low, &WS2812_T1L_NS)?, 67 | ), 68 | ); 69 | 70 | Ok(Self { bit0, bit1 }) 71 | } 72 | 73 | /// Encodes a block of data as a sequence of RMT items. 74 | /// 75 | /// # Arguments 76 | /// 77 | /// * `src` - The block of data to encode. 78 | /// 79 | /// # Returns 80 | /// 81 | /// An iterator over the RMT items that represent the encoded data. 82 | #[inline] 83 | fn encode_iter<'a, 'b, T>(&'a self, src: T) -> impl Iterator + Send + 'a 84 | where 85 | 'b: 'a, 86 | T: Iterator + Send + 'b, 87 | { 88 | src.flat_map(move |v| { 89 | (0..(u8::BITS as usize)).map(move |i| { 90 | if v & (1 << (7 - i)) != 0 { 91 | self.bit1 92 | } else { 93 | self.bit0 94 | } 95 | }) 96 | }) 97 | } 98 | } 99 | 100 | /// WS2812 ESP32 RMT Driver error. 101 | #[derive(Debug)] 102 | #[repr(transparent)] 103 | pub struct Ws2812Esp32RmtDriverError { 104 | source: EspError, 105 | } 106 | 107 | #[cfg(not(feature = "std"))] 108 | impl Ws2812Esp32RmtDriverError { 109 | /// The `EspError` source of this error, if any. 110 | /// 111 | /// This is a workaround function until `core::error::Error` added to `esp_sys::EspError`. 112 | pub fn source(&self) -> Option<&EspError> { 113 | Some(&self.source) 114 | } 115 | } 116 | 117 | impl Error for Ws2812Esp32RmtDriverError { 118 | fn source(&self) -> Option<&(dyn Error + 'static)> { 119 | #[cfg(feature = "std")] 120 | { 121 | Some(&self.source) 122 | } 123 | #[cfg(not(feature = "std"))] 124 | { 125 | None 126 | } 127 | } 128 | } 129 | 130 | impl fmt::Display for Ws2812Esp32RmtDriverError { 131 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 132 | self.source.fmt(f) 133 | } 134 | } 135 | 136 | impl From for Ws2812Esp32RmtDriverError { 137 | fn from(source: EspError) -> Self { 138 | Self { source } 139 | } 140 | } 141 | 142 | /// WS2812 ESP32 RMT driver wrapper. 143 | /// 144 | /// # Examples 145 | /// 146 | /// ``` 147 | /// # #[cfg(not(target_vendor = "espressif"))] 148 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 149 | /// # 150 | /// use esp_idf_hal::peripherals::Peripherals; 151 | /// use ws2812_esp32_rmt_driver::driver::Ws2812Esp32RmtDriver; 152 | /// use ws2812_esp32_rmt_driver::driver::color::{LedPixelColor, LedPixelColorGrb24}; 153 | /// 154 | /// let peripherals = Peripherals::take().unwrap(); 155 | /// let led_pin = peripherals.pins.gpio27; 156 | /// let channel = peripherals.rmt.channel0; 157 | /// let mut driver = Ws2812Esp32RmtDriver::new(channel, led_pin).unwrap(); 158 | /// 159 | /// // Single LED with RED color. 160 | /// let red = LedPixelColorGrb24::new_with_rgb(30, 0, 0); 161 | /// let pixel: [u8; 3] = red.as_ref().try_into().unwrap(); 162 | /// assert_eq!(pixel, [0, 30, 0]); 163 | /// 164 | /// driver.write_blocking(pixel.clone().into_iter()).unwrap(); 165 | /// ``` 166 | pub struct Ws2812Esp32RmtDriver<'d> { 167 | /// TxRMT driver. 168 | tx: TxRmtDriver<'d>, 169 | /// `u8`-to-`rmt_item32_t` Encoder 170 | #[cfg(target_vendor = "espressif")] 171 | encoder: Ws2812Esp32RmtItemEncoder, 172 | 173 | /// Pixel binary array to be written 174 | /// 175 | /// If the target vendor does not equals to "espressif", pixel data is written into this 176 | /// instead of genuine encoder. 177 | #[cfg(not(target_vendor = "espressif"))] 178 | pub pixel_data: Option>, 179 | /// Dummy phantom to take care of lifetime for `pixel_data`. 180 | #[cfg(not(target_vendor = "espressif"))] 181 | phantom: PhantomData<&'d Option>>, 182 | } 183 | 184 | impl<'d> Ws2812Esp32RmtDriver<'d> { 185 | /// Creates a WS2812 ESP32 RMT driver wrapper. 186 | /// 187 | /// RMT driver of `channel` shall be initialized and installed for `pin`. 188 | /// `channel` shall be different between different `pin`. 189 | /// 190 | /// # Errors 191 | /// 192 | /// Returns an error if the RMT driver initialization failed. 193 | pub fn new( 194 | channel: impl Peripheral

+ 'd, 195 | pin: impl Peripheral

+ 'd, 196 | ) -> Result { 197 | #[cfg(target_vendor = "espressif")] 198 | { 199 | let config = TransmitConfig::new().clock_divider(1); 200 | let tx = TxRmtDriver::new(channel, pin, &config)?; 201 | 202 | Self::new_with_rmt_driver(tx) 203 | } 204 | #[cfg(not(target_vendor = "espressif"))] // Mock implement 205 | { 206 | let config = TransmitConfig::new(); 207 | let tx = TxRmtDriver::new(channel, pin, &config)?; 208 | Ok(Self { 209 | tx, 210 | pixel_data: None, 211 | phantom: Default::default(), 212 | }) 213 | } 214 | } 215 | 216 | /// Creates a WS2812 ESP32 RMT driver wrapper with `TxRmtDriver`. 217 | /// 218 | /// The clock divider must be set to 1 for the `driver` configuration. 219 | /// 220 | /// ``` 221 | /// # #[cfg(not(target_vendor = "espressif"))] 222 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 223 | /// # 224 | /// # use esp_idf_hal::peripherals::Peripherals; 225 | /// # use esp_idf_hal::rmt::config::TransmitConfig; 226 | /// # use esp_idf_hal::rmt::TxRmtDriver; 227 | /// # 228 | /// # let peripherals = Peripherals::take().unwrap(); 229 | /// # let led_pin = peripherals.pins.gpio27; 230 | /// # let channel = peripherals.rmt.channel0; 231 | /// # 232 | /// let driver_config = TransmitConfig::new() 233 | /// .clock_divider(1); // Required parameter. 234 | /// let driver = TxRmtDriver::new(channel, led_pin, &driver_config).unwrap(); 235 | /// ``` 236 | /// 237 | /// # Errors 238 | /// 239 | /// Returns an error if the RMT driver initialization failed. 240 | pub fn new_with_rmt_driver(tx: TxRmtDriver<'d>) -> Result { 241 | #[cfg(target_vendor = "espressif")] 242 | { 243 | let clock_hz = tx.counter_clock()?; 244 | let encoder = Ws2812Esp32RmtItemEncoder::new(clock_hz)?; 245 | 246 | Ok(Self { tx, encoder }) 247 | } 248 | #[cfg(not(target_vendor = "espressif"))] // Mock implement 249 | { 250 | Ok(Self { 251 | tx, 252 | pixel_data: None, 253 | phantom: Default::default(), 254 | }) 255 | } 256 | } 257 | 258 | /// Writes pixel data from a pixel-byte sequence to the IO pin. 259 | /// 260 | /// Byte count per LED pixel and channel order is not handled by this method. 261 | /// The pixel data sequence has to be correctly laid out depending on the LED strip model. 262 | /// 263 | /// # Errors 264 | /// 265 | /// Returns an error if an RMT driver error occurred. 266 | /// 267 | /// # Warning 268 | /// 269 | /// Iteration of `pixel_sequence` happens inside an interrupt handler so beware of side-effects 270 | /// that don't work in interrupt handlers. 271 | /// See [esp_idf_hal::rmt::TxRmtDriver#start_iter_blocking()] for details. 272 | pub fn write_blocking<'a, 'b, T>( 273 | &'a mut self, 274 | pixel_sequence: T, 275 | ) -> Result<(), Ws2812Esp32RmtDriverError> 276 | where 277 | 'b: 'a, 278 | T: Iterator + Send + 'b, 279 | { 280 | #[cfg(target_vendor = "espressif")] 281 | { 282 | let signal = self.encoder.encode_iter(pixel_sequence); 283 | self.tx.start_iter_blocking(signal)?; 284 | } 285 | #[cfg(not(target_vendor = "espressif"))] 286 | { 287 | self.pixel_data = Some(pixel_sequence.collect()); 288 | } 289 | Ok(()) 290 | } 291 | 292 | /// Writes pixel data from a pixel-byte sequence to the IO pin. 293 | /// 294 | /// Byte count per LED pixel and channel order is not handled by this method. 295 | /// The pixel data sequence has to be correctly laid out depending on the LED strip model. 296 | /// 297 | /// Note that this requires `pixel_sequence` to be [`Box`]ed for an allocation free version see [`Self::write_blocking`]. 298 | /// 299 | /// # Errors 300 | /// 301 | /// Returns an error if an RMT driver error occurred. 302 | /// 303 | /// # Warning 304 | /// 305 | /// Iteration of `pixel_sequence` happens inside an interrupt handler so beware of side-effects 306 | /// that don't work in interrupt handlers. 307 | /// See [esp_idf_hal::rmt::TxRmtDriver#start_iter()] for details. 308 | #[cfg(feature = "alloc")] 309 | pub fn write<'b, T>( 310 | &'static mut self, 311 | pixel_sequence: T, 312 | ) -> Result<(), Ws2812Esp32RmtDriverError> 313 | where 314 | T: Iterator + Send + 'static, 315 | { 316 | #[cfg(target_vendor = "espressif")] 317 | { 318 | let signal = self.encoder.encode_iter(pixel_sequence); 319 | self.tx.start_iter(signal)?; 320 | } 321 | #[cfg(not(target_vendor = "espressif"))] 322 | { 323 | self.pixel_data = Some(pixel_sequence.collect()); 324 | } 325 | Ok(()) 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/driver/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low-level LED pixel driver API. 2 | 3 | pub mod color; 4 | mod esp32_rmt; 5 | 6 | pub use esp32_rmt::Ws2812Esp32RmtDriver; 7 | pub use esp32_rmt::Ws2812Esp32RmtDriverError; 8 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 3 | #![doc = include_str!("../README.md")] 4 | 5 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 6 | extern crate alloc; 7 | 8 | pub mod driver; 9 | 10 | pub use driver::{Ws2812Esp32RmtDriver, Ws2812Esp32RmtDriverError}; 11 | 12 | #[cfg(feature = "embedded-graphics-core")] 13 | pub mod lib_embedded_graphics; 14 | 15 | #[cfg(feature = "smart-leds-trait")] 16 | pub mod lib_smart_leds; 17 | 18 | #[cfg(not(target_vendor = "espressif"))] 19 | pub mod mock; 20 | 21 | #[cfg(feature = "smart-leds-trait")] 22 | pub use lib_smart_leds::{LedPixelEsp32Rmt, Ws2812Esp32Rmt, RGBW8}; 23 | #[cfg(feature = "smart-leds-trait")] 24 | pub use smart_leds_trait::RGB8; 25 | -------------------------------------------------------------------------------- /src/lib_embedded_graphics.rs: -------------------------------------------------------------------------------- 1 | //! embedded-graphics draw target API. 2 | 3 | use crate::driver::color::{LedPixelColor, LedPixelColorGrb24, LedPixelColorImpl}; 4 | use crate::driver::{Ws2812Esp32RmtDriver, Ws2812Esp32RmtDriverError}; 5 | use core::marker::PhantomData; 6 | use core::ops::DerefMut; 7 | use embedded_graphics_core::draw_target::DrawTarget; 8 | use embedded_graphics_core::geometry::{OriginDimensions, Point, Size}; 9 | use embedded_graphics_core::pixelcolor::{Rgb888, RgbColor}; 10 | use embedded_graphics_core::Pixel; 11 | use esp_idf_hal::rmt::TxRmtDriver; 12 | 13 | #[cfg(not(target_vendor = "espressif"))] 14 | use crate::mock::esp_idf_hal; 15 | use esp_idf_hal::{gpio::OutputPin, peripheral::Peripheral, rmt::RmtChannel}; 16 | 17 | /// LED pixel shape 18 | pub trait LedPixelShape { 19 | /// Returns the number of pixels 20 | fn pixel_len() -> usize { 21 | let size = Self::size(); 22 | (size.width * size.height) as usize 23 | } 24 | /// Physical size of the LED pixel equipment. 25 | fn size() -> Size; 26 | /// Convert from `point` to the index. 27 | /// Returns `None` if it is out of the bounds. 28 | fn pixel_index(point: Point) -> Option; 29 | } 30 | 31 | /// LED pixel shape of `W`x`H` matrix 32 | pub struct LedPixelMatrix {} 33 | 34 | impl LedPixelMatrix { 35 | /// Physical size of the LED pixel matrix. 36 | pub const SIZE: Size = Size::new(W as u32, H as u32); 37 | /// The number of pixels. 38 | pub const PIXEL_LEN: usize = W * H; 39 | } 40 | 41 | impl LedPixelShape for LedPixelMatrix { 42 | #[inline] 43 | fn size() -> Size { 44 | Self::SIZE 45 | } 46 | #[inline] 47 | fn pixel_len() -> usize { 48 | Self::PIXEL_LEN 49 | } 50 | 51 | fn pixel_index(point: Point) -> Option { 52 | if (0..W as i32).contains(&point.x) && (0..H as i32).contains(&point.y) { 53 | Some((point.x + point.y * W as i32) as usize) 54 | } else { 55 | None 56 | } 57 | } 58 | } 59 | 60 | /// Default data storage type for `LedPixelDrawTarget`. 61 | #[cfg(feature = "std")] 62 | type LedPixelDrawTargetData = Vec; 63 | 64 | /// Default data storage type for `LedPixelDrawTarget`. 65 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 66 | type LedPixelDrawTargetData = alloc::vec::Vec; 67 | 68 | /// Default data storage type for `LedPixelDrawTarget`. 69 | /// In case of heapless, allocate 256-byte capacity vector. 70 | #[cfg(all(not(feature = "std"), not(feature = "alloc")))] 71 | type LedPixelDrawTargetData = heapless::Vec; 72 | 73 | /// Target for embedded-graphics drawing operations of the LED pixels. 74 | /// 75 | /// This is a generalization for the future extension. 76 | /// Use [`Ws2812DrawTarget`] for typical RGB LED (WS2812B/SK6812) consisting of 8-bit GRB (total 24-bit pixel). 77 | /// 78 | /// * `CDraw` - color type for embedded-graphics drawing operations 79 | /// * `CDev` - the LED pixel color type (device dependant). It shall be convertible from `CDraw`. 80 | /// * `S` - the LED pixel shape 81 | /// * `Data` - (optional) data storage type. It shall be `Vec`-like struct. 82 | /// 83 | /// [`flush()`] operation shall be required to write changes from a framebuffer to the display. 84 | /// 85 | /// For non-`alloc` no_std environment, `Data` should be explicitly set to some `Vec`-like struct: 86 | /// e.g., `heapless::Vec` where `PIXEL_LEN` equals to `S::size() * CDev::BPP`. 87 | /// 88 | /// [`flush()`]: #method.flush 89 | pub struct LedPixelDrawTarget<'d, CDraw, CDev, S, Data = LedPixelDrawTargetData> 90 | where 91 | CDraw: RgbColor, 92 | CDev: LedPixelColor + From, 93 | S: LedPixelShape, 94 | Data: DerefMut + FromIterator + IntoIterator, 95 | { 96 | driver: Ws2812Esp32RmtDriver<'d>, 97 | data: Data, 98 | brightness: u8, 99 | changed: bool, 100 | _phantom: PhantomData<(CDraw, CDev, S, Data)>, 101 | } 102 | 103 | impl<'d, CDraw, CDev, S, Data> LedPixelDrawTarget<'d, CDraw, CDev, S, Data> 104 | where 105 | CDraw: RgbColor, 106 | CDev: LedPixelColor + From, 107 | S: LedPixelShape, 108 | Data: DerefMut + FromIterator + IntoIterator, 109 | { 110 | /// Create a new draw target. 111 | /// 112 | /// `channel` shall be different between different `pin`. 113 | pub fn new( 114 | channel: impl Peripheral

+ 'd, 115 | pin: impl Peripheral

+ 'd, 116 | ) -> Result { 117 | let driver = Ws2812Esp32RmtDriver::<'d>::new(channel, pin)?; 118 | let data = core::iter::repeat(0) 119 | .take(S::pixel_len() * CDev::BPP) 120 | .collect::(); 121 | Ok(Self { 122 | driver, 123 | data, 124 | brightness: u8::MAX, 125 | changed: true, 126 | _phantom: Default::default(), 127 | }) 128 | } 129 | 130 | /// Create a new draw target with `TxRmtDriver`. 131 | /// 132 | /// The clock divider must be set to 1 for the `driver` configuration. 133 | /// 134 | /// ``` 135 | /// # #[cfg(not(target_vendor = "espressif"))] 136 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 137 | /// # 138 | /// # use esp_idf_hal::peripherals::Peripherals; 139 | /// # use esp_idf_hal::rmt::config::TransmitConfig; 140 | /// # use esp_idf_hal::rmt::TxRmtDriver; 141 | /// # 142 | /// # let peripherals = Peripherals::take().unwrap(); 143 | /// # let led_pin = peripherals.pins.gpio27; 144 | /// # let channel = peripherals.rmt.channel0; 145 | /// # 146 | /// let driver_config = TransmitConfig::new() 147 | /// .clock_divider(1); // Required parameter. 148 | /// let driver = TxRmtDriver::new(channel, led_pin, &driver_config).unwrap(); 149 | /// ``` 150 | pub fn new_with_rmt_driver(tx: TxRmtDriver<'d>) -> Result { 151 | let driver = Ws2812Esp32RmtDriver::<'d>::new_with_rmt_driver(tx)?; 152 | let data = core::iter::repeat(0) 153 | .take(S::pixel_len() * CDev::BPP) 154 | .collect::(); 155 | Ok(Self { 156 | driver, 157 | data, 158 | brightness: u8::MAX, 159 | changed: true, 160 | _phantom: Default::default(), 161 | }) 162 | } 163 | 164 | /// Set maximum brightness. 165 | /// Each channel values of the returned shall be scaled down to `(brightness + 1) / 256`. 166 | #[inline] 167 | pub fn set_brightness(&mut self, brightness: u8) { 168 | self.brightness = brightness; 169 | self.changed = true; 170 | } 171 | 172 | /// Returns maximum brightness. 173 | #[inline] 174 | pub fn brightness(&self) -> u8 { 175 | self.brightness 176 | } 177 | 178 | /// Clear with black. 179 | /// Same operation as `clear(black_color)`. 180 | pub fn clear_with_black(&mut self) -> Result<(), Ws2812Esp32RmtDriverError> { 181 | self.data.fill(0); 182 | self.changed = true; 183 | Ok(()) 184 | } 185 | 186 | /// Write changes from a framebuffer to the LED pixels 187 | pub fn flush(&mut self) -> Result<(), Ws2812Esp32RmtDriverError> { 188 | if self.changed { 189 | self.driver.write_blocking(self.data.iter().copied())?; 190 | self.changed = false; 191 | } 192 | Ok(()) 193 | } 194 | } 195 | 196 | impl<'d, CDraw, CDev, S, Data> OriginDimensions for LedPixelDrawTarget<'d, CDraw, CDev, S, Data> 197 | where 198 | CDraw: RgbColor, 199 | CDev: LedPixelColor + From, 200 | S: LedPixelShape, 201 | Data: DerefMut + FromIterator + IntoIterator, 202 | { 203 | #[inline] 204 | fn size(&self) -> Size { 205 | S::size() 206 | } 207 | } 208 | 209 | impl<'d, CDraw, CDev, S, Data> DrawTarget for LedPixelDrawTarget<'d, CDraw, CDev, S, Data> 210 | where 211 | CDraw: RgbColor, 212 | CDev: LedPixelColor + From, 213 | S: LedPixelShape, 214 | Data: DerefMut + FromIterator + IntoIterator, 215 | { 216 | type Color = CDraw; 217 | type Error = Ws2812Esp32RmtDriverError; 218 | 219 | fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> 220 | where 221 | I: IntoIterator>, 222 | { 223 | for Pixel(point, color) in pixels { 224 | if let Some(pixel_index) = S::pixel_index(point) { 225 | let index = pixel_index * CDev::BPP; 226 | let color_device = CDev::from(color).brightness(self.brightness); 227 | for (offset, v) in color_device.as_ref().iter().enumerate() { 228 | self.data[index + offset] = *v; 229 | } 230 | self.changed = true; 231 | } 232 | } 233 | Ok(()) 234 | } 235 | 236 | fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { 237 | let c = CDev::from(color).brightness(self.brightness); 238 | for (index, v) in self.data.iter_mut().enumerate() { 239 | *v = c.as_ref()[index % CDev::BPP]; 240 | } 241 | self.changed = true; 242 | Ok(()) 243 | } 244 | } 245 | 246 | impl< 247 | const N: usize, 248 | const R_ORDER: usize, 249 | const G_ORDER: usize, 250 | const B_ORDER: usize, 251 | const W_ORDER: usize, 252 | > From for LedPixelColorImpl 253 | { 254 | fn from(x: Rgb888) -> Self { 255 | Self::new_with_rgb(x.r(), x.g(), x.b()) 256 | } 257 | } 258 | 259 | /// LED pixel shape of `L`-led strip 260 | pub type LedPixelStrip = LedPixelMatrix; 261 | 262 | /// 8-bit GRB (total 24-bit pixel) LED draw target, Typical RGB LED (WS2812B/SK6812) draw target 263 | /// 264 | /// * `S` - the LED pixel shape 265 | /// * `Data` - (optional) data storage type. It shall be `Vec`-like struct. 266 | /// 267 | /// [`flush()`] operation shall be required to write changes from a framebuffer to the display. 268 | /// 269 | /// For non-`alloc` no_std environment, `Data` should be explicitly set to some `Vec`-like struct: 270 | /// e.g., `heapless::Vec` where `PIXEL_LEN` equals to `S::size() * LedPixelColorGrb24::BPP`. 271 | /// 272 | /// [`flush()`]: #method.flush 273 | /// 274 | /// # Examples 275 | /// 276 | /// ``` 277 | /// # #[cfg(not(target_vendor = "espressif"))] 278 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 279 | /// # 280 | /// use embedded_graphics::pixelcolor::Rgb888; 281 | /// use embedded_graphics::prelude::*; 282 | /// use embedded_graphics::primitives::{Circle, PrimitiveStyle}; 283 | /// use esp_idf_hal::peripherals::Peripherals; 284 | /// use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelMatrix, Ws2812DrawTarget}; 285 | /// 286 | /// let peripherals = Peripherals::take().unwrap(); 287 | /// let led_pin = peripherals.pins.gpio27; 288 | /// let channel = peripherals.rmt.channel0; 289 | /// let mut draw = Ws2812DrawTarget::>::new(channel, led_pin).unwrap(); 290 | /// draw.set_brightness(40); 291 | /// draw.clear_with_black().unwrap(); 292 | /// let mut translated_draw = draw.translated(Point::new(0, 0)); 293 | /// Circle::new(Point::new(0, 0), 5) 294 | /// .into_styled(PrimitiveStyle::with_fill(Rgb888::RED)) 295 | /// .draw(&mut translated_draw) 296 | /// .unwrap(); 297 | /// draw.flush().unwrap(); 298 | /// ``` 299 | pub type Ws2812DrawTarget<'d, S, Data = LedPixelDrawTargetData> = 300 | LedPixelDrawTarget<'d, Rgb888, LedPixelColorGrb24, S, Data>; 301 | 302 | #[cfg(test)] 303 | mod test { 304 | use super::*; 305 | use crate::mock::esp_idf_hal::peripherals::Peripherals; 306 | 307 | #[test] 308 | fn test_led_pixel_matrix() { 309 | assert_eq!(LedPixelMatrix::<10, 5>::PIXEL_LEN, 50); 310 | assert_eq!(LedPixelMatrix::<10, 5>::SIZE, Size::new(10, 5)); 311 | assert_eq!(LedPixelMatrix::<10, 5>::pixel_len(), 50); 312 | assert_eq!(LedPixelMatrix::<10, 5>::size(), Size::new(10, 5)); 313 | assert_eq!( 314 | LedPixelMatrix::<10, 5>::pixel_index(Point::new(0, 0)), 315 | Some(0) 316 | ); 317 | assert_eq!( 318 | LedPixelMatrix::<10, 5>::pixel_index(Point::new(9, 4)), 319 | Some(49) 320 | ); 321 | assert_eq!( 322 | LedPixelMatrix::<10, 5>::pixel_index(Point::new(-1, 0)), 323 | None 324 | ); 325 | assert_eq!( 326 | LedPixelMatrix::<10, 5>::pixel_index(Point::new(0, -1)), 327 | None 328 | ); 329 | assert_eq!( 330 | LedPixelMatrix::<10, 5>::pixel_index(Point::new(10, 4)), 331 | None 332 | ); 333 | assert_eq!(LedPixelMatrix::<10, 5>::pixel_index(Point::new(9, 5)), None); 334 | } 335 | 336 | #[test] 337 | fn test_led_pixel_strip() { 338 | assert_eq!(LedPixelStrip::<10>::PIXEL_LEN, 10); 339 | assert_eq!(LedPixelStrip::<10>::SIZE, Size::new(10, 1)); 340 | assert_eq!(LedPixelStrip::<10>::pixel_len(), 10); 341 | assert_eq!(LedPixelStrip::<10>::size(), Size::new(10, 1)); 342 | assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(0, 0)), Some(0)); 343 | assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(9, 0)), Some(9)); 344 | assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(-1, 0)), None); 345 | assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(0, -1)), None); 346 | assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(10, 0)), None); 347 | assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(9, 1)), None); 348 | } 349 | 350 | #[test] 351 | fn test_ws2812draw_target_new() { 352 | let peripherals = Peripherals::take().unwrap(); 353 | let led_pin = peripherals.pins.gpio0; 354 | let channel = peripherals.rmt.channel0; 355 | 356 | let draw = Ws2812DrawTarget::>::new(channel, led_pin).unwrap(); 357 | assert_eq!(draw.changed, true); 358 | assert_eq!( 359 | draw.data, 360 | core::iter::repeat(0).take(150).collect::>() 361 | ); 362 | } 363 | 364 | #[test] 365 | fn test_ws2812draw_target_new_with_custom_data_struct() { 366 | const VEC_CAPACITY: usize = LedPixelMatrix::<10, 5>::PIXEL_LEN * LedPixelColorGrb24::BPP; 367 | 368 | let peripherals = Peripherals::take().unwrap(); 369 | let led_pin = peripherals.pins.gpio0; 370 | let channel = peripherals.rmt.channel0; 371 | 372 | let draw = Ws2812DrawTarget::, heapless::Vec>::new( 373 | channel, led_pin, 374 | ) 375 | .unwrap(); 376 | assert_eq!(draw.changed, true); 377 | assert_eq!( 378 | draw.data, 379 | core::iter::repeat(0) 380 | .take(150) 381 | .collect::>() 382 | ); 383 | } 384 | 385 | #[test] 386 | fn test_ws2812draw_target_draw() { 387 | let peripherals = Peripherals::take().unwrap(); 388 | let led_pin = peripherals.pins.gpio1; 389 | let channel = peripherals.rmt.channel1; 390 | 391 | let mut draw = Ws2812DrawTarget::>::new(channel, led_pin).unwrap(); 392 | 393 | draw.draw_iter( 394 | [ 395 | Pixel(Point::new(0, 0), Rgb888::new(0x01, 0x02, 0x03)), 396 | Pixel(Point::new(9, 4), Rgb888::new(0x04, 0x05, 0x06)), 397 | Pixel(Point::new(10, 5), Rgb888::new(0xFF, 0xFF, 0xFF)), // out of matrix shape 398 | ] 399 | .iter() 400 | .cloned(), 401 | ) 402 | .unwrap(); 403 | assert_eq!(draw.changed, true); 404 | assert_eq!(draw.data[0..3], [0x02, 0x01, 0x03]); 405 | assert_eq!(draw.data[3..147], [0x00; 144]); 406 | assert_eq!(draw.data[147..150], [0x05, 0x04, 0x06]); 407 | draw.changed = false; 408 | 409 | draw.clear(Rgb888::new(0x07, 0x08, 0x0A)).unwrap(); 410 | assert_eq!(draw.changed, true); 411 | assert_eq!( 412 | draw.data, 413 | core::iter::repeat([0x08, 0x07, 0x0A]) 414 | .take(50) 415 | .flatten() 416 | .collect::>() 417 | ); 418 | draw.changed = false; 419 | 420 | draw.clear_with_black().unwrap(); 421 | assert_eq!(draw.changed, true); 422 | assert_eq!(draw.data, [0x00; 150]); 423 | draw.changed = false; 424 | } 425 | 426 | #[test] 427 | fn test_ws2812draw_target_flush() { 428 | let peripherals = Peripherals::take().unwrap(); 429 | let led_pin = peripherals.pins.gpio2; 430 | let channel = peripherals.rmt.channel2; 431 | 432 | let mut draw = Ws2812DrawTarget::>::new(channel, led_pin).unwrap(); 433 | 434 | draw.changed = true; 435 | draw.data.fill(0x01); 436 | draw.driver.pixel_data = None; 437 | draw.flush().unwrap(); 438 | assert_eq!(draw.driver.pixel_data.unwrap(), draw.data); 439 | assert_eq!(draw.changed, false); 440 | 441 | draw.driver.pixel_data = None; 442 | draw.flush().unwrap(); 443 | assert_eq!(draw.driver.pixel_data, None); 444 | assert_eq!(draw.changed, false); 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/lib_smart_leds.rs: -------------------------------------------------------------------------------- 1 | //! smart-leds driver wrapper API. 2 | 3 | use crate::driver::color::{LedPixelColor, LedPixelColorGrb24, LedPixelColorImpl}; 4 | use crate::driver::{Ws2812Esp32RmtDriver, Ws2812Esp32RmtDriverError}; 5 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 6 | use alloc::vec::Vec; 7 | use core::marker::PhantomData; 8 | use esp_idf_hal::rmt::TxRmtDriver; 9 | #[cfg(feature = "alloc")] 10 | use smart_leds_trait::SmartLedsWrite; 11 | use smart_leds_trait::{RGB8, RGBW}; 12 | 13 | #[cfg(not(target_vendor = "espressif"))] 14 | use crate::mock::esp_idf_hal; 15 | use esp_idf_hal::{gpio::OutputPin, peripheral::Peripheral, rmt::RmtChannel}; 16 | 17 | /// 8-bit RGBW (RGB + white) 18 | pub type RGBW8 = RGBW; 19 | 20 | impl< 21 | const N: usize, 22 | const R_ORDER: usize, 23 | const G_ORDER: usize, 24 | const B_ORDER: usize, 25 | const W_ORDER: usize, 26 | > From for LedPixelColorImpl 27 | { 28 | fn from(x: RGB8) -> Self { 29 | Self::new_with_rgb(x.r, x.g, x.b) 30 | } 31 | } 32 | 33 | impl< 34 | const N: usize, 35 | const R_ORDER: usize, 36 | const G_ORDER: usize, 37 | const B_ORDER: usize, 38 | const W_ORDER: usize, 39 | > From for LedPixelColorImpl 40 | { 41 | fn from(x: RGBW8) -> Self { 42 | Self::new_with_rgbw(x.r, x.g, x.b, x.a.0) 43 | } 44 | } 45 | 46 | /// ws2812-like smart led driver wrapper providing smart-leds API 47 | /// 48 | /// This is a generalization to handle variants such as SK6812-RGBW 4-color LED. 49 | /// Use [`Ws2812Esp32Rmt`] for typical RGB LED (WS2812B/SK6812) consisting of 8-bit GRB (total 24-bit pixel). 50 | /// 51 | /// # Examples 52 | /// 53 | /// ``` 54 | /// # #[cfg(not(target_vendor = "espressif"))] 55 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 56 | /// # 57 | /// use esp_idf_hal::peripherals::Peripherals; 58 | /// use smart_leds::{SmartLedsWrite, White}; 59 | /// use ws2812_esp32_rmt_driver::{LedPixelEsp32Rmt, RGBW8}; 60 | /// use ws2812_esp32_rmt_driver::driver::color::LedPixelColorGrbw32; 61 | /// 62 | /// let peripherals = Peripherals::take().unwrap(); 63 | /// let led_pin = peripherals.pins.gpio26; 64 | /// let channel = peripherals.rmt.channel0; 65 | /// let mut ws2812 = LedPixelEsp32Rmt::::new(channel, led_pin).unwrap(); 66 | /// 67 | /// let pixels = std::iter::repeat(RGBW8 {r: 0, g: 0, b: 0, a: White(30)}).take(25); 68 | /// ws2812.write(pixels).unwrap(); 69 | /// ``` 70 | pub struct LedPixelEsp32Rmt<'d, CSmart, CDev> 71 | where 72 | CDev: LedPixelColor + From, 73 | { 74 | driver: Ws2812Esp32RmtDriver<'d>, 75 | phantom: PhantomData<(CSmart, CDev)>, 76 | } 77 | 78 | impl<'d, CSmart, CDev> LedPixelEsp32Rmt<'d, CSmart, CDev> 79 | where 80 | CDev: LedPixelColor + From, 81 | { 82 | /// Create a new driver wrapper. 83 | /// 84 | /// `channel` shall be different between different `pin`. 85 | pub fn new( 86 | channel: impl Peripheral

+ 'd, 87 | pin: impl Peripheral

+ 'd, 88 | ) -> Result { 89 | let driver = Ws2812Esp32RmtDriver::<'d>::new(channel, pin)?; 90 | Ok(Self { 91 | driver, 92 | phantom: Default::default(), 93 | }) 94 | } 95 | 96 | /// Create a new driver wrapper with `TxRmtDriver`. 97 | /// 98 | /// The clock divider must be set to 1 for the `driver` configuration. 99 | /// 100 | /// ``` 101 | /// # #[cfg(not(target_vendor = "espressif"))] 102 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 103 | /// # 104 | /// # use esp_idf_hal::peripherals::Peripherals; 105 | /// # use esp_idf_hal::rmt::config::TransmitConfig; 106 | /// # use esp_idf_hal::rmt::TxRmtDriver; 107 | /// # 108 | /// # let peripherals = Peripherals::take().unwrap(); 109 | /// # let led_pin = peripherals.pins.gpio27; 110 | /// # let channel = peripherals.rmt.channel0; 111 | /// # 112 | /// let driver_config = TransmitConfig::new() 113 | /// .clock_divider(1); // Required parameter. 114 | /// let driver = TxRmtDriver::new(channel, led_pin, &driver_config).unwrap(); 115 | /// ``` 116 | pub fn new_with_rmt_driver(tx: TxRmtDriver<'d>) -> Result { 117 | let driver = Ws2812Esp32RmtDriver::<'d>::new_with_rmt_driver(tx)?; 118 | Ok(Self { 119 | driver, 120 | phantom: Default::default(), 121 | }) 122 | } 123 | } 124 | 125 | impl< 126 | 'd, 127 | CSmart, 128 | const N: usize, 129 | const R_ORDER: usize, 130 | const G_ORDER: usize, 131 | const B_ORDER: usize, 132 | const W_ORDER: usize, 133 | > LedPixelEsp32Rmt<'d, CSmart, LedPixelColorImpl> 134 | where 135 | LedPixelColorImpl: From, 136 | { 137 | /// Writes pixel data from a color sequence to the driver without data copy 138 | /// 139 | /// # Errors 140 | /// 141 | /// Returns an error if an RMT driver error occurred. 142 | pub fn write_nocopy(&mut self, iterator: T) -> Result<(), Ws2812Esp32RmtDriverError> 143 | where 144 | T: IntoIterator, 145 | I: Into, 146 | ::IntoIter: Send, 147 | { 148 | self.driver 149 | .write_blocking(iterator.into_iter().flat_map(|color| { 150 | let c = 151 | LedPixelColorImpl::::from(color.into()); 152 | c.0 153 | }))?; 154 | Ok(()) 155 | } 156 | } 157 | 158 | #[cfg(feature = "alloc")] 159 | impl<'d, CSmart, CDev> SmartLedsWrite for LedPixelEsp32Rmt<'d, CSmart, CDev> 160 | where 161 | CDev: LedPixelColor + From, 162 | { 163 | type Error = Ws2812Esp32RmtDriverError; 164 | type Color = CSmart; 165 | 166 | /// Writes pixel data from a color sequence to the driver 167 | /// 168 | /// # Errors 169 | /// 170 | /// Returns an error if an RMT driver error occurred. 171 | fn write(&mut self, iterator: T) -> Result<(), Self::Error> 172 | where 173 | T: IntoIterator, 174 | I: Into, 175 | { 176 | let pixel_data = iterator.into_iter().fold(Vec::new(), |mut vec, color| { 177 | vec.extend_from_slice(CDev::from(color.into()).as_ref()); 178 | vec 179 | }); 180 | self.driver.write_blocking(pixel_data.into_iter())?; 181 | Ok(()) 182 | } 183 | } 184 | 185 | /// 8-bit GRB (total 24-bit pixel) LED driver wrapper providing smart-leds API, 186 | /// Typical RGB LED (WS2812B/SK6812) driver wrapper providing smart-leds API 187 | /// 188 | /// # Examples 189 | /// 190 | /// ``` 191 | /// # #[cfg(not(target_vendor = "espressif"))] 192 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 193 | /// # 194 | /// use esp_idf_hal::peripherals::Peripherals; 195 | /// use smart_leds::{RGB8, SmartLedsWrite}; 196 | /// use ws2812_esp32_rmt_driver::Ws2812Esp32Rmt; 197 | /// 198 | /// let peripherals = Peripherals::take().unwrap(); 199 | /// let led_pin = peripherals.pins.gpio27; 200 | /// let channel = peripherals.rmt.channel0; 201 | /// let mut ws2812 = Ws2812Esp32Rmt::new(channel, led_pin).unwrap(); 202 | /// 203 | /// let pixels = std::iter::repeat(RGB8::new(30, 0, 0)).take(25); 204 | /// ws2812.write(pixels).unwrap(); 205 | /// ``` 206 | /// 207 | /// The LED colors may flicker randomly when using Wi-Fi or Bluetooth with Ws2812 LEDs. 208 | /// This issue can be resolved by: 209 | /// 210 | /// - Separating Wi-Fi/Bluetooth processing from LED control onto different cores. 211 | /// - Use multiple memory blocks (memory symbols) in the RMT driver. 212 | /// 213 | /// To do the second option, prepare `TxRmtDriver` yourself and 214 | /// initialize with [`Self::new_with_rmt_driver`] as shown below. 215 | /// 216 | /// ``` 217 | /// # #[cfg(not(target_vendor = "espressif"))] 218 | /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal; 219 | /// # 220 | /// # use esp_idf_hal::peripherals::Peripherals; 221 | /// # use esp_idf_hal::rmt::config::TransmitConfig; 222 | /// # use esp_idf_hal::rmt::TxRmtDriver; 223 | /// # use smart_leds::{RGB8, SmartLedsWrite}; 224 | /// # use ws2812_esp32_rmt_driver::Ws2812Esp32Rmt; 225 | /// # 226 | /// # let peripherals = Peripherals::take().unwrap(); 227 | /// # let led_pin = peripherals.pins.gpio27; 228 | /// # let channel = peripherals.rmt.channel0; 229 | /// # 230 | /// let driver_config = TransmitConfig::new() 231 | /// .clock_divider(1) // Required parameter. 232 | /// .mem_block_num(2); // Increase the number depending on your code. 233 | /// let driver = TxRmtDriver::new(channel, led_pin, &driver_config).unwrap(); 234 | /// 235 | /// let mut ws2812 = Ws2812Esp32Rmt::new_with_rmt_driver(driver).unwrap(); 236 | /// # 237 | /// # let pixels = std::iter::repeat(RGB8::new(30, 0, 0)).take(25); 238 | /// # ws2812.write(pixels).unwrap(); 239 | /// ``` 240 | pub type Ws2812Esp32Rmt<'d> = LedPixelEsp32Rmt<'d, RGB8, LedPixelColorGrb24>; 241 | 242 | #[cfg(test)] 243 | mod test { 244 | use super::*; 245 | use crate::mock::esp_idf_hal::peripherals::Peripherals; 246 | 247 | #[test] 248 | fn test_ws2812_esp32_rmt_smart_leds() { 249 | let sample_data = [RGB8::new(0x00, 0x01, 0x02), RGB8::new(0x03, 0x04, 0x05)]; 250 | let expected_values: [u8; 6] = [0x01, 0x00, 0x02, 0x04, 0x03, 0x05]; 251 | 252 | let peripherals = Peripherals::take().unwrap(); 253 | let led_pin = peripherals.pins.gpio0; 254 | let channel = peripherals.rmt.channel0; 255 | 256 | let mut ws2812 = Ws2812Esp32Rmt::new(channel, led_pin).unwrap(); 257 | ws2812.write(sample_data.iter().cloned()).unwrap(); 258 | assert_eq!(ws2812.driver.pixel_data.unwrap(), &expected_values); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/mock/mod.rs: -------------------------------------------------------------------------------- 1 | //! Mock modules for local testing 2 | 3 | /// Mock module for `esp_idf_hal` 4 | pub mod esp_idf_hal { 5 | pub use super::esp_idf_sys as sys; 6 | 7 | /// Mock module for `esp_idf_hal::gpio` 8 | pub mod gpio { 9 | use super::peripheral::Peripheral; 10 | use paste::paste; 11 | 12 | /// Mock trait for `esp_idf_hal::gpio::OutputPin`. 13 | pub trait OutputPin {} 14 | 15 | macro_rules! define_pins_struct { 16 | ($($num:expr),*) => { 17 | paste! { 18 | /// Mock struct for `esp_idf_hal::gpio::Pins`. 19 | #[derive(Debug, Default)] 20 | pub struct Pins { 21 | $( 22 | pub []: [], 23 | )* 24 | } 25 | } 26 | } 27 | } 28 | define_pins_struct!( 29 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30 | 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 31 | 46, 47, 48 32 | ); 33 | 34 | impl Pins { 35 | pub(super) fn new() -> Self { 36 | Default::default() 37 | } 38 | } 39 | 40 | macro_rules! define_gpio_structs { 41 | ($($num:expr),*) => { 42 | paste! { 43 | $( 44 | #[doc = concat!("Mock struct for `esp_idf_hal::gpio::Gpio", stringify!($num) ,"`")] 45 | #[derive(Debug, Default)] 46 | pub struct [] {} 47 | 48 | //impl [] { 49 | // pub(super) fn new() -> Self { 50 | // Self {} 51 | // } 52 | //} 53 | 54 | impl OutputPin for [] {} 55 | impl Peripheral for [] { 56 | type P=[]; 57 | } 58 | )* 59 | } 60 | }; 61 | } 62 | define_gpio_structs!( 63 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 64 | 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 65 | 46, 47, 48 66 | ); 67 | } 68 | 69 | /// Mock module for `esp_idf_hal::peripheral` 70 | pub mod peripheral { 71 | /// Mock trait for `esp_idf_hal::peripheral::Peripheral` 72 | pub trait Peripheral: Sized { 73 | /// Peripheral singleton type 74 | type P; 75 | } 76 | } 77 | 78 | /// Mock module for `esp_idf_hal::peripherals` 79 | pub mod peripherals { 80 | use super::gpio; 81 | use super::rmt; 82 | 83 | /// Mock struct for `esp_idf_hal::peripherals::Peripherals` 84 | pub struct Peripherals { 85 | pub pins: gpio::Pins, 86 | pub rmt: rmt::RMT, 87 | } 88 | 89 | impl Peripherals { 90 | pub fn take() -> Result { 91 | Ok(Self::new()) 92 | } 93 | 94 | // Create `Peripherals` instance. 95 | // 96 | // This function shall not used usually because 97 | // the original `esp_idf_hal::peripherals::Peripherals::new()` is unsafe, 98 | // and `take()` should be used instead. 99 | pub fn new() -> Self { 100 | Self { 101 | pins: gpio::Pins::new(), 102 | rmt: rmt::RMT::new(), 103 | } 104 | } 105 | } 106 | } 107 | 108 | /// Mock module for `esp_idf_hal::rmt` 109 | pub mod rmt { 110 | use super::gpio::OutputPin; 111 | use super::peripheral::Peripheral; 112 | use super::sys::EspError; 113 | use config::TransmitConfig; 114 | use core::marker::PhantomData; 115 | use paste::paste; 116 | 117 | macro_rules! define_channel_structs { 118 | ($($num:expr),*) => { 119 | paste! { 120 | $( 121 | #[doc = concat!("Mock struct for `esp_idf_hal::rmt::CHANNEL", stringify!($num) ,"`")] 122 | #[derive(Debug, Default)] 123 | pub struct [] {} 124 | 125 | impl [] { 126 | pub fn new() -> Self { 127 | Self {} 128 | } 129 | } 130 | 131 | impl Peripheral for [] { 132 | type P=[]; 133 | } 134 | 135 | impl RmtChannel for [] {} 136 | )* 137 | } 138 | }; 139 | } 140 | define_channel_structs!(0, 1, 2, 3, 4, 5, 6, 7); 141 | 142 | /// mock struct for `esp_idf_hal::rmt::RMT` 143 | #[derive(Debug, Default)] 144 | pub struct RMT { 145 | pub channel0: CHANNEL0, 146 | pub channel1: CHANNEL1, 147 | pub channel2: CHANNEL2, 148 | pub channel3: CHANNEL3, 149 | pub channel4: CHANNEL4, 150 | pub channel5: CHANNEL5, 151 | pub channel6: CHANNEL6, 152 | pub channel7: CHANNEL7, 153 | } 154 | 155 | impl RMT { 156 | pub fn new() -> Self { 157 | Default::default() 158 | } 159 | } 160 | 161 | /// Mock trait fo `esp_idf_hal::rmt::RmtChannel` 162 | pub trait RmtChannel {} 163 | 164 | //pub type RmtTransmitConfig = config::TransmitConfig; 165 | 166 | /// Mock module for `esp_idf_hal::rmt::TxRmtDriver` 167 | pub struct TxRmtDriver<'d> { 168 | _p: PhantomData<&'d mut ()>, 169 | } 170 | 171 | impl<'d> TxRmtDriver<'d> { 172 | /// Initialize the mock of `TxRmtDriver`. 173 | /// No argument is used in this mock. 174 | pub fn new( 175 | _channel: impl Peripheral

+ 'd, 176 | _pin: impl Peripheral

+ 'd, 177 | _config: &TransmitConfig, 178 | ) -> Result { 179 | Ok(Self { _p: PhantomData }) 180 | } 181 | } 182 | 183 | /// Mock module for `esp_idf_hal::rmt::config` 184 | pub mod config { 185 | /// Mock struct for `esp_idf_hal::rmt::config::TransmitConfig` 186 | #[derive(Debug, Clone)] 187 | pub struct TransmitConfig { 188 | pub clock_divider: u8, 189 | pub mem_block_num: u8, 190 | // Other parameters are omitted 191 | } 192 | 193 | impl TransmitConfig { 194 | pub fn new() -> Self { 195 | Self { 196 | mem_block_num: 1, 197 | clock_divider: 80, 198 | } 199 | } 200 | #[must_use] 201 | pub fn clock_divider(mut self, divider: u8) -> Self { 202 | self.clock_divider = divider; 203 | self 204 | } 205 | #[must_use] 206 | pub fn mem_block_num(mut self, mem_block_num: u8) -> Self { 207 | self.mem_block_num = mem_block_num; 208 | self 209 | } 210 | } 211 | } 212 | } 213 | } 214 | 215 | /// Mock module for `esp_idf_sys` 216 | pub mod esp_idf_sys { 217 | use core::fmt; 218 | 219 | /// Mock struct for `esp_idf_sys::EspError` 220 | #[repr(transparent)] 221 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] 222 | pub struct EspError(); 223 | 224 | #[cfg(feature = "std")] 225 | impl std::error::Error for EspError {} 226 | 227 | impl fmt::Display for EspError { 228 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 229 | fmt::Display::fmt("EspError", f) 230 | } 231 | } 232 | } 233 | --------------------------------------------------------------------------------