2. LED闪烁

任务说明

本案例使用 Rust Embedded HAL 编写一个最小可复现的 LED 闪烁程序,目标为:LED 以 1Hz 频率闪烁(即每 500ms 翻转一次电平)。案例基于 STM32F103 蓝色药丸开发板,使用 stm32f1xx-hal 抽象层,不直接操作寄存器。

硬件连接

板载 LED 接在 PC13 引脚(低电平点亮),因此需要配置为推挽输出,并通过翻转输出数据寄存器实现闪烁。无需额外外部元件。

实现代码


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

#![no_std]
#![no_main]

use panic_halt as _;
use cortex_m_rt::entry;
use stm32f1xx_hal::{pac, prelude::*, timer::Timer};

#[entry]
fn main() -> ! {
    // 获取外设与时钟
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();

    let mut flash = dp.FLASH.constrain();
    let mut rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.freeze(&mut flash.acr);

    // 配置 PC13 为推挽输出(低电平点亮)
    let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
    let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);

    // 初始化 500ms 周期定时器(1Hz 翻转)
    let mut timer = Timer::syst(cp.SYST, &clocks).start_count_down(500.millis());

    loop {
        // 翻转 LED 电平
        led.toggle();
        // 阻塞等待定时器溢出
        nb::block!(timer.wait()).unwrap();
    }
}

代码说明

上述代码完成以下核心步骤:

  • 冻结时钟至 8MHz 系统主频(默认配置);
  • 将 PC13 配置为推挽输出模式;
  • 使用 SysTick 定时器生成 500ms 周期;
  • 主循环中翻转 LED 电平后阻塞等待定时器溢出,实现每 500ms 翻转一次,即 1Hz 闪烁频率。

编译与烧录


# 编译(在项目根目录执行)
cargo build --release

# 烧录(使用 stm32flash 或 OpenOCD,示例为 stm32flash)
stm32flash -w target/thumbv7m-none-eabi/release/led_blink -v /dev/ttyUSB0

目标平台为 thumbv7m-none-eabi,需在 .cargo/config.toml 中指定:


[build]
target = "thumbv7m-none-eabi"

验证方法

烧录后观察板载 LED:

  • LED 应持续以 1Hz 频率闪烁(亮 500ms,灭 500ms);
  • 使用秒表或手机计时器对比 10 次闪烁周期,误差应在 ±5% 以内(即 10 次闪烁约 10 秒);
  • 若 LED 常亮或常灭,检查 PC13 连接或代码中引脚配置是否正确。

判定标准:LED 以稳定 1Hz 频率闪烁且无异常停顿,即视为通过。

关键时序示意

LED 电平时序(1Hz 闪烁) 灭(高电平) 亮(低电平) 灭 亮 0ms 500ms 1000ms 1500ms 2000ms ← 1 个完整周期(1s)→

验证观测点

上电后观察 PC13 引脚电平变化:使用逻辑分析仪或万用表测量 PC13 对 GND 电压,应看到方波信号,高电平持续 500ms,低电平持续 500ms,周期为 1s。若使用示波器,可直接测量频率应为 1.00Hz ±0.05Hz。