3. PRU汇编指令集与编程模型

PRU(Programmable Real-time Unit)是TI Sitara AM335x/AM57x等芯片中独立于ARM Cortex-A核的32位RISC协处理器,主频200MHz(AM335x),单周期指令吞吐,没有流水线冒险、没有Cache、没有MMU。它的可预测性正是实时控制场景最看重的特性。要写出可靠的PRU固件,必须先吃透它的汇编指令格式、寄存器组和寻址方式,否则很容易在时序上"差之毫厘,谬以千里"。

一、指令格式:32位定长,三段式编码

PRU的每条指令都是32位定长,格式非常规整,可以拆成三个字段:

  • 操作码(Opcode):位于bit[31:24],8位,决定指令类型(如MOV、ADD、LDI、QBA、XIN等)。
  • 目的/源寄存器字段:位于bit[23:16]和bit[15:8],各8位,用于指定Rd(目的)和Rs(源)。
  • 立即数/偏移字段:位于bit[7:0],8位,用于短立即数、IO偏移或分支偏移。

这种"8+8+8+8"的布局意味着:PRU汇编中所有寄存器编号、立即数、IO偏移都必须在8位范围内。例如寄存器R0~R31编号只需5位,但字段留了8位;而IO偏移最大255字节,正好用满8位。当需要16位或32位立即数时,必须用LDI指令分两次装载(LDI Rd, #imm16 装载低16位,LDI Rd.w2, #imm16 装载高16位),这是初学者最容易踩的坑。

二、寄存器组:R0~R31 + 特殊寄存器

PRU有32个通用寄存器R0~R31,每个32位。它们并非完全等价,有几条硬性约定:

  • R0:复位后默认指向PRU本地数据RAM的起始地址(0x0000),常作为基址寄存器使用。
  • R1:复位后默认指向PRU共享RAM(Shared RAM)的起始地址(0x00010000),用于与ARM通信。
  • R2:复位后默认指向PRU中断控制器(INTC)的基址,用于配置中断。
  • R3~R31:完全通用,可自由使用。

此外还有几个特殊寄存器:PC(程序计数器)、SP(栈指针,实际由R31.w0充当)、ST(状态寄存器,记录进位、溢出、零标志等)。状态寄存器ST的bit0是进位C,bit1是零Z,bit2是溢出V,bit3是符号N,条件分支就依赖这些标志位。

PRU没有硬件乘法器(AM335x的PRU-ICSS),乘法必须用软件循环实现。如果算法中乘法密集,要么改用查表,要么把乘法放到ARM侧完成,PRU只做搬运和时序控制。

三、寻址模式:只有三种,但够用

PRU的寻址模式非常精简,只有三种:

  1. 寄存器直接寻址:操作数就是寄存器本身,如 MOV R4, R5。
  2. 寄存器间接寻址:寄存器中存放的是地址,如 LBBO R4, R5, 0, 4 表示从R5指向的地址偏移0处读取4字节到R4。这是访问内存和IO的主要方式。
  3. 立即数寻址:操作数直接写在指令中,如 LDI R4, 0x1234。

注意PRU没有变址寻址、没有自动递增、没有复杂的寻址模式。所有内存访问都必须通过LBBO(Load Byte Burst Offset)和SBBO(Store Byte Burst Offset)指令,配合基址寄存器+偏移量完成。偏移量可以是8位立即数,也可以是寄存器。

四、循环控制:硬件循环是PRU的杀手锏

PRU最独特的特性之一是硬件循环指令,这是它区别于普通RISC核的关键。PRU提供三条循环指令:

  • LOOP:执行循环体,循环次数由指令中的立即数指定(1~65535)。
  • ILOOP:与LOOP类似,但循环体在指令之后立即开始,不需要额外的跳转。
  • QBA:快速分支,用于条件循环。

硬件循环的最大优势是零开销:循环计数、条件判断、跳转全部由硬件完成,不占用指令周期。例如要产生一个精确的1μs延时(200MHz下200个周期),可以这样写:

; 精确延时示例:200个周期
; 假设进入时已消耗若干周期,此处仅示意
LOOP  R10, 50      ; 循环50次,每次4周期
    NOP            ; 1周期
    NOP            ; 1周期
    NOP            ; 1周期
    NOP            ; 1周期
; 循环结束,共200周期

如果用普通跳转实现,每次循环都要执行"减法+比较+跳转"三条指令,不仅多占周期,还会引入不可预测的流水线效应。硬件循环让PRU的时序完全可计算,这是它用于电机控制、PWM生成等场景的根本原因。

五、分支与IO操作

PRU的分支指令包括JMP(无条件跳转)、JAL(跳转并链接,用于函数调用)、QBGT/QBLE/QBEQ等条件分支。条件分支依赖ST寄存器中的标志位,例如 QBGT label, R4, R5 表示当R4 > R5时跳转到label。

IO操作是PRU的另一大特色。PRU有专门的XIN/XOUT指令用于与外部设备交换数据,以及XCHG用于原子交换。更常用的是通过PRU IO寄存器直接读写引脚:

; 读取PRU IO输入
LBBO R4, R31, 0x00, 4   ; 从R31偏移0读取4字节
; 写PRU IO输出
SBBO R4, R31, 0x04, 4   ; 写4字节到R31偏移4

这里的R31是PRU的IO基址寄存器,偏移0x00对应输入,0x04对应输出。通过这种方式,PRU可以在单周期内完成引脚状态读取或输出翻转,实现纳秒级响应。

PRU 汇编编程模型核心结构 32位指令格式 Opcode [31:24] Rd [23:16] Rs [15:8] Imm [7:0] 定长32位 · 8位字段对齐 寄存器组 R0~R31 R0 本地RAM基址 R1 共享RAM基址 R2 INTC基址 R3~R30 通用 R31 = IO/栈指针 · ST = 状态标志 三种寻址模式 ① 寄存器直接:MOV R4, R5 ② 寄存器间接:LBBO R4, R5, 0, 4 ③ 立即数:LDI R4, 0x1234 硬件循环控制 LOOP ILOOP QBA 零开销循环 · 计数/判断/跳转由硬件完成 时序完全可计算 · 适合精确延时与PWM 分支与IO操作 JMP / JAL / QBGT XIN / XOUT / XCHG 条件分支依赖 ST 标志位(C/Z/V/N) IO通过 R31 基址 + LBBO/SBBO 读写 编程模型要点 32位定长指令 · 8位字段对齐 · 32个通用寄存器 · 三种寻址模式 硬件零开销循环 · 条件分支依赖ST标志 · IO通过R31基址直接访问

六、编程模型实战要点

把上述知识串起来,写PRU汇编时要注意几个关键点:

  • 寄存器分配:R0~R2有默认用途,建议保留;R3~R30自由使用,但函数调用时要保存现场(PRU没有硬件栈,需要手动用R31.w0做栈指针)。
  • 立即数装载:超过8位的立即数必须用LDI分两次装载,且要注意字节序(PRU是小端)。
  • 循环嵌套:硬件循环不支持嵌套,嵌套循环需要手动用寄存器计数+条件分支实现。
  • IO时序:LBBO/SBBO访问IO有固定延迟(通常2~3周期),精确时序控制时要计入。
  • 中断处理:PRU中断向量表固定在0x0000,中断服务程序要尽量短,避免影响主循环时序。
PRU汇编没有"伪指令"概念,所有指令都是硬件直接执行的。写代码时不要假设有编译器优化,每一条指令的周期数都要自己算清楚。建议用TI提供的PRU汇编器(pasm)编译后,用PRU调试器查看反汇编,确认指令编码和周期数符合预期。