4. GPIO输出驱动蜂鸣器

任务说明

本案例实现按键控制LED翻转:每次按下按键(检测到下降沿),LED状态翻转一次。使用GPIO外部中断方式检测按键,避免轮询占用CPU。

硬件连接

MCU STM32F103 PA0 (EXTI0) PC13 KEY 10k 3.3V GND LED 330Ω GND 按下按键 → PA0下降沿 → 中断触发 → LED翻转

代码实现

// Cargo.toml 依赖:
// [dependencies]
// embedded-hal = "0.2.7"
// nb = "1"
// cortex-m = "0.7"
// cortex-m-rt = "0.7"
// panic-halt = "0.2"
// stm32f1xx-hal = { version = "0.9", features = ["rt", "stm32f103"] }

use stm32f1xx_hal::{
    prelude::*,
    pac,
    gpio::{gpioc::PC13, gpioa::PA0, Edge, ExtiPin, Input, PullUp, Output, PushPull},
    interrupt,
    timer::Timer,
};
use core::cell::RefCell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;

// 全局LED状态(由中断修改,主循环读取)
static LED_ON: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));

// 中断中使用的LED引脚句柄(通过临界区访问)
static mut LED_PIN: Option<PC13<Output<PushPull>>> = None;

#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();
    let mut rcc = dp.RCC.constrain();
    let mut flash = dp.FLASH.constrain();
    let mut pwr = dp.PWR.constrain();
    let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr);

    // 初始化GPIO
    let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
    let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);

    // PA0: 按键输入,上拉(默认高电平,按下接地)
    let mut key = gpioa.pa0.into_pull_up_input(&mut gpioa.crl);
    key.make_interrupt_source(&mut dp.AFIO);
    key.trigger_on_edge(&dp.EXTI, Edge::Falling);
    key.enable_interrupt(&dp.EXTI);

    // PC13: LED输出(板上LED,低电平点亮)
    let led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
    led.set_high(); // 初始熄灭

    // 保存LED句柄到全局
    unsafe {
        LED_PIN = Some(led);
    }

    // 使能EXTI0中断
    unsafe {
        cortex_m::peripheral::NVIC::unmask(pac::Interrupt::EXTI0);
    }

    loop {
        // 主循环可做其他任务
        // 此处仅演示,实际可休眠或执行其他逻辑
        cortex_m::asm::wfi();
    }
}

// EXTI0中断服务函数
#[interrupt]
fn EXTI0() {
    cortex_m::interrupt::free(|cs| {
        // 清除中断标志
        let dp = unsafe { pac::Peripherals::steal() };
        let exti = &dp.EXTI;
        exti.pr.write(|w| w.pr0().set_bit());

        // 翻转LED
        if let Some(led) = unsafe { LED_PIN.as_mut() } {
            if led.is_set_high() {
                led.set_low();
            } else {
                led.set_high();
            }
        }

        // 更新全局状态(可选,供主循环查询)
        let mut state = LED_ON.borrow(cs).borrow_mut();
        *state = !*state;
    });
}

关键说明

  • 按键消抖:本案例使用硬件上拉+下降沿中断,未做软件消抖。若实际使用中出现抖动导致多次翻转,可在中断中加入延时消抖(如循环延时10ms)或使用定时器消抖。
  • 中断安全:LED引脚通过Mutex<RefCell<>>保护,确保中断与主循环之间不会产生数据竞争。
  • 中断标志清除:必须在中断服务函数中清除EXTI挂起位,否则中断会反复触发。

验证方法

  1. 将代码编译烧录至STM32F103开发板(PA0接按键到GND,PC13接板载LED)。
  2. 上电后LED处于熄灭状态。
  3. 每次按下按键并释放,观察LED状态翻转:灭→亮→灭→亮……
  4. 若按下一次LED翻转多次,说明按键抖动影响,需增加消抖处理。

判定标准:每次完整按下并释放按键,LED状态恰好翻转一次,且状态变化与按键动作一一对应。