1. 项目概述与核心价值最近在搞一个基于创龙DR1系列核心板的工业网关项目里面需要同时接入RS232、RS485和RS422三种不同标准的串口设备。如果全用PS端的原生UART引脚和资源根本不够分而且布线也是个麻烦事。这时候FPGAPL端的可编程灵活性就派上用场了。我决定把串口控制器这部分逻辑放到PL端来实现通过AXI总线让ARM核PS端去控制这样既能灵活扩展串口数量又能利用PL的并行处理能力。这个axi_uart_rw案例就是我折腾过程的完整记录它完美演示了如何用PS通过AXI4-Lite总线去驱动PL端的AXI UARTLite IP核最终在评估板上实现了三路串口RS232/RS485/RS422的收发功能。这个案例的价值远不止是“让串口跑起来”那么简单。它本质上是一个标准的PS-PL异构协同通信的范本。如果你正在接触Zynq或者类似的ARMFPGA架构并且苦恼于如何让两者高效、可靠地对话那么这个从IP核配置、总线连接、设备树适配到上层应用测试的完整流程具有很高的参考价值。无论是做工业通信、数据采集还是协议转换这套方法都能帮你快速打通PS和PL之间的数据通道。接下来我就把这其中的设计思路、关键步骤以及我踩过的坑毫无保留地分享出来。2. 工程整体架构与设计思路拆解2.1 为什么选择AXI UARTLite与AXI4-Lite首先得搞清楚我们为什么这么设计。DR1核心板的PS端是双核ARM Cortex-A7PL端是安路的FPGA。我们需要在PL端实现多个串口控制器并让PS端的Linux系统能够像操作普通设备文件一样去操作它们。最直接的想法就是在PL端用硬件逻辑HDL编写UART控制器然后通过AXI总线暴露给PS。但自己从头写UART控制器和AXI接口逻辑验证周期长且稳定性存疑。所以使用经过验证的IP核是更高效可靠的选择。Xilinx以及兼容的安路生态提供的AXI UARTLite IP核就是一个轻量级、易于集成的解决方案。它实现了完整的UART功能并自带标准的AXI4-Lite从机接口可以直接挂载到AXI总线上PS端的CPU通过读写这个接口的寄存器就能控制串口收发。为什么是AXI4-Lite而不是完整的AXI4这是由串口控制器的特性决定的。UART通信是低速、顺序的控制寄存器如波特率、数据寄存器的访问也是简单的读写操作不需要突发传输Burst Transfer、缓存对齐等复杂特性。AXI4-Lite是AXI4的一个子集它简化了接口只支持单次读写事务非常适合这种低速、寄存器映射式的控制场景能节省PL端的逻辑资源。我们的核心任务就是搭建一座桥梁让PS端的ARM处理器能通过这条简化的“高速公路”AXI4-Lite准确找到并控制PL端的各个UARTLite“站点”。2.2 系统架构框图与数据流分析整个工程的硬件架构可以用下面这个简化的框图来理解---------------------- AXI4-Lite Bus ----------------------------------- | | | | | PS (ARM Cortex-A7) | | PL (FPGA) | | - Linux Kernel | GPIO Control | ----------------------------- | | - Device Driver | ------------------- | | AXI Interconnect (Matrix) | | | | | ----------------------------- | ---------------------- | | | | | | v v v | | -------------------- | | | UARTLite | UARTLite | ... | | | | (RS232) | (RS485) | | | | --------------------------- | | | ----------------------------------- | v Physical UART Ports (TX, RX, RTS, CTS...)PS端运行Linux操作系统。内核通过标准的tty设备驱动框架来管理串口。当应用程序如echo、cat命令或自定义程序读写/dev/ttySLx设备文件时内核的驱动会将这些操作转换为对特定物理地址的读写。AXI总线与互联矩阵AXI Interconnect / Matrix这是连接PS和PL的“交通枢纽”。PS的Master接口通常是M_AXI_GP0发出读写请求。AXI Interconnect IP核在图中简化为Matrix负责路由这些请求。由于我们有多个UARTLite IP核每个对应一个物理串口Interconnect需要根据请求的地址将其分发到对应的IP核上。这就像快递分拣中心根据地址把包裹送到不同街区。PL端IP核AXI UARTLite核心功能单元。每个IP核独立负责一个串口通道的物理层和数据链路层。它内部包含波特率发生器、发送/接收移位寄存器、状态寄存器等。PS通过AXI4-Lite读写其内部寄存器来控制它。AXI Protocol Converter可选但常见由于PS端的AXI主接口可能是AXI4而UARTLite是AXI4-Lite有时需要这个IP核进行协议转换确保通信兼容。GPIO控制对于RS485/RS422这类需要方向控制的串口除了TX/RX数据线还需要控制“使能”DE和“接收使能”RE_n引脚。这些引脚通常通过PS端的GPIO来控制。在硬件设计中这些GPIO信号也被引出到PL连接到对应的电平转换芯片如MAX3485的控制脚上。软件上通过Linux的sysfsGPIO接口/sys/class/gpio来控制其高低电平从而切换串口的收发模式。这个架构的优势在于清晰的分层和解耦。PS端专注于高层协议和应用PL端实现标准的、可复用的通信接口两者通过标准化的AXI总线连接极大提高了设计的模块化和可维护性。3. Vivado工程创建与IP核配置详解3.1 工程创建与平台设置首先需要在Vivado或安路对应的TD工具中创建一个新工程选择正确的芯片型号对应DR1核心板的FPGA型号。工程类型选择“RTL Project”并添加或创建顶层HDL文件。关键的一步是添加Zynq Processing System IP核对于安路平台可能是类似的“ARM Processor System”IP。这个IP核代表了PS端的整个ARM子系统。双击配置它确认PS-PL接口在“PS-PL Configuration”选项中确保使能了用于一般用途的M_AXI_GP0接口Master AXI General Purpose。这是我们PS主动访问PL的总线出口。数据宽度通常保持32位。配置GPIO在“MIO Configuration”或“GPIO”部分我们需要引出用于控制RS485/RS422方向的控制引脚。根据原理图找到PS端EMIOExtended MIO或Bank 500的GPIO。在本案例中我们使用了4个GPIO信号例如GPIO_0_tri_io[3:0]并将其导出到顶层端口命名为类似rs485_1_de,rs485_1_re_n,rs422_de,rs422_re_n这样的网络名。这里有个细节GPIO PL(Width)参数要设置为4以匹配我们引出的4位GPIO总线宽度。时钟与复位确保为PL部分提供时钟如FCLK_CLK0和复位FCLK_RESET0_N信号。这些信号会连接到后续的AXI互联和UARTLite IP核。3.2 AXI UARTLite IP核参数化配置接下来从IP Catalog中添加“AXI UARTLite”IP核。我们需要添加4个实例分别对应RS232、RS422和两个RS485通道。双击每个UARTLite IP核进行配置主要参数如下BAUD Rate: 115200。这是与PC端串口助手通信的约定速率。注意这个波特率是IP核内部根据输入的时钟频率s_axi_aclk计算分频产生的。必须确保输入的时钟频率准确且稳定否则会产生波特率偏差导致通信错误。Data Bits: 8。Parity: None。Stop Bits: 1。Use RX Interrupt/Use TX Interrupt: 通常可以禁用采用轮询Polling方式。如果对实时性要求高可以启用中断但这需要在PS端编写或配置对应的中断驱动复杂度增加。本案例为演示简便采用轮询。Clock Frequency 这里填写输入时钟ACLK的频率例如50MHz。IP核会根据这个频率和设定的波特率自动计算分频系数。一个重要的实操心得四个UARTLite IP核的配置最好完全一致除了实例名包括时钟频率。这能保证它们的行为一致方便软件统一处理。配置完成后每个IP核都会暴露出一个AXI4-Lite Slave接口用于PS控制和一组UART接口包括TX、RX如果需要还有RTS、CTS。3.3 总线连接与地址分配这是硬件设计中最需要细心的一环。我们需要使用“AXI Interconnect”IP核来管理PS与多个PL从设备之间的通信。添加并连接AXI Interconnect从IP Catalog添加一个AXI Interconnect IP。将PS的M_AXI_GP0接口连接到Interconnect的S00_AXI一个从端口。将4个UARTLite IP的AXI4-Lite接口分别连接到Interconnect的M00_AXI到M03_AXI四个主端口。时钟与复位连接将PS提供的FCLK_CLK0连接到Interconnect和所有UARTLite IP的s_axi_aclk引脚。将FCLK_RESET0_N连接到它们的s_axi_aresetn引脚注意是低电平复位有效。地址分配连接完成后Vivado会自动或手动分配地址空间。点击“Address Editor”标签页为每个UARTLite IP核分配一个唯一的基地址Base Address和范围Range。例如axi_uartlite_0: 0x8000_0000 - 0x8000_FFFFaxi_uartlite_1: 0x8001_0000 - 0x8001_FFFFaxi_uartlite_2: 0x8002_0000 - 0x8002_FFFFaxi_uartlite_3: 0x8003_0000 - 0x8003_FFFF 这个地址范围是PS端Linux驱动识别和访问每个串口设备的依据必须记下来后续编写设备树时会用到。连接UART物理引脚将每个UARTLite IP的TX、RX引脚引出到顶层端口并根据原理图将这些端口名与约束文件.xdc或.adc中的FPGA物理引脚对应起来。例如uart0_tx信号约束到某个Bank的某个引脚这个引脚在评估板上连接到了RS232电平转换芯片的输入端。连接GPIO控制引脚将之前从PS端引出的4位GPIO总线分别连接到对应的顶层端口同样在约束文件中绑定到控制RS485/RS422方向芯片的FPGA引脚。3.4 生成输出产品与硬件导出完成所有连接和地址分配后进行“Validate Design”确保没有错误。然后运行“Generate Output Products”和“Create HDL Wrapper”。最后执行“Generate Bitstream”生成PL端的配置文件.bit文件。生成成功后最关键的一步是导出硬件平台。使用“File - Export - Export Hardware”功能。务必勾选“Include bitstream”。这会生成一个.xsa文件Vivado 2019.1及以后版本或.hdf文件旧版本。这个文件包含了整个硬件平台的完整信息PS配置、PL逻辑网表、比特流以及最重要的地址映射关系。这个文件是后续在Vitis或Petalinux中创建软件平台、编译设备树和驱动的基础。4. Linux设备树与驱动适配关键4.1 设备树源文件DTS的编写原理Linux内核通过设备树Device Tree来描述硬件。对于PL端动态加载的IP我们需要一个“叠加层”Overlay设备树片段告诉内核“在某个地址上有一个符合某种协议的设备”。首先我们需要从导出的硬件平台.xsa文件中提取地址信息。通常可以使用Vitis或Petalinux提供的命令如dtc或专用的xsct脚本来自动生成一个基础的设备树源文件.dts。但自动生成的往往需要手动调整。以本案例的一个UARTLite节点为例一个典型的设备树节点如下amba_pl { // 这是一个对根节点下amba_pl标签的追加 axi_uartlite_0: serial80000000 { compatible xlnx,xps-uartlite-1.00.a; clock-frequency 50000000; current-speed 115200; device_type serial; port-number 0; reg 0x0 0x80000000 0x0 0x10000; xlnx,baudrate 0x1c200; xlnx,data-bits 0x8; xlnx,family artix7; xlnx,odd-parity 0x0; xlnx,s-axi-aclk-freq-hz-d 50.0; xlnx,use-parity 0x0; }; };关键属性解析compatible 驱动匹配字符串。“xlnx,xps-uartlite-1.00.a”是Linux内核中Xilinx UARTLite驱动的标准匹配字。内核会根据这个字符串加载对应的驱动程序。reg这是核心它定义了设备的物理地址和范围。0x0 0x80000000 0x0 0x10000表示地址高位为0x0低位为0x80000000长度高位为0x0低位为0x1000064KB。这必须与Vivado中分配的地址完全一致。clock-frequency 输入给IP核的时钟频率单位Hz这里是50MHz。current-speed 默认波特率驱动加载时会尝试设置为此值。port-number 逻辑端口号影响生成的ttySLx设备节点中的x。通常从0开始顺序编号。我们需要为4个UARTLite IP核分别编写这样的节点并确保reg地址和port-number正确无误。4.2 设备树编译与动态加载在传统的嵌入式Linux中设备树被编译进内核.dtb。但对于FPGA动态重配置的场景我们更常用设备树叠加Device Tree Overlay技术。编译设备树叠加层将编写好的.dts文件只包含我们新增的PL设备节点编译成.dtbo文件。dtc -O dtb -o pl.dtbo pl.dtsi内核配置确保Linux内核配置了CONFIG_OF_OVERLAY和CONFIG_OF_CONFIGFS支持这样才能在运行时加载dtbo。运行时加载这就是案例中提到的操作步骤。# 挂载configfs文件系统通常启动脚本已做 mount -t configfs none /sys/kernel/config # 创建一个叠加层目录名字任意如full mkdir /sys/kernel/config/device-tree/overlays/full # 将dtbo文件内容写入path文件内核会自动解析并应用叠加层 cat pl.dtbo /sys/kernel/config/device-tree/overlays/full/dtbo加载成功后使用ls /dev/ttySL*应该能看到新生成的设备节点如ttySL0,ttySL1等。一个我踩过的坑如果加载dtbo后系统不稳定或设备未出现首先检查dmesg内核日志。常见的错误有地址冲突reg属性与已有设备重叠、compatible字符串拼写错误导致驱动未绑定、或者PL的比特流文件.bit未正确加载。一定要先加载.bit文件再加载.dtbo文件顺序不能错。4.3 PL端比特流加载机制PL端的FPGA配置比特流.bit文件也需要在运行时加载。通常的流程是将.bit文件拷贝到文件系统如/lib/firmware/目录下。通过向/sys/class/fpga_manager/fpga0/firmware写入比特流文件名来触发加载。或者像本案例中将.bit文件重命名为system_wrapper.bit并放入/lib/firmware/内核的FPGA管理器FPGA Manager可能在启动或加载设备树叠加层时自动识别并加载它。重要提示确保比特流文件与硬件设计.xsa和设备树描述是匹配的。一个常见的错误是修改了Vivado工程中的地址分配但忘记更新设备树中的reg属性导致PS访问不到正确的硬件寄存器。5. 串口功能测试与深度实操5.1 测试环境搭建与硬件连接在开始软件测试前确保硬件连接正确RS232使用USB转RS232串口线注意是交叉线还是直连线通常USB转串口线是直连而两台设备间的RS232通信需要交叉但PC与评估板之间通常使用直连线即可具体需参考评估板手册连接评估板的RS232接口到PC的USB口。RS485由于PC通常没有RS485接口需要使用一个RS232转RS485模块。模块的RS232端接PC的USB转RS232串口线RS485端A/B线接评估板的RS485接口。务必注意A、B线的极性接反了无法通信。RS422类似RS485使用RS232转RS422模块。RS422是全双工有TX、TX-、RX、RX-四根线需要按照评估板原理图正确连接。在PC端使用串口调试助手如SecureCRT、MobaXterm、Putty或开源的CuteCom打开对应的COM口波特率设置为1152008位数据位1位停止位无校验位。5.2 串口参数配置与原始模式设置在Linux终端通过调试串口登录中使用stty命令配置串口设备。案例中的命令非常关键stty -F /dev/ttySL0 115200 cs8 -cstopb -parenb -crtscts clocal raw -echo-F /dev/ttySL0 指定操作的设备文件。115200 设置波特率。cs8 设置8位数据位。-cstopb 使用1位停止位cstopb表示2位停止位-表示禁用。-parenb 禁用奇偶校验。-crtscts 禁用硬件流控RTS/CTS。clocal 忽略调制解调器控制线如DCD。raw这是重要选项设置原始模式禁用输入输出处理如回车换行转换、信号字符处理等数据被原样传输。对于二进制通信或简单的字符回显测试必须使用此模式。-echo 禁用本地回显。这样你在终端输入字符不会在本地显示完全由对端设备决定是否回传。5.3 收发测试与问题排查接收测试cat命令cat /dev/ttySL0这个命令会打开ttySL0设备并持续读取其输入将接收到的数据直接打印到标准输出当前终端。此时在PC的串口调试助手发送字符串“Tronlong”并回车评估板终端应该会显示“Tronlong”。注意cat命令会一直阻塞直到你按CtrlC终止它。发送测试echo命令echo tronlong /dev/ttySL0这个命令将字符串“tronlong”写入ttySL0设备即通过该串口发送出去。在PC的串口调试助手上应该能看到接收到的字符串。RS485/RS422方向控制 对于半双工的RS485和需要使能控制的RS422在收发切换前必须操作GPIO。以案例中的RS422为例# 配置GPIO421假设为RE_n为输出低电平使能接收器 echo 421 /sys/class/gpio/export echo out /sys/class/gpio/gpio421/direction echo 0 /sys/class/gpio/gpio421/value # 配置GPIO420假设为DE为输出高电平使能发送器 echo 420 /sys/class/gpio/export echo out /sys/class/gpio/gpio420/direction echo 1 /sys/class/gpio/gpio420/value关键点在发送数据前必须设置DE为高RE_n为低或根据芯片手册要求。在接收数据前必须设置DE为低RE_n为高。硬件设计必须保证这些控制信号正确连接到电平转换芯片的控制引脚上软件操作才有效。5.4 常见问题排查速查表现象可能原因排查步骤/dev/ttySL0不存在1. 设备树叠加层未加载成功。2. 驱动未匹配或未编译进内核。1. 检查dmesgcat或echo命令无反应1. 波特率等参数不匹配。2. 硬件连接错误线接反、松动。3. PL比特流未加载或加载错误。1. 用stty -F /dev/ttySL0 -a确认当前参数并与PC端严格一致。2. 用万用表或示波器检查TX/RX引脚是否有数据波形。3. 检查/sys/class/fpga_manager/fpga0/status确保为operating。4. 尝试回环测试短接评估板该串口的TX和RX用echo发送并用cat接收看是否能自发自收。收发数据乱码1. 波特率偏差大。2. 数据位、停止位、校验位设置错误。1. 确认PL端供给UARTLite IP核的时钟频率s_axi_aclk是否准确如50MHz。2. 使用示波器测量实际波特率计算偏差是否在可接受范围通常3%。3. 反复核对PC端和Linux端的stty设置确保完全一致。RS485/RS422只能收或只能发1. GPIO方向控制信号未正确设置。2. 控制信号对应的物理引脚连接错误。3. 电平转换芯片损坏或供电不正常。1. 使用cat /sys/class/gpio/gpio420/value等命令确认GPIO输出电平是否符合预期。2. 用示波器或万用表测量DE、RE_n引脚在收发命令前后的电平变化。3. 检查原理图确认GPIO引脚是否确实连接到了对应芯片的控制脚。加载dtbo或bit文件时报错1. 文件格式错误或损坏。2. 内存不足。3. 地址冲突。1. 使用file命令检查文件类型。2. 检查系统剩余内存free -m。3. 详细查看dmesg输出的错误信息重点关注地址映射相关的报错。6. 进阶应用与性能优化思考6.1 从轮询到中断驱动的优化案例中默认使用的是轮询方式。这对于低速、非实时应用可以接受。但在高波特率或需要低延迟响应的系统中轮询会大量占用CPU资源。此时可以启用AXI UARTLite IP核的中断功能。硬件修改在Vivado中重新配置UARTLite IP核使能“Use RX Interrupt”和/或“Use TX Interrupt”。IP核会多出一个interrupt输出信号。连接中断将这个interrupt信号连接到PS的IRQ_F2PFabric to Processor中断输入端口上。并在PS IP核配置中使能对应的中断控制器。设备树修改在设备树节点中增加中断属性例如interrupts 0 29 4;具体值由硬件连接决定需参考芯片手册和Vivado地址编辑器中的中断号。驱动层面Linux内核的标准xuartlite驱动已经支持中断模式。重新编译内核或加载驱动模块即可。使用中断后CPU只在有数据到达或发送缓冲区空时才被唤醒大大提高了效率。6.2 多串口并发管理与应用层设计当同时管理多个活跃串口时在应用层需要注意非阻塞IO与多路复用避免使用cat、echo这样的阻塞式命令。应编写C/C或Python程序使用select()、poll()或epoll()等多路复用机制来同时监听多个串口文件描述符提高程序效率。缓冲区管理高速率通信时确保应用层有足够的缓冲区并妥善处理粘包问题特别是基于帧的协议如Modbus。日志与调试为每个串口通道建立独立的日志文件便于问题追踪。6.3 自定义IP核与AXI通信扩展掌握了这个标准流程后你就可以举一反三将AXI UARTLite替换成任何自定义的AXI4-Lite IP核。无论是自定义的传感器接口、算法加速器还是协议转换模块硬件上只需遵循AXI4-Lite总线规范设计接口软件上同样通过设备树描述地址和中断并编写对应的内核驱动或用户空间mmap程序来访问。这才是PSPL异构架构威力的真正体现——将灵活可编程的硬件逻辑与强大的软件生态无缝结合。整个流程走下来从Vivado画图连线到设备树编写再到最后的终端测试命令每一步都需要耐心和细致。尤其是地址映射、设备树节点、物理引脚约束这三者之间的对应关系必须保证百分百准确。一旦打通你会发现这种软硬协同的设计方式为嵌入式系统带来了前所未有的灵活性和性能潜力。