Jan 23, 2026
5 min read
ESP32,
Rust,
C,
Embedded Systems,
WS2812,

使用ESP32-S3控制WS2812 LED:C与Rust实现对比

本文对比了使用C和Rust两种语言在ESP32-S3上控制WS2812 LED的实现方式。涵盖了硬件设置(GPIO 48)、两种语言的工具链配置,以及各自的完整代码示例。对比突出了传统ESP-IDF C开发与现代Rust嵌入式开发在开发体验、库使用和性能考虑方面的差异。

硬件

我的硬件是esp32s3

外设有一个 ws2812 led 灯,它接在了 GPIO 48 上。

硬件显示:

esp32s

电路图:

ws2812

我们的目标是点亮它。

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 版本好像是社区维护的。

RepositoryDescriptionSupport status
esp-rs/esp-halno_std官方维护
esp-rs/esp-idf-halstd社区维护

官方流行文档基本都是 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-driver crate 可以使用。

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);
    }
}

运行结果: output