4. 按键切换LED状态

任务说明

本案例基于中微CMS32系列MCU,实现按键切换LED状态的功能:每次按键按下时,LED的亮灭状态翻转一次。核心学习点为按键消抖与边沿检测(仅在按键从按下到释放的下降沿触发一次),以及基于状态机的编程思路。

硬件连接

  • LED正极经限流电阻接PB0引脚(输出高电平点亮)
  • 按键一端接PA0引脚,另一端接地(按下为低电平)
  • PA0内部使能上拉电阻

代码实现


#include "CMS32L051.h"

// 引脚定义
#define LED_PIN      GPIO_PB0
#define KEY_PIN      GPIO_PA0

// 状态定义
typedef enum {
    LED_OFF = 0,
    LED_ON
} LED_State_t;

// 按键状态机
typedef enum {
    KEY_IDLE = 0,      // 空闲(未按下)
    KEY_PRESSED,       // 已按下(等待释放)
    KEY_RELEASE        // 已释放(等待下一次按下)
} Key_State_t;

volatile LED_State_t led_state = LED_OFF;
volatile Key_State_t key_state = KEY_IDLE;

// 简单延时函数(非阻塞场景下可替换为定时器)
void delay_ms(uint32_t ms) {
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 8000; j++);  // 根据系统时钟调整
}

// GPIO初始化
void GPIO_Init(void) {
    // 使能GPIO时钟
    SYS_Enable_Clock(CLOCK_GPIOA);
    SYS_Enable_Clock(CLOCK_GPIOB);
    
    // PB0配置为推挽输出
    GPIO_SetMode(PB, BIT0, GPIO_MODE_OUTPUT);
    // PA0配置为输入,使能内部上拉
    GPIO_SetMode(PA, BIT0, GPIO_MODE_INPUT);
    GPIO_PullUp_Enable(PA, BIT0);
    
    // 初始LED熄灭
    GPIO_WritePin(PB, BIT0, 0);
}

// 按键扫描(状态机+边沿检测)
void Key_Scan(void) {
    uint8_t key_val = GPIO_ReadPin(PA, BIT0);  // 读取当前电平
    
    switch (key_state) {
        case KEY_IDLE:
            if (key_val == 0) {          // 检测到按下
                delay_ms(10);            // 消抖
                if (GPIO_ReadPin(PA, BIT0) == 0) {
                    key_state = KEY_PRESSED;
                }
            }
            break;
            
        case KEY_PRESSED:
            if (key_val == 1) {          // 检测到释放(下降沿完成)
                delay_ms(10);            // 消抖
                if (GPIO_ReadPin(PA, BIT0) == 1) {
                    key_state = KEY_RELEASE;
                    // 边沿触发:翻转LED状态
                    if (led_state == LED_OFF) {
                        led_state = LED_ON;
                        GPIO_WritePin(PB, BIT0, 1);
                    } else {
                        led_state = LED_OFF;
                        GPIO_WritePin(PB, BIT0, 0);
                    }
                }
            }
            break;
            
        case KEY_RELEASE:
            if (key_val == 0) {          // 等待下一次按下
                key_state = KEY_IDLE;
            }
            break;
            
        default:
            key_state = KEY_IDLE;
            break;
    }
}

int main(void) {
    GPIO_Init();
    
    while (1) {
        Key_Scan();
    }
}

状态机说明

按键状态机包含三个状态:KEY_IDLE(等待按下)、KEY_PRESSED(已按下,等待释放)、KEY_RELEASE(已释放,等待下一次按下)。LED翻转动作只在KEY_PRESSED状态检测到按键释放(即电平从低变高的边沿)时执行一次,从而避免按键持续按住时LED反复翻转。

验证方法

  1. 编译并烧录程序至CMS32L051开发板;
  2. 上电后观察LED初始状态为熄灭;
  3. 每次按下并释放按键,LED亮灭状态应翻转一次;
  4. 快速连续按键时,每次释放只翻转一次,不会出现多次翻转;
  5. 按住按键不放,LED状态保持不变,松开后才翻转。

判定标准:按键每完成一次“按下-释放”过程,LED状态恰好翻转一次,且无抖动导致的误触发,即判定通过。

按键状态机与LED翻转时序 按键信号 高 低 KEY_IDLE KEY_PRESSED KEY_RELEASE KEY_IDLE KEY_PRESSED KEY_RELEASE 下降沿 上升沿(触发翻转) LED状态 亮 灭 第1次翻转 第2次翻转 按键信号 LED状态 下降沿 上升沿(触发点)

关键要点

  • 边沿检测:本设计在按键释放(上升沿)时触发翻转,而非按下时触发,可避免按键按下瞬间的抖动干扰;
  • 消抖处理:状态切换前后各加入10ms延时确认,确保电平稳定;
  • 状态机优势:通过显式状态管理,代码逻辑清晰,便于扩展(如长按、双击等);
  • 实时性:主循环持续调用Key_Scan(),无阻塞等待,可在此基础上添加其他任务。