2.Rust嵌入式入门——点亮LED

585 阅读7分钟

2.Rust嵌入式入门——点亮LED

这一节,我们将使用RMT控制WS2812灯珠。

RMT介绍

红外遥控 (RMT)是ESP32中的一个外设,它是一个灵活的串行通信接口,可以用于实现多种通信协议,比如IR(红外)和LED控制等。RMT模块可以配置为发射器(TX)或接收器(RX)模式。

RMT的主要特点包括:

  1. 多通道支持:ESP32的RMT模块支持多达8个通道,每个通道都可以独立配置和操作。
  2. 灵活的时序控制:RMT可以精确控制信号的高低电平持续时间,适合需要精确时序控制的通信协议。
  3. 可编程数据位长度:RMT支持可编程的数据位长度,可以适应不同的通信协议。
  4. 中断支持:RMT模块支持中断,可以在接收到数据或发送完成时触发中断,便于实现更复杂的通信逻辑。
  5. 内存管理:RMT模块具有自己的内存,可以存储发送和接收的数据,减少了对主CPU的依赖。

RMT的应用场景:

  • 红外通信:RMT可以用于发送和接收红外信号,实现遥控器功能或与其他红外设备通信。
  • LED控制:通过精确控制LED的亮灭时间,RMT可以用于实现各种LED效果,如呼吸灯、跑马灯等。
  • 自定义通信协议:RMT的灵活性使其可以用于实现自定义的串行通信协议。

参考

红外遥控 (RMT) - ESP32-C3

WS2812介绍

WS2812是一种高度集成的RGB LED灯珠,通常被称为“NeoPixel”或“Addressable RGB LED”。它将LED驱动电路和控制电路集成在一个单一的封装内,通常是5050封装,这意味着它与标准的RGB LED灯珠尺寸相同,但是具备了智能化控制的能力。

WS2812的结构与工作原理

内部结构

  • 控制电路:包括智能数字端口数据锁存器和信号整形放大驱动电路。
  • 振荡器:用于提供精确的时钟信号。
  • 恒流源:控制RGB LED的电流,确保色彩一致性。
  • PWM驱动:用于控制LED的亮度和颜色。
  • 数据锁存器:用于存储接收到的颜色数据。
  • 信号整形电路:确保数据在长链中传输不失真。

数据传输

WS2812使用单线串行数据传输协议,通常称为SPI-like,但并非标准SPI。数据传输速率为800 Kbps,意味着1比特的数据传输时间为1.25 μs。数据格式是24位,按绿-红-蓝(GRB)或蓝-红-绿(BRG)顺序排列,每个颜色8位,用来表示亮度等级(0-255)。

数据传输流程

  1. 初始化:上电或复位后,WS2812准备接收数据。
  2. 数据输入:从控制器(如微控制器)通过DIN端口接收24位数据。
  3. 数据处理:第一个WS2812 LED捕获24位数据并存储在内部锁存器中。
  4. 数据传递:处理完数据后,通过DOUT端口将剩余数据传送给下一个WS2812 LED。
  5. 重复:过程在每个LED之间重复,直到所有LED都被更新。

应用

WS2812因其易用性和多功能性,在各种项目中广泛应用,如:

  • LED灯光装饰和动态照明系统。
  • 家庭自动化和物联网项目。
  • 互动艺术装置和舞台灯光。
  • 电子产品开发板上的状态指示灯。

参考

一文带你了解WS2812原理及驱动-CSDN博客

实现

在上一节中,我们完成了开发环境的搭建和连接wifi的功能。这里我们在项目目录下新建文件src/led.rs

use std::time::Duration;
​
use anyhow::Result;
use esp_idf_svc::hal::{
    gpio::OutputPin,
    peripheral::Peripheral,
    rmt::{ config::TransmitConfig, FixedLengthSignal, PinState, Pulse, RmtChannel, TxRmtDriver },
};
​
pub use rgb::RGB8;
​
​
pub struct WS2812RMT<'a> {
    tx_rmt_derive: TxRmtDriver<'a>,
}
​
impl<'a> WS2812RMT<'a> {
    pub fn new(
        led: impl Peripheral<P = impl OutputPin> + 'a,
        channel: impl Peripheral<P = impl RmtChannel> + 'a
    ) -> Result<Self> {
        // 配置RMT的传输参数
        let config = TransmitConfig::new().clock_divider(2);
        // 初始化RMT驱动
        let tx = TxRmtDriver::new(channel, led, &config)?;
        Ok(Self { tx_rmt_derive: tx })
    }
​
    pub fn set_pixel(&mut self, rgb: RGB8) -> Result<()> {
        // 将RGB颜色值转换为一个32位的整数。
        // RGB颜色由红、绿、蓝三部分组成,每部分占用8位。
        // 这里通过位移操作将它们组合在一起。
        let color: u32 = ((rgb.g as u32) << 16) | ((rgb.r as u32) << 8) | (rgb.b as u32);
​
        // 获取发送器的时钟频率,这将用于计算脉冲的持续时间。
        let ticks_hz = self.tx_rmt_derive.counter_clock()?;
​
        // 定义一个短的高电平脉冲,通常用于表示二进制中的'0'
        let t0h = Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(400))?;
        // 定义一个短的低电平脉冲,与上面的高电平脉冲一起构成一个完整的'0'脉冲对
        let t0l = Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(850))?;
​
        // 定义一个长的高电平脉冲,通常用于表示二进制中的'1'
        let t1h = Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(800))?;
        // 定义一个长的低电平脉冲,与上面的高电平脉冲一起构成一个完整的'1'脉冲对
        let t1l = Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(450))?;
​
        // 创建一个固定长度为24的信号序列,用于存储脉冲对。
        let mut signal = FixedLengthSignal::<24>::new();
        
        // 生成RMT脉冲序列来表示颜色
        // 从最高位开始遍历颜色值的每一位(从23到0)
        for i in (0..24).rev() {
            // 计算当前位的权重,即2的i次方
            let p = (2u32).pow(i);
​
            // 检查当前位是否为1,如果为1则bit为true,否则为false
            let bit = (p & color) != 0;
​
            // 根据bit的值选择脉冲对:如果bit为true则选择表示'1'的脉冲对,否则选择表示'0'的脉冲对
            let pulse = if bit { (t1h, t1l) } else { (t0h, t0l) };
​
            // 将选择的脉冲对设置到信号序列中对应的位置上
            // 注意,由于是从高位到低位遍历,所以位置需要从23开始递减
            signal.set(23 - (i as usize), &pulse)?;
        }
        Ok(self.tx_rmt_derive.start_blocking(&signal)?)
    }
​
    pub fn shutdown(&mut self) -> Result<()> {
        self.set_pixel(RGB8::new(0, 0, 0))?;
        Ok(())
    }
}

修改lib.rs,在头部添加pub mod led; 将该模块导出。

新建bin/led.rs

use std::time::{ Duration, Instant };
use esp_idf_svc::hal::peripherals::Peripherals;
use rust_embedded_study::led::{ RGB8, WS2812RMT };
​
fn main() -> anyhow::Result<()> {
    // 链接ESP-IDF的补丁,以确保程序与ESP-IDF环境正确集成
    esp_idf_svc::sys::link_patches();
    // 初始化日志系统,以便能够输出日志信息
    esp_idf_svc::log::EspLogger::initialize_default();
    // 获取板子的外设,用于后续控制LED
    let peripherals = Peripherals::take()?;
    // 选取GPIO8作为LED的控制引脚
    let led = peripherals.pins.gpio8;
    // 选取RMT的通道0作为WS2812的通信通道
    let channel = peripherals.rmt.channel0;
    // 初始化WS2812LED控制器
    let mut ws2812 = WS2812RMT::new(led, channel)?;
​
    // 记录程序启动的瞬间,用于计算运行时间
    let instant = Instant::now();
​
    // 无限循环,用于控制LED的点亮和关闭
    loop {
        // 检查是否已经运行了3秒以上
        if instant.elapsed() > Duration::from_secs(3) {
            // 如果超过3秒,关闭LED,并记录日志
            log::info!("关灯");
            ws2812.shutdown()?;
            std::thread::sleep(Duration::from_secs(1));
            log::info!("关机");
            return Ok(());
        } else {
            // 如果未超过3秒,点亮LED为绿色,并记录当前运行时间
            ws2812.set_pixel(RGB8::new(0, 255, 0))?;
            log::info!("time:{}", instant.elapsed().as_secs());
        }
        // 每次循环等待1秒,以便观察LED的状态变化
        std::thread::sleep(Duration::from_secs(1));
    }
}
​

接下来只需要连接开发板并cargo run就能看到亮起绿色的灯。

下节我们将连接wifi,通过http服务控制LED灯的颜色和亮度。

最后

🎉恭喜您成功实现了LED的点亮功能。您所探索的代码可以在GitHub仓库yexiyue/rust-embedded-study中找到。

如果您觉得不错,请点个关注或者收藏,感谢您的支持。