esp32笔记[8]-rust的定时器中断点灯

发布时间 2023-10-21 21:01:06作者: qsBye

摘要

使用rust开发esp32c3实现定时器中断点亮led灯.

超链接

esp32笔记[7]-使用rust+zig开发入门

平台信息

  • esp32c3
  • rust

Mutex锁

use core::cell::RefCell;
use critical_section::Mutex;//no-std库专用的Mutex

我们首先注意到的是静态变量BUTTON。我们需要它,因为在中断处理程序中,我们必须清除按钮上的待处理中断,并且我们需要将按钮从主函数传递到中断处理程序中。由于中断处理程序不能有参数,我们需要使用静态变量将按钮传递到中断处理程序中。我们需要使用互斥锁(Mutex)来确保对按钮的访问安全。请注意,这里使用的Mutex不同于你可能熟悉的libstd中的Mutex,而是来自临界区(critical-section),这也是为什么我们需要将其作为依赖项的原因。

use critical_section::Mutex; 是导入了一个名为Mutex的类型,它是为无标准库环境(no-std)设计的一种互斥锁。在嵌入式系统或某些特定的环境中,可能无法使用标准库中的互斥锁,因此需要使用类似critical_section这样的库来提供互斥锁功能。

互斥锁是一种同步机制,用于控制对共享资源的访问。当多个线程或中断处理程序需要同时访问某个共享资源时,互斥锁可以确保每一时刻只有一个线程或中断处理程序可以访问该资源,从而避免出现竞争条件和数据损坏。

在提到代码中的Mutex时,它可能是用于保护静态变量BUTTON的访问。通过在代码中使用Mutex,可以在不同的线程或中断处理程序之间提供互斥性,确保每一时刻只有一个线程或中断处理程序可以访问按钮。这样可以安全地在中断处理程序中处理按钮的待处理中断,并在主函数中使用按钮的值而不会出现竞争条件。

需要注意的是,该Mutex类型来自于critical_section库,而不是标准库中的Mutex。这是因为在无标准库环境中,通常无法使用标准库提供的互斥锁实现,而需要使用特定于该环境的替代方案,如critical_section库中提供的互斥锁。

构造io口的全局变量以在中断中操作io口

要在中断中访问led变量,需要将其设置为一个全局静态变量。这样,在中断处理函数中就可以直接使用它,而不需要通过锁来访问。

关键代码:

use core::cell::RefCell;//内部可变性类型RefCell
use critical_section::Mutex;//no-std库专用的Mutex
static mut LED_D4: Option<esp32c3_hal::gpio::Gpio12<Output<PushPull>>> = None;

unsafe {
    LED_D4.replace(io.pins.gpio12.into_push_pull_output());
}

//翻转led_d4电平
unsafe {
    if let Some(led) = &mut LED_D4 {
        led.toggle();
    } else {
        esp_println::println!("Toggle LED_D4 failed!");
    }
}

完整示例参照如下代码.

esp-rs的中断及中断服务函数

[https://esp-rs.github.io/no_std-training/03_4_interrupt.html]

//! GPIO interrupt
//!
//! This prints "Interrupt" when the boot button is pressed.
//! It also blinks an LED like the blinky example.

#![no_std]
#![no_main]

use core::cell::RefCell;

use critical_section::Mutex;
use esp32c3_hal::{
    clock::ClockControl,
    gpio::{Event, Gpio9, Input, PullDown, IO},
    interrupt,
    peripherals::{self, Peripherals},
    prelude::*,
    riscv,
    Delay,
};
use esp_backtrace as _;

static BUTTON: Mutex<RefCell<Option<Gpio9<Input<PullDown>>>>> = Mutex::new(RefCell::new(None));//我们需要使用互斥锁Mutex来保证对按钮的访问是安全的

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take();
    let system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // Set GPIO5 as an output
    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
    let mut led = io.pins.gpio5.into_push_pull_output();

    // Set GPIO9 as an input
    let mut button = io.pins.gpio9.into_pull_down_input();

    /*
    我们需要在按钮引脚上调用listen函数来配置外设以触发中断。我们可以为不同的事件触发中断 - 在这里,我们希望在下降沿时触发中断。
    */
    button.listen(Event::FallingEdge);

    /*
    在 critical_section::with 闭包中运行的代码在临界区内执行,cs 是一个令牌,你可以用它来向某个 API "证明" 这一点。
    */
    critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).replace(button));

    /*
    这里的第一个参数是我们想要的中断类型。有几种可能的中断类型。第二个参数选择了优先级,我们选择了Priority3。优先级决定了在多个中断同时触发时首先运行哪个中断。
    */
    interrupt::enable(peripherals::Interrupt::GPIO,interrupt::Priority::Priority3).unwrap();


    // 允许中断
    unsafe {
        riscv::interrupt::enable();
    }

    let mut delay = Delay::new(&clocks);
    loop {
        led.toggle().unwrap();
        delay.delay_ms(500u32);
    }
}

//中断服务函数,函数名`GPIO`需要匹配`peripherals::Interrupt::GPIO,`的GPIO函数
#[interrupt]
fn GPIO() {
    critical_section::with(|cs| {
        esp_println::println!("GPIO interrupt");
        BUTTON
            .borrow_ref_mut(cs)
            .as_mut()
            .unwrap()
            .clear_interrupt();
    });
}

实现

[https://esp-rs.github.io/no_std-training/03_4_interrupt.html]
[https://github.com/esp-rs/esp-hal]
[https://github.com/esp-rs/esp-hal/blob/main/esp32c3-hal/examples/timer_interrupt.rs]

主要代码

src/main.rs

/*
备注:
- 使用no-std,没有常规的main函数
目标平台:
- esp32c3(riscv32imc)
依赖:
- esp32c3-hal(0.12.0)
- esp-backtrace(0.8.0)
- esp-println(0.6.0)
- critical_section(1.1.1)
编译及烧录命令:
- cargo-offline run
- cargo-offline build --release
*/
#![no_std]
#![no_main]

/* start 与zig代码通信 */
#[link(name = "main")]
extern "C" {
    fn add(x: i32, y: i32) -> i32;
}
/* end 与zig代码通信 */

/* start 导入库 */
use esp_println::println;//串口打印
use core::cell::RefCell;//内部可变性类型RefCell
use critical_section::Mutex;//no-std库专用的Mutex

use esp32c3_hal::{
    clock::ClockControl, 
    peripherals::{self, Peripherals, TIMG0, TIMG1},
    prelude::*, 
    timer::{Timer, Timer0, TimerGroup},
    Rtc,
    Delay,
    gpio::{AnyPin,Input, Output, PullDown, PushPull, IO},
    systimer::SystemTimer,
    interrupt,
    riscv,
};

use esp_backtrace as _;// 获取调用堆栈信息
/* end 导入库 */

/* start 全局变量 */
static TIMER0: Mutex<RefCell<Option<Timer<Timer0<TIMG0>>>>> = Mutex::new(RefCell::new(None));
static TIMER1: Mutex<RefCell<Option<Timer<Timer0<TIMG1>>>>> = Mutex::new(RefCell::new(None));
static mut LED_D4: Option<esp32c3_hal::gpio::Gpio12<Output<PushPull>>> = None;
/* end 全局变量 */

/* start 程序入口点 */
#[entry]
fn main() -> ! {
    // 实例化对象和定义变量
    let peripherals = Peripherals::take();
    let mut system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // TIMG0和TIMG1各自包含一个通用定时器和一个看门狗定时器
    let mut rtc = Rtc::new(peripherals.RTC_CNTL);
    let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks,&mut system.peripheral_clock_control);
    let mut wdt0 = timer_group0.wdt;
    let mut timer0 = timer_group0.timer0;
    let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks,&mut system.peripheral_clock_control);
    let mut wdt1 = timer_group1.wdt;
    let mut timer1 = timer_group1.timer0;

    // 延时函数初始化
    let mut delay = Delay::new(&clocks);

    // 配置gpio口
    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);

    // 配置引脚功能
    //let mut led_d4 = io.pins.gpio12.into_push_pull_output();
    let mut led_d5 = io.pins.gpio13.into_push_pull_output();

    unsafe {
        LED_D4.replace(io.pins.gpio12.into_push_pull_output());
    }

    // 初始化定时器0中断
    interrupt::enable(
        peripherals::Interrupt::TG0_T0_LEVEL,
        interrupt::Priority::Priority1,
    )
    .unwrap();
    timer0.start(500u64.millis());
    timer0.listen();

    // 初始化定时器1中断
    interrupt::enable(
        peripherals::Interrupt::TG1_T0_LEVEL,
        interrupt::Priority::Priority1,
    )
    .unwrap();
    timer1.start(1u64.secs());
    timer1.listen();

    // 打开定时器引用锁Mutex,使得定时器中断handler安全跳转
    critical_section::with(|cs| {
        TIMER0.borrow_ref_mut(cs).replace(timer0);
        TIMER1.borrow_ref_mut(cs).replace(timer1);
    });

    // 允许中断
    unsafe {
        riscv::interrupt::enable();
    }

    // 关闭rtc和wdt
    //rtc.swd.disable();
    //rtc.rwdt.disable();
    //wdt0.disable();
    //wdt1.disable();

    println!("Compute");
    unsafe {
        println!("{}", add(4,5));
    }

    // 开始循环
    loop {
        println!("Compute(ziglang):");
        unsafe {
            println!("{}", add(4,5));//println波特率115200
        }

        // 翻转led_d5电平
        led_d5.toggle().unwrap();

        delay.delay_ms(2000u32);//延时2000ms
    }

}
/* end 程序入口点 */

/* start 中断处理函数 */
#[interrupt]
fn TG0_T0_LEVEL() {
    critical_section::with(|cs| {
        esp_println::println!("Interrupt 1");

        let mut timer0 = TIMER0.borrow_ref_mut(cs);
        let timer0 = timer0.as_mut().unwrap();

        timer0.clear_interrupt();
        timer0.start(500u64.millis());
    });
}

#[interrupt]
fn TG1_T0_LEVEL() {
    critical_section::with(|cs| {
        esp_println::println!("Interrupt 11");

        //翻转led_d4电平
        unsafe {
            if let Some(led) = &mut LED_D4 {
                led.toggle();
            } else {
                esp_println::println!("Toggle LED_D4 failed!");
            }
        }

        let mut timer1 = TIMER1.borrow_ref_mut(cs);
        let timer1 = timer1.as_mut().unwrap();

        timer1.clear_interrupt();
        timer1.start(1u64.secs());
    });
}
/* end 中断处理函数 */

.cargo/config.toml

[target.riscv32imc-unknown-none-elf]
runner = "espflash flash --monitor"
#runner = "cargo espflash flash --release --monitor"
#runner = "espflash: cargo build --release && espflash flash './target/riscv32imc-unknown-none-elf/release/esp32c3_zig'"
#runner = "wokwi-server --chip esp32c3"

[build]
rustflags = [
  # enable the atomic codegen option for RISCV
  "-C", "target-feature=+a",

  # Tell the `core` library that we have atomics, even though it's not
  # specified in the target definition
  "--cfg", "target_has_atomic_load_store",
  "--cfg", 'target_has_atomic_load_store="8"',
  "--cfg", 'target_has_atomic_load_store="16"',
  "--cfg", 'target_has_atomic_load_store="32"',
  "--cfg", 'target_has_atomic_load_store="ptr"',
  "--cfg", "target_has_atomic",
  "--cfg", 'target_has_atomic="8"',
  "--cfg", 'target_has_atomic="16"',
  "--cfg", 'target_has_atomic="32"',
  "--cfg", 'target_has_atomic="ptr"',

  # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.)
  # NOTE: May negatively impact performance of produced code
  "-C", "force-frame-pointers",
  "-C", "link-arg=-Tlinkall.x",
  "-C", "link-arg=-Lziglib",
  "-C", "link-arg=-lmain"
]
target = "riscv32imc-unknown-none-elf"

[unstable]
build-std = ["core"]

Cargo.toml

[package]
name = 'esp32c3_zig'
edition = '2021'
version = '0.1.0'
authors = [
    'Juraj Michálek <juraj.michalek@espressif.com>',
    'qsbye',
]
license = 'MIT OR Apache-2.0'

[package.metadata]
last-modified-system-time = 1697885007
[dependencies.critical-section]
version = '1.1.1'
features = []

[dependencies.esp-backtrace]
version = '0.8.0'
features = [
    'esp32c3',
    'panic-handler',
    'print-uart',
    'exception-handler',
]

[dependencies.esp-println]
version = '0.6.0'
features = ['esp32c3']

[dependencies.esp32c3-hal]
version = '0.12.0'
features = []

编译及运行

#cargo-offline可以用cargo命令替代
#测试编译
cargo-offline build --release
#编译及运行
cargo-offline run

效果

闪灯效果动图

输出:

Compute(ziglang):
9
Interrupt 11
Interrupt 1
Interrupt 1
Interrupt 11
Interrupt 1
Interrupt 1
Compute(ziglang):
9
Interrupt 11
Interrupt 1
Interrupt 1
Interrupt 11
Interrupt 1
Interrupt 1
Compute(ziglang):
9
Interrupt 11
Interrupt 1
Interrupt 1
Interrupt 11
Interrupt 1
Interrupt 1
Compute(ziglang):
9
Interrupt 11
Interrupt 1