2. PRU硬件架构与内存映射

PRU(Programmable Real-time Unit)是TI Sitara AM335x/AM57x等SoC内部独立于ARM Cortex-A核的实时协处理器子系统。理解PRU,关键不是把它当成“另一个CPU”,而是把它看成一套紧耦合的实时执行环境:每个PRU核拥有自己的指令RAM、数据RAM、寄存器组和常量表,同时通过共享内存与外设接口和系统其余部分交换数据。本节围绕PRU核心、指令RAM、数据RAM、共享内存、外设接口、中断控制器与常量表七个对象,梳理它们的地址空间与访问关系。

一、PRU核心与双核结构

一个PRU-ICSS(Industrial Communication SubSystem)通常包含两个PRU核,记为PRU0和PRU1。每个核是32位RISC结构,指令字长32位,绝大多数指令单周期执行,主频与PRU子系统时钟一致(AM335x上典型为200MHz,即5ns一个周期)。核内包含通用寄存器R0~R31、程序计数器PC、以及一组用于状态和事件控制的寄存器。

两个核之间不是主从关系,而是对等并行。它们各自独立取指、独立执行,但共享同一片数据RAM和部分外设资源。这种结构决定了PRU编程的两个基本约束:一是实时性由指令周期数直接决定,二是双核共享资源时必须显式处理互斥。例如PRU0负责EtherCAT帧收发,PRU1负责GPIO同步采样,两者通过共享内存中的标志位或事件寄存器协调,而不是靠操作系统调度。

二、指令RAM与数据RAM

每个PRU核有独立的指令RAM,容量通常为8KB(AM335x),用于存放PRU固件。指令RAM在PRU本地地址空间中映射到0x0000_0000起始处,ARM侧则通过PRU_ICSS_IRAM窗口访问。固件加载流程是:ARM将编译生成的二进制镜像写入指令RAM,然后释放PRU核复位,PRU从地址0开始取指。

数据RAM每个核通常为8KB,本地映射到0x0000_0000之外的独立区域(如PRU0为0x0000_0000,PRU1为0x0000_2000,具体以TRM为准)。数据RAM用于存放变量、缓冲区、栈和查找表。由于容量有限,PRU程序必须精打细算:一个EtherCAT过程数据缓冲区可能就占去数KB,剩余空间要留给状态机和临时变量。

指令RAM和数据RAM都是PRU本地高速存储,访问延迟固定且极低。把频繁访问的数据放在数据RAM,而不是每次都走共享内存,是保证实时性的基本手段。

三、共享内存与外设接口

共享内存(Shared RAM)是PRU与ARM、PRU与PRU之间交换数据的主要通道,典型容量为12KB,映射在PRU本地地址空间的高端区域(如0x0001_0000附近)。ARM通过L3/L4互连访问同一片物理内存,因此双方看到的是同一份数据。共享内存的访问需要关注两点:一是字节序与对齐,PRU为小端,ARM通常也是小端,但结构体对齐要显式控制;二是一致性,PRU写入后ARM读取前,需通过内存屏障或事件通知确保可见。

外设接口方面,PRU-ICSS集成了专用外设:ECAP、UART、eCAP、MII接口、GPIO以及PRU-ICSS IEP(工业以太网外设)。这些外设通过PRU本地总线映射到固定地址,PRU可以用LBCO/SBCO指令直接读写,而不经过ARM。例如MII的发送FIFO和接收FIFO就映射在PRU地址空间,PRU0可以逐字节搬运以太网帧,实现微秒级确定性收发。

四、中断控制器与常量表

PRU-ICSS的中断控制器(INTC)负责把外设事件、系统事件和PRU核间事件汇聚、映射到PRU的R31寄存器。PRU没有传统意义上的中断向量表,而是通过R31的位域读取中断状态,再用XIN/XOUT指令或条件跳转响应。典型配置是:将MII接收事件映射到R31的某一位,PRU主循环轮询该位,一旦置位就进入收包处理。这种“事件驱动+轮询”的混合模式,比纯轮询更省周期,又比ARM中断更可预测。

常量表(Constant Table)是PRU的一个特色机制,通过LDI指令配合常量表索引,可以快速访问一组预定义的系统资源,如C0~C31。其中C0~C15通常映射到PRU本地寄存器或外设,C16~C31可映射到共享内存或系统事件。常量表的价值在于:用一条指令就能完成对外设寄存器的读写,无需先计算地址再加载,从而压缩关键路径的指令数。

PRU-ICSS 硬件架构与地址映射关系 ARM Cortex-A L3/L4 互连访问 加载固件 / 读写共享内存 共享内存 Shared RAM 12KB,PRU与ARM共同可见 数据交换 / 标志位 / 缓冲区 PRU0 核 指令RAM 8KB 固件 数据RAM 8KB 变量/缓冲 R0–R31 通用寄存器 / PC 32位 RISC,单周期指令为主 常量表 C0–C31 LDI 快速访问外设/系统资源 PRU1 核 指令RAM 8KB 固件 数据RAM 8KB 变量/缓冲 R0–R31 通用寄存器 / PC 与PRU0对等并行执行 常量表 C0–C31 LDI 快速访问外设/系统资源 中断控制器 INTC → R31 外设接口 MII / UART / ECAP / GPIO / IEP PRU本地总线直接读写 实线:数据/固件通路 虚线:事件/中断通路 共享内存为双核与ARM的公共交汇点

五、地址空间与访问方式的实际含义

把上述对象放到一张地址图上,PRU本地地址空间大致分为三段:低端是指令RAM和数据RAM,中段是外设寄存器,高端是共享内存。PRU用LBBO/SBBO访问本地数据RAM,用LBCO/SBCO访问常量表映射的外设,用普通加载/存储访问共享内存。ARM侧则通过PRU_ICSS_CFG、PRU_ICSS_IRAM、PRU_ICSS_SHARED_RAM等窗口访问同一批资源。

实际工程中,一个常见的初始化顺序是:ARM先使能PRU-ICSS时钟,配置PRU_ICSS_CFG中的PRUSS_CFG寄存器,将PRU固件写入指令RAM,设置共享内存中的初始参数,然后清除PRU_CONTROL中的复位位,让PRU0和PRU1开始运行。PRU启动后,先读取共享内存中的配置结构体,再初始化常量表映射,最后进入主循环。这个顺序如果颠倒,比如PRU先跑而共享内存还没写好,就会出现参数为随机值的问题。

PRU的指令RAM和数据RAM容量都很小,且没有MMU。写PRU程序时不要假设有虚拟内存或自动栈增长,栈溢出会直接踩坏相邻变量,且不会有任何异常提示。

六、常量表与中断的配合

常量表常被用来做“外设快捷方式”。例如把C16配置为指向PRU-ICSS IEP的全局配置寄存器,PRU就可以用LDI R1, C16加偏移的方式读取IEP计数器,而不必先算基地址。中断方面,INTC把多个系统事件映射到R31的位,PRU主循环用QBBS/QBBC判断某位是否置位,再跳转到对应处理段。这种设计让PRU可以在一个循环里同时处理收包、定时和GPIO事件,而不会因为中断嵌套打乱时序。

总结来看,PRU硬件架构的核心是“本地存储+共享内存+常量表外设+事件位”四件套。指令RAM和数据RAM保证执行速度,共享内存解决与ARM的数据交换,外设接口和常量表提供低延迟I/O,INTC和R31提供事件响应。把这四者的地址和访问方式记清楚,后续写PRU固件、调实时性、排查数据不一致时,才能快速定位到是本地RAM、共享内存还是外设映射出了问题。