公司动态
ZYNQ PS Hello World实战:从Vivado配置到串口输出
简介本资源是面向嵌入式初学者与Zynq-7000开发入门者的PS端基础实践工程聚焦Xilinx ZYNQ7000 SoC的Processing SystemARM Cortex-A9双核部分解决从硬件平台搭建到裸机“Hello World”程序运行的全流程实操问题。压缩包共622个文件涵盖130个XML配置、101个头文件h、87个C源码c、46个目标文件o、41个Verilog模块v及12个Makefile等核心构建要素完整包含PS初始化ps7_init.c、外设驱动xadcps.c、xdmaps.c、系统级BD设计ARM_CORE.bd、比特流ARM_CORE_wrapper.bit及可执行镜像elf包体仅3.52MB结构紧凑、开箱即用。已有308人下载学习资源直接提供可编译运行的SDK工程框架与配套脚本runme.bat省去Vivado环境配置与硬件导出环节特别适合快速验证PS启动流程、串口输出机制及软硬协同调试路径。 刚拿到ZYNQ开发板的人大部分第一件事就是找例程点灯。点灯确实能带来成就感但对ZYNQ来说点灯只是PL可编程逻辑那边最基础的动作真正值钱的是PS——那颗双核ARM Cortex-A9处理器。我这次做的项目很简单标题里写得很直白PS部分的Hello World。本质就是把经典的Hello World程序跑在ZYNQ的ARM核上让它从串口吐出一行字。但这一行字背后牵扯到硬件工程创建、处理系统配置、DDR初始化、FSBL启动流程、JTAG下载、串口调试这一整条链路。对于第一次接触ZYNQ的人来说这条链路比那行字本身值钱得多。这篇博文把我的操作过程、配置细节和踩过的坑都记下来你照着走一遍就能跑通跑完之后再回头看架构手册会觉得轻松很多。无论是学生做课程设计、工程师从FPGA转嵌入式还是纯粹想搞明白ARM核和FPGA到底怎么协同这篇内容都适用。1. 为什么ZYNQ的第一个程序要先跑PS——双核ARM是地基不是选配很多人拿到ZYNQ-7000第一反应是“这是个FPGA”倒也没错但只对了一半。ZYNQ-7000严格来说是一颗SoC片上系统芯片里同时集成了两套完全不同的子系统PSProcessing System处理系统和PLProgrammable Logic可编程逻辑。PS部分就是一颗完整的双核ARM Cortex-A9处理器带L1/L2缓存、DDR控制器、DMA控制器、定时器、UART、SPI、I2C、USB、以太网等等一大堆外设PL部分才是传统意义上那个FPGA跑RTL逻辑。两者之间通过AXI总线相连。1.1 为什么Hello World是软件程序而不是硬件逻辑想清楚一件事Hello World的本质是“让CPU取指、执行、输出字符串”。在ZYNQ上能干这件事的只有ARM核也就是PS侧。PL那边是一堆查找表和触发器没有指令集干不了这活。所以哪怕你只想让PL点个灯你通常也得先把PS跑起来因为DDR、时钟、PLL这些最基础的系统资源都在PS侧管理。这也就解释了为什么工程名叫“PS部分HELLO_WORLD”——它明确告诉你这是跑在ARM上的裸机软件程序。RAR压缩包也暗示了这个工程是可以直接解压、导入、上板的小项目。对于零基础起步的开发者把这个过程走通相当于把ZYNQ的“最小系统”摸了一遍。1.2 启动流程从BootROM到FSBL再到你的程序ZYNQ-7000上电后芯片内部固化在ROM里的BootROM会先跑它负责读启动模式引脚Mode Pins决定从哪启动QSPI Flash、SD卡、JTAG等。在SDK里用JTAG调试模式跑裸机程序时并不需要把程序烧到Flash里而是由调试器把FSBL和应用程序直接加载进内存。FSBLFirst Stage Boot Loader是Xilinx自动生成的一段启动代码它的核心职责是初始化DDR、配置PLL和时钟、把应用加载到DDR指定地址然后跳过去执行。简单类比一下BootROM相当于电脑的BIOSFSBL相当于引导加载程序我们的Hello World那个ELF相当于被加载进内存的应用程序。这个流程在后续做Linux启动、做双核AMP时还会遇上很多次纯粹用Vivado的人可能永远不需要碰FSBL但只要你在SDK里选“Launch on Hardware”它就一直在背后帮你干活。1.3 为什么这个工程不需要写一行Verilog纯PS的Hello World不需要PL参与因此你在Vivado里可以不生成比特流Bitstream甚至在导出硬件时可以取消勾选“Include bitstream”。有人会担心不生成比特流ARM能跑吗能。ARM所有外设和时钟都是硬核PL只是默认处于未配置状态而已。当然如果你之后想用PL里的自定义逻辑或者想通过AXI访问PL那边的寄存器就必须在SDK下载程序前先把比特流配置进PL。这是后话先把PS跑通再说。2. 硬件工程里三步配置一个能用的PS——DDR、UART、时钟是重点虽然Hello World不涉及PL逻辑但你仍然需要先在Vivado里搭一个硬件工程因为SDK/Vitis需要一份硬件描述文件hdf或xsa来知道“芯片上有什么、外设怎么配置、DDR用什么颗粒、UART走哪组引脚”。这一步省不了很多人卡在这个阶段其实也就三个关键动作。2.1 创建Vivado工程与Block Design打开Vivado创建一个RTL工程。器件型号按你自己板子上那颗芯片选如果手里的板子是常见的ZedBoard或国产兼容板通常是XC7Z020-1CLG484如果是MicroZed可能是XC7Z010。选错型号会导致SDK里引脚和资源对不上所以这个环节别凭感觉选。工程建好之后在左侧Flow Navigator里点“Create Block Design”名字随意。然后往空白画布里添加ZYNQ7 Processing System这个IP。添加完会看到一个黄色的小方框它就是整个PS子系统的封装。对纯PS工程来说这个方框就是全部硬件。2.2 配置PS的最关键三项DDR、UART、时钟双击ZYNQ7 Processing System的图标进入配置界面。左侧有多个配置页大部分保持默认就能用但以下三项必须仔细看。DDR配置DDR Configuration开发板用的DDR3颗粒型号ZedBoard等很多板子用的是镁光MT41K256M16系列。需要选择对应的memory part如果列表里找不到完全一致的型号可以选择同厂家、同容量、同位宽的近似型号再手动核对Row Address Bits、Column Address Bits等参数。很多人觉得DDR不配置也能跑其实不行Cortex-A9上电后如果要跑C语言程序DDR控制器必须正确初始化否则程序一跳到DDR就死机。UART配置MIO Configuration这个工程要往串口打印字符串必须确认UART1或者UART0看你板子的丝印被勾选且引脚分配在MIO上。绝大多数开发板把UART1的TX、RX分配在MIO 48和MIO 49PS端的MIO不能随意换引脚引脚是芯片固定的。配置完后在PS-PL Configuration里不需要勾选任何PL接口因为不涉及PL。时钟配置Clock Configuration输入时钟通常是33.333MHz由开发板晶振提供。CPU时钟默认666.666MHzDDR时钟一般533MHzDDR3常见或400MHz。如果这个值配错可能导致系统时序跑飞程序时灵时不灵。这里有一个经验PS的UART默认波特率是1152008位数据、无校验、1位停止位也就是常说的115200 8N1。SDK生成的BSP会自动按这个参数初始化串口所以我们后面的串口终端也要设置成同样的参数。2.3 连线、生成输出与导出硬件配置完成之后点击画布里的“Run Block Automation”它会自动帮你完成IP之间的连接。这时候画布里可能会自动出现一些外部接口比如DDR的地址引脚。对于纯PS工程连完线后几乎不需要手动操作。接下来在Sources窗口右键点击block design的顶层选择“Generate Output Products”——这一步会生成IP相关的输出文件第一次运行可能比较慢属于正常现象。再右键点block design或顶层wrapper选择“Create HDL Wrapper”让Vivado生成一个顶层Verilog/VHDL文件。如果你不需要PL跑任何逻辑到这里就可以直接导出硬件了。在Vivado菜单栏选File - Export Hardware注意不要勾选“Include bitstream”。导出的文件后缀跟版本走Vivado 2019.2及更早版本导出的是hdf文件2020.1之后导出的是xsa文件。虽然名字变了用途没变SDK/Vitis全靠它来理解你的硬件配置。把这个文件导出来Vivado这半边的事情就结束了。3. 从硬件导出到SDK——Hello World是怎么跑在A9核上的这一步是整个项目里“软件味”最浓的部分。硬件工程师第一次打开SDK会有点懵因为满屏的C代码和构建配置项但实际上逻辑很清晰SDK根据hdf/xsa生成BSPBSP提供串口、定时器、GPIO等外设的驱动库我们的应用代码调用这些库函数链接器脚本把程序放到DDR里调试器再把它下载进去。整套东西跑通之后你基本就理解嵌入式开发的全貌了。3.1 创建Application Project与Hello World模板打开SDK先设置一个Workspace目录建议新建一个干净的文件夹不要跟Vivado工程混在一起。然后File - New - Application Project给工程随便起个名字比如ps_hello_world。在弹出的硬件平台里确保已经加载了刚才导出的hdf/xsa文件。下一步会问你“从哪个模板创建”这里有一大堆模板Peripheral Tests、Empty Application、Hello World等。选“Hello World”。如果列表里找不到这个模板大概率是BSP生成时出了问题删除BSP重新生成试试还有一个可能是hdf里没有有效的外设配置。模板本质上就是帮我们生成一个最小工程包含helloworld.c、链接脚本lscript.ld和一个BSP。点击Finish之后SDK会花十几秒到一分钟不等自动生成BSP和工程。左边Project Explorer里会出现两个关键东西一个是ps_hello_world应用工程一个是ps_hello_world_bsp板级支持包里面包含xil_printf、串口驱动等库。先在树形目录里展开ps_hello_world - src双击helloworld.c你会看到这样一段代码#include stdio.h #include platform.h #include xil_printf.h int main() { init_platform(); print(Hello World\n\r); cleanup_platform(); return 0; }这段代码就是整个项目的核心。init_platform()会初始化UART等基础外设print是SDK里提供的极简输出函数底层走UARTcleanup_platform()做一些清理动作。整个逻辑简单到不能再简单但能跑通这几个函数说明时钟、DDR、串口、FSBL、调试器全部工作正常。3.2 xil_printf和printf到底该用哪个模板默认用的是xil_printf这是Xilinx提供的一个轻量级printf实现支持字符串和整数格式化。它最大的优势是不依赖完整的C标准库编译出来的二进制小很多而且不会因为堆栈配置问题随机崩溃。缺点是浮点格式化支持很弱如果你要打印float建议老老实实改用printf或者自己处理一下浮点格式。从个人习惯来说裸机调试阶段我会优先用xil_printf够用、稳定、问题少。如果你在代码里改成printf也能跑但需要确保BSP配置里链接了libc库在某些配置下因为堆没初始化、malloc内部状态异常导致打印几次之后死机这种问题排查起来特别容易头疼。3.3 链接脚本lscript.ld决定了程序住在哪里工程生成的lscript.ld文件是链接脚本它告诉编译器程序的栈放哪、堆放哪、代码段放哪。以默认配置为例代码一般会放到DDR的某个地址比如0x00100000或0x100000栈和堆也都在DDR里。如果你的DDR配置不对程序加载到DDR地址后取指失败表现就是调试器显示程序已经运行但什么都没发生。这种问题不是改代码能解决的得回头核对Vivado里的DDR配置。不夸张地说ZYNQ裸机调试的大部分诡异问题最后都能追溯到DDR配置错误。所以万一行不通先回去查DDR这是经验之谈。4. 连接开发板实战——从下载程序到串口吐出第一行字工程编译通过这只是万里长征走完第一步。真正让人踏实的是看到串口打印出Hello World。这一步涉及物理连接、驱动安装、调试器配置、串口工具设置几个环节任何一个环节出错结果都是“没输出”。4.1 先把JTAG和串口线接明白先确认开发板配套的JTAG调试器。很多开发板板载了USB-JTAG插一根USB线到电脑就行也有一部分板子需要外接JTAG仿真器。无论哪种方式第一件事是装驱动。Windows下常见的问题是插上USB后设备管理器里出现黄色感叹号或者显示“USB Serial”而不是“Xilinx Cable”。这时候需要安装Xilinx Cable Drivers这个驱动随Vivado安装包一起发布也可以在Vivado安装目录的data/cable_drivers目录下找到手动指定驱动路径进行安装。装完之后设备管理器里应该能看到“Xilinx USB Cable”或类似名字的设备节点。串口方面绝大多数带USB转串口功能的开发板插上USB线后电脑会多出一个COM口设备管理器里能看到对应的COM号记下来。如果是外接USB转TTL模块注意TXD接板的RXDRXD接板的TXDGND共地这个接反是串口无输出的头号原因。板载串口则不需要担心接线问题。4.2 串口终端设置115200 8N1没有商量余地Windows上可以用MobaXterm、SecureCRT或者最简单的串口助手。打开串口前先确认串口参数是1152008位数据位无校验1位停止位无流控。很多人在串口助手界面里不小心勾了“DTR”或“RTS”可能导致ZYNQ复位或者Boot模式被拉低板子不启动。如果发现一打开串口板子就重启请检查是不是DTR/RTS被勾选了。打开串口助手的时机也有讲究。建议先打开串口终端再接板子电源、再给JTAG下载程序。这样程序一跑起来打印信息就能被完整收到不会漏掉开头几个字符。4.3 用SDK把程序下载到DDR插好JTAG、打开串口终端之后回到SDK。确认工程已经在Project Explorer里高亮右键选择Run As - Launch on Hardware。SDK会先编译工程然后自动连接JTAG目标接着在DDR里初始化FSBL把ELF加载进去再把PC指针跳到main的入口。整个过程快则十几秒慢则几十秒。Console窗口里会滚动一堆启动日志。等调试器把控制权交给ARM核之后串口终端就会出现“Hello World”。如果Console窗口本身就显示了Hello World那是因为SDK集成了串口终端如果用的外接串口工具就只会在串口工具里看到输出。一定要确认自己看的是哪个窗口不要弄混。4.4 命令行方式XSCT备选方案有些场景下图形界面不方便特别是你想做自动化测试或者只装了命令行工具。这时可以用XSCTXilinx Software Command-Line Tool来干活。启动XSCT后连接目标connect targets -set -filter {name ~ *Cortex-A9*} rst -system after 500 fpga -file /path/to/design.bit ;# 如果不需要PL这步可以跳过 dow /path/to/ps_hello_world.elf con这段命令的作用是连接JTAG、复位系统、加载比特流如果存在、下载ELF、运行程序。对你理解整个下载流程很有帮助很多时候图形界面卡住了用命令行反而能快速定位是驱动问题、地址问题还是ELF文件本身问题。5. 第一次上板最容易翻车的三个坑——我替你们先踩一遍我见过太多人卡在“Hello World出不来”这个阶段其实大部分问题既不是代码问题也不是Vivado配置问题而是几个非常琐碎的外围因素。这里按出现的概率从高到低排一下。5.1 坑一JTAG识别不到设备现象SDK里Run As报错说“Cannot connect to target”或者Target List里空空的连Cortex-A9的影子都见不到。排查链路先看设备管理器确认Xilinx USB Cable设备出现了并且没有感叹号。没有设备先拔了重插换一个USB口试试。台式机尽量插后面板直连的USB口不要经过HUB。检查开发板电源是否正常有些板子JTAG链路需要核心电压起来才能做边界扫描。检查Boot Mode跳线。JTAG调试模式下boot mode必须设置成JTAG模式通常是全为0的组合如果板子当前调成QSPI启动JTAG扫描可能失败。用Vivado Hardware Manager打开设备看能不能枚举到目标。如果可以枚举到把错误信息记下来再回到SDK排查如果连Hardware Manager都识别不到问题出在驱动和物理连接上。换一台电脑、换一根USB线这种“玄学”问题有时候真的是线材老化导致的。5.2 坑二程序下载成功但串口没有任何输出现象SDK显示下载完成、程序运行了但串口终端一片空白。排查链路确认串口终端连的是正确的COM口波特率是115200。可以在终端打开时手动把TXD和RXD短接敲键盘看能不能回显能回显说明串口工具本身没坏。确认板子的UART引脚确实配到了MIO 48/49。有人会在Vivado里改错MIO比如把UART1勾到了EMIO上那PS内部的串口号就不是从MIO直出串口自然没信号。回Vivado里双击ZYNQ IP在MIO Configuration里检查UART1是否勾选且分配到MIO。确认代码里调用的是正确串口号。BSP默认有stdin/stdout的概念ZYNQ有UART0和UART1两个串口BSP默认标准输入输出可能绑定在UART0而硬件上你的串口接在UART1就发不出东西。在BSP设置里把stdin和stdout改成板子上实际使用的那个UART。如果串口终端之前收到过乱码说明线路和数据路径通了问题可能在波特率两边不一致如果完全没信号则更可能是硬件连接或UART配置问题。5.3 坑三程序一运行就卡死或者跑飞现象Run As之后Console里显示程序已经运行但没有任何现象或者Debug模式下全速运行后暂停PC指针跑到了奇怪的地址。排查链路十有八九是DDR配置错误。回到Vivado检查DDR型号、位宽、bank地址位数等参数与开发板原理图仔细核对。哪怕型号不一样地址参数必须一致。链接脚本lscript.ld里指定的内存地址不在DDR范围内。默认模板一般没错但如果手改过就可能把代码放到了不存在的地址上。FSBL初始化失败。在Console里看启动日志如果FSBL打印的信息在DDR初始化那一步就卡住基本可以确定DDR时序或配置有问题。跑飞还有一种可能是cleanup_platform()之后return 0程序回到了一个空地址此时调试器会报异常。可以改成while(1)空循环来测试或者直接删除return 0之前的清理逻辑看是否稳定。5.4 附带坑版本不匹配的雷Xilinx工具链版本比较讲究。Vivado工程文件、导出的xsa/hdf、SDK版本如果各是各的常常会出现“找不到平台文件”“BSP生成失败”的怪问题。如果你照着网上的教程做尽量保持教程的Vivado版本和你本机一致。2020.1之后SDK被整合进Vitis界面上很多操作变了但底层逻辑还是一样的。另外较新版本的Vivado对操作系统有要求Windows下记得用管理员权限运行安装器否则驱动装不干净后面全是麻烦。6. 跑通之后这个工程还能怎么用Hello World跑通并不是终点它是你后续所有ZYNQ开发的起点。我习惯把这种最小系统工程单独留一个备份每次用新板子、新版本工具第一件事就是跑一遍PS Hello World验证工具链、验证JTAG、验证DDR、验证串口。这一套东西没跑通之前不要去碰PL逻辑更不要去碰Linux启动否则到时候出了问题你会陷入“硬件问题还是软件问题”的死循环。跑通之后可以往几个方向扩展第一用GPIO MIO控制LED这比PL点灯更加“PS风格”代码里用XGpio驱动库就能操作第二给UART加中断配合FIFO做一个简易的串口回环开始理解中断驱动的编程模型第三尝试双核AMP让CPU1跑第二个程序用SGI中断互相通信这会让你对ZYNQ的多核架构有更深的认识。最后说一个小技巧SDK工程目录里有个ps_hello_world_bsp文件夹里面包含了所有外设驱动的源码如果你对某个寄存器的配置有疑问直接去翻驱动源码比查手册快得多。比如xuartps.h里能查到BSP把波特率设成多少xvitis_drivers列表里能查到外设基地址。把这些代码当参考书用你对ZYNQ的理解速度会快很多。本文还有配套的精品资源点击获取