2. 系统架构设计:ARM与FPGA的通信架构与数据流设计

好,咱们进入第二章。这一章我打算聊聊系统架构设计,说白了就是ARM和FPGA之间怎么“说话”,数据怎么“流动”。

很多刚入行的朋友,一上来就急着写代码,结果后面发现ARM和FPGA各干各的,数据传不过去,或者传得慢得要命。我早年就踩过这个坑,一个运动控制项目,ARM算好了轨迹,FPGA死活收不到,最后查出来是总线配置错了。嗯,咱们今天就把这个事彻底讲清楚。

2.1 通信架构选型:AXI4-Lite vs AXI4-Stream

ARM和FPGA通信,在Zynq或者STM32MP1这类异构芯片里,最常用的就是AXI总线。我个人习惯把AXI分成两类来理解:

  • AXI4-Lite:轻量级,适合传控制指令、寄存器配置。一次传一个数据,简单可靠。
  • AXI4-Stream:流式接口,适合传大批量数据,比如位置指令流、编码器反馈流。没有地址概念,只管往管道里灌数据。

你想想看,运动控制里,ARM下发“目标位置”、“速度模式”这种指令,用AXI4-Lite就够。但如果是连续轨迹插补,每毫秒要传几百个位置点,那就得上AXI4-Stream了。

核心原则:控制走Lite,数据走Stream。别混着用,否则调试起来你会想哭。

2.2 数据流设计:从ARM到FPGA的路径

我一般把数据流分成三个环节:

  1. ARM端:应用程序计算轨迹,通过Linux驱动或裸机程序,把数据写到AXI总线。
  2. 总线传输:数据经过AXI互联,到达FPGA的IP核。
  3. FPGA端:IP核解析数据,驱动电机控制逻辑。

这里有个关键点:数据一致性。ARM是32位或64位处理器,FPGA是并行逻辑,两边时钟域不同。我曾经遇到过ARM写了一半数据,FPGA就读走了,结果读到个半残废的数据,电机直接抖了一下。解决办法就是加握手信号,或者用双缓冲。

我的小技巧:在FPGA里做一个“数据有效”寄存器。ARM先写数据,再写“有效位”。FPGA检测到有效位为1,才去读数据。读完后清掉有效位。这样永远不会读到脏数据。

2.3 系统架构图

下面这张图是我自己画的,展示了ARM和FPGA之间的通信架构。你可以看到数据流和控制流是分开走的。

ARM 处理器 (STM32MP1 / Zynq PS) 运动控制应用 (轨迹规划) Linux 驱动 / 裸机驱动 (AXI 总线封装) AXI 主接口 (Lite / Stream) AXI 互联总线 控制流 (Lite) 数据流 (Stream) FPGA 逻辑 (Zynq PL / Artix-7) AXI 从接口 (地址译码 / FIFO) 寄存器组 / 数据缓冲区 (双缓冲设计) 电机控制逻辑 (PWM / 编码器) 控制流 (AXI4-Lite) 数据流 (AXI4-Stream)

从图里你能看到,ARM端跑应用和驱动,通过AXI总线把控制指令和数据分别送到FPGA。FPGA这边有专门的接口模块来接收,然后转给电机控制逻辑。我个人习惯在FPGA里做一个寄存器映射表,ARM直接读写这些寄存器,就像操作内存一样简单。

2.4 寄存器映射表设计

这是实际项目中我常用的寄存器映射表。你设计的时候可以直接参考:

地址偏移 寄存器名称 位宽 读写属性 描述
0x00 CTRL_REG 32 R/W 控制寄存器(使能、复位、模式选择)
0x04 STATUS_REG 32 R 状态寄存器(忙、错误、完成标志)
0x08 TARGET_POS_L 32 W 目标位置低32位
0x0C TARGET_POS_H 32 W 目标位置高32位(64位位置指令)
0x10 CURRENT_POS_L 32 R 当前位置低32位(编码器反馈)
0x14 CURRENT_POS_H 32 R 当前位置高32位
0x18 SPEED_CMD 32 W 速度指令(浮点数格式)
0x1C DATA_VALID 1 W 数据有效标志(写1触发FPGA读取)

注意:地址偏移必须是4字节对齐,因为AXI4-Lite的最小传输单位是32位。我曾经图省事把偏移设成0x01,结果仿真没问题,上板子死活读不对。后来查了三天手册才发现这个坑。

2.5 数据流时序设计

数据流设计里,时序是最容易出问题的。我一般遵循这个流程:

  1. ARM写数据:先写TARGET_POS_L和TARGET_POS_H,再写SPEED_CMD,最后写DATA_VALID为1。
  2. FPGA检测:每个时钟周期检查DATA_VALID寄存器。如果为1,锁存所有数据,然后清DATA_VALID。
  3. FPGA执行:把锁存的数据送入电机控制状态机,开始运动。
  4. ARM查询:轮询STATUS_REG,等FPGA执行完,再发下一组指令。

这里有个性能问题:轮询太浪费CPU了。我建议用中断方式——FPGA执行完一个指令后,拉高一个中断信号给ARM。ARM收到中断再发下一条。这样CPU利用率能提高不少。

我的经验:如果运动控制周期要求很严(比如1ms以内),建议用AXI4-Stream + DMA。ARM配置好DMA,让数据自己流过去,CPU完全解放。我在一个六轴机器人项目里就是这么干的,效果非常好。

2.6 避坑指南

最后,我总结几个实际项目中容易踩的坑:

  • 跨时钟域问题:ARM和FPGA可能跑在不同时钟下。所有跨时钟域的信号,必须做同步处理。我一般用两级触发器同步,或者用异步FIFO。
  • 数据溢出:FPGA处理速度跟不上ARM发送速度时,数据会丢。解决办法是在FPGA里加FIFO,深度根据你的数据量来定。我一般设256深度,够用。
  • 总线死锁:AXI协议有严格的握手要求。如果FPGA的从接口没按协议回复,总线可能卡死。建议用Xilinx或ST官方的AXI IP核,别自己从头写,除非你真的很闲。

嗯,这一章的内容差不多就这些。系统架构设计是运动控制的基础,架构没想好,后面写再多代码也是白搭。你先把ARM和FPGA怎么通信、数据怎么流搞清楚,后面章节咱们再深入具体的实现细节。