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的寻址模式非常精简,只有三种:
- 寄存器直接寻址:操作数就是寄存器本身,如
MOV R4, R5。 - 寄存器间接寻址:寄存器中存放的是地址,如
LBBO R4, R5, 0, 4表示从R5指向的地址偏移0处读取4字节到R4。这是访问内存和IO的主要方式。 - 立即数寻址:操作数直接写在指令中,如
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汇编时要注意几个关键点:
- 寄存器分配:R0~R2有默认用途,建议保留;R3~R30自由使用,但函数调用时要保存现场(PRU没有硬件栈,需要手动用R31.w0做栈指针)。
- 立即数装载:超过8位的立即数必须用LDI分两次装载,且要注意字节序(PRU是小端)。
- 循环嵌套:硬件循环不支持嵌套,嵌套循环需要手动用寄存器计数+条件分支实现。
- IO时序:LBBO/SBBO访问IO有固定延迟(通常2~3周期),精确时序控制时要计入。
- 中断处理:PRU中断向量表固定在0x0000,中断服务程序要尽量短,避免影响主循环时序。