esp-rs / esp-idf-hal

embedded-hal implementation for Rust on ESP32 and ESP-IDF
https://docs.esp-rs.org/esp-idf-hal/
Apache License 2.0
466 stars 171 forks source link

Controlling a WS2812 LED #448

Closed weiying-chen closed 3 months ago

weiying-chen commented 4 months ago

I want to control the WS2812 LED of my ESP32-C3-Zero using esp_idf_hal's SPI. I tried this:

use anyhow::Result;
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::spi::{config::Config, SpiDeviceDriver, SpiDriver, SpiDriverConfig, SPI2};
use esp_idf_svc::log::EspLogger;
use esp_idf_sys::{self as _};
use log::info;

fn main() -> Result<()> {
    esp_idf_svc::sys::link_patches();
    EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;
    let spi = peripherals.spi2;

    let sclk = peripherals.pins.gpio6; // SCLK
    let serial_in = peripherals.pins.gpio4; // SDI (MISO)
    let serial_out = peripherals.pins.gpio7; // SDO (MOSI)
    let cs_1 = peripherals.pins.gpio10; // Chip Select for device 1 (LED pin)

    println!("Starting SPI loopback test");

    let driver = SpiDriver::new::<SPI2>(
        spi,
        sclk,
        serial_out,
        Some(serial_in),
        &SpiDriverConfig::new(),
    )?;

    let config_1 = Config::new().baudrate(3_000_000.into());
    let mut device_1 = SpiDeviceDriver::new(&driver, Some(cs_1), &config_1)?;

    let led_data = [
        0b11101110, 0b10001000, 0b10001000, // Red
        0b10001000, 0b11101110, 0b10001000, // Green
        0b10001000, 0b10001000, 0b11101110, // Blue
    ]; // Buffer to hold the LED data (8 bits per color, 3 colors)

    loop {
        FreeRtos::delay_ms(500);

        device_1.write(&led_data)?;
        info!("WS2812: Sent LED data {:x?}", led_data);
    }
}

No compiler errors. And there's output. But the WS2812 LED doesn't turn on.

Note: This is a rust library for WS2812. But I don't think it's compatible with esp_idf_hal.

weiying-chen commented 4 months ago

I also tried something a little more complex like this:

use anyhow::Result;
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::{AnyOutputPin, Output, OutputPin, PinDriver};
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::spi::{config::Config, SpiDeviceDriver, SpiDriver, SpiDriverConfig, SPI2};
use esp_idf_svc::log::EspLogger;
use esp_idf_sys::{self as _};
use log::info;

struct WS2812<'a, CS: OutputPin> {
    spi: SpiDeviceDriver<'a, &'a SpiDriver<'a>>,
    cs: PinDriver<'a, CS, Output>,
}

impl<'a, CS> WS2812<'a, CS>
where
    CS: OutputPin,
{
    pub fn new(spi: SpiDeviceDriver<'a, &'a SpiDriver<'a>>, cs: PinDriver<'a, CS, Output>) -> Self {
        Self { spi, cs }
    }

    pub fn write_byte(&mut self, data: u8) -> Result<()> {
        let patterns = [0b1000_1000, 0b1000_1110, 0b1110_1000, 0b1110_1110];
        let mut buffer = [0u8; 4];
        let mut data = data;

        for i in 0..4 {
            let bits = (data & 0b1100_0000) >> 6;
            buffer[i] = patterns[bits as usize];
            data <<= 2;
        }

        self.cs.set_low()?;
        self.spi.write(&buffer)?;
        self.cs.set_high()?;

        Ok(())
    }

    pub fn flush(&mut self) -> Result<()> {
        let buffer = [0u8; 140];
        self.cs.set_low()?;
        self.spi.write(&buffer)?;
        self.cs.set_high()?;
        Ok(())
    }
}

fn main() -> Result<()> {
    esp_idf_svc::sys::link_patches();
    EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;
    let spi = peripherals.spi2;

    let sclk = peripherals.pins.gpio6; // SCLK
    let serial_in = peripherals.pins.gpio4; // SDI (MISO)
    let serial_out = peripherals.pins.gpio7; // SDO (MOSI)
    let cs_pin = peripherals.pins.gpio10; // Chip Select for device (LED pin)

    println!("Starting SPI WS2812 control");

    let driver = SpiDriver::new(
        spi,
        sclk,
        serial_out,
        Some(serial_in),
        &SpiDriverConfig::new(),
    )?;

    let config = Config::new().baudrate(3_000_000.into());
    let spi_device = SpiDeviceDriver::new(&driver, None::<AnyOutputPin>, &config)?; // No CS pin here
    let cs = PinDriver::output(cs_pin)?; // CS pin managed separately

    let mut ws2812 = WS2812::new(spi_device, cs);

    let red = 255;
    let green = 0;
    let blue = 0;

    loop {
        FreeRtos::delay_ms(500);

        ws2812.flush()?;

        ws2812.write_byte(green)?;
        ws2812.write_byte(red)?;
        ws2812.write_byte(blue)?;

        ws2812.flush()?;
        info!("WS2812: Sent LED data for Red color");
    }
}

Same: No compile errors. There's output. But the WS2812 LED doesn't turn on.

Vollbrecht commented 4 months ago

Are you sure that the ws2812 are fast enough to be able to work with a 3MHz clocked signal?

weiying-chen commented 4 months ago

Ah, maybe not (Sorry, I'm not very experienced with this). I guess it'll be too complicated to use SPI.

Now I'm using this RMT code. It works for external ws2812s. But not with the built-in ws2812 of the ESP32-C3-Zero. It used to be off all the time. And now it's always green no matter what. (The exact same thing happens with the official Arduino code for the ESP32-C3-Zero.)

okhsunrog commented 3 months ago

The https://github.com/smart-leds-rs/ws2812-spi-rs you mentioned is fully compatible with esp-idf-hal. The pull request for embedded-hal 1.0 is merged now. You also need to enable features = ["mosi_idle_high"] for it to work, and probably need to change RGB order. I did a fork of ws2812-spi-rs before embedded-hal 1.0 support was merged, the fork consists of the pull request with embedded-hal 1.0 support + RGB color order. You can see the working code for esp32-c3 with SPI + DMA here: https://github.com/okhsunrog/esp_pd_rs @Vollbrecht I think this issue can be closed

okhsunrog commented 3 months ago

Are you sure that the ws2812 are fast enough to be able to work with a 3MHz clocked signal?

It most certainly is, I'm using 3.2 MHz in my code, the SPI signal is used to form correct timings, the timings I get with 3.2 MHz match the ones described in the datasheet for WS2812B

okhsunrog commented 3 months ago

@weiying-chen the code you can find in my links is tested to work with ESP32-C3-Zero built-in WS2812 led

weiying-chen commented 3 months ago

@okhsunrog Thanks a lot! I'll try it out.

weiying-chen commented 3 months ago

@okhsunrog Your code works. Thanks a lot again!