Funpack5-3 - 基于 MAX32690EVKIT 实现 SW2 按键控制 LED 闪烁
该项目使用了MAX32690EVKIT 评估套件,实现了按键控制 LED 闪烁的设计,它的主要功能为:外部中断的形式检测按键按下,并进行一定消抖后控制led闪烁。
标签
嵌入式系统
Funpack活动
led闪烁
MAX32690EVKIT
中断
消抖
BOBOEMS
更新2026-08-12
26

Funpack5-3 - 基于 MAX32690EVKIT 实现 SW2 按键控制 LED 闪烁

本报告为 Funpack S5 #3 活动 T1 基础题(按键控制 LED)的项目总结,用于提交至电子森林项目页面。报告正文超过 1500 字,涵盖项目介绍、硬件、方案框图、软件流程、关键代码、功能展示、问题解决与心得。


一、项目介绍

本项目基于 Analog Devices(ADI)MAX32690EVKIT 评估套件,使用官方 MaximSDK(MSDK)裸机开发环境,完成 Funpack S5 #3 活动的 T1 基础题:SW2 按键控制红色 LED 闪烁


实现的核心功能为:系统上电后红色 LED 保持熄灭;第一次有效按下按键 SW2,红色 LED 开始以约 1 Hz 的频率闪烁;再次有效按下 SW2,闪烁停止且 LED 保持熄灭。如此往复,每次按压只触发一次状态切换。


MAX32690 是 ADI 面向可穿戴设备、工业和物联网应用的高性能微控制器,采用 Arm Cortex-M4F 内核,最高主频 120 MHz,集成 3 MB 闪存、1 MB SRAM,并内置 BLE 5.2 无线电。MAX32690EVKIT 评估板将 MCU 的丰富外设以板载外设形式呈现,包括两个用户按键(SW2、SW3)、多颗 LED、USB 转串口、CMSIS-DAP 调试器、音频编解码 MAX9867 及多种扩展接口,非常适合进行嵌入式系统开发学习与原型验证。


本项目选用 T1 基础题并采用 MSDK 裸机路线,不使用操作系统,直接通过寄存器驱动封装完成 GPIO 按键检测、定时器中断和 LED 控制,有助于深入理解 MCU 的中断、定时器与状态机设计。


二、硬件介绍

2.1 硬件平台

主控 : ADI MAX32690(Arm Cortex-M4F @120 MHz,3 MB Flash / 1 MB SRAM)

评估板 : MAX32690EVKIT 评估套件

用户按键 : SW2(P4.0,上拉,按下为低电平)

红色 LED : LED1(P0.14,低电平点亮 active-low)

调试/烧录 : 板载 CMSIS-DAP 调试器 + OpenOCD

串口 : USB 转串口(COM8,115200 8-N-1)


2.2 关键硬件特性

SW2 的特殊性:MAX32690 的 SW2 连接在专用 GPIO4 唤醒端口(P4.0)。该端口在硬件层面只支持双边沿(上升沿与下降沿)唤醒中断,无法直接请求单个下降沿中断。这与普通 GPIO 端口不同,是本项目软件设计需要重点适配的硬件约束。


LED 为低电平有效:LED1 连接在 P0.14,输出低电平时点亮,高电平时熄灭。代码中对应使用 `LED_On`/`LED_Off`/`LED_Toggle` 驱动 API,无需关心电平极性细节。


JP5 跳线:官方 TMR 示例说明,使用 LED1 时需要闭合 JP5(LED1_EN)跳线,确保 LED 供电使能。


三、方案框图

                    ┌─────────────────────────────────────────────┐
                    │              MAX32690EVKIT                  │
                    │                                             │
  ┌──────────┐      │  ┌──────────────┐    ┌──────────────────┐   │
  │   SW2    │──────┼─▶│  GPIO4 (P4.0)│───▶│ PB 驱动(按键回调) │   │
  │ 按键输入 │      │  │  双边沿中断   │    │  仅置中断挂起标志 │   │
  └──────────┘      │  └──────────────┘    └────────┬─────────┘   │
                    │                               ▼             │
                    │                    ┌──────────────────┐     │
                    │                    │  主循环按键状态机  │     │
                    │                     (50ms 消抖, 确认  │     │
                    │                    │  稳定按下/松开)    │     │
                    │                    └────────┬─────────┘     │
                    │                             ▼               │
                    │                    ┌──────────────────┐     │
                    │                    │ 切换闪烁使能状态   │     │
                    │                    └────────┬─────────┘     │
                    │                             ▼               │
                    │  ┌──────────────┐    ┌──────────────────┐   │
                    │  │  TMR0 定时器  │───▶│ TMR0 中断处理     │   │
                    │  │ 连续模式 2Hz  │    │ LED_Toggle(LED1) │   │
                    │  └──────────────┘    └────────┬─────────┘   │
                    │                               ▼             │
  ┌──────────┐      │                    ┌──────────────────┐   │
  │  LED1    │◀─────┼────────────────────│  P0.14 (active-low)│   │
  │ 红色LED  │      │                    └──────────────────┘   │
  └──────────┘      │                                             │
                    └─────────────────────────────────────────────┘


系统由三个中断源驱动:SysTick 提供 1 ms 毫秒计数时基;GPIO4 双边沿中断唤醒按键处理;TMR0 定时器以 2 Hz 频率产生中断用于翻转 LED。所有实时要求不高的逻辑(按键消抖、状态切换)都放在主循环中处理。


四、软件流程

4.1 总体流程


系统上电


SystemInit()Board_Init() 初始化板级资源(MSDK 自动调用)


main():
├─ PB_Init() 按键驱动初始化(检查返回值)
├─ LED_Init() LED 驱动初始化(检查返回值)
├─ LED_Off() 初始熄灭 LED
├─ SysTick_Config() 配置 1 ms 系统时基
├─ blink_timer_init() 配置 TMR0 连续模式 2 Hz 中断
├─ PB_Set_Polarity(SW2, 低电平有效)
├─ PB_RegisterCallbackRiseFall() 注册按键回调
└─ 使能按键中断


主循环(死循环):
├─ process_button() 按键状态机(消抖处理)
└─ __WFI() 无事件时休眠省电


4.2 按键消抖状态机

由于 GPIO4 只能产生双边沿中断,中断回调无法直接区分按下与松开。本项目将中断回调简化为"仅置位挂起标志",而在主循环中通过四态状态机完成消抖与语义过滤:

状态

含义

转移条件

IDLE(空闲)

等待按键事件

收到中断挂起标志 → 禁用按键中断,进入 VERIFY_PRESS

VERIFY_PRESS(验证按下)

等待 50 ms 消抖

消抖后仍为低电平 → 视为有效按下,切换闪烁,进入 WAIT_RELEASE;否则重新武装中断

WAIT_RELEASE(等待松开)

等待按键释放

检测到高电平 → 记录截止时刻,进入 VERIFY_RELEASE

VERIFY_RELEASE(验证松开)

等待松开消抖 50 ms

稳定松开 → 重新武装中断,回到 IDLE;再次变低 → 回到 WAIT_RELEASE

该设计确保一次完整的"按下-松开"过程只触发一次闪烁状态切换,有效过滤机械抖动与 GPIO4 双沿中断带来的干扰。


五、关键代码

5.1 定时器周期计算与初始化

定时器以 2 Hz 产生中断,每次中断翻转一次 LED 电平,占空比 50%,因此 LED 的完整亮灭周期约为 1 秒,即约 1 Hz 闪烁。

period_ticks = MXC_TMR_GetPeriod(BLINK_TIMER, BLINK_TIMER_CLOCK,
                                 BLINK_TIMER_PRESCALER, BLINK_TOGGLE_HZ);
if (period_ticks == 0U) {
    return E_BAD_PARAM;
}


timer_cfg.pres = TMR_PRES_32;          /* 预分频 32 */
timer_cfg.mode = TMR_MODE_CONTINUOUS;  /* 连续模式 */
timer_cfg.bitMode = TMR_BIT_MODE_32;   /* 32 位计数 */
timer_cfg.clock = BLINK_TIMER_CLOCK;   /* APB 时钟 */
timer_cfg.cmp_cnt = period_ticks;      /* 计数周期 */
error = MXC_TMR_Init(BLINK_TIMER, &timer_cfg, false);


5.2 定时器中断处理

中断函数保持短小,只做清标志和条件翻转;是否翻转由主循环设置的 `g_blink_enabled` 标志控制。

static void blink_timer_handler(void)
{
    MXC_TMR_ClearFlags(BLINK_TIMER);  /* 先清中断标志 */


    if (g_blink_enabled) {            /* 仅在使能时翻转 */
        LED_Toggle(BLINK_LED);
    }
}


5.3 按键回调与状态切换

`toggle_blink_state` 通过关中断保护"读取-修改-写入"临界区,避免与定时器中断并发导致状态不一致。

static void button_callback(void *callback_data)
{
    (void)callback_data;
    g_button_irq_pending = 1U;  /* 中断中只置标志 */
}


static void toggle_blink_state(void)
{
    __disable_irq();                    /* 进入临界区 */
    enable = !g_blink_enabled;


    MXC_TMR_Stop(BLINK_TIMER);          /* 停表清零 */
    MXC_TMR_SetCount(BLINK_TIMER, 0U);
    MXC_TMR_ClearFlags(BLINK_TIMER);


    if (enable) {
        LED_On(BLINK_LED);
        g_blink_enabled = true;
        MXC_TMR_Start(BLINK_TIMER);
    } else {
        g_blink_enabled = false;
        LED_Off(BLINK_LED);
    }
    __enable_irq();
}


5.4 溢出安全的截止时间比较

毫秒计数器为 32 位无符号数,运行约 49.7 天后会回绕。该函数利用无符号减法回绕与有符号解释,在计数器溢出时依然能正确判断截止时间是否到达。

static bool deadline_reached(uint32_t now, uint32_t deadline)
{
    return (int32_t)(now - deadline) >= 0;
}


六、功能展示

image.png

按下开始闪烁

image.png

按下结束闪烁


image.png

串口打印信息

七、问题解决

7.1 GPIO4 只能双边沿中断

问题:SW2 位于 MAX32690 专用 GPIO4 唤醒端口,硬件只支持双边沿中断,普通 `PB_RegisterCallback()` 尝试请求下降沿但底层驱动未向上层传播 `E_NOT_SUPPORTED` 失败。


处理:改用与硬件能力匹配的 `PB_RegisterCallbackRiseFall()` 注册双边沿回调,回调内仅置位挂起标志;在主循环状态机中通过 50 ms 消抖只接受"稳定低电平按下",并在确认稳定松开后才重新武装中断,实现了等效的"下降沿语义"。


7.2 按键机械抖动

问题:机械按键按下和松开瞬间会产生毫秒级的触点抖动,若不处理会导致一次按压被识别为多次。


处理:按下和释放方向各设置 50 ms 消抖窗口,使用 SysTick 1 ms 时基非阻塞计时,消抖期间不阻塞主循环。


7.3 工具链 PATH 问题

问题:直接以绝对路径启动 `make.exe` 不会自动让其子进程找到 GCC/OpenOCD;若 PATH 未包含 MSDK 工具目录,会出现 `arm-none-eabi-gcc`/`openocd.exe` 找不到的错误。MSDK 自带的 `setenv.bat` 会调用更新器,存在改写 SDK 安装的风险。


处理:不运行 `setenv.bat`,改为显式将 GCC、Make、OpenOCD 目录前置到当前 PowerShell PATH;并添加项目本地 `.vscode/` 一键工作流(构建、烧录运行、Cortex-Debug 调试),固定 SDK 与工具路径,无需手工配置环境。


7.4 闪烁频率与定时器计算

问题:需要产生精确的约 1 Hz 闪烁,直接手算定时器重载值容易出错且不便移植。


处理:使用 `MXC_TMR_GetPeriod()` 由时钟频率、分频系数与目标频率统一计算计数周期,参数化定义(`BLINK_TOGGLE_HZ = 2`、`BLINK_TIMER_PRESCALER = 32`),便于调整。


八、心得与总结

通过本项目,我完整走通了"需求分析 → 硬件资源梳理 → 驱动适配 → 状态机设计 → 构建烧录 → 实机验证"的嵌入式开发全流程。

在软件设计上收获最大的是**中断上下文的职责划分**:中断服务函数保持极短(只置标志、清标志、翻转 LED),把需要耗时的逻辑(消抖、状态转移)全部放到主循环,配合 `__WFI()` 实现无事件时的低功耗休眠,这是嵌入式实时系统设计的通用范式。其次是对**硬件约束驱动的设计**印象深刻。GPIO4 只支持双边沿中断这一硬件事实,直接决定了按键软件不能依赖硬件下降沿,而必须用软件状态机补足语义。这提醒我:好的嵌入式设计一定是先读懂硬件手册,再设计软件,而不是反过来。32 位毫秒计数器回绕问题的处理也让我体会到嵌入式系统里"时间"的严谨性——一个看似简单的截止时间比较,在考虑溢出后需要用无符号减法回绕技巧,否则长时间运行的设备会出现隐蔽的偶发故障。

本项目代码量不大,但涉及按键消抖、定时器中断、状态机、临界区保护、溢出安全等多个嵌入式核心知识点,是一次非常扎实的实战训练。后续如果继续深入,可以在本基础上扩展 T1 进阶题(长按平滑调频),或进一步探索 T3 音频频谱分析等更有挑战性的方向。

附件下载
t1-basic-msdk.7z
项目源码
团队介绍
电子爱好者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号