MAX32690 按键控制 LED 闪烁
一、项目介绍
本项目基于 MAX32690EVKIT 评估板,完成"按键控制 LED 闪烁"赛题,包含基础题与进阶题两部分。
基础题(⭐):使用板载按键 SW2,单次按键 LED 开始以约 1Hz 闪烁,再次按键 LED 停止闪烁。要求正确初始化 GPIO 中断、LED 控制逻辑清晰无误触发、闪烁频率约 1Hz。
进阶题(⭐⭐⭐):在基础题之上实现长按按键调节 LED 闪烁频率。短按开关闪烁,长按连续调节频率,频率在 1Hz→5Hz 之间线性变化,到达 5Hz 后保持长按可反向变化回 1Hz,循环往复,频率变化过程平滑无跳变。TFT LCD 屏幕实时显示当前频率。
项目使用 Maxim SDK(MSDK)提供的板级支持包(BSP)和外围驱动库,通过 GPIO 中断、通用定时器(TMR)、TFT 显示驱动等外设协同实现全部功能。
二、硬件介绍
2.1 主控芯片 MAX32690
MAX32690 是 Analog Devices推出的一款超低功耗微控制器,主要特性:
参数 | 规格 |
|---|---|
内核 | ARM Cortex-M4F(带浮点单元) |
主频 | 120 MHz |
Flash | 1 MB |
SRAM | 560 KB |
封装 | WLP / TQFP |
低功耗 | 支持多种低功耗模式 |
2.2 MAX32690EVKIT 评估板
本项目使用的评估板 MAX32690EVKIT 提供以下板载资源:
- 板载按键:SW2(连接 GPIO,按下为低电平,用于触发下降沿中断)
- 板载 LED:LED1、LED2(通过 GPIO 输出控制亮灭)
- TFT LCD 屏幕:1.8 英寸 ST7735 彩色屏,分辨率 128×128,SPI 接口
- USB 调试接口:CN2 接口,集成 CMSIS-DAP 调试器,用于程序烧录和串口通信
2.3 本项目使用的外设
外设 | 用途 |
|---|---|
GPIO(按键) | SW2 按键中断检测,双边沿触发 |
GPIO(LED) | LED 亮灭控制,定时器中断中翻转 |
TMR0(通用定时器) | 连续模式,产生周期中断驱动 LED 闪烁 |
TFT LCD(ST7735) | 实时显示当前闪烁频率(进阶题) |
UART | 串口调试输出,115200 波特率 |
三、方案框图与项目设计思路
3.1 系统方案框图
┌─────────────────────────────────────────────────────────┐
│ MAX32690 MCU │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ SW2 │ │ TMR0 │ │ LED │ │
│ │ 按键 │───▶│ 连续模式 │───▶│ GPIO输出 、│ │
│ │ GPIO中断 │ │ 周期中断 │ │ 翻转 │ │
│ └────┬─────┘ └──────┬───────┘ └──────────────┘ │
│ │ ▲ │
│ │ │ 比较寄存器 │
│ │ │ 实时更新 │
│ ▼ │ │
│ ┌──────────┐ ┌──────┴───────┐ │
│ │ 主循环 │───▶│ 频率调节 │ │
│ │ 短按/长按 │ │ SetCompare │ │
│ │ 状态机 │ └──────────────┘ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ TFT LCD │ 实时显示频率 │
│ │ ST7735 │ │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
3.2 设计思路
基础题设计:
- 使用 MSDK 的 PushButton 驱动注册 SW2 的下降沿中断回调(
PB_RegisterCallback) - 在中断回调中加 50ms 消抖延时,设置
buttonPressed标志位 - 主循环消费标志位,切换 TMR0 定时器的启停
- TMR0 配置为连续模式,中断频率 2Hz(LED 翻转频率 = 2 × 闪烁频率),实现 500ms 亮 / 500ms 灭的 1Hz 闪烁
进阶题设计:
- 按键改用双边沿中断(
PB_RegisterCallbackRiseFall),单个回调同时处理按下和释放,在回调内通过PB_Get()区分边沿 - 短按/长按判定:按下后轮询 800ms,期间释放为短按(切换闪烁),超时为长按(进入频率调节)
- 频率调节:长按期间每 300ms 步进 1Hz,到达 5Hz 反向为递减,到达 1Hz 再反向为递增,形成三角波扫描
- 平滑更新核心:频率变化时仅调用
MXC_TMR_SetCompare()修改比较寄存器,不停机不重初始化,避免计数器复位导致的闪烁节奏中断 - TFT 屏幕实时显示频率,使用
MXC_TFT_FillRect()仅刷新文本带而非全屏清除,减少 SPI 流量
四、调试软件介绍、软件流程图及关键代码介绍
4.1 开发与调试工具
工具 | 用途 |
|---|---|
VSCode + Make | 代码编辑与编译(MSDK 构建系统) |
Eclipse CDT | 备选 IDE,支持导入工程直接编译烧录 |
OpenOCD | 程序烧录与调试,通过 CMSIS-DAP 接口 |
arm-none-eabi-gdb | 嵌入式 GDB 调试器 |
串口终端 | 查看调试输出(115200, 8-N-1) |
4.2 软件流程图
开始
│
▼
初始化LED/TMR0/按键中断/TFT
TFT显示"Ready"
│
▼
┌─── 主循环 ──────────────────────────┐
│ │
│ 按键按下? │
│ │ 是 │
│ ▼ │
│ 轮询800ms判断短按/长按 │
│ │ │
│ ┌──┴──┐ │
│ 短按 长按 │
│ │ │ │
│ ▼ ▼ │
│ 切换 若未闪烁则启动闪烁 │
│ 闪烁 ┌────────────────────┐ │
│ │ 长按循环: │ │
│ │ 延时300ms │ │
│ │ 频率步进1Hz │ │
│ │ 边界?→反向方向 │ │
│ │ SetCompare更新 │ │
│ │ TFT显示频率 │ │
│ └────────────────────┘ │
│ 直到按键释放 │
└─────────────────────────────────────┘
4.3 关键代码介绍
4.3.1 按键中断回调
void buttonHandler(void *pb)
{
MXC_Delay(MXC_DELAY_MSEC(50)); /* 消抖 */
if (PB_Get(0)) {
pressed = 1; /* 下降沿 -> 按键按下 */
} else {
pressed = 0; /* 上升沿 -> 按键释放 */
}
}
说明:PB_RegisterCallbackRiseFall 为双边沿注册单个回调,在回调内通过 PB_Get() 读取当前电平区分按下与释放。50ms 消抖延时过滤机械抖动。
4.3.2 定时器中断处理
void blinkTimerHandler(void)
{
MXC_TMR_ClearFlags(BLINK_TIMER);
LED_Toggle(0);
}
说明:TMR0 连续模式每次计数到比较值触发中断,清除标志后翻转 LED。中断频率 = 2 × 闪烁频率(LED 翻转两次为一个完整闪烁周期)。
4.3.3 频率平滑更新
static void blinkTimerUpdateFreq(float freqHz)
{
uint32_t newCmp = MXC_TMR_GetPeriod(BLINK_TIMER, BLINK_CLOCK_SOURCE,
32, (uint32_t)(freqHz * 2));
if (newCmp <= MXC_TMR_GetCount(BLINK_TIMER)) {
MXC_TMR_SetCount(BLINK_TIMER, 0);
}
MXC_TMR_SetCompare(BLINK_TIMER, newCmp);
}
说明:这是解决闪烁卡顿的关键函数。频率变化时仅更新比较寄存器(MXC_TMR_SetCompare),不调用 MXC_TMR_Stop/Shutdown/Init,避免计数器复位导致的节奏中断。当新比较值小于当前计数值时,清零计数器防止 32 位溢出等待。
4.3.4 主循环长按步进逻辑
while (pressed) {
MXC_Delay(MXC_DELAY_MSEC(RAMP_STEP_MS)); /* 300ms */
currentFreq += (float)rampDir * FREQ_STEP_HZ; /* 步进1Hz */
if (currentFreq >= FREQ_MAX_HZ) {
currentFreq = FREQ_MAX_HZ;
rampDir = -1; /* 到达上限,反向递减 */
} else if (currentFreq <= FREQ_MIN_HZ) {
currentFreq = FREQ_MIN_HZ;
rampDir = +1; /* 到达下限,反向递增 */
}
blinkTimerUpdateFreq(currentFreq); /* 平滑更新频率 */
tftShowFreq(currentFreq); /* TFT显示频率 */
}
说明:长按期间每 300ms 步进 1Hz,a到达边界后反转 rampDir,形成 1Hz→5Hz→1Hz 的三角波扫描。rampDir 在松开后保持,下次长按从同方向继续。
4.3.5 TFT 显示优化
static void tftPrintCentered(const char *str, int len)
{
int x = (TFT_WIDTH - len * FONT_CELL_W) / 2;
int y = (TFT_HEIGHT - FONT_CELL_H) / 2;
area_t band = { 0, y, TFT_WIDTH, FONT_CELL_H };
MXC_TFT_FillRect(&band, WHITE); /* 仅清除文本带 */
MXC_TFT_SetForeGroundColor(BLACK);
text_t text = { (char *)str, len };
MXC_TFT_PrintFont(x, y, (int)&Liberation_Sans24x24[0], &text, NULL);
}
说明:用 MXC_TFT_FillRect 仅刷新 128×24 的文本带而非全屏 ClearScreen,将 SPI 流量从 ~16KB 降到 ~0.5KB,消除显示刷新导致的闪烁卡顿。
五、功能展示图及说明

【图片1:进阶题-TFT显示频率】
- 说明:长按调节时 TFT 实时显示当前频率

【图片2:基础题-LED停止】
- 说明:再次按键 LED 停止闪烁

【图片3:进阶题-高频闪烁】
- 长按到达 4Hz ,LED 快速闪烁
六、项目中遇到的难题及解决方法
难题1:按键中断回调函数签名确认
问题:进阶题需要检测按键的按下和释放两个边沿。最初假设 PB_RegisterCallbackRiseFall 接受两个回调函数(按下回调 + 释放回调),但查阅 SDK 源码后发现该函数只接受单个回调,为双边沿注册同一个回调。
解决:在回调函数内部调用 PB_Get() 读取当前电平,PB_Get 返回 1 表示按下,0 表示释放,从而在单个回调中区分两个边沿。
难题2:长按后 LED 常亮不闪烁
问题:进阶题长按调节频率时,频率数值在 TFT 上正常变化,但 LED 保持常亮不闪烁。短按停止后重新启动才能恢复闪烁。
根因:频率更新函数 blinkTimerSetFreqAndRun() 内部调用了 MXC_TMR_Stop() + MXC_TMR_Shutdown() + MXC_TMR_Init() 重新配置定时器,但没有调用 MXC_TMR_Start() 重新启动。而主循环中的 if (!blinkRunning) 守卫条件因 blinkRunning 已为 1 而跳过启动,导致定时器始终处于停止状态。
解决:将频率更新拆分为配置函数和启动函数,确保每次频率更新后都调用 MXC_TMR_Start()。后续进一步优化为仅更新比较寄存器(见难题3)。
难题3:频率调节时 LED 闪烁卡顿
问题:长按调节频率时 LED 闪烁出现明显卡顿,不满足"频率变化过程应平滑(无明显跳变)"的要求。
根因:每次频率变化都执行 MXC_TMR_Stop() + MXC_TMR_Shutdown() + MXC_TMR_Init() + MXC_TMR_Start() 完整的重配置流程。Shutdown + Init 会将定时器计数器复位为 0,导致当前周期被截断,LED 翻转节奏中断一个周期,表现为可见的卡顿。
解决:改用 MXC_TMR_SetCompare() 仅修改比较寄存器,不停机不重初始化。定时器继续运行,计数器不复位,LED 翻转节奏不受影响。额外处理:当新比较值小于当前计数值时,调用 MXC_TMR_SetCount(0) 清零计数器,避免 32 位溢出等待导致的超长间隔。
难题4:TFT 显示刷新导致闪烁卡顿
问题:TFT 显示频率时使用 MXC_TFT_ClearScreen() 全屏清除,每次清除 128×128 = 16KB 像素通过 SPI 传输,耗时约 50ms,阻塞主循环导致 LED 闪烁卡顿。
解决:改用 MXC_TFT_FillRect() 仅清除文本所在的 128×24 像素带,SPI 流量从 ~16KB 降到 ~0.5KB,刷新耗时从 ~50ms 降到 ~2ms,彻底消除显示刷新对闪烁节奏的影响。
难题5:float 到 double 的隐式提升警告
问题:使用 printf("%.2f", currentFreq) 时,float 类型的 currentFreq 被隐式提升为 double,触发 -Wdouble-promotion 警告,且 MSDK 默认将警告视为错误(-Werror)导致编译失败。
解决:显式转换为 (double)currentFreq,消除隐式提升警告。
七、心得体会
通过本次基于 MAX32690EVKIT 的项目开发,我深入掌握了嵌入式系统中 GPIO 中断、通用定时器、TFT 显示等外设的配置与使用,对中断驱动的设计模式有了更深刻的理解。
最大的收获在于"平滑"二字的工程实现。题目要求频率变化"平滑无跳变",看似简单,但实际调试中发现多个隐藏的卡顿源:定时器重配置导致计数器复位、TFT 全屏清除阻塞主循环、float 隐式提升编译错误等。每一个卡顿都需要从根因分析入手,结合 SDK 源码和寄存器级理解才能彻底解决。这让我认识到,嵌入式开发中"能用"和"好用"之间往往隔着大量细节打磨。
对 MSDK 的认识:Analog Devices 的 MSDK 提供了完善的板级支持包和外围驱动库,API 封装层次清晰(从寄存器级到高层抽象均有覆盖)。但文档中对部分函数(如 PB_RegisterCallbackRiseFall 的单回调设计)的描述不够显式,需要结合源码确认,这也锻炼了我阅读驱动源码的能力。
对 MAX32690 的认识:Cortex-M4F 内核 + 120MHz 主频对于本项目的 GPIO 中断和定时器应用绰绰有余。板载 TFT 屏幕为交互反馈提供了很好的载体,SPI 接口的刷新性能在优化后完全满足实时显示需求。
工程方法论:本项目让我体会到"分层调试"的价值——先确保 GPIO 中断能触发(串口打印验证),再确保定时器能产生周期中断(LED 翻转验证),最后整合按键+定时器+TFT 实现完整功能。每一步独立验证后再整合,比一次性写完再调试效率高得多。