DSP

TI AM335x 可编程实时模块(PRUSS)详解

2019-07-13 16:46发布

转自:http://www.itdadao.com/articles/c15a388010p0.html

1. PRU内核理解

可编程实时单元(Programmable Real-time Unit SubSystem,PRUSS),是Cotex A8内核中的一个子系统,它可运行在1/2CPU时钟频率下,具有本地的指令和数据RAM,并可寻址访问整个片上系统资源。 PRU模块的认识:

2. 地址映射

地址映射与PRU模块的寻址方式密切相关,这里所说的寻址有两个含义,一是PRU对其系统内部的寻址,二是PRU对其外部整个片上系统的寻址。因而对应的地址映射就分为局部地址映射和全局地址映射。

2.1 局部地址映射

PRUSS模块存储器分为指令存储器和数据存储器,相应的局部地址映射也将两部分分开处理。 指令存储区映射:
每个PRU模块拥有8KB的指令存储区,在PRU运行指令之前,必须由主控制器对其进行初始化,当PRU运行之后,主控制器便失去对该区域的访问权。 数据存储区映射:
数据存储区映射允许PRU内核访问PRUSS内的可寻址空间以及该子系统外的地址空间。当其访问外部存储空间时,起始地址为0x0008_0000,默认的0x0000_0000-0x0007_FFFF对应的为内部存储区,当需要访问地址空间在0x0000_0000-0x0007_FFFF的外部空间时,需要借助寄存器操作。 从表中可以看出,PRU0的优先数据存储区为RAM0,PRU1的优先数据存储区为RAM1. 2.2 全局地址映射
表3给出了PRUSS内部空间和控制单元的全局地址映射。 全局地址映射是针对主处理器而言的,PRU同样也可通过全局地址映射访问内部空间。比较表3和表4可以发现,其资源地址完全相同,局部地址映射仅仅是在PRU0和PRU1数据存储区做了区分。但是通过全局地址映射访问内部空间时,速度会比通过局部映射访问的速度慢,这时因为通过全局地址映射访问时需要通过PRUSS外部总线最终再通过接口返回到PRUSS内部。

3. PRU系统结构

3.1 PRU系统结构框图

图1.PRU系统结构框图 重点讲述其中两个资源信息,特殊寄存器R30,R31和常量表,前者和PRU系统的状态和事件相关,后者与PRU系统寻址相联系。

3.2 特殊寄存器 R30,R31

PRU模块接口是由两个内部寄存器R30和R31构成。R31为PRU的通用输入端(GPI)中断控制器(INTC)提供接口。通过读取R31的值便可获得GPI引脚以及INTC的状态信息。写R31则可为PRU系统产生事件信息。R30为PRU模块的通用输出端(GPO)的状态接口。

3.2.1 状态接口映射(R31):中断事件输入

PRU模块的实时状态信息直接填入R31寄存器中,PRU根据这些信息决定程序如何执行。这些信息是由PRUSS内不同模块产生的。主机的中断被放置在R31的第31位和30位,进而提供给中断控制器。

3.2.2 事件接口映射(R31):PRU系统事件

PRU事件接口可以直接从ALU中产生事件信息并将其投递到PRUSS之外片上系统级的中断控制器中。事件接口也可以将PRU产生软件中断投递给主控制器。 在Pru_r31_vec_vavid(R31 bit5)中写“1”,并在Pru_r31_vec[3:0]写入数据就会产生一个通道号为1-16的中断。

3.3 常量表(Constant Table)

常量表是一个与PRU模块接口相关的结构,PRU模块通过该常量表为数据装载和储存提供基址(LBCO,SBCO指令)。通过将大量常用数据以及计算得到的基址数据从内部寄存器移到外部表中,从而更大限度的利用内部寄存器。PRU模块内常量表定义如下: 在PRU模块寻址过程中,常量表常用于为寻址提供基址,且可用于大量块数据的传输,用到的指令为LBCO和SBCO。 LBCO (Load Byte Burst with Constant Table Offset)
LBCO指令用于将一块数据从存储器装载到寄存器中,存储器的基址是由常量表提供的,偏移量是由寄存器或立即数提供的。
定义:LBCO REG1, Cn2, OP(255), IM(124)
说明:REG1代表操作数为寄存器,Cn2表示常量表入口,OP表示该操作数既可是寄存器,又可是立即数(不超过255),IM(124)表示不超过124的立即数。 例如: LBCO r2, c1, 5, 8 //从C1+5地址空间取出8个字节放入R2,R3寄存器中 //基址为C1,偏移量为立即数5,目的地址R2,装载数目8字节 LBCO r3, c1, r2.w0, r0.b0 //从C1+b0地址空间取出R2.W0个字节放入R31寄存器中 //基址C1,偏移量R2.W0,目的地址R3,装载数目R0.b0字节 PRU内部对寄存器的寻址极为灵活,一个32位的寄存器,可以按位寻址,也可以按字节,或按字寻址,上述指令中,r2.w0表示R2寄存器的低字[15:0],r0.b0表示R0寄存器的低字节[7:0]。同理,r0.t12表示R0寄存器的第12位。 SBCO (Store Byte Burst with Constant Table Offset)
SBCO指令用于将一组连续寄存器中的值存入存储器中。
定义:SBCO REG1, Cn2, OP(255), IM(124) 例如: SBCO r2, c1, 5, 8 //将8个字节数据从R2,R3寄存器中存入地址为C1+5的存储器中 具体例程: 在PRU模块中,利用中断进行响应是其重要应用之一,通过中断,可以实现一些事件的实时响应,或者用于实现数据的传输。在中断使用之前,需对中断寄存器进行配置,这就是通过常量表和SBCO指令配合实现的。
通过表7,可以查到INTC的常量表入口为C0,对应的基址为0x0002_0000。至于其各个寄存器的偏移量,可以查阅其技术手册PRU模块中INTC相关章节(P381)。 例如:主机中断允许寄存器GER,其对应的地址偏移量为0x10,中断状态寄存器SRSR1,SRSR2对应的地址偏移量为0x200,0x204.
由此我们就可以配置中断状态: 在 *HP文件中 …… #define CONST_PRUSSINTC C0 //INTC在常量表中的入口 …… #define GER_OFFSET 0x10 //主机中断寄存器偏移量 #define HIESR_OFFSET 0x34 //开对应主机中断 #define SICR_OFFSET 0x24 // 清除中断标志 #define EISR_OFFSET 0x28 //开系统中断 #define SRSR2_OFFSET 0x204 //中断状态 #define SECR2_OFFSET 0x284 //清除中断 #define ECR2_OFFSET 0x384 #define INTC_CHNMAP_REGS_OFFSET 0x0400 //通道映射 #define INTC_HOSTMAP_REGS_OFFSET 0x0800 //主机映射 #define INTC_HOSTINTPRIO_REGS_OFFSET 0x0900 //优先级 #define INTC_HOSTNEST_REGS_OFFSET 0x1100 //中断类型 …… 在 *HP文件中,宏定义了INTC配置寄存器的基址(C0)及其偏移量。HP文件是对PRU编程所用到的文件,相当于C语言中的头文件。 在 *P文件中,具体实现了对INTC寄存器的配置。 在对应的 *P文件中 #include "PRU_DSPtoPRU_Interrupt.hp" //包含上述头文件 // Initialize pointer to INTC registers MOV32 regOffset, 0x00000000 // Clear SYS_EVT32 MOV32 r31, 0x00000000 // 开整个主机中断 LDI regvalue.w0, 0x0001 SBCO regvalue, CONST_PRUSSINTC, GER_OFFSET, 2 //开0号主机中断 MOV32 regvalue, (0x00000000 | HOST_NUM) SBCO regvalue, CONST_PRUSSINTC, HIESR_OFFSET, 4 // 将0号中断通道映射到0号主机中断 LDI regOffset.w0, INTC_HOSTMAP_REGS_OFFSET ADD regOffset.w0, regOffset.w0, HOST_NUM LDI regvalue.w0, CHN_NUM SBCO regvalue, CONST_PRUSSINTC, regOffset.w0, 1 // 将 SYS_EVT32 中断映射到通道0 LDI regOffset.w0, INTC_CHNMAP_REGS_OFFSET ADD regOffset, regOffset, SYS_EVT LDI regvalue.b0, CHN_NUM SBCO regvalue.b0, CONST_PRUSSINTC, regOffset.w0, 1 // 确保SYS_EVT32中断被清除 MOV32 regvalue, (0x00000000 | SYS_EVT) SBCO regvalue, CONST_PRUSSINTC, SICR_OFFSET, 4 // 允许 SYS_EVT32 中断 SBCO regvalue, CONST_PRUSSINTC, EISR_OFFSET, 4 //产生 SYS_EVT32中断 MOV32 r31, SYS_EVT ……

4. 文件组成及编译方式

有上述例程可以看出,对PRU模块编程需要用到的文件有两个,*HP文件和*P文件,前者相当于头文件,后者相当于源文件。这两种文件均可在TI提供的集成开发环境CCS(Code Composer Studio)中生成,但CCS并不能编译它们,编译*P文件需要输入命令行参数执行。 首先,在网站http://www.ti.com.cn/tool/cn/sprc940下载PRU软件开发包SPRC940.ZIP 。 解压安装,在文件夹binwindowsPASM.exe即为所需文件。 点击开始->运行->cmd,进入命令行程序,通过CD指令进入到pasm.exe所在的文件夹,输入pasm,可以看到如下界面: 从其中的内容可以看出,首先显示的是软件信息,接着说明了软件的用法,也即各种命令,通过命令可以生成各种对应文件。例如-b指令用于生成二进制文件,-c指令用于生成C数组文件。 例如执行命令pasm –b “C:PRUexample.p”,即可在对应文件夹下生成二进制文件example.bin。命令行中注意路径选取。 pasm –c “C:PRUexample.p”,即可在对应文件夹下生成二进制“C数组”文件example_bin.h。主控制器通过该文件将编译得到的目标文件装载进PRU的指令RAM空间。后面详述这一过程。

5. 指令的装载与运行

Cotex-A8内核中文件组织有如下递进关系:
  • * P文件和* HP文件,PRU文件的源文件和头文件
  • *C文件,运行在ARM内核上的程序
  • *PRJ文件,整个CCS项目文件
需要注意的是,PRU的程序代码必须由SoC主处理器加载,也必须由SoC主处理器启动。在集成开发环境CCS中, 勾选PASM工具编译 *p文件,生成二进制C数组文件通过PRU_LOAD()函数加载到PRU指令RAM。
例如,TI提供中断例程中,包含两个文件: PRU_DSPtoPRU_Interrupt.hp和PRU_DSPtoPRU_Interrupt.p,运行命令行程序
”PASM –c PRU_DSPtoPRU_Interrupt.p”,
编译生成C数组文件PRU_DSPtoPRU_Interrupt._bin.H。将其内容复制如下: /* This file contains the PRU instructions in a C array which are to */ /* be downloaded from the host CPU to the PRU instruction memory. */ /* This file is generated by the PRU assembler. */ const unsigned int PRUCode[] = { 0x2400008c, 0x240000cc, 0x2400009f, 0x240000df, 0x24000190, 0x81100090, 0x24000090, 0x240000d0, 0x81342090, 0x2408008c, 0x01008c8c, 0x24000090, 0x808c0010, 0x2404008c, 0x0120ecec, 0x24000010, 0x808c0010, 0x24002090, 0x240000d0, 0x81242090, 0x81282090, 0x2400209f, 0x240000df, 0xd11eff02, 0x21001700, 0x24000190, 0x240000d0, 0x24028483, 0x240000c3, 0x80e32090, 0x24000190, 0x240000d0, 0x24038483, 0x240000c3, 0x80e32090, 0x24002482, 0x2401c2c2, 0x24000083, 0xe1000283, 0x24000080, 0x240000c0, 0x24702ce1, 0xe1002180, 0x91003f85, 0x0501e5e5, 0x81003f85, 0x2a000000 }; 注意绿 {MOD}的文件自述:该文件以C数组的形式包含了PRU指令,该指令将由CPU下载到PRU指令空间。该文件是由PRU编译器产生的。 由此推断,PRUCode[]数组中的内容为PRU可直接执行的机器码。在主处理器上运行的C程序中,有如下代码: …… #include "PRU_DSPtoPRU_Interrupt_bin.h" …… void main() { CSL_TmrRegsOvly timer0Regs = (CSL_TmrRegsOvly)CSL_TMR_0_REGS; printf("Starting %s example. ",exampleName); // Make sure PRU sub system is first disabled/reset PRU_disable(); // Enable and load the code to the specified pru printf(" INFO: Loading example. "); PRU_load(PRU_NUM, (Uint32*)PRU_Code, (sizeof(PRU_Code)/sizeof(Uint32))); …… 通过#include将生成的C数组文件包含进工程,在有PRU_load()函数将其下载到PRU模块当中。 由于PRU模块的下载,运行,禁止等操作均是由主控制器操作的,故TI提供了一些接口函数用于实现这些功能,类似于API。除了上述的PRU_load()函数实现指令装载外,还有以下函数:
  • PRU_disable() 将PRU置于复位状态
  • PRU_enble() 将PRU置于允许状态
  • PRU_run(),运行指定内核
  • PRU_waitForHalt()等待特定的PRU进入暂停状态
该函数的定义在
X:Texas InstrumentsPRU Software Development_1_03hostcommonsrc文件加下的PRU.C文件中,下面仅就PRU_load()函数讲解其实现。 //在PRU.C文件中 // Load the specified PRU with code Uint32 PRU_load (Uint8 pruNum, Uint32* pruCode, Uint32 codeSizeInWords) { Uint32 *pruIram; Uint32 i; if (pruNum == CSL_PRUCORE_0) { pruIram = (Uint32 *) PRU0_PROG_RAM_START; } else if (pruNum == CSL_PRUCORE_1) { pruIram = (Uint32 *) PRU1_PROG_RAM_START; } else { return E_FAIL; } PRU_enable(); // Copy dMAX code to its instruction RAM for(i=0; i 该函数接收的参数有三个,第一个参数为指定下载指令的PRU内核,应为0或者1。第二个参数用以接收C数组文件的首地址,即上述PRU_DSPtoPRU_Interrupt._bin.H文件中的PRUCode[],第三个参数用以确定该数组的大小。参见上述例程中的实现
PRU_load(PRU_NUM, (Uint32*)PRU_Code, (sizeof(PRU_Code)/sizeof(Uint32)));不难理解其原理。 内部实现上,首先根据传递来的第一个参数确定对哪个PRU模块进行下载操作。两个PRU模块指令RAM起始地址已在PRU.H中定义:
#define PRU0_PROG_RAM_START (0x01C38000)
#define PRU1_PROG_RAM_START (0x01C3C000)
将其起始地址赋予Uint32 *pruIram,将PRU_Code[]中的指令一一赋值给pruIram[]空间即可。 其他函数功能的实现见HOST.C。
PRU内核代码编译装载的流程如下: