6.Rust嵌入式入门——中断

338 阅读6分钟

6.Rust嵌入式入门——中断

本节将学习如何利用中断来控制灯光的开关。

1.中断介绍

中断这个概念可以想象成我们在日常生活中经常会遇到的情况。比如你正在厨房专心致志地做晚餐,这时候门铃响了。门铃声就是一种“中断”,它打断了你正在做的事情(做饭),迫使你去处理另一件事情(开门)。处理完之后(比如看看是谁来了,可能是快递或者邻居),你可以回到厨房继续做饭。在计算机或微控制器的世界里,“中断”就是类似这样的机制。

中断的基本概念

  • 中断源:门铃就像是中断源,它可以是外部的物理事件(比如按钮被按下)或者是内部的某些条件满足(比如定时器计数完成)。
  • 中断服务程序:当你听到门铃响后去开门查看是谁来的这一系列动作,就相当于中断服务程序。它是预先定义好的一段代码,当某个特定的中断发生时就会执行这段代码。
  • 中断返回:处理完门铃后回到厨房继续做饭,这就好比中断服务程序执行完毕后返回到被打断的地方继续执行原来的任务。

ESP32-C3 中的中断

ESP32-C3 是一款低成本、低功耗的微控制器,非常适合物联网应用。它也支持中断机制来处理各种事件。

  • GPIO 中断:想象一下你把一个按钮连接到了ESP32-C3的一个引脚上。当你按下按钮时,这个动作就可以触发一个中断,使得ESP32-C3停止正在做的事情并执行预先设定好的中断服务程序。例如,这个程序可能就是让一个LED灯亮起来。
  • 定时器中断:你也可以设置一个定时器,当定时器计数达到一定值时触发中断。这就像是设置了一个闹钟,到了时间就提醒你去做某件事。比如,你可以设定每过一分钟,ESP32-C3 就检查一次传感器的数据。

如何使用中断

在ESP32-C3中使用中断通常涉及以下步骤:

  1. 配置中断:你需要告诉ESP32-C3哪个引脚会触发中断,以及中断应该在什么条件下触发(如上升沿、下降沿等)。
  2. 编写中断服务程序:定义当中断发生时要执行的操作。
  3. 启用中断:确保中断已经被正确启用,这样当条件满足时,中断就能生效。

通过这种方式,ESP32-C3 可以高效地处理各种外部事件,而不需要一直轮询检查这些事件是否发生,从而节省了资源并且提高了响应速度。

2.官方案例

在esp32c3中,GPIO9即BOOT按钮

use anyhow::Result;
use esp_idf_svc::hal::{
    gpio::{InterruptType, PinDriver, Pull},
    peripherals::Peripherals,
    task::notification::Notification,
};
use std::num::NonZeroU32;

fn main() -> Result<()> {
    esp_idf_svc::sys::link_patches(); // 链接补丁

    let peripherals = Peripherals::take()?; // 获取外围设备句柄

    let mut button = PinDriver::input(peripherals.pins.gpio9)?; // 创建 GPIO 9 的输入引脚驱动
    button.set_pull(Pull::Up)?; // 设置引脚为上拉模式
    button.set_interrupt_type(InterruptType::PosEdge)?; // 设置中断类型为上升沿触发

    let notification = Notification::new(); // 创建一个通知对象
    let notifier = notification.notifier(); // 获取通知器

    unsafe {
        // 使用不安全代码块来注册中断服务程序
        button.subscribe(move || {
            // 注册中断服务程序
            notifier.notify_and_yield(NonZeroU32::new(1).unwrap()); // 触发通知并挂起当前任务
        })?;
    }

    loop {
        // 主循环
        button.enable_interrupt()?; // 启用中断
        notification.wait(esp_idf_svc::hal::delay::BLOCK); // 等待通知
        println!("Button pressed!"); // 打印消息,表示按钮被按下
    }
}

关键点解释

  • 中断源:在这个例子中,中断源是 peripherals.pins.gpio9,即 GPIO 9 这个引脚。
  • 中断配置
    • 设置为上拉模式 (Pull::Up),意味着没有外力作用时,引脚保持高电平。
    • 设置中断类型为上升沿触发 (InterruptType::PosEdge),这意味着当引脚从低电平变为高电平时会触发中断。
  • 中断服务程序:中断服务程序是通过 button.subscribe() 注册的匿名函数。在这个函数中,当中断发生时,调用 notifier.notify_and_yield() 方法来发送通知并挂起当前任务。
  • 主循环
    • 在主循环开始前,需要启用中断 (button.enable_interrupt()).
    • 循环中,使用 notification.wait() 来等待通知。一旦收到通知,就打印出 "Button pressed!",表示按钮被按下。

通过这种方式,每当 GPIO 9 的电平发生变化时,中断就会被触发,进而执行相应的操作。

3.中断控制开关灯

新建bin/interrupt.rs文件

// 导入标准库和相关外部 crate,用于硬件抽象、多线程通信等
use std::{
    num::NonZeroU32,
    sync::{mpsc::channel, Arc},
};

// 导入 esp-idf-svc 和 hal 层相关模块,用于 GPIO 操作和任务通知
use esp_idf_svc::hal::{
    gpio::{InterruptType, PinDriver, Pull},
    task::notification::Notification,
};
// 导入项目中用于控制 WS2812 LED 的模块
use rust_embedded_study::led::WS2812RMT;

// 主函数,返回一个 Result 类型以处理可能的错误
fn main() -> anyhow::Result<()> {
    // 初始化系统和外设
    let (_sys, peripherals, _nvs) = rust_embedded_study::init()?;

    // 创建 WS2812 LED 驱动实例
    let mut led = WS2812RMT::new(peripherals.pins.gpio8, peripherals.rmt.channel0)?;
    // 创建按钮输入引脚驱动实例
    let mut button = PinDriver::input(peripherals.pins.gpio9)?;

    // 创建一个无缓冲的通道,用于在线程间传输数据
    let (tx, rx) = channel::<bool>();
    // 配置按钮引脚为上拉,并设置中断类型为上升沿触发
    button.set_pull(Pull::Up)?;
    button.set_interrupt_type(InterruptType::PosEdge)?;

    // 创建一个新的线程来处理按钮中断
    std::thread::spawn(move || -> Result<(), anyhow::Error> {
        // 创建一个通知对象,用于在中断发生时通知其他线程
        let notification = Arc::new(Notification::new());
        let notifier = notification.notifier();
        // 定义一个变量来表示 LED 的状态(开/关)
        let mut open = false;
        // 使用 unsafe 块来调用可能不安全的中断订阅函数
        unsafe {
            button.subscribe(move || {
                notifier.notify_and_yield(NonZeroU32::new(1).unwrap());
            })?;
        }
        // 无限循环,等待中断发生并切换 LED 状态
        loop {
            button.enable_interrupt()?;
            notification.wait(esp_idf_svc::hal::delay::BLOCK);
            open = !open;
            tx.send(open)?;
        }
    });

    // 主线程循环接收 LED 状态,并根据状态设置 LED 颜色
    while let Ok(open) = rx.recv() {
        if open {
            led.set_pixel(rust_embedded_study::led::RGB8::new(255, 255, 0))?;
        } else {
            led.set_pixel(rust_embedded_study::led::RGB8::new(0, 0, 0))?;
        }
    }

    Ok(())
}

然后写入开发板

cargo run --bin interrupt

写入完成后,按下开发板上的BOOT按钮,就能控制灯光的开关了。

最后

🎉恭喜您成功实现了中断控制灯光功能。您所探索的代码可以在GitHub仓库yexiyue/rust-embedded-study中找到。

如果您觉得不错,请点个关注或者收藏,感谢您的支持。