3. Hello World实验:PS端最小系统搭建、串口打印输出、工程结构解析
说实话,很多初学者一上来就盯着Zynq的PL端(FPGA逻辑)看,觉得那才是“硬核”的东西。但我个人的经验是,先把PS端(ARM处理器)跑通了,后面的路才会顺。毕竟Zynq是个异构平台,ARM才是那个“大脑”。
这一章,我们就从最经典的Hello World开始。别小看它,这是你验证整个PS端最小系统是否健康的“金标准”。
3.1 最小系统长什么样?
一个能跑起来的PS最小系统,其实就三个核心要素:
- 供电与时钟:PS端需要稳定的电源和参考时钟。开发板上一般已经做好了,我们只需要在Vivado里配置好时钟频率。
- 复位与启动:POR(上电复位)和SRST(系统复位)必须正确。启动模式我们通常选QSPI Flash或SD卡。
- 串口通信:这是我们的“眼睛”,用来打印信息。Zynq的UART1通常映射到MIO 48/49。
核心观点:最小系统 = 能跑裸机程序 + 能输出信息。其他外设(DDR、以太网)都是可选的,但串口必须有。
我在项目里见过有人上来就配了DDR、USB、千兆网,结果串口没配通,程序卡死在bootrom里,连个错误信息都看不到。嗯,那排查起来真是抓瞎。
3.2 搭建步骤:从Vivado到SDK
流程其实很固定,我把它拆成5步:
- 创建Vivado工程:选好芯片型号(比如XC7Z020)。
- 例化Zynq Processing System IP:双击打开配置界面。
- 配置PS端:主要配时钟、DDR、UART、MIO。
- 生成Bitstream并导出硬件:注意要勾选“Include Bitstream”。
- 启动SDK,创建Hello World工程:选“Empty Application”模板。
这里有个容易踩的坑——时钟配置。Zynq的CPU时钟默认是666.666MHz,但很多开发板的外部晶振是33.333MHz。Vivado会自动计算PLL参数,但如果你手动改了,就得小心了。
小技巧:在Vivado的“Clock Configuration”页面,我习惯把CPU时钟设为650MHz(留点余量),DDR时钟设为525MHz(对应DDR3-1066)。这样既稳定,又不会太热。
3.3 串口打印:你的第一个程序
硬件搭好了,就该写代码了。在SDK里,新建一个“Hello World”工程,系统会自动生成一个helloworld.c。核心代码其实就几行:
#include <stdio.h>
#include "platform.h"
int main()
{
init_platform();
print("Hello World!\n\r");
cleanup_platform();
return 0;
}
这里有个细节:print()函数是Xilinx提供的,它直接操作UART寄存器,比printf()轻量得多。我刚开始用的时候,总想用printf(),结果发现要额外配置标准库,麻烦。后来就老老实实用print()了。
编译、下载、运行。如果一切顺利,串口终端上会看到:
Hello World!
就这一行,说明你的PS端最小系统已经活了。
注意:串口波特率默认是115200,8位数据,无校验,1位停止位。终端软件(如Putty、MobaXterm)必须设置一致,否则打印出来的是乱码。
3.4 工程结构解析:Vivado与SDK的分工
很多新手搞不清Vivado和SDK到底谁管什么。我画个图你就明白了:
你看,Vivado负责“造硬件”,SDK负责“写软件”。两者通过.hdf文件(硬件描述文件)对接。这个文件里包含了所有外设的基地址、中断号、时钟频率等信息。
我个人习惯在Vivado里把硬件配置得尽量完整,哪怕暂时用不到的外设也先配好(比如GPIO、定时器)。这样后面软件改起来方便,不用来回切工具。
3.5 工程文件结构:每个文件夹是干嘛的?
打开SDK工程,你会看到一堆文件夹。我刚开始也觉得乱,后来总结了一张表:
| 文件夹/文件 | 作用 | 备注 |
|---|---|---|
src/ |
存放你的C源文件 | main.c、helloworld.c等 |
bsp/ |
板级支持包(驱动库) | 包含UART、GPIO、定时器等驱动 |
platform/ |
平台初始化代码 | init_platform()、cleanup_platform() |
lscript.ld |
链接脚本 | 定义内存布局、堆栈大小 |
.hdf |
硬件描述文件 | 由Vivado导出,SDK读取 |
这里我想重点说说lscript.ld。它定义了程序放在哪里、堆栈有多大。默认情况下,代码放在DDR的0x00100000地址,堆栈大小是1MB。如果你跑的程序比较大,或者用了malloc,记得调大堆栈。
避坑指南:我曾经在一个项目里,因为堆栈设得太小(默认256KB),导致程序跑着跑着就死机了。查了两天才发现是栈溢出。后来我习惯把堆栈设到2MB,反正DDR管够。
3.6 调试技巧:串口没输出怎么办?
Hello World最常见的故障就是——串口没反应。别慌,按这个顺序排查:
- 检查硬件连接:串口线插对了吗?开发板供电了吗?JTAG下载器连上了吗?
- 检查终端设置:波特率、数据位、停止位、流控——必须和Vivado配置一致。
- 检查MIO分配:UART1的TX/RX是不是映射到了MIO 48/49?有没有被其他外设占用?
- 检查启动模式:如果从QSPI启动,确保镜像烧录正确。如果从JTAG启动,确保SDK下载成功。
- 检查代码:
init_platform()有没有调用?print()有没有拼写错误?
嗯,其实90%的问题出在前两步。我见过有人拿着USB转串口线,结果TX和RX接反了,折腾半天。
3.7 小结
这一章我们干了三件事:搭最小系统、打印Hello World、搞懂工程结构。说白了,就是让Zynq的ARM核“开口说话”。
别小看这一步。后面所有的外设驱动、中断处理、AXI总线通信,都是在这个基础上搭建的。基础打牢了,后面才能跑得快。