Rust开发ESP32S3物联网应用(二)—— IO控制

B站影视 电影资讯 2025-10-31 15:38 5

摘要:ESP32的Rust库是基于ESP32的IDF库来的,是对它的C库的一个绑定和封装。ESP32S3的SDK有三层:esp_idf_sys、esp_idf_hal和esp_idf_svc是三个核心库,它们基于 ESP-IDF(ESP32 官方 C SDK)构建,

我们还是用易芯联的小智AI聊天机器人开进一步介绍其IO的使用还控制,来控制LED。

ESP32的Rust库是基于ESP32的IDF库来的,是对它的C库的一个绑定和封装。ESP32S3的SDK有三层:esp_idf_sys、esp_idf_hal和esp_idf_svc是三个核心库,它们基于 ESP-IDF(ESP32 官方 C SDK)构建,形成了从底层到高层的抽象层次,各自承担不同角色:

是 ESP-IDF 的原始 C API 的 Rust 绑定(通过bindgen自动生成),直接映射了 ESP-IDF 的所有函数、结构体和常量。

特点:完全对应 ESP-IDF 的 C 接口,没有额外封装,语法和使用方式接近 C 语言。提供了最底层的硬件访问能力,但使用时需要手动管理内存、处理指针和错误码,安全性较低。是其他高层库(esp_idf_hal、esp_idf_svc)的基础,它们都依赖esp_idf_sys实现功能。

需要直接调用 ESP-IDF 的特定 C API,或实现高层库未覆盖的功能时使用。

2.esp_idf_hal:硬件抽象层(Hardware Abstraction Layer)

在esp_idf_sys之上封装了 ESP32 的硬件外设接口,提供类型安全、符合 Rust 风格的硬件操作方式。

特点:抽象了 GPIO、UART、SPI、I2C、ADC、DAC、定时器等硬件外设,提供面向对象的 API。基于 Rust 的类型系统保证资源安全(例如避免同一 GPIO 被多次初始化),减少内存安全问题。屏蔽了底层 C API 的细节,无需直接处理指针或错误码,使用 Rust 的Result类型处理错误。

一般地直接操作硬件外设时使用,例如控制 LED、读取传感器数据、与外部设备通信等。

在esp_idf_hal和esp_idf_sys之上,封装了 ESP-IDF 提供的系统服务和网络功能,是更高层次的抽象。

特点:提供 WiFi(STA/AP 模式)、以太网、HTTP 客户端 / 服务器、MQTT、NVS(非易失性存储)、定时器、日志等系统服务的 Rust 接口。接口设计更贴近应用开发,例如通过WifiController轻松配置 WiFi 连接,通过HttpClient发送 HTTP 请求。内部处理了复杂的状态管理和资源释放,简化了应用开发流程。

开发需要网络通信、存储数据、系统调度等功能的应用时使用,无需关注底层实现细节。

依赖关系:esp_idf_svc --> esp_idf_hal --> esp_idf_sysesp_idf_sys:最底层,直接映射 C API,灵活性最高但使用复杂。esp_idf_hal:中间层,抽象硬件外设,兼顾安全性和易用性。esp_idf_svc:最高层,封装系统服务,专注于应用功能开发。

实际开发中,通常优先使用esp_idf_svc和esp_idf_hal,仅在需要特殊硬件操作或系统功能时才直接调用esp_idf_sys。

易芯联小智AI机器人有一个可控的LED灯,使用的是GPIO3输出模式,我们看一下官方的示例:https://github.com/esp-rs/esp-idf-hal/blob/master/examples/blinky.rs:

use esp_idf_hal::delay::FreeRtos;use esp_idf_hal::gpio::*;use esp_idf_hal::peripherals::Peripherals;fn main -> anyhow::Result { esp_idf_hal::sys::link_patches; let peripherals = Peripherals::take?; let mut led = PinDriver::output(peripherals.pins.gpio3)?; loop { led.set_high?; // we are sleeping here to make sure the watchdog isn't triggered FreeRtos::delay_ms(1000); led.set_low?; FreeRtos::delay_ms(1000); }}

这个是最标准最简单的用法,先用Peripherals::take获取外设的一个对象,然后对IO进行控制。但是这个函数只能被调用一次,而且返回的是个非线程安全的单例对象,那就是说只能在main函数里来访问这个对象,不能在别的函数里访问,非常难用,尤其是想在多线程应用中使用。

如果我们想封装一下Led灯的驱动,还想再把别的IO封装一下,这么用就很麻烦了,需要使用一些不安全的方法。

在易芯联小智AI的板儿上还有一个引用GPIO47作为输入引脚检测是否正在充电,如果我们把LED的控制封装成一Led对象,对电源的检测封装成Power对象,使用上面的对象就不行了,看了SDK的源代码发现esp_idf_hal::gpio::Pins有一个new函数,多New几个出来就可以独立封装了。但这个函数被标记为unsafe,也就是说同一个IO口有可能被多次使用,要程序员自己保证安全 。我们在这里就用这个方法:

use std::{ sync::Mutex, thread, time::Duration};use esp_idf_hal::{gpio::{PinDriver, Pull}, prelude::Peripherals};fn main{ // 这一行不能去掉,每个main函数里都要有,好像是ESP的IDF里没有stat函数,使用CMake链接时容易出错,这里是打了个补丁,定义了这个函数,不能去掉 esp_idf_sys::link_patches; esp_idf_svc::log::EspLogger::initialize_default; let mut led = Led::new; let mut power = Power::new; loop{ log::info!("ON"); led.set_on; thread::sleep(Duration::from_millis(1000)); log::info!("OFF"); led.set_off; thread::sleep(Duration::from_millis(1000)); if(power.is_charging){ log::info!("Charging..."); } }}// 定义一个LED的封装struct Led{ pin: esp_idf_hal::gpio::PinDriver,}impl Led { fn new -> Self { let pins = unsafe{ esp_idf_hal::gpio::Pins::new }; Led { pin: PinDriver::output(pins.gpio3).unwrap } } fn set_on(&mut self){ self.pin.set_high.unwrap; } fn set_off(&mut self){ self.pin.set_low.unwrap; }}// 定义一个电源管理的对象struct Power{ pin_charge_stat: esp_idf_hal::gpio::PinDriver,}impl Power { fn new -> Self { let pins = unsafe{ esp_idf_hal::gpio::Pins::new }; let mut pin = PinDriver::input(pins.gpio47).unwrap; pin.set_pull(Pull::Up).unwrap; Power { pin_charge_stat: pin } } fn is_charging(&self) -> bool { self.pin_charge_stat.is_low } }

这样Power和Led就可以独立使用了。

如果我们有多个同类的IO,比如多个LED都要控制,使用esp_idf_svc里定义的这些类就不方便了,它相当于对每一个IO引脚都定义成了一个对象,而且每个对象的类型都不相同不能使用数组来控制,反而麻烦,我们可以用esp_idf_sys里底层的API来控制。

use std::thread;use std::time::Duration;use std::sync::{Arc, atomic::{AtomicBool, Ordering}};pub struct Led { pin: u8, // IO口编号 freq_hz: f32, // 闪烁频率 duty_cycle: f32, // 占空比 (0.0~1.0) running: Arc, handle: Option>, // 可注入的回调,用于测试或替换实际IO操作: (pin, level) pin_setter: Option>,}#[allow(dead_code)]impl Led { pub fn new(pin: u8, freq_hz: f32, duty_cycle: f32) -> Self { unsafe{ // Reset the pin to a known state (safe no-op if already configured) let _ = esp_idf_sys::gpio_reset_pin(pin as esp_idf_sys::gpio_num_t); // Try to set direction to output (ignore error) let _ = esp_idf_sys::gpio_set_direction(pin as esp_idf_sys::gpio_num_t, esp_idf_sys::gpio_mode_t_GPIO_MODE_OUTPUT); } Led { pin, freq_hz, duty_cycle, running: Arc::new(AtomicBool::new(false)), handle: None, pin_setter: None, } } /// 注入自定义的引脚设置函数,方便测试或替换实际GPIO控制 pub fn with_pin_setter(mut self, f: F) -> Self where F: Fn(u8, bool) + Send + Sync + 'static, { self.pin_setter = Some(Arc::new(f)); self } // 启动LED闪烁 pub fn start(&mut self) { if self.running.load(Ordering::SeqCst) { return; } self.running.store(true, Ordering::SeqCst); let running = self.running.clone; let pin = self.pin; let freq_hz = self.freq_hz; let duty_cycle = self.duty_cycle; let pin_setter = self.pin_setter.clone; self.handle = Some(thread::spawn(move || { while running.load(Ordering::SeqCst) { // 点亮LED if let Some(cb) = &pin_setter { cb(pin, false); } else { Led::set_pin(pin, false); } let on_time = (1.0 / freq_hz) * duty_cycle; thread::sleep(Duration::from_secs_f32(on_time)); // 熄灭LED if let Some(cb) = &pin_setter { cb(pin, true); } else { Led::set_pin(pin, true); } let off_time = (1.0 / freq_hz) * (1.0 - duty_cycle); thread::sleep(Duration::from_secs_f32(off_time)); } // 退出时确保LED熄灭 if let Some(cb) = &pin_setter { cb(pin, false); } else { Led::set_pin(pin, false); } })); } // 停止LED闪烁 pub fn stop(&mut self) { self.running.store(false, Ordering::SeqCst); if let Some(handle) = self.handle.take { let _ = handle.join; } if let Some(cb) = &self.pin_setter { cb(self.pin, false); } else { Led::set_pin(self.pin, true); } } // 设置频率 pub fn set_freq(&mut self, freq_hz: f32) { self.freq_hz = freq_hz; } // 设置占空比 pub fn set_duty_cycle(&mut self, duty_cycle: f32) { self.duty_cycle = duty_cycle; } // 实际IO控制函数(需根据平台实现) fn set_pin(pin: u8, high: bool) { // Use esp-idf-sys FFI to set GPIO level. We cast u8 to gpio_num_t and call gpio_set_level. // Ensure we configure the pin as output before setting level (best-effort). #[allow(unused_unsafe)] unsafe { // Finally set level: 1 for high, 0 for low let level: u32 = if high { 1 } else { 0 }; let _ = esp_idf_sys::gpio_set_level(pin as esp_idf_sys::gpio_num_t, level); } }}impl Drop for Led { fn drop(&mut self) { self.stop; }}

这里我们定义了一个Led的对象,创建对象是引脚号作为参数传进去,这样我们可以定义多个LED使用相同的类型,就可以对多个LED进行控制。

五、未来

我现在发布的最新的esp_idf_hal是0.45.2

esp-idf-hal = "0.45.2"

而在其git的主干里已经进一步优化了new函数:https://github.com/esp-rs/esp-idf-hal/blob/master/src/gpio.rs

Rust本身特性比较复杂,ESP32对GPIO的这种抽象导致类型泛滥,使用起来不太好用,对简单的DEMO还可以,尤其对带多线程的std应用非常不友好,加大了复杂性。但看现在的代码进展速度很快,开发组一直也在优化,希望后面能做得更好用一些。

后续我们再基于AI小智机器人再探索更多的特性使用。

来源:物联一尘

相关推荐