Funpack5-3 基于MAX32690EVKIT评估套件上手测试——非阻塞状态机按键控制实践报告
该项目使用了MAX32690 评估套件,实现了按键控制LED闪烁的设计,它的主要功能为:使用 MAX32690EVKIT 的板载按键功能,实现以下逻辑:单次按键 → LED开始闪烁;再次按键 → LED停止闪烁。
标签
嵌入式系统
Funpack活动
测试
开发板
都市木头
更新2026-08-20
17

基于MAX32690EVKIT评估套件上手测试——非阻塞状态机按键控制实践报告

一、项目介绍

本次实践基于 ADI 公司的 MAX32690EVKIT 评估套件,摒弃传统延时或简单轮询架构,采用”事件驱动 + 有限状态机(FSM)“的非阻塞设计模式,构建一套可靠、可扩展的按键控制逻辑,避免CPU 资源浪费、系统实时性差,且极易受机械按键抖动等的影响。

核心功能需求:1. 单次短按 SW2 按键,红色 LED(LED0,即 P0.14 / D1 红色)开始以 1Hz 频率闪烁。 2. 再次短按 SW2 按键,LED 停止闪烁并保持熄灭。 3. 全程采用非阻塞设计,确保系统在处理按键消抖和 LED 闪烁时,主循环依然保持全速运转。

1.1 硬件介绍

本项目基于MAX32690 评估套件实现。MAX32690 评估套件提供了一个平台,用于评估 MAX32690 微控制器的功能,该微控制器是一款先进的片内系统 (SoC)。它采用了 Arm Cortex-M4F CPU,可高效计算复杂的功能和算法,并采用了最新一代的 Bluetooth 5 低功耗(蓝牙 LE)射频,专为可穿戴和便携式健身设备、便携式和可穿戴式医疗设备、工业传感器/网络、物联网 (IoT) 和资产追踪而设计。图片.png

1.2 板卡功能优势和特点

MAX32690EVKIT评估套件基于Arm Cortex-M4F与BLE 5.2 SoC,板载HyperRAM、立体声音频编解码器及1.45英寸TFT彩屏,提供SMA蓝牙天线、CAN 2.0及全引出GPIO等丰富接口,并配备完善的多级电源管理(3.3V、1.8V 和 1.1V LDO)与功耗测量跳线。

二、项目功能实现

本项目实现任务1按键控制LED闪烁:使用 MAX32690EVKIT 的板载按键功能,实现以下逻辑:单次按键 → LED开始闪烁;再次按键 → LED停止闪烁

功能要求:正确初始化 GPIO 中断;LED 控制逻辑清晰,不出现误触发;闪烁频率约 1Hz。

2.1 任务分析

根据板上资源,这个任务按键选择使用SW2(connected to GPIO P4.0),LED选择使用LED0(D1 (red) is connected to GPIO P0.14),则需要:
GPIO P4.0为输入模式,默认上拉。
GPIO P0.14为输出模式。

图片.png

图片.png

2.2 设计思路

正确初始化 GPIO 中断,就要求使用中断功能。注意不能在中断中使用print输出,否则会有延迟不能快进快出。
LED 控制逻辑清晰,不出现误触发,就要采取按键防抖措施,最常用使用时间戳,使用心跳,在中断中记录时间戳。
闪烁频率约 1Hz,可使用计数器完成频率设定。

本系统设计将硬件底层与业务逻辑彻底解耦,严格遵循”中断极简,时基统一,主循环状态机非阻塞”架构:

  1. 硬件中断极简原则:GPIO 中断回调函数中严禁执行任何耗时操作,仅负责将事件标志位(btn_flag)清零,实现”快进快出”,避免阻塞其他中断。
  2. 全局时基与心跳:利用 ARM Cortex-M4 内核自带的 SysTick 定时器,配置为 10ms 触发一次中断,提供全局毫秒级时间戳(sys_ms)。
  3. 非阻塞状态机驱动:主循环(while(1))全速运行,通过比对时间戳完成所有时序逻辑。系统被划分为4个工作状态:
    BTN_IDLE(空闲态):等待按键事件触发。
    BTN_DEBOUNCE(消抖态):过滤机械抖动,确认有效按压并翻转 LED 意图。
    BTN_WAIT_RELEASE(等待松开态):锁定按键,防止长按或松手抖动引发状态死锁。
    BTN_RUN(运行态):执行 LED 1Hz 闪烁及关闭逻辑。

2.3 上手测试过程

2.3.1 准备硬件和软件环境

2.3.1.1 硬件连接:

根据套件说明,开发板只需一根microUSB数据线就可以快速上手了:

图片.png

MAX32690 芯片内部已经预先烧录了演示代码。只需将附带的 USB 线插入 CN2 接口为开发板供电(将 USB 线的另一端连接到电脑或电源适配器)即可运行这个演示程序。检查 4V5 蓝色 LED 指示灯(D5)以及 3V3 (DS1)、1V8 (DS3)、1V1 (DS4) 和 BLE LDO (DS5) 这几个绿色 LED 指示灯是否均已点亮,如果亮起,说明这些电压供电轨(voltage rails)都已正常上电。这也说明板子上不同的电压域(比如 4.5V、3.3V、1.8V 和 1.1V)都已正常供电。

图片.png

如果要烧录示例程序,还需要使用套件自带的MAX32625PICO 调试器。要连接该功能的带状线MAX32625PICO 调试器至 MAX32690 EV 套件的 J3 接口,通过SWD 接口进行烧录和调试。此外,MAX32625PICO还用作UART桥,无需额外USB即可实现。

图片.png

2.3.1.2 软件环境搭建:

安装CodeFusion Studio (CFS)

图片.png

在 VS Code 中安装codefusion studio扩展插件

图片.png

CodeFusion Studio 扩展依赖于其他 VS Code 扩展(包括 Microsoft C/C++、Cortex-Debug 以及 Zephelin Trace Viewer 等)来实现调试和开发功能。如果缺少某些依赖扩展,这些扩展将会自动被安装。

如果首次安装 CodeFusion Studio,或者刚升级到新版本,可能会收到确认 CFS SDK 的路径是否正确设置的提示。 如果还没有安装SDK,就按照提示下载并安装SDK,当文件夹缺失、被移动或位置错误时,就会出现这些提示信息,那么需要设置CFS SDK的安装路径。

图片.png

探索 CodeFusion Studio 所提供的功能。

图片.png

使用工作区创建向导创建新的工作区。创建后,工作区文件将显示在文件资源管理器中,并且系统规划器配置工具仪表板将打开。

图片.png

图片.png

System Planner Configuration Tools提供了一个仪表板,轻松配置引脚多路复用、时钟设置和片上系统(SoC)资源。可以打开配置文件.cfsconfig​以打开配置工具。

在设置所有配置(如引脚多路复用、时钟设置和内存分配)之后,可以为应用程序生成必要的源文件。

图片.png

注意:如果现有项目尚未配置为CodeFusion Studio工作区,需要等待“配置”对话框完成后再继续。

三、软件设计及功能实现

3.1 代码架构说明

整体采用 三层(中断极简、全局时基、状态机驱动) 架构,结合了硬件SysTick 心跳 + GPIO 中断分层与软件状态机,实现了按键防抖、LED闪烁控制以及串口日志的安全输出。代码严格遵循了中断快进快出原则,构建了轻量的底层支撑:

  1. SysTick 心跳(纯时基):SysTick_Handler 中断服务函数中,仅有一行 sys_ms += TICK_MS​。它不处理任何业务,只为整个系统提供全局、无阻塞的时间基准。
  2. GPIO 中断(纯通知):button_isr 回调函数中,仅执行 btn_flag = 0​。当按键按下触发硬件中断时,仅负责向主循环通知,避免了在 ISR 中执行耗时操作(如printf 或延时)导致的系统崩溃风险。
          按下(中断清零flag)

┌──────────▼──────────┐
BTN_DEBOUNCE │ 等待 30ms 消抖
│ 确认按下后翻转 │ led_blinking ^= 1
LED 状态 │
└──────────┬──────────┘
│ 30ms 到
┌──────────▼──────────┐
BTN_WAIT_RELEASE │ 等待物理松手
│ 松手后恢复 flag │ 防止长按重复触发
└──────────┬──────────┘
│ 松手
┌──────────▼──────────┐
BTN_RUN │◄──┐
│ ·检测新按键→DEBOUNCE│ │ 每 500ms
│ ·非阻塞 LED 翻转 │───┘ LED_Toggle
└─────────────────────┘

┌─────────────────────┐
BTN_IDLE │ 初始/异常恢复
│ 等待按下事件 │
└─────────────────────┘


3.2 状态流转详解

主循环中的 switch(st) 将按键与 LED 的控制逻辑划分为四个互斥的业务状态,形成了一个严密的逻辑闭环:

  1. BTN_IDLE(空闲态):系统处于待命状态。当检测到按键被按下(pressed && !btn_flag)时,记录当前时间戳(t0 = now),并无缝切入消抖状态。
  2. BTN_DEBOUNCE(消抖态):通过 if ((now - t0) >= DEBOUNCE_MS) 进行非阻塞时间差比对。当 30ms 消抖期满,系统立即翻转 LED 意图标志(blinking = !blinking),并根据意图执行开/关灯动作,随后切入等待松手状态。
  3. BTN_WAIT_RELEASE(等待松手态):这是防死锁的关键。系统在此状态死等按键物理释放(!pressed)。只有确认用户手指离开按键,才会重置事件标志(btn_flag = 1)并进入运行态。这彻底杜绝了”长按不松手”或”松手弹片抖动”引发的状态机卡死。
  4. BTN_RUN(运行态):系统在此状态并行处理两件事。一是监听新的按键事件以准备关闭 LED;二是通过 (now - t_led) >= BLINK_HALF_PERIOD_MS 实现 1Hz 的非阻塞 LED 翻转。

3.3 关键变量与函数解析

核心变量

• static volatile uint32_t sys_ms = 0;
作用:系统的全局毫秒级时间戳(心跳)。
解析:volatile 关键字告诉编译器这个变量随时会被硬件中断修改,必须每次都从内存中读取,防止被优化掉。它是整个系统非阻塞设计的基石,所有的延时(消抖、闪烁)都依赖它。

• static volatile uint8_t btn_flag = 1;
作用:按键事件的”门铃”标志位。
解析:初始化为 1(表示无事件)。当按键按下触发中断时,ISR 将其清零(0);当主循环处理完按键并确认松手后,再将其置为 1。它实现了中断与主循环之间的异步通信。

• btn_state_t st
作用:记录当前系统处于哪个业务阶段。
解析:它的取值被严格限制在 BTN_IDLE、BTN_DEBOUNCE、BTN_WAIT_RELEASE、BTN_RUN 四个状态中。主循环的 switch(st) 就是根据这个变量的值来决定当前该干什么。

• uint32_t t0
作用:记录”按键刚被按下”那一刻的系统时间。
解析:在进入 BTN_DEBOUNCE 状态时,t0 被赋值为当前的 sys_ms。随后通过 (now - t0) >= DEBOUNCE_MS 来判断 30ms 的消抖时间是否过去。

• uint32_t t_led
作用:记录”LED 上一次翻转”那一刻的系统时间。
解析:在 BTN_RUN 状态下,通过 (now - t_led) >= BLINK_HALF_PERIOD_MS 来判断是否该翻转 LED 了。每次翻转后,t_led 会被更新为当前时间,从而实现非阻塞的 1Hz 闪烁。

• uint8_t blinking
作用:LED 的”意图”标志(0=灭,1=闪烁)。
解析:这是解耦设计的核心。在 BTN_DEBOUNCE 阶段,系统只负责执行 blinking = !blinking;(翻转意图),而不管 LED 到底怎么闪。LED 的实际翻转动作完全交给 BTN_RUN 状态根据 blinking 的值和时间戳来驱动。

核心函数

• void SysTick_Handler(void)
作用:系统心跳中断服务函数。
解析:每 10ms 触发一次,内部仅执行 sys_ms += TICK_MS;。它绝对不包含任何业务逻辑,保证了系统时基的绝对精准和 ISR 的”快进快出”。

• static void button_isr(void *pb)
作用:GPIO 硬件中断回调函数。
解析:当 SW2 被按下(产生下降沿)时由硬件触发。内部仅执行 btn_flag = 0;。它的作用仅仅是”打铃”,通知主循环”有按键动作,请去检查”。

• uint8_t pressed = PB_Get(0);
作用:实时读取 SW2 的物理电平状态。
解析:在主循环的每一次迭代中都会执行。它不依赖中断,而是主动去”看”按键现在的真实状态。这是解决”按键死锁”和实现”松手检测”的最关键依据。

4、变量与函数的协同工作流程

  1. 按下按键:button_isr 将 btn_flag 清零。
  2. 主循环捕获:pressed 读到 1,且 btn_flag 为 0,状态机从 BTN_IDLE 跳入 BTN_DEBOUNCE,并记录 t0。
  3. 消抖与翻转:经过 30ms,blinking 被翻转,状态机跳入 BTN_WAIT_RELEASE。
  4. 等待松手:主循环持续读取 pressed,直到用户松手(pressed 变为 0),btn_flag 被重新置 1,状态机跳入 BTN_RUN。
  5. 非阻塞闪烁:在 BTN_RUN 中,状态机一边通过 t_led 控制 LED 以 1Hz 闪烁,一边继续监听 pressed 和 btn_flag,随时准备响应下一次按键。

5、核心机制亮点

非阻塞时差算法:代码全程没有使用 delay() 等挂起 CPU 的指令。所有的时间控制(30ms 消抖、500ms 闪烁)均通过”当前时间 - 记录时间 >= 目标时间”来实现。这意味着 CPU 在主循环中以最高频率全速空转,随时可以响应外部突发事件。

意图与动作分离:在 BTN_DEBOUNCE 阶段,代码只负责翻转逻辑变量 blinking,而不直接控制 LED 的闪烁频率。LED 的闪烁完全交由 BTN_RUN 状态根据 blinking 的值和时间戳来驱动。这种设计使得状态机逻辑极其清晰,未来若要扩展”呼吸灯”或”双击”功能,只需增加新的状态分支即可。

双重检测保护机制:代码不仅在 BTN_WAIT_RELEASE 状态中检测松手,还在 while(1) 主循环的最末尾加入了 if (!pressed) { btn_flag = 1; } 的全局逻辑。这确保了无论状态机处于何种异常状态,只要按键松开,事件标志都会被正确复位,为下一次有效按压做好了准备。

6、完整代码如下:

/*
* 按键控制 LED 闪烁 — 状态机非阻塞实现
*
* 硬件:
* SW2 = P4.0, 低电平有效, 内部上拉 (BSP Pushbutton 0)
* LED0 = P0.14 (D1 红色), 低电平点亮
*
* 功能:
* 短按 SW2 → LED 开始 1Hz 闪烁
* 再按 SW2 → LED 停止闪烁
*
* 架构:
* GPIO 中断 (下降沿) → 仅设置事件标志, 不做业务逻辑
* SysTick (10ms) → 仅提供 ms 时基, 不做业务逻辑
* 主循环 → 状态机处理按键 + 非阻塞 LED 闪烁
*/

#include <stdio.h>
#include <stdint.h>
#include "mxc_device.h"
#include "led.h"
#include "pb.h"
#include "board.h"
#include "soc_init.h"
#include "core_cm4.h"

/* ================================================================
* 宏定义
* ================================================================ */
#define TICK_MS 10
#define DEBOUNCE_MS 30
#define BLINK_HALF_PERIOD_MS 500

/* ================================================================
* 状态机定义
* ================================================================ */
typedef enum {
BTN_IDLE,
BTN_DEBOUNCE,
BTN_WAIT_RELEASE,
BTN_RUN
} btn_state_t;

/* ================================================================
* 全局变量
* ================================================================ */
static volatile uint32_t sys_ms = 0;
static volatile uint8_t btn_flag = 1;

/* ================================================================
* SysTick ISR — 仅累加毫秒计数
* ================================================================ */
void SysTick_Handler(void)
{
sys_ms += TICK_MS;
}

/* ================================================================
* GPIO 中断回调 — 仅清零标志, 通知主循环
* ================================================================ */
static void button_isr(void *pb)
{
btn_flag = 0;
}

/* ================================================================
* 主函数
* ================================================================ */
int main(void)
{
PinInit();
PeripheralInit();
LED_Off(LED_RED);

PB_Init();
PB_RegisterCallback(0, button_isr);

SysTick_Config(SystemCoreClock / (1000 / TICK_MS));

printf("\r\n=== LED Blink Toggle (State Machine) ===\r\n");
printf("Press SW2 to start/stop. Status: LED OFF\r\n");

btn_state_t st = BTN_IDLE;
uint32_t t0 = 0;
uint32_t t_led = 0;
uint8_t blinking = 0;

while (1) {
uint32_t now = sys_ms;
uint8_t pressed = PB_Get(0);

switch (st) {

case BTN_IDLE:
if (pressed && !btn_flag) {
st = BTN_DEBOUNCE;
t0 = now;
}
break;

case BTN_DEBOUNCE:
if ((now - t0) >= DEBOUNCE_MS) {
blinking = !blinking;
if (blinking) {
LED_On(LED_RED);
t_led = now;
printf("Status: LED BLINKING (1Hz)\r\n");
} else {
LED_Off(LED_RED);
printf("Status: LED OFF\r\n");
}
st = BTN_WAIT_RELEASE;
}
break;

case BTN_WAIT_RELEASE:
if (!pressed) {
btn_flag = 1;
st = BTN_RUN;
}
break;

case BTN_RUN:
if (pressed && !btn_flag) {
st = BTN_DEBOUNCE;
t0 = now;
} else if (blinking && (now - t_led) >= BLINK_HALF_PERIOD_MS) {
LED_Toggle(LED_RED);
t_led = now;
}
break;

default:
st = BTN_IDLE;
break;
}

if (!pressed) {
btn_flag = 1;
}
}
}


四、功能展示

软件设计好后,需要通过附带的MAX32625PICO 调试器接到SWD 接口进行烧录和调试。烧录成功后就可看到效果。经过多轮编译与真机烧录测试,系统表现稳定: - 响应零延迟:由于主循环无任何阻塞延时,按键响应极其迅速。 - 逻辑零误触:在连续快速按压、长按不松手等操作下,LED 均能按照”按一次闪烁,再按一次熄灭”的逻辑运行,串口终端准确打印状态切换信息。

图片.png

五、总结

5.1 遇到的问题

  1. CodeFusion Studio 扩展对SDK兼容性有要求,要选择 CFS 扩展版本相匹配的 SDK。SDK 的较小版本必须与扩展程序的较小版本(版本号第二个小数点前)相同或更高。例如,版本 2.1.x 的扩展程序需要使用 2.1.x 或更高版本的 SDK,而无法与 2.0.x 或任何 1.x.x 版本的 SDK 协同工作。而补丁版本(版本号第二个小数点后)的更新并不会影响软件的兼容性。例如,扩展模块 2.2.1 和开发工具包 2.2.0 被认为是兼容的。如果继续使用旧版本的 SDK,需要先关闭 CFS 扩展功能的自动更新,并选择“安装特定版本”来安装想用的旧版本。
  2. 首先遇到的一个硬件问题,不知为什么我这个板子复位按键不灵敏,经常没有反应,反复按几次又能用,可能是个体问题。临时解决办法是用金属镊子或针触发按键左上角引脚直接导地。
    图片.png
  3. 遇到的最大的问题是调试器突然出现无法正常工作,错误 Remote communication error. Target disconnected: could not peek into pipe (error 109)排查了很长,加上复位键异常几乎要放弃了。后来网上查了一下表明 GDB 在与 OpenOCD 调试服务器进行通信时,底层管道(pipe)连接意外断开了。这通常与调试器硬件连接、OpenOCD 配置或系统环境有关。
    排除了硬件连接问题后,尝试在终端中手动运行该 OpenOCD 命令,发现是命令路径错误,手动找到正确的命令路径后,成功识别到了目标芯片的 DPIDR (0x2ba01477)。这说明硬件连接和驱动都是正常的。
    但是,在尝试读取芯片内部寄存器时,出现了关键错误:Error: Failed to read memory at 0xe000ed04
    Error: [max32xxx.cpu] Examination failed
    xe000ed04 是 ARM Cortex-M 内核的 AIRCR (Application Interrupt and Reset Control Register) 寄存器。读取这个寄存器失败,通常意味着 OpenOCD 无法与芯片内核进行正常的通信。网上搜索极有可能(最常见)是之前烧录的程序让芯片进入了深度睡眠(Deep Sleep),或者开启了读保护(RDP),调试接口会被禁用,导致无法读取寄存器。这是一个已知的调试器行为/小 Bug:在使用 OpenOCD 时,当 GDB 执行 detach 命令断开连接时,OpenOCD 默认会停止目标芯片的运行,而不是让它继续跑起来。这导致 GDB 和 OpenOCD 之间的管道状态出现异常,最终引发管道断开错误 (error 109)。分析可能是之前烧录的程序让 MAX32690 进入了深度睡眠(Deep Sleep)或低功耗模式,芯片会关闭内核时钟,导致调试接口无法访问 CPU 寄存器。
    • 解决办法:在启动 OpenOCD 时,按住目标板上的硬件复位按键(RESET)不要松手,然后执行 OpenOCD 命令。等看到 Listening on port 3333 for gdb connections​ 后,再松开复位键。这可以强制芯片在启动瞬间被调试器捕获。关键操作——在 5 秒内移除短接:执行完 monitor reset halt 后,必须在 5 秒内移除 RSTN 到 GND 的短接(或松开复位按键)。这样芯片会从复位状态释放,但调试器已经抢先 halt 住了内核,睡眠代码来不及执行。
    • 由于我这个板子的复位键有问题,在最开始的时候就尝试使用该办法,但由于无效而转到排查其它原因,绕了一圈最后发现复位键也有问题,用按键左上角引脚直接导地方法复位,再用上面办法才解锁了调试器。


5.2 心得体会

本以为会很容易上手,谁知一开始就选择困难了,开发环境搭建中各种各样问题,尝试先在UOS系统中安装,发现不好用,只能转在win系统中使用了,再加上复位键异常加上调试器突然出现无法正常工作,本以为自身问题打算放弃。最后还是通过AI和实践找到了问题原因,但时间原因只能先简单的将任务简化到初始化外设和点灯。

此次收获主要一是首次使用了CodeFusion Studio还是比较好用的,比如它无缝集成与便捷安装,作为 VS Code 扩展只需搜索并安装 CodeFusion Studio 扩展即可使用;可视化资源分配:提供类似 STM32CubeMX 的图形化配置界面,开发者可以直观地配置引脚、时钟、功耗模式和中间件,减少手动设置误差;代码自动生成:配置工具能够自动生成高质量、可直接用于生产的源代码。二是,利用时间戳替代延时函数,是构建实时可靠嵌入式软件的成熟方法。这套非阻塞状态机架构不仅完美解决了基本任务的按键控制需求,还为高级交互功能打下了坚实的底层基础。后续它的端到端的嵌入式 AI 工作流、异构多核调试与诊断、Zephyr RTOS 模块化框架更值得尝试一下。

再次感谢电子森林《Funpack》 系列活动,接触到新的产品生态体系,拓展了视野,理论结合实践,提高了动手能力。



附件下载
codetest2.rar
vscode工作区文件
m4.elf
m4.elf是2进制代码文件
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号