2.Rust嵌入式入门——点亮LED
这一节,我们将使用RMT控制WS2812灯珠。
RMT介绍
红外遥控 (RMT)是ESP32中的一个外设,它是一个灵活的串行通信接口,可以用于实现多种通信协议,比如IR(红外)和LED控制等。RMT模块可以配置为发射器(TX)或接收器(RX)模式。
RMT的主要特点包括:
- 多通道支持:ESP32的RMT模块支持多达8个通道,每个通道都可以独立配置和操作。
- 灵活的时序控制:RMT可以精确控制信号的高低电平持续时间,适合需要精确时序控制的通信协议。
- 可编程数据位长度:RMT支持可编程的数据位长度,可以适应不同的通信协议。
- 中断支持:RMT模块支持中断,可以在接收到数据或发送完成时触发中断,便于实现更复杂的通信逻辑。
- 内存管理:RMT模块具有自己的内存,可以存储发送和接收的数据,减少了对主CPU的依赖。
RMT的应用场景:
- 红外通信:RMT可以用于发送和接收红外信号,实现遥控器功能或与其他红外设备通信。
- LED控制:通过精确控制LED的亮灭时间,RMT可以用于实现各种LED效果,如呼吸灯、跑马灯等。
- 自定义通信协议:RMT的灵活性使其可以用于实现自定义的串行通信协议。
参考
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)。
数据传输流程
- 初始化:上电或复位后,WS2812准备接收数据。
- 数据输入:从控制器(如微控制器)通过DIN端口接收24位数据。
- 数据处理:第一个WS2812 LED捕获24位数据并存储在内部锁存器中。
- 数据传递:处理完数据后,通过DOUT端口将剩余数据传送给下一个WS2812 LED。
- 重复:过程在每个LED之间重复,直到所有LED都被更新。
应用
WS2812因其易用性和多功能性,在各种项目中广泛应用,如:
- LED灯光装饰和动态照明系统。
- 家庭自动化和物联网项目。
- 互动艺术装置和舞台灯光。
- 电子产品开发板上的状态指示灯。
参考
实现
在上一节中,我们完成了开发环境的搭建和连接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中找到。
如果您觉得不错,请点个关注或者收藏,感谢您的支持。