找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2789

积分

0

好友

383

主题
发表于 昨天 03:24 | 查看: 0| 回复: 0

作为一名对嵌入式开发感兴趣的开发者,你是否也想尝试用现代、安全的 Rust 语言来控制硬件?本文将以一块具体的 ESP32-S3 开发板和一个 WS2812 RGB LED 为例,带你从零开始,实现一个简单的闪烁灯效,并对比传统的 C 语言实现方式。希望通过这篇在 云栈社区 分享的实战记录,能为你开启 Rust 嵌入式开发的大门。

硬件准备

我使用的硬件是一块 ESP32-S3-WROOM-1 开发板。

外设方面,接入了一个 WS2812 LED 灯珠,其信号线连接到了开发板的 GPIO 48 引脚。

开发板的实物图如下:

ESP32-S3-WROOM-1开发板正面特写

其与 WS2812 连接的简易电路示意图如下,清晰地标明了控制引脚:

WS2812 LED与ESP32-S3 GPIO48连接电路图

我们的目标很简单:用 Rust 编程,让这个 LED 灯亮起来。

传统实现:C语言版本

如果使用经典的 C 语言来实现,过程会相对直接,因为 ESP-IDF 生态已经相当成熟和完善。核心代码如下:

#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 组件库。当然,整个项目需要基于官方的 ESP-IDF 项目模板来构建。

现代实践:Rust 实现

现在,让我们看看如何用 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,它是一个功能强大的嵌入式调试和交互工具包。需要注意的是,esp-generate 命令在国内网络环境下可能会在下载工具链(espup)时卡住。如果遇到问题,可以尝试手动使用 espup install 来安装 ESP 相关的 Rust 工具链。

重要提示:ESP32 不同系列的芯片,编译目标(target)是不同的。例如,C3 系列是 RISC-V 架构,而 S3 系列是 xtensa-esp32s3-none-elf。安装完工具链后,记得像安装 Rust 本身后需要 source $HOME/.cargo/env 一样,也需要 source 由 espup 导出的环境变量脚本(例如 source /home/kkch/export-esp.sh),否则 Rust 工具链会使用默认的系统环境。

no_stdstd 环境的选择

在 Rust 生态中,针对 ESP32 有两套主要的开发环境:stdno_std

  • no_std:官方 esp-rs 团队主要维护和支持的环境。它不依赖操作系统的标准库,更适合资源受限的嵌入式平台。
  • std:由社区维护,它提供了类似桌面开发的标准库体验,但可能会引入更多资源开销。

官方流行文档中很多例子基于 std,但本着更贴近硬件、更节省资源的原则,本文的实现将基于 no_std 环境。

Repository Description Support status
esp-rs/esp-hal no_std 官方维护
esp-rs/esp-idf-hal std 社区维护

依赖配置

no_std 环境下控制 WS2812,我们需要在 Cargo.toml 中添加以下依赖。这些 crates 大部分可由 esp-generate 自动生成。

[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”

其中,用于控制 WS2812 的关键 crate 是 blinksy(及其平台适配 blinksy-esp)。blinksy 是一个用于控制 LED 阵列的库,它恰好支持 WS2812 灯珠和 ESP 平台,能够处理 1D、2D、3D 的 LED 布局。

如果你选择 std 环境,也可以考虑 ws2812-esp32-rmt-driver 这个 crate。

代码实现

由于我们只有一个 LED 灯,所以定义为一维布局,且数量为 1:

layout1d!(Layout, 1); //只有一个灯

//如果有多个灯组成灯条,那么可以这样写:
//layout1d!(Layout,60); //60个灯

1. 初始化 ESP32S3 的 RMT 驱动

RMT(Remote Control)是 ESP32 上用于生成精确时序脉冲的外设,非常适合驱动 WS2812。

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

2. 构建 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();

3. 设置颜色与亮度

这里 RGB 值和亮度的取值范围都是 0.0 到 1.0。

control.set_color_correction(blinksy::color::ColorCorrection { red: 0.5, green: 0.5, blue: 0.5 });
control.set_brightness(0.5); //取值区间是 0-1

4. 在主循环中刷新

loop {
    let elapsed_in_ms = elapsed().as_millis();
    control.tick(elapsed_in_ms).unwrap();
}

完整代码示例

要将上面 C 语言的效果(蓝灯交替亮灭)用 Rust 实现,完整的 main.rs 代码如下:

#![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);
    }
}

最终效果

无论是 C 语言版本还是 Rust 版本,最终实现的效果是相同的:连接在 GPIO 48 上的 WS2812 LED 灯会以约 0.5 秒的间隔交替亮起(蓝色)和熄灭。通过这个简单的项目,你可以直观地感受到 Rust 在嵌入式领域的应用方式,虽然初始工具链配置和概念理解可能比传统的 C/C++ 稍复杂,但其强大的类型安全和丰富的现代语言特性,为构建更可靠、更易维护的嵌入式软件提供了可能。感兴趣的开发者可以在此基础上,探索更多来自 开源实战 领域的复杂项目和驱动库。




上一篇:Claude Skills深度指南:原理剖析、实战教程与AI产品趋势
下一篇:聊聊香橙派新出的AI Station:176TOPS算力,能直接跑32B大模型了
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-9 00:32 , Processed in 1.461372 second(s), 46 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表