第4章:Zynq PS端入门
好,咱们今天聊聊Zynq的PS端。说白了,PS就是Processing System,也就是ARM Cortex-A9双核处理器那一套。很多刚接触Zynq的朋友会问:这东西跟普通ARM芯片有啥区别?嗯,区别大了去了——它旁边还挂着一大块FPGA逻辑,也就是PL端。两者通过AXI总线高速互联,这才是Zynq的精髓。
4.1 ARM Cortex-A9处理器介绍
Cortex-A9是个啥水平?我个人的看法是:它属于ARMv7-A架构,主打高性能。双核、顺序执行、支持NEON SIMD指令集和VFPv3浮点单元。跑Linux?完全没问题。裸机跑实时控制?也绰绰有余。
我记得第一次在Zynq上跑双核AMP(非对称多处理)时,踩了个坑——两个核默认都去抢同一个中断控制器。后来才发现,每个核有自己的GIC CPU接口,得单独配置。嗯,这里要注意。
关键特性一览:
- 双核ARM Cortex-A9,主频最高可达1GHz
- 32KB L1指令缓存 + 32KB L1数据缓存(每核独立)
- 512KB L2缓存(两核共享)
- NEON SIMD引擎,适合信号处理
- VFPv3浮点单元,单精度/双精度都支持
你想想看,有了这些硬件资源,Zynq的PS端完全可以独立跑一套完整的嵌入式系统。FPGA那边呢?专心做高速数据采集、接口协议转换、硬件加速。分工明确,各司其职。
4.2 PS端外设概览
Zynq的PS端集成了大量常用外设。我习惯把它们分成两类:一类是通信接口,一类是控制接口。咱们挑几个最常用的聊聊。
4.2.1 UART
UART,串口,嵌入式工程师的老朋友。Zynq的PS端集成了两个UART控制器,支持最高115200bps甚至更高的波特率。调试、打印日志、跟PC通信,全靠它。
我在项目中遇到过一个问题:UART发送数据时偶尔丢字节。查了半天,发现是FIFO深度设置太小,加上中断响应不及时。后来把FIFO触发阈值调高,问题就解决了。说白了,中断服务程序别写太啰嗦。
小技巧:调试阶段建议把UART波特率设为115200,兼容性好,大部分串口工具都支持。量产时如果对速度有要求,可以上到921600。
4.2.2 I2C
I2C,两根线(SCL、SDA),挂一堆设备。Zynq的PS端有两个I2C控制器,支持主模式和从模式。我常用它来读温度传感器、配置视频解码芯片、跟EEPROM打交道。
曾经有一次,I2C总线莫名其妙卡死。SCL正常,SDA被拉低不放。嗯,典型的从机死锁。解决办法?给SCL来几个脉冲,让从机复位状态机。或者干脆硬件上加个I2C总线恢复电路。
4.2.3 SPI
SPI比I2C快,全双工,四线制(MOSI、MISO、SCLK、CS)。Zynq的PS端有两个SPI控制器,支持主从模式,时钟频率可编程。我一般用它来配ADC、DAC,或者跟FPGA内部的逻辑通信。
这里有个坑:SPI的时钟极性(CPOL)和相位(CPHA)一定要跟从机对上。我曾经因为CPOL设反了,读回来的数据全是0xFF。排查了半天,最后拿示波器一看——时钟空闲电平不对。嗯,示波器是嵌入式工程师最好的朋友。
4.2.4 GPIO
GPIO,最基础也是最灵活的外设。Zynq的PS端提供了多达54个MIO引脚,可以配置为输入、输出、中断触发。另外还有EMIO,可以通过PL端扩展更多GPIO。
我个人习惯:按键用MIO,LED也用MIO。如果不够用,再通过EMIO挂到PL端。注意一点,MIO的电压域是固定的(Bank0/1是3.3V,Bank2/3是1.8V或2.5V),选引脚时别搞错电平。
注意事项:MIO引脚功能是复用的。比如UART、I2C、SPI、GPIO都共用同一组引脚。配置时一定要检查有没有冲突。我见过有人把UART的TX和I2C的SCL设到同一个引脚上,结果两个外设都工作不正常。
4.3 第一个Hello World程序
好,理论说完了,咱们动手写个Hello World。开发环境我选Vitis(以前叫SDK)。流程很简单:建工程、写代码、编译、下载、看串口输出。
4.3.1 创建Vitis工程
- 打开Vitis,选择工作空间目录
- 点击 File → New → Application Project
- 输入工程名,比如 hello_world_ps
- 选择硬件平台(.xsa文件),这个文件由Vivado生成
- 选择模板:Hello World
Vitis会自动生成一个main.c文件,里面已经写好了基本的打印逻辑。咱们只需要改改串口号和波特率就行。
4.3.2 代码示例
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
int main()
{
init_platform();
xil_printf("Hello from Zynq PS!\r\n");
xil_printf("This is my first ARM Cortex-A9 program.\r\n");
xil_printf("UART is working at 115200 baud.\r\n");
cleanup_platform();
return 0;
}
注意这里用的是 xil_printf,不是标准C的 printf。为啥?因为 xil_printf 更轻量,不依赖堆,适合裸机环境。标准 printf 也能用,但会占用更多内存。
4.3.3 编译与下载
点击锤子图标编译。没报错的话,右键工程 → Run As → Launch on Hardware (Single Application Debug)。Vitis会自动把程序下载到Zynq的DDR里,然后运行。
打开串口助手(比如PuTTY、SecureCRT),设置波特率115200、8数据位、1停止位、无校验。你应该能看到:
Hello from Zynq PS!
This is my first ARM Cortex-A9 program.
UART is working at 115200 baud.
看到这行输出,恭喜你,第一个Zynq PS程序跑起来了!
调试小贴士:如果串口没输出,先检查三件事:1)串口线接对了没?2)Vivado里UART的MIO引脚分配对了没?3)串口助手的波特率跟代码里设的是否一致?90%的问题出在这三个地方。
4.4 本章知识体系
为了让你更直观地理解本章内容,我画了一张结构图。它展示了Zynq PS端的核心组成和本章涉及的知识点。
这张图把本章内容串起来了:从ARM Cortex-A9处理器出发,到四个常用外设,再到Hello World实战,最后落到开发工具上。你跟着这个脉络走,思路会清晰很多。
好了,第4章就到这里。代码不多,但概念很重要。PS端是Zynq的大脑,搞懂它,后面学PL端和AXI总线会轻松很多。下一章咱们聊聊PL端的基础——FPGA逻辑设计入门。
课后小练习:试着修改Hello World程序,让它每隔1秒打印一次 "Tick..."。提示:可以用 sleep(1) 函数,或者自己写个延时循环。
可可精品资料库