1. 项目背景与硬件选型解析
这个实验的核心在于利用Zynq-7020 SoC芯片的AXI GPIO接口实现按键控制LED的基础功能。Zynq-7020作为Xilinx(现AMD)推出的经典SoC器件,其独特之处在于集成了双核ARM Cortex-A9处理器与可编程逻辑单元(PL),通过高效的AXI总线实现软硬件协同设计。我选择正点原子开发板作为硬件平台,主要考虑其完善的周边电路设计和丰富的学习资源。
在硬件连接方面,开发板通常已经将PL端的GPIO引脚连接到板载LED和按键。以正点原子Zynq开发板为例,LED一般连接在PL Bank13的IO上,按键则连接在Bank14。这种物理连接关系需要在Vivado中进行正确的引脚约束,这也是后续操作的基础。
关键提示:不同厂商的开发板GPIO连接方式可能不同,务必查阅具体板卡的原理图。正点原子开发板的LED和按键默认使用PL端GPIO,而非PS端的MIO。
2. Vitis 2025.1开发环境搭建
最新版的Vitis 2025.1相比早期版本在工程管理流程上有显著优化。安装时需要注意:
- 必须同时安装对应版本的Vivado,因为Vitis依赖Vivado进行硬件设计
- 建议选择"Vitis Unified IDE"安装模式,这会包含所有必要的组件
- 安装完成后需要配置许可证文件,AMD提供免费的WebPACK许可证
环境变量配置是新手常踩的坑。在Linux系统下,需要正确设置XILINX_VITIS和XILINX_VIVADO路径。Windows用户则要注意避免安装路径包含中文或空格,这可能导致工具链异常。
我个人的经验是:首次启动Vitis时创建独立的工作空间(Workspace),不要使用默认路径。同时为Zynq项目预留至少50GB磁盘空间,因为综合过程会产生大量临时文件。
3. Vivado硬件平台创建
3.1 新建工程与芯片选型
启动Vivado后选择"Create Project",在芯片选择页面输入"xc7z020clg400-1"(正点原子Zynq-7020开发板的核心芯片型号)。这里容易出错的是:
- 误选纯FPGA型号(如xc7a35t)
- 忽略封装信息(clg400表示484引脚封装)
3.2 添加Zynq Processing System IP
在Block Design中点击"+"添加IP,搜索"Zynq7 Processing System"。双击配置:
- 在PS-PL Configuration中启用GPIO MIO
- 在Clock Configuration设置CPU时钟为666MHz
- 在DDR Configuration中选择正确的内存型号(正点原子通常使用MT41J128M16)
3.3 添加AXI GPIO IP
这是本实验的核心IP:
- 添加两个AXI GPIO实例:一个用于LED输出,一个用于按键输入
- 配置LED的GPIO为8位输出(All Outputs)
- 配置按键的GPIO为4位输入(All Inputs)
- 注意总线接口选择"AXI Lite",数据宽度32位
完成连接后,需要执行"Run Block Automation"和"Run Connection Automation"让工具自动完成剩余连线。最终设计应包含:
- Zynq PS通过AXI Interconnect连接两个GPIO模块
- 每个GPIO模块有独立的地址空间
- 系统时钟和复位信号正确连接
4. 引脚约束与硬件生成
4.1 创建XDC约束文件
在"Sources"窗口右键选择"Add Sources"→"Add or create constraints"。新建XDC文件并添加如下约束:
tcl复制# LED引脚约束 (以正点原子原理图为准)
set_property PACKAGE_PIN T22 [get_ports {led_tri_o[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_o[*]}]
...
# 按键引脚约束
set_property PACKAGE_PIN M20 [get_ports {btn_tri_i[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {btn_tri_i[*]}]
4.2 生成比特流文件
- 点击"Generate Bitstream"
- 等待综合、实现和比特流生成完成
- 导出硬件(File→Export→Export Hardware)
- 勾选"Include bitstream"
- 选择导出路径(建议放在工程根目录下)
常见问题:如果遇到时序违例,可以尝试在"Implementation Settings"中降低时钟频率或调整综合策略。
5. Vitis软件开发流程
5.1 创建应用工程
- 启动Vitis 2025.1并选择工作空间
- File→New→Application Project
- 选择之前导出的硬件平台(.xsa文件)
- 输入工程名(如"axi_gpio_led")
- 选择"Empty Application"模板
5.2 编写驱动程序
在src文件夹新建main.c文件,关键代码如下:
c复制#include "xparameters.h"
#include "xgpio.h"
#define LED_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define BTN_DEVICE_ID XPAR_AXI_GPIO_1_DEVICE_ID
XGpio LED_Gpio, BTN_Gpio;
int main() {
// 初始化GPIO
XGpio_Initialize(&LED_Gpio, LED_DEVICE_ID);
XGpio_Initialize(&BTN_Gpio, BTN_DEVICE_ID);
// 设置方向:LED输出,按键输入
XGpio_SetDataDirection(&LED_Gpio, 1, 0x00);
XGpio_SetDataDirection(&BTN_Gpio, 1, 0xFF);
while(1) {
// 读取按键状态
u32 btn_state = XGpio_DiscreteRead(&BTN_Gpio, 1);
// 按键按下时点亮LED
if(btn_state & 0x01) {
XGpio_DiscreteWrite(&LED_Gpio, 1, 0xFF);
} else {
XGpio_DiscreteWrite(&LED_Gpio, 1, 0x00);
}
}
return 0;
}
5.3 工程配置要点
- 在"Board Support Package Settings"中启用xil_printf支持
- 设置优化级别为-O0(调试阶段)
- 确认链接脚本正确包含DDR内存区域
6. 调试与问题排查
6.1 常见编译错误
- 未定义引用错误:通常是因为BSP配置不全,需要重新生成BSP或手动添加库路径
- 内存区域冲突:检查lscript.ld文件中的内存分配,确保代码段在DDR范围内
6.2 硬件调试技巧
- 使用ILA(Integrated Logic Analyzer)抓取AXI总线信号
tcl复制
create_debug_core u_ila_0 ila set_property ALL_PROBE_SAME_MU true [get_debug_cores u_ila_0] ... - 通过Vitis Serial Terminal查看打印信息
- 使用JTAG调试器单步执行代码
6.3 按键消抖处理
原始代码没有考虑机械按键的抖动问题,实际应用中应该添加消抖逻辑:
c复制#define DEBOUNCE_DELAY 10000 // 10ms延时
u32 debounce(u32 btn_state) {
static u32 last_state = 0;
static u32 count = 0;
if(btn_state != last_state) {
count++;
if(count > DEBOUNCE_DELAY) {
last_state = btn_state;
count = 0;
}
} else {
count = 0;
}
return last_state;
}
7. 功能扩展与进阶
7.1 多LED控制模式
可以扩展为多种显示模式:
c复制enum LED_MODE {
SINGLE_LED,
ROTATE_LEFT,
ROTATE_RIGHT,
BREATHING
};
void set_led_mode(enum LED_MODE mode) {
switch(mode) {
case ROTATE_LEFT:
static u8 pattern = 0x01;
pattern = (pattern << 1) | (pattern >> 7);
XGpio_DiscreteWrite(&LED_Gpio, 1, pattern);
break;
// 其他模式实现...
}
}
7.2 使用中断方式检测按键
比轮询更高效的方式:
- 在Vivado中启用GPIO中断
- 配置GIC(Generic Interrupt Controller)
- 编写中断服务程序:
c复制void BTN_Handler(void *CallbackRef) { // 读取中断状态并清除 XGpio_InterruptGetStatus(&BTN_Gpio); XGpio_InterruptClear(&BTN_Gpio, 0xFFFFFFFF); // 处理按键事件 u32 btn_state = XGpio_DiscreteRead(&BTN_Gpio, 1); ... }
7.3 性能优化建议
- 将频繁调用的函数标记为
__attribute__((section(".ram")))放入RAM执行 - 使用DMA加速GPIO批量数据传输
- 在PL端实现硬件加速逻辑,通过AXI Stream接口与PS交互
我在实际调试中发现,当系统负载较高时,简单的轮询方式会导致LED响应延迟。这时可以采用以下策略:
- 使用RTOS(如FreeRTOS)创建独立的任务处理按键和LED
- 将GPIO访问封装成原子操作
- 在PL端实现硬件去抖动电路
