硬件
我的硬件是esp32s3。
外设有一个 ws2812 led 灯,它接在了 GPIO 48 上。
硬件显示:

电路图:

我们的目标是点亮它。
C 的实现
如果用 C 语言实现,很简单,因为 esp32 的生态比较完善。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "esp_log.h"
#include "led_strip.h"
static const char *TAG = "main";
#define GPIO_NUM_48 48
led_strip_handle_t led_flash_init(void) {
// Initialize LED strip
led_strip_config_t led_cfg = {
.strip_gpio_num = GPIO_NUM_48,
.max_leds = 1,
.flags.invert_out = false,
.led_model = LED_MODEL_WS2812,
};
// led strip backend config rmt
led_strip_rmt_config_t rmt_cfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = (10*1000*1000),
.flags.with_dma = false,
};
led_strip_handle_t led_strip = NULL;
led_strip_new_rmt_device(&led_cfg, &rmt_cfg, &led_strip);
ESP_LOGI(TAG, "LED strip initialized");
return led_strip;
}
void app_main(void)
{
led_flash_init();
ESP_LOGI(TAG, "Starting");
led_strip_handle_t led_strip = led_flash_init();
bool led_on_of = false;
// 设置亮度
uint8_t brightness = 50;
uint8_t red = (0 * brightness) / 100;
uint8_t green = (0 * brightness) / 100;
uint8_t blue = (139 * brightness) / 100;
while (1)
{
if (led_on_of)
{
led_strip_set_pixel(led_strip, 0, red, green, blue);
led_strip_refresh(led_strip);
ESP_LOGI(TAG, "LED on");
}else {
led_strip_clear(led_strip);
ESP_LOGI(TAG, "LED off");
}
led_on_of = !led_on_of;
vTaskDelay(pdMS_TO_TICKS(500));
}
}
C 使用到了官方的 led_strip 库。当然,项目得用官方的模板。
Rust 的实现
Rust 开发需要的工具链比较多,这里只列出来部分,不考虑命令是否重复:
#Espressif 工具链
cargo install cargo-espflash espflash # 新的调试现在推荐用 probe-rs 了
#debian/ubuntu
sudo apt install llvm-dev libclang-dev clang
但是其实最好是使用esp-generate.
创建项目,这个命令会根据实际的芯片下载相关的工具链并创建项目。
esp-generate --chip esp32s3 -o alloc -o vscode -o wokwi -o esp-backtrace -o log led
# or use probe-rs
esp-generate --chip esp32s3 -o alloc -o vscode -o wokwi -o probe-rs led
现在比较推荐使用 probe-rs,它是一个嵌入式调试和目标交互工具包。它使用户能够通过调试探针编程和调试微控制器。
上面的命令在国内得看运气,运气不好的话,我们可能会卡在 espup 下载工具链上。可以自己使用 espup install 安装 esp 相关的工具链。
esp32 不同系列的芯片, target 是不一样的,比如 c系列好像是 risv 架构的。而 s3 是 xtensa-esp32s3-none-elf。
想一下我们安装rust开发环境的时候是不是有提示要求我们,运行 source $HOME/.cargo/env ?
上面这个 espup 安装后也是要的,不然,rust 工具会找自带的 rust 运行环境。
比如我的:
source /home/kkch/export-esp.sh
esp32 在 rust 上有2套运行环境,一个是 std ,另一个是 no_std。
官方更推荐使用 no_std, 我个人觉得 std 虽然比较方便,但是对于esp32 这种资源受限的平台来说,std 并不会那么好用。
官方自己维护的就是 no_std 版本。 std 版本好像是社区维护的。
| Repository | Description | Support status |
|---|---|---|
| esp-rs/esp-hal | no_std | 官方维护 |
| esp-rs/esp-idf-hal | std | 社区维护 |
官方流行文档基本都是 std 的例子。但是我这里实现的是no_std 版本。
想要使用 Rust 控制 led 灯,需要使用需要以下这些crates:
[dependencies]
esp-hal = { version = "~1.0", features = ["esp32s3"] }
anyhow = {version = "=1.0.100", default-features = false}
esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32s3"] }
critical-section = "1.2.0"
esp-alloc = "0.9.0"
rtt-target = "0.6.2"
blinksy-esp = {version = "0.11.0",features = ["esp32s3"]}
blinksy = "0.11.0"
上面这些大多数是esp-generate 生成的。其中 控制 ws2812 的crate 由于需要的是 no_std 版本。我这里选择的是 blinksy。
如果你使用 std 环境,那么还有一个
ws2812-esp32-rmt-drivercrate 可以使用。
blinksy 是一个控制 led 灯布局阵列的库。它只是刚好支持 ws2812 灯以及 esp 平台。它支持1维、2维、3维的 led 灯阵列。
我的 esp32s3 只有一个 ws2812 灯,所以是1维。
layout1d!(Layout,1); //只有一个灯
//如果有多个灯组成灯条,那么可以这样写:
//layout1d!(Layout,60); //60个灯
初始化 esp32s3 driver:
let ws2812_driver = {
let data_pin = p.GPIO48;
let rmt_clk_freq = hal::time::Rate::from_mhz(80);
let rmt = hal::rmt::Rmt::new(p.RMT, rmt_clk_freq).unwrap();
let rmt_channel = rmt.channel0;
ClocklessDriver::default().with_led::<Ws2812>().with_writer(
ClocklessRmtBuilder::default()
.with_rmt_buffer_size::<{ Layout::PIXEL_COUNT * 3 * 8 + 1 }>()
.with_led::<Ws2812>()
.with_channel(rmt_channel)
.with_pin(data_pin)
.build(),
)
};
初始化 led 控制器:
let mut control = ControlBuilder::new_1d()
.with_layout::<Layout, { Layout::PIXEL_COUNT }>()
.with_pattern::<Rainbow>(RainbowParams {
..Default::default()
})
.with_driver(ws2812_driver)
.with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
.build();
设置led 灯的颜色:
control.set_color_correction(blinksy::color::ColorCorrection { red: 0.5, green: 0.5, blue: 0.5 });
这里的 rgb 三个值取值区间是 0-1。
设置led 灯的亮度:
control.set_brightness(0.5); //取值区间是 0-1
之后就可以在循环里面使用:
loop {
let elapsed_in_ms = elapsed().as_millis();
control.tick(elapsed_in_ms).unwrap();
}
如果要实现上面 C 语言上的效果,那么完整代码如下:
#![no_std]
#![no_main]
#![deny(
clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
#![deny(clippy::large_stack_frames)]
use esp_alloc as _;
use esp_hal::{self as hal, delay::Delay};
use esp_hal::main;
use blinksy::{
ControlBuilder,
driver::ClocklessDriver,
layout::Layout1d,
layout1d,
leds::Ws2812,
patterns::rainbow::{Rainbow, RainbowParams},
};
use blinksy_esp::{rmt::ClocklessRmtBuilder, time::elapsed};
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
extern crate alloc;
esp_bootloader_esp_idf::esp_app_desc!();
#[allow(
clippy::large_stack_frames,
reason = "it's not unusual to allocate larger buffers etc. in main"
)]
#[main]
fn main() -> ! {
let cpu_clock = hal::clock::CpuClock::max();
let config = hal::Config::default().with_cpu_clock(cpu_clock);
let p = hal::init(config);
// layout1d!(Layout, 60 * 5);
layout1d!(Layout, 1);
let ws2812_driver = {
let data_pin = p.GPIO48;
let rmt_clk_freq = hal::time::Rate::from_mhz(80);
let rmt = hal::rmt::Rmt::new(p.RMT, rmt_clk_freq).unwrap();
let rmt_channel = rmt.channel0;
ClocklessDriver::default().with_led::<Ws2812>().with_writer(
ClocklessRmtBuilder::default()
.with_rmt_buffer_size::<{ Layout::PIXEL_COUNT * 3 * 8 + 1 }>()
.with_led::<Ws2812>()
.with_channel(rmt_channel)
.with_pin(data_pin)
.build(),
)
};
let mut control = ControlBuilder::new_1d()
.with_layout::<Layout, { Layout::PIXEL_COUNT }>()
.with_pattern::<Rainbow>(RainbowParams {
..Default::default()
})
.with_driver(ws2812_driver)
.with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
.build();
control.set_color_correction(blinksy::color::ColorCorrection {
red: 0.0,
green: 0.0,
blue: 1.0,
});
control.set_brightness(0.5); // Set initial brightness (0.0 to 1.0)
let mut led_on_off = false;
let delay = Delay::new();
loop {
if led_on_off {
control.set_color_correction(blinksy::color::ColorCorrection {
red: 0.0,
green: 0.0,
blue: 1.0,
});
} else {
control.set_color_correction(blinksy::color::ColorCorrection {
red: 0.0,
green: 0.0,
blue: 0.0,
});
}
led_on_off = !led_on_off;
let elapsed_in_ms = elapsed().as_micros();
control.tick(elapsed_in_ms).unwrap();
delay.delay_millis(500);
}
}
运行结果:
