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;
}
编译与运行
- 在Vivado中创建Zynq硬件工程,使能MIO50和MIO51为GPIO,导出硬件并启动SDK。
- 在SDK中新建Application Project,将上述代码复制到helloworld.c中。
- 编译工程,生成.elf文件。
- 连接开发板,通过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延时消抖。