Zynq 7010实战笔记:从GPIO到中断的嵌入式系统交互
1. Zynq 7010开发板开箱体验第一次拿到Zynq 7010开发板时我注意到它比想象中要小巧精致。这块板子集成了ARM处理器和FPGA这种架构在嵌入式领域确实很独特。板载资源相当丰富54个MIO引脚、4个UART接口、2个USB接口还有千兆以太网口。最让我惊喜的是开发板还自带了一个JTAG调试接口这对于初学者来说非常友好。开发环境搭建是第一个挑战。我选择了Vivado 2018.3版本这个版本对Zynq 7000系列支持很稳定。安装过程大约需要2小时建议预留足够的磁盘空间至少30GB。安装完成后第一次启动Vivado时我遇到了许可证问题后来发现需要在Xilinx官网申请免费的WebPACK许可证。硬件连接也很简单用Micro USB线连接开发板的UART接口到电脑再连接JTAG调试器。电源方面开发板支持5V DC输入或者USB供电。我建议使用独立电源供电特别是在使用PL部分时电流需求可能会比较大。2. GPIO基础操作实战2.1 第一个LED闪烁程序GPIO是嵌入式开发最基础的外设接口。在Zynq中GPIO分为四个bank其中bank0和bank1通过MIO连接到PS引脚。我选择MIO0连接LED因为这个引脚在大多数开发板上都预接了LED。在Vivado中创建工程时需要特别注意Zynq处理器的配置。在Block Design中双击Zynq IP核在Peripheral I/O Pins选项卡下启用GPIO MIO。保存设计后生成比特流文件然后导出到SDK。SDK中的代码比想象中简单。Xilinx提供了完善的驱动库主要用到的函数有XGpioPs_LookupConfig() // 查找GPIO配置 XGpioPs_CfgInitialize() // 初始化GPIO XGpioPs_SetDirectionPin() // 设置引脚方向 XGpioPs_WritePin() // 写引脚状态我的第一个LED闪烁程序遇到了问题LED完全不亮。经过排查发现是引脚方向没有设置正确。记住必须先用XGpioPs_SetDirectionPin()将引脚设置为输出再用XGpioPs_SetOutputEnablePin()使能输出最后才能用XGpioPs_WritePin()控制LED。2.2 按键输入检测接下来我尝试用GPIO读取按键状态。开发板上的按键连接到了MIO12引脚。与输出不同输入检测需要XGpioPs_SetDirectionPin(Gpio, MIO12_KEY, 0); // 设置为输入 u32 key_state XGpioPs_ReadPin(Gpio, MIO12_KEY); // 读取引脚状态这里我踩了个坑没有考虑按键消抖。机械按键在按下和释放时会产生抖动导致多次误触发。解决方法很简单在检测到按键按下后加入50ms的延时if(key_state 0) { // 按键按下 usleep(50000); // 延时消抖 // 处理按键逻辑 }3. EMIO扩展应用3.1 连接PL端外设当PS端的54个MIO引脚不够用时就需要使用EMIO扩展。EMIO允许PS外设通过PL引脚连接到外部设备。我在项目中需要控制多个LEDMIO引脚已经用完于是决定使用EMIO。在Vivado中配置EMIO需要几个步骤在Zynq IP核配置中启用GPIO EMIO设置需要的EMIO引脚数量我设置了8个在Block Design中添加GPIO IP核并连接到Zynq的EMIO接口在约束文件中指定PL端引脚分配软件代码与MIO操作类似唯一区别是EMIO的引脚号从54开始计数#define EMIO_LED0 54 XGpioPs_SetDirectionPin(Gpio, EMIO_LED0, 1); // 设置EMIO为输出3.2 EMIO性能测试我比较了MIO和EMIO的翻转速度。使用示波器测量MIO引脚最高可以做到约50MHz的翻转频率而EMIO受PL部分限制最高约25MHz。这个性能对大多数应用已经足够。需要注意的是使用EMIO时PL部分的布线延迟会影响信号时序。在我的测试中不同EMIO引脚之间的延迟差异最大达到了3ns。对于高速应用需要在约束文件中设置正确的时序约束。4. 中断系统深度解析4.1 中断控制器配置Zynq的中断系统基于ARM的GICGeneric Interrupt Controller。配置中断需要几个关键步骤初始化GIC控制器XScuGic_Config *IntcConfig XScuGic_LookupConfig(INTC_DEVICE_ID); XScuGic_CfgInitialize(Intc, IntcConfig, IntcConfig-CpuBaseAddress);设置异常处理Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, Intc); Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);连接中断处理函数XScuGic_Connect(Intc, GPIO_INTERRUPT_ID, (Xil_ExceptionHandler)IntrHandler, Gpio);4.2 GPIO中断实现我使用MIO12连接按键实现中断功能。首先需要设置中断类型XGpioPs_SetIntrTypePin(Gpio, MIO12_KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);然后编写中断处理函数void IntrHandler(void *CallbackRef) { XGpioPs *GpioPtr (XGpioPs *)CallbackRef; XGpioPs_IntrClearPin(GpioPtr, MIO12_KEY); // 处理中断逻辑 printf(Interrupt occurred!\n); }调试中断时我遇到了一个棘手问题中断触发一次后就不再响应。后来发现需要在中断处理函数末尾重新使能中断XGpioPs_IntrEnablePin(GpioPtr, MIO12_KEY);4.3 中断性能优化默认的中断延迟大约在20-30个时钟周期。为了优化性能我做了以下改进将中断处理函数放在OCMOn-Chip Memory中执行减少了访问DDR的延迟使用Xil_DCacheDisable()关闭数据缓存避免缓存一致性问题简化中断处理函数只做必要的操作其他处理放到主循环中经过优化中断响应时间缩短到了15个时钟周期以内。对于实时性要求高的应用还可以考虑使用PL端的FPGA逻辑实现更快速的中断响应。