1. 从零开始:理解ZYNQ PS与PL的“双核”对话
大家好,我是老张,一个在嵌入式领域摸爬滚打了十多年的工程师。今天咱们不聊那些虚的,就聊一个非常具体、非常实用的场景:如何用LabVIEW,让ZYNQ芯片里的ARM(PS端)和FPGA(PL端)真正“聊”起来,实时捕捉几个按键的状态。
听起来是不是有点复杂?别怕,我刚开始接触ZYNQ时也觉得它像个“黑盒子”,ARM和FPGA各干各的,怎么让它们协同工作是个大难题。但后来我发现,一旦掌握了它们之间通信的“桥梁”,事情就变得异常简单。这个桥梁,就是我们今天要重点玩的寄存器(Reg)通道。
你可以把ZYNQ芯片想象成一个“双核”大脑。PS端的ARM处理器,就像大脑的“逻辑思考”部分,运行着Linux RT实时系统,负责复杂的决策和任务调度。而PL端的FPGA,则像大脑的“条件反射”部分,由无数个可编程的逻辑单元组成,能对硬件信号(比如按键按下)做出纳秒级的即时反应。我们做的,就是在这两者之间搭一座高速数据桥,让“条件反射”的结果能瞬间传递给“逻辑思考”中心。
这个实验的目标非常明确:用LabVIEW写一个跑在FPGA上的小程序,实时读取开发板上三个物理按键的电平;同时,再写一个跑在ARM上的Linux RT程序,通过寄存器通道,把FPGA读到的按键状态“抓”过来,并在电脑上显示出来。整个过程,你不需要写一行Verilog或C代码,全程图形化编程,像搭积木一样直观。
我实测下来,这套方法特别适合做嵌入式控制、设备状态监测、快速原型验证。比如,你可以用它来做一个智能控制面板,FPGA负责毫秒不差地扫描所有按钮和传感器,ARM则负责处理复杂的UI交互和网络通信。两者分工合作,效率和稳定性远超传统的单片机方案。
2. 硬件准备与核心原理:你的“积木”和“桥梁”
工欲善其事,必先利其器。在动手写代码之前,咱们得先把家伙事儿准备好,并搞清楚它们是怎么连在一起的。
2.1 硬件清单与接线图
这次实验,你需要准备以下几样东西:
- 正点原子领航者ZYNQ开发板:这是我们的主战场。
- Xilinx JTAG下载器:主要用于在线调试和观察FPGA VI的前面板。(这里有个小技巧:如果你只关心ARM端程序运行结果,不打算实时看FPGA的界面,这个下载器其实可以不接,因为FPGA程序可以由ARM动态加载,后面会详细说。)
- 一根千兆网线:这是连接你的电脑(上位机)和ZYNQ开发板(下位机)的生命线,ARM端的程序全靠它来部署。
接线非常简单:用网线连接电脑和开发板的PS端网口;如果需要观察FPGA运行,再用JTAG下载器连接电脑和开发板的JTAG口。给开发板通上电,硬件准备就完成了。
2.2 关键引脚与原理:找到那三个“开关”
我们的目标是读取底板上的三个按键:PL_RESET、PL_KEY0、PL_KEY1。别看名字里带“RESET”,在ZYNQ的PL端(FPGA部分),它就是个普通的IO引脚,按下去并不会复位FPGA。(这里插一句:从7系列FPGA开始,就没有传统MCU那种专用的复位引脚了,FPGA的可靠性极高,程序烧进去就能一直跑,除非芯片物理损坏。真正的PS端ARM复位引脚是核心板上的PS_RESET。)
通过查看原理图,我们可以找到这三个按键分别连接到了FPGA的哪几个脚:
PL_RESET->N16PL_KEY0->L14PL_KEY1->K16
这三个引脚都属于BANK 35。请务必记住这三个引脚编号,等会儿在LabVIEW里配置FPGA I/O时,我们就要把它们“召唤”出来。
2.3 通信核心:寄存器(Reg)通道详解
这是本实验的灵魂所在。PS和PL之间有很多种通信方式(比如高速的DMA、FIFO),但对于按键这种简单的开关量信号,用寄存器通道是最简单、最直接的。
你可以把寄存器通道想象成连接ARM和FPGA的一组共享变量。这组变量被划分成了很多“格子”,每个格子都有固定的地址和方向。
- PS2PL_Reg:方向是ARM -> FPGA。ARM可以把数据写到这些格子里,FPGA来读。好比ARM给FPGA下命令。
- PL2PS_Reg:方向是FPGA -> ARM。FPGA可以把数据写到这些格子里,ARM来读。好比FPGA向ARM汇报状态。
我们封装好的通道里,有32路专门用于传输布尔(Bool)类型数据的PL2PS寄存器。这就完美契合我们的需求:FPGA把3个按键的“高/低”(1/0)状态,放进其中任意3个“格子”里;ARM端的程序定时去读取这3个“格子”,就知道按键是按下还是松开了。
这种方式的优势是极低的延迟和确定的时序。数据直接在芯片内部通过AXI总线传递,没有操作系统调度开销,非常适合对实时性要求高的控制场景。
3. 动手搭建:PL端FPGA程序开发
理论说再多不如动手做一遍。咱们先从FPGA端程序开始,它的任务就是持续读取引脚电平,并把状态“塞”进寄存器通道。
3.1 创建FPGA VI与配置I/O
首先,打开或创建一个LabVIEW项目(.lvproj),里面应该包含ZYNQ的FPGA终端和Linux RT终端。在FPGA终端下,新建一个VI,我习惯命名为“实验5-读取FPGA按键状态(ZYNQ PL端).vi”。
接下来是关键一步:添加FPGA I/O资源。在项目浏览器中右键点击FPGA终端,选择“New” -> “FPGA I/O”。在弹出的窗口中,找到BANK 35,展开IN文件夹,从列表里精准地找到N16、L14、K16这三个引脚,把它们添加到右侧,并分别重命名为我们熟悉的PL_RESET、PL_KEY0、PL_KEY1。点击确定后,这三个I/O节点就会出现在项目浏览器中,等着被我们调用。
3.2 编写FPGA程序框图
打开VI的程序框图,开始“搭积木”:
- 设置引脚为输入:从项目浏览器中,把刚才创建的三个I/O的
ENA(使能)节点拖到程序框图中。右键点击每个ENA节点,将其转换为“写入”模式。然后创建一个常量False(假)连接到这三个ENA端口。这个操作相当于把这三个FPGA引脚配置为输入模式,高阻态,准备读取外部信号。 - 创建主循环:从函数选板放置一个定时循环。这里有个非常重要的细节:定时循环的时钟源必须选择
lv_FCLK_CLK0_PS2PL。这个50MHz的时钟是由PS端ARM产生并提供给PL端FPGA的,是两者协同工作的“心跳”。只有使用这个时钟,FPGA和ARM之间的寄存器通信才能同步。 - 读取引脚并显示:把三个I/O的
IN(输入)节点也拖进定时循环。为每个IN节点创建一个指示灯显示控件,这样编译后,我们就能通过JTAG在线看到这三个指示灯的状态,直观地调试。 - 连接寄存器通道:这是打通“任督二脉”的一步。在项目浏览器中,找到并展开
PS_System_Reg Data这个Socket CLIP。里面有一组lv_PL2PS_Reg_Bool_x(x从1到32)的节点,这些就是FPGA向ARM发送布尔数据的通道。我们随便选三个,比如1、2、3,拖到程序框图中。 - 完成连接:将三个
IN节点(按键状态)的数据线,分别连接到三个lv_PL2PS_Reg_Bool_x节点上。这样,按键的电平状态就会在每个时钟周期被送入对应的寄存器通道。
至此,一个简洁而完整的FPGA端程序就完成了。它的逻辑非常清晰:在一个由ARM提供的50MHz时钟驱动下,循环读取三个物理引脚的电平,一方面送到前面板指示灯用于本地调试,另一方面实时写入到PL2PS寄存器通道,等待ARM来取。
3.3 编译与生成BIT文件
程序写好了,得把它变成FPGA能执行的“机器码”——也就是BIT文件。
- 首先,确保FPGA终端的执行模式是“FPGA Target”,而不是仿真模式。
- 在编译前,我强烈建议你运行一个我们提供的辅助VI:
License-ID-Bitfile-ZYNQ.vi。把它放在“我的电脑”下运行。它的作用是拦截LabVIEW编译过程中生成的原始BIT文件,并保存到你指定的路径(比如E:\ZYNQ_FPGA_Bit_Files\)。路径不要太深,且不能有中文!我吃过亏,因为路径有中文导致后续加载失败,排查了半天。 - 点击VI的运行箭头,LabVIEW会询问使用哪种编译服务器。对于个人开发,选择“本地编译服务器”即可,它会调用你电脑上安装的Xilinx Vivado进行编译。
- 在编译配置中,务必勾选“Run when loaded to FPGA”(加载至FPGA时运行)。这个选项决定了生成的BIT文件一旦被加载到FPGA,程序是自动运行还是处于挂起状态。不勾选的话,你会发现加载后程序没反应。
- 点击确定开始编译。等待几分钟,Vivado会完成综合、布局布线。编译成功后,在你刚才设置的路径里就能找到新鲜的
.bit文件了,比如我的是5-PL2PS-KEY.bit。
踩坑提醒:编译过程中如果报错找不到引脚,请回头检查I/O配置的引脚编号是否正确。如果编译时间异常长,可以查看“Estimated device utilization”报告,如果资源占用超过80%,可能需要优化代码。
4. ARM端Linux RT程序开发
FPGA部分准备就绪,现在该ARM端的Linux RT程序上场了。它的任务就是初始化系统、加载FPGA程序、然后不断地从寄存器通道里读取按键状态。
4.1 程序流程与核心函数
ARM端程序的流程是标准化的,对于任何需要与FPGA交互的Linux RT程序,几乎都遵循以下几步,我把它总结为一个“六步法”:
- PS_Load_FPGA_bit.vi:动态加载FPGA比特流。这是第一步,也是必不可少的一步。它把刚才编译好的
.bit文件加载到ZYNQ的PL端,让FPGA程序跑起来。这是ZYNQ设计最精妙的地方之一,PS可以随时重置和加载PL的硬件逻辑,灵活性极高。 - PSLoadGPIOKO(SubVI).vi:加载GPIO内核模块(KO)。我们要操作寄存器,本质上是在操作GPIO。这个VI负责把必要的Linux内核驱动动态加载到内存中。
- PS_reg_Open.vi:打开寄存器通道。相当于给PS和PL之间的寄存器“桥梁”通电,做好通信准备。
- PL_Reg_Read.vi:读取PL端寄存器数据。这就是我们的核心操作,从这个VI里,我们能拿到FPGA传过来的按键状态。它位于
PS2PL_Reg_Data函数选板中,注意图标上有“PL2PS”字样,表示方向。 - PS_Reg_Close.vi:关闭寄存器通道。程序退出前,礼貌地“关闭桥梁”,释放资源。
- PSUnloadGPIOKO(SubVI).vi:卸载GPIO内核模块。与第二步对应,程序退出前卸载驱动,保持系统干净。
这六个VI,像一套组合拳,打下来就完成了从初始化到读取再到清理的全过程。在LabVIEW里编程,就是按顺序把这些图标用数据线连起来,中间加个While循环实现持续读取。
4.2 编写Linux RT VI
在项目的Linux RT终端下,新建一个VI,命名为“实验5-读取PL端FPGA寄存器映射的按键状态(ZYNQ PS端).vi”。
- 摆放函数:从“PowerGod-RIO-RT”函数选板中,依次找到上述六个VI,拖到程序框图中。
- 连线与配置:
- 将
PS_Load_FPGA_bit.vi的bitfile path输入控件,连接到前面板的一个路径控件上。这个路径需要指向ZYNQ Linux RT系统内的一个固定位置,通常是/home/lvuser/natinst/bin/data/。我们只需要把文件名改成自己的BIT文件名即可,比如5-PL2PS-KEY.bit。 PL_Reg_Read.vi需要配置参数。它有一个Reg_Channel输入,是一个下拉枚举框,里面列出了所有可用的寄存器通道,如PL2PS_Reg_Bool_1、PL2PS_Reg_Bool_2等。这里必须和FPGA程序里选择的通道严格对应!如果FPGA用的是1、2、3通道,这里也要选择1、2、3。- 为
PL_Reg_Read.vi的Reg_Data_Read输出创建三个布尔指示灯,用于显示按键状态。
- 将
- 构建主循环:将
PL_Reg_Read.vi放入一个While循环中,并添加一个等待函数(例如50ms),以避免CPU占用率过高。这样,程序就会以20Hz的频率不断查询按键状态。 - 错误处理:良好的习惯是为整个VI链添加简单的错误处理连线,这样一旦某一步出错,程序能有序停止并报错。
程序框图搭建完成后,前面板就包含了BIT文件路径输入框、三个状态指示灯,以及一个可能用于显示循环次数的数值框。整个ARM端程序结构清晰,逻辑直接。
5. 系统联调与现象观察
最激动人心的时刻到了,让整个系统跑起来,看看PS和PL是不是真的“对上话”了。
5.1 部署与运行PS端程序
首先,我们需要把FPGA的BIT文件和Linux RT程序部署到ZYNQ开发板上。
- 网络配置:确保你的电脑和ZYNQ板子用网线直连。将电脑的以太网IP地址设置为静态,例如
192.168.2.10,子网掩码255.255.255.0。这是因为ZYNQ Linux RT系统默认有一个静态IP192.168.2.99。 - 添加BIT文件:在LabVIEW项目的Linux RT终端下,通常有一个名为“ZYNQ PL FPGA bit files”的文件夹。右键点击它,选择“Add File”,把之前编译好的
5-PL2PS-KEY.bit文件添加进来。这一步是为了在部署时,LabVIEW能自动将这个文件传输到板载系统的指定目录。 - 创建应用程序:右键点击Linux RT终端下的“Build Specifications”,新建一个“Real-Time Application”。在属性设置中,将我们写好的PS端VI添加到“Startup VIs”,并将BIT文件添加到“Always Included”文件列表里。这样,编译部署时会一并打包。
- 连接与部署:右键点击Linux RT终端,选择“Connect”。如果网络设置正确,LabVIEW会连接到板子。然后右键点击刚创建的应用程序生成规范,选择“Deploy”。稍等片刻,程序就会被下载到ZYNQ的ARM中并运行。
此时,你应该能在电脑上看到Linux RT程序的前面板“活”了,Running_PS的数值开始增加,说明循环正在运行。但按键指示灯可能还没反应,因为FPGA逻辑可能还没加载。
5.2 动态加载FPGA逻辑与联合调试
接下来是关键一步:让PS端程序加载FPGA逻辑。
- 确保PS端程序前面板上BIT文件路径是正确的(指向
/home/lvuser/natinst/bin/data/5-PL2PS-KEY.bit)。 - 点击PS端VI的运行箭头。程序会执行
PS_Load_FPGA_bit.vi,将BIT文件加载到PL端。此时,FPGA硬件逻辑开始运行。 - (可选)观察FPGA前面板:如果你想同时观察FPGA端的运行情况,可以打开之前写的FPGA VI。但这里有个重要设置:需要修改一个配置文件(通常是
ini.txt),将其中的某个标志位从1改为0。这个操作是告诉LabVIEW:不要尝试通过JTAG下载BIT文件(因为已经由PS端加载了),而是直接进入“在线前面板”模式,通过JTAG仅进行通信和调试。然后运行这个FPGA VI,你就能看到它的前面板也“活”了,Running_FPGA的数值飞速增长(50MHz时钟驱动)。
5.3 验证交互效果
现在,整个系统已经准备就绪:
- FPGA端:正在以50MHz的速度实时扫描
PL_KEY0、PL_KEY1、PL_RESET三个引脚的电平,并将状态写入PL2PS_Reg_Bool_1/2/3通道。 - ARM端:正在以约20Hz的速度,从
PL2PS_Reg_Bool_1/2/3通道读取数据,并显示在前面板上。
动手实验:
- 找到开发板上的三个PL端按键。
- 按下其中一个键,比如
PL_KEY0。你会立刻看到:- FPGA VI前面板上,对应的
PL_KEY0指示灯熄灭(因为按键按下接地,为低电平)。 - Linux RT程序前面板上,对应的
Reg_Data_Read_Bool-1指示灯也同步熄灭。
- FPGA VI前面板上,对应的
- 同时按下三个按键,则两边的三个指示灯会全部熄灭。
- 松开按键,所有指示灯恢复点亮(因为原理图上拉,默认高电平)。
这种两边前面板状态实时同步变化的现象,就是PS和PL通过寄存器通道进行实时、可靠交互的最直接证明。整个过程没有复杂的协议,没有繁琐的驱动,就是简单的读写共享变量,但效率却极高。
6. 深度优化与避坑指南
按照上面的步骤,你应该已经成功实现了功能。但作为实战指南,我还想分享一些更深度的优化技巧和常见问题的排查方法,这些都是我踩过坑后总结的经验。
6.1 性能优化与稳定性提升
- 读取频率的权衡:在PS端的While循环里,我加了50ms的等待。对于按键检测,这绰绰有余。但如果你需要检测更快的脉冲信号,这个间隔就需要缩短。不过要注意,读取频率越高,ARM的CPU占用率也会上升。你需要根据实际信号的频率和系统负载找到一个平衡点。对于微秒级的信号,建议在FPGA端做边沿检测或脉冲计数,然后通过寄存器传递结果,而不是让ARM去轮询高速信号。
- 使用中断替代轮询(高级话题):本例用的是ARM不断查询(轮询)寄存器的方式。对于多个信号或低功耗应用,更好的方式是使用中断。我们封装的寄存器通道也支持中断功能。可以在FPGA端配置当某个寄存器值变化时,向ARM发送一个中断信号,ARM收到中断后再去读取数据。这样可以极大降低ARM的负载,实现事件驱动。这部分内容会在更高级的实验中涉及。
- 错误处理与资源释放:在最终的产品化程序中,一定要完善错误处理。确保即使加载BIT文件失败、打开寄存器失败,程序也能给出明确的提示并安全退出,调用
PS_Reg_Close和Unload_GPIO_KO来释放资源。否则,驱动模块残留可能会导致下次程序运行异常。
6.2 常见问题与解决方案
PS端程序部署失败,连接超时:
- 检查网线:确保是千兆网线,且连接稳定。
- 检查IP设置:确认电脑IP(如192.168.2.10)和ZYNQ目标IP(192.168.2.99)在同一网段,且子网掩码一致(255.255.255.0)。
- 关闭防火墙:有时电脑的防火墙会阻止LabVIEW与目标的通信,尝试暂时关闭防火墙。
- 重启服务:在LabVIEW中,尝试断开目标连接,然后重新扫描或连接。
PS端加载FPGA BIT文件失败:
- 检查文件路径和名称:确保路径是ZYNQ Linux RT系统内的有效路径,且文件名没有中文和特殊字符,最好全是英文和数字。
- 检查BIT文件:确认BIT文件是通过我们提供的
License-ID-Bitfile-ZYNQ.vi导出的原始文件,并且编译时勾选了“加载时运行”。 - 权限问题:确保BIT文件已被成功包含在“Always Included”中,并部署到了目标板。可以尝试通过LabVIEW的终端窗口,用命令行查看
/home/lvuser/natinst/bin/data/目录下文件是否存在。
PS端读取到的寄存器值始终不变或不对:
- 通道号不匹配:这是最常见的问题。百分百确认FPGA程序里使用的
lv_PL2PS_Reg_Bool_x通道编号(例如1,2,3),与PS端PL_Reg_Read.vi中Reg_Channel下拉框选择的通道编号完全一致。 - 时钟不同步:确保FPGA程序中的定时循环使用的是
lv_FCLK_CLK0_PS2PL这个由PS提供的时钟。如果用了其他时钟,寄存器的读写可能无法同步。 - 硬件连接问题:用万用表测量一下按键按下和松开时,对应FPGA引脚(N16, L14, K16)的电压是否在0V(按下)和3.3V左右(松开)之间变化。
- 通道号不匹配:这是最常见的问题。百分百确认FPGA程序里使用的
FPGA VI在线前面板无法连接:
- 检查JTAG连接和驱动:确认Xilinx JTAG下载器已连接,且电脑设备管理器中能识别到“Xilinx USB Cable”。
- 检查INI文件配置:如果希望通过PS端动态加载FPGA,那么在用JTAG进行在线调试时,需要将
ini.txt文件中的相应标志位设为0,以跳过下载步骤,直接进行通信。 - PL端已由PS加载:如果PS端程序已经成功加载了BIT文件,那么FPGA硬件正在运行我们的逻辑。此时再用LabVIEW FPGA VI去连接,是可以正常通信并显示前面板的。如果PS端没加载,FPGA里是空的或者别的程序,连接自然会失败。
把这个实验吃透,你就掌握了ZYNQ异构系统协同开发中最基础、最核心的一环——数据交互。它就像你学会了如何让大脑的左右半球开始对话。基于这个基础,后续无论是传输数组、图像数据,还是使用更高效的DMA、FIFO,都会变得有章可循。