2. 按键输入与消抖

任务目标

在Zynq SoC平台上,通过MIO连接一个按键和一颗LED,实现按键按下时LED状态翻转。由于物理按键存在机械抖动,需要采用软件延时消抖,确保一次按下只触发一次翻转。

硬件连接

  • 按键:连接至PS端MIO引脚(本案例使用MIO 50,对应PS_KEY)
  • LED:连接至PS端MIO引脚(本案例使用MIO 51,对应PS_LED)
  • 按键按下时MIO电平为低(按下接地),释放时为高(上拉)

实现原理

软件消抖的核心思路:检测到按键电平变化后,延时约20ms再次读取电平,若与变化后的状态一致,则确认按键状态稳定,否则视为抖动忽略。本案例采用状态机方式,仅在检测到按键从释放变为按下(下降沿)且消抖确认后,翻转LED状态。

完整代码

#include <stdio.h>
#include "xil_io.h"
#include "xparameters.h"
#include "sleep.h"

// 寄存器地址定义(基于Zynq-7000 PS MIO)
#define SLCR_BASEADDR   0xF8000000
#define SLCR_MIO_PIN_50  (SLCR_BASEADDR + 0x0A8)  // MIO50配置
#define SLCR_MIO_PIN_51  (SLCR_BASEADDR + 0x0AC)  // MIO51配置
#define GPIO_DATA_LSW   0xE000A040  // GPIO Bank0 低16位数据
#define GPIO_DIRM_LSW   0xE000A244  // GPIO Bank0 方向控制(低16位)
#define GPIO_OEN_LSW    0xE000A248  // GPIO Bank0 输出使能(低16位)

#define KEY_PIN    (1 << 18)   // MIO50对应Bank0第18位
#define LED_PIN    (1 << 19)   // MIO51对应Bank0第19位
#define DEBOUNCE_MS 20

// 初始化MIO引脚为GPIO功能
void mio_init(void) {
    // MIO50: 按键输入,配置为GPIO
    Xil_Out32(SLCR_MIO_PIN_50, 0x00000300);  // 引脚功能GPIO,内部上拉
    // MIO51: LED输出,配置为GPIO
    Xil_Out32(SLCR_MIO_PIN_51, 0x00000200);  // 引脚功能GPIO,推挽输出

    // 设置方向:MIO50输入,MIO51输出
    u32 dir = Xil_In32(GPIO_DIRM_LSW);
    dir &= ~KEY_PIN;    // 按键方向为输入
    dir |= LED_PIN;     // LED方向为输出
    Xil_Out32(GPIO_DIRM_LSW, dir);

    // 输出使能:MIO50输入不需使能,MIO51输出使能
    u32 oen = Xil_In32(GPIO_OEN_LSW);
    oen |= KEY_PIN;     // 按键输出禁用(输入模式)
    oen &= ~LED_PIN;    // LED输出使能
    Xil_Out32(GPIO_OEN_LSW, oen);
}

// 读取按键状态(返回1表示按下,0表示释放)
int read_key(void) {
    u32 data = Xil_In32(GPIO_DATA_LSW);
    return ((data & KEY_PIN) == 0) ? 1 : 0;
}

// 设置LED状态(1点亮,0熄灭)
void set_led(int state) {
    u32 data = Xil_In32(GPIO_DATA_LSW);
    if (state) {
        data |= LED_PIN;
    } else {
        data &= ~LED_PIN;
    }
    Xil_Out32(GPIO_DATA_LSW, data);
}

int main(void) {
    int led_state = 0;
    int key_state = 0;      // 当前稳定按键状态
    int last_key = 0;       // 上一次稳定状态
    int key_raw = 0;
    int stable_cnt = 0;

    mio_init();
    set_led(0);  // 初始LED熄灭

    while (1) {
        key_raw = read_key();

        // 软件消抖:连续读取到相同状态DEBOUNCE_MS毫秒才确认
        if (key_raw != key_state) {
            stable_cnt++;
            if (stable_cnt >= DEBOUNCE_MS) {
                key_state = key_raw;
                stable_cnt = 0;

                // 检测下降沿(按下动作)
                if (key_state == 1 && last_key == 0) {
                    led_state = !led_state;
                    set_led(led_state);
                }
                last_key = key_state;
            }
        } else {
            stable_cnt = 0;
        }

        usleep(1000);  // 1ms轮询间隔
    }

    return 0;
}

编译与运行

  1. 在Vivado中创建Zynq硬件工程,使能MIO50和MIO51为GPIO,导出硬件并启动SDK。
  2. 在SDK中新建Application Project,将上述代码复制到helloworld.c中。
  3. 编译工程,生成.elf文件。
  4. 连接开发板,通过JTAG下载程序并运行。

验证方法

  • 程序运行后,LED初始为熄灭状态。
  • 手动按下按键并释放,观察LED状态翻转(熄灭→点亮,或点亮→熄灭)。
  • 快速连续按下按键多次,每次按下LED都应翻转一次,不应出现一次按下多次翻转或漏翻转的情况。
  • 若LED状态与按键动作不一致,可调整DEBOUNCE_MS值(建议范围10~50ms)以适应实际按键机械特性。

关键说明

  • 本代码直接操作PS GPIO寄存器,未使用Xilinx GPIO驱动,适用于Zynq-7000系列(如XC7Z010/020)。
  • MIO引脚编号与Bank对应关系:MIO50属于Bank0第18位,MIO51属于Bank0第19位,代码中已正确映射。
  • 消抖采用轮询方式,每1ms采样一次,连续20次相同状态才确认,等效20ms延时消抖。