4、GPIO驱动调试:MIO与EMIO配置、寄存器读写、中断模式实现
GPIO 是 Zynq 里最基础、也最常用的外设。说白了,它就是那些可以让你直接拉高拉低的引脚。但 Zynq 的 GPIO 分两种:MIO 和 EMIO。很多人刚开始搞不清楚区别,结果代码写对了,硬件没反应。今天我们就把它彻底讲透。
4.1 MIO 与 EMIO:到底有什么区别?
MIO 是 PS 端专用的引脚,一共 54 个。它们直接连到 PS 内部,不经过 PL。所以配置 MIO 时,你完全不用碰 FPGA 那部分。EMIO 则不同——它走的是 PL 的 I/O,需要先在 PL 里把引脚分配好,PS 才能通过 EMIO 接口控制它们。
我个人的习惯是:能用 MIO 的尽量用 MIO。为什么呢?因为 MIO 的时序是确定的,延迟小,调试起来也简单。EMIO 虽然灵活,但多了 PL 这一层,信号路径变长,容易出问题。
| 特性 | MIO | EMIO |
|---|---|---|
| 物理位置 | PS 端专用引脚 | PL 端 I/O |
| 数量 | 54 个 | 最多 64 个(通过 PL) |
| 配置方式 | PS 内部寄存器 | PS 寄存器 + PL 约束 |
| 延迟 | 低(固定路径) | 较高(经过 PL 布线) |
| 调试难度 | 低 | 中(需同时看 PS 和 PL) |
4.2 寄存器读写:从地址到比特
Zynq 的 GPIO 控制器有 4 个 Bank。Bank0 和 Bank1 对应 MIO,Bank2 和 Bank3 对应 EMIO。每个 Bank 有独立的控制寄存器。你想想看,如果不知道基地址,连第一步都迈不出去。
GPIO 的基地址是 0xE000_A000。常用的寄存器有这几个:
- DATA (0x00~0x0C):数据寄存器,读引脚状态,写输出值。
- DIRM (0x204~0x210):方向控制,1 为输出,0 为输入。
- OEN (0x208~0x214):输出使能,1 使能输出。
- INTEN (0x300~0x30C):中断使能。
- INT_TYPE (0x310~0x31C):中断类型,0 为电平,1 为边沿。
- INT_POLARITY (0x320~0x32C):中断极性,0 为低电平/下降沿,1 为高电平/上升沿。
举个例子,我想把 MIO 的第 7 脚设为输出并拉高:
// 伪代码,实际用 XGpioPs 库更安全
#define GPIO_BASE 0xE000A000
#define DIRM_OFFSET 0x204
#define DATA_OFFSET 0x00
// 设置 Bank0 的 bit7 为输出
*(volatile u32*)(GPIO_BASE + DIRM_OFFSET) |= (1 << 7);
// 使能输出
*(volatile u32*)(GPIO_BASE + 0x208) |= (1 << 7);
// 输出高电平
*(volatile u32*)(GPIO_BASE + DATA_OFFSET) |= (1 << 7);
4.3 中断模式实现:别让 CPU 空转
轮询 GPIO 状态是最笨的办法。CPU 一直在那里读寄存器,啥也干不了。中断模式才是正经做法。Zynq 的 GPIO 中断支持电平触发和边沿触发,还能单独配置每个引脚的极性。
配置中断的步骤其实不复杂:
- 设置引脚为输入(DIRM 对应位清 0)。
- 配置中断类型(INT_TYPE)和极性(INT_POLARITY)。
- 使能中断(INTEN 对应位置 1)。
- 在 GIC(通用中断控制器)里注册中断处理函数。
- 中断到来时,读取 INT_STAT 寄存器确认来源,处理完后写 1 清除中断标志。
这里有个坑——很多人忘了清除中断标志。结果中断处理函数一退出,中断又来了,系统直接卡死。我刚开始做 Zynq 时也犯过这个错,后来养成了习惯:进中断第一件事就是读状态,处理完立刻清标志。
// 中断处理函数示例
void GPIO_IntrHandler(void *data) {
u32 intr_status;
// 读取中断状态
intr_status = XGpioPs_IntrGetStatusPin(&Gpio, GPIO_PIN_7);
if (intr_status) {
// 处理中断(比如翻转另一个引脚)
XGpioPs_WritePin(&Gpio, GPIO_PIN_8, !XGpioPs_ReadPin(&Gpio, GPIO_PIN_8));
// 清除中断标志
XGpioPs_IntrClearPin(&Gpio, GPIO_PIN_7);
}
}
4.4 知识结构图:GPIO 驱动调试全景
下面这张图把 MIO/EMIO 配置、寄存器操作和中断流程串在了一起。你可以把它当作调试时的检查清单。
4.5 避坑指南:我踩过的那些雷
做 GPIO 驱动调试,有几个地方特别容易翻车。我一个个说给你听。
- 引脚复用没配好:MIO 默认可能被其他外设占用了。比如 MIO 7 可能被 UART 用了。你得先查 TRM 里的 MIO 分配表,确认没冲突。
- EMIO 没加约束:EMIO 的引脚必须在 Vivado 里用
set_property指定物理位置。否则综合出来全是未连接,PS 端怎么写都没用。 - 中断标志没清:这个前面说过了。不清标志,中断会一直触发。我建议你在中断处理函数的第一行就读状态,最后一行清标志,养成肌肉记忆。
- Bank 号搞混:MIO 0~31 是 Bank0,32~53 是 Bank1。EMIO 0~31 是 Bank2,32~63 是 Bank3。写寄存器时一定要算对偏移。
嗯,GPIO 这部分内容就这些。MIO 和 EMIO 的选择、寄存器读写、中断配置,这三块搞明白了,Zynq 的 GPIO 驱动你就拿下了。剩下的就是多练,多踩坑,然后记住怎么爬出来。