TMS320C6455入门实践(二)
本系列文章链接
- TMS320C6455入门实践(一)——编译CSL静态库
- TMS320C6455入门实践(二)——中断相关
- TMS320C6455入门实践(三)——PLL相关
- TMS320C6455入门实践(四)——GPIO与定时器
- TMS320C6455入门实践(五)——cmd链接脚本
- TMS320C6455入门实践(六)——编写boot程序
- TMS320C6455入门实践(七)——生成启动镜像
- TMS320C6455入门实践(八)——Flash驱动与程序固化
- TMS320C6455入门实践(九)——DDR上运行代码
- TMS320C6455入门实践(十)——EDMA3相关
本文主要介绍TMS320C6455的中断相关的内容,主要的的参考文档有:
- SPRS276M - TMS320C6455 Fixed-Point Digital Signal Processor
- SPRU871 - TMS320C64x+ DSP Megamodule Reference Guide
- SPRU732 - TMS320C64x/C64x+ DSP CPU and Instruction Set Reference Guide
上面的第一个文档就是TMS320C6455的数据手册,是内容最全面的,但对各个外设都是粗略的介绍,详细的介绍需要从另外的专门的文档中找。在数据手册的2.8.2.2 Documentation Support中介绍了其它的参考文档,便于大家查阅。
中断控制器
SPRU871的第7章对C6455的中断控制器有详细的介绍。以下是中断控制器的框图。
我理解的中断控制器的主要功能就是将128个系统事件映射到CPU的12个中断输入上。
事件标识:总共有124个从INTC外输入的系统事件,和四个事件标志寄存器(Event flag registers,, EVTFLAGx),每个事件都对应都和寄存器中的一个bit相对应。其中EVTFLAG0中的低4位没有和任何事件对应。这样每个事件就都有了对应的标识,与之配套的分别还有四个标识清除寄存器(Clear flag registers, EVTCLRx)和标识置位寄存器(Set flag registers, EVTSETx)。
事件组合:采用“或”逻辑,将多个事件组合起来产生一个新的事件。如上图所示,EVT0~EVT3就是组合事件。这四个事件加上原有的124个事件一同送入中断选择器。在事件组合过程中,可以用EVTMASKx(Event Mask Register)屏蔽一些不关心的事件,然后在MEVTFLAGx(Masked Event Flag Register)中就可以看到最终的组合事件的标识。
中断选择器:128个事件都可以映射到12个CPU中断输入中的任意一个,在INTMUXn (Interrupt Mux Registers) 中进行配置。总共有三个INTMUX寄存器,寄存器中总共有12个INTSEL字段,每个字段为7bit,字段值表示事件号。由此将12个中断控制器的中断输出和128个事件输入相对应。
异常组合:事件标识除了构成组合事件,还可以通过Exception combiner将不同的事件组合起来构成一个异常输出,送给CPU。和事件组合类似,异常组合也有两种寄存器EXPMASKx(Exception Mask Register)和MEXPFLAGx(Masked Exception Flag Register)。不同的是,这里是将124个事件都“或”起来组合成一个异常输出,当然要考虑EXPMASK的作用。默认情况下,所有事件都是被屏蔽的,需要用户对EXPMASK寄存器做修改才能让一些事件产生异常信号。在异常组合器这里被屏蔽的一些事件可以继续用作中断源,而不会相互影响。
复位是最高优先级的,始终使能。不可屏蔽事件(NMIEVT)直接送入CPU的NMI中断。在没有使能异常(使能interrupt task register中的global exception enable字段)时,NMI输入是作为中断进行响应的,NMI信号直接作用于中断标识寄存器(IFR);而使能异常后,IFR中不再产生相应的NMI标识,而是在异常标识寄存器中产生标识。
中断异常主要指CPU在中断还未响应结束时又收到新的中断,就像是“落下(Drop)”了一个中断。中断控制器中有相应的中断异常状态寄存器(INTXSTAT)、中断异常清除寄存器(INTXSTAT)和遗漏中断屏蔽寄存器(INTDMASK)。每次有这类异常产生就会产生编号96的外部事件送入中断控制器。值得注意的是,每次有中断产生时,应当及时清除事件标识,因为如果没有清除事件标识时又产生新的同样的事件,那么CPU是检测不到的,CPU只能检测到在CPU输入端落下的中断。
CPU中断
SPRU732中的第5章和第6章分别介绍了CPU对中断和异常的处理。异常这部分我还没有仔细看,主要讲讲中断这部分;中断这部分很多也都讲得很细节,所以主要挑一些我看得懂的进行介绍ORZ。
C6455有一个比较特别的地方,就是它的中断向量表的位置不是特定的。大多数处理器总是固定的中断向量表,一般中断向量表的第一个就是复位,程序也从这里开始执行。文档中都说在具体设备的手册中找默认的中断向量表,但是我一直都没有找到。
上图中的IST(Interrupt Service Table)就是类似中断向量表的一个东西。它是由一个个ISFP(Interrupt Service Fetch Packet)组成的。因为C6455每次取指可以取8条32bit的指令,所以这一次性取指的8条指令被称为一个Fetch Packet。如果一个中断服务函数能在8条指令执行完,那当然最好,但是如果执行不完就要像上面这样在这8条指令中插入一条跳转的指令,从而执行一些额外的指令。
这里的跳转指令都有5个delay slot,就是在执行跳转指令后,需要再执行5条指令才会真正跳转。所以上图中的跳转指令后还有5条指令,这些指令都是会被执行的!!很多时候在跳转指令后跟一条“NOP 5”也是出于这个目的。上面的“B IRP”指令就是专门用于中断返回的指令,IRP指的是Interrupt Return Pointer Register,专门用于可屏蔽中断服务函数的返回;而与之类似的还有一个NRP,是专门用于不可屏蔽中断的服务函数的范围地址。
前面说到它的中断向量表位置不固定,这是因为有一个Interrupt Service Table Pointer Register(ISTP),这里的ISTB字段存的是IST的基地址,用户可以在程序中修改这个基地址从而绑定新的中断向量表的位置。对于IST有一个基本的要求就是它的基地址要满足1k字节边界对齐,也就是地址的低10bit均为0。因为在ISTB字段只有22bit,作为地址的高22位。
这里还有一个字段是HPEINT,Highest priority enabled interrupt,在没有中断产生时,这部分字段为0;而有中断产生时,这部分字段就是最高优先级的中断的中断号,用户可以用这个去手动调用中断服务函数。
有一个一直困惑我很久的问题就是这个重新定位的IST中的响应复位的ISFP是什么样子的?答案是没有这部分ISFP。复位后,ISTP寄存器中的ISTB字段也恢复默认值,甚至都找不到IST了,自然没有这部分代码;所以我现在认为复位就是硬件完成的,没有相关的代码,用户自己创建的IST里面直接把复位那部分当作Reserve处理就可以。
CSL的使用
CSL与中断控制器相关的库是单独编译的,函数也不多,在这里做一些零散的总结。
凡是用到这一中断控制器相关的工程,都需要在链接脚本命令文件中添加“.csl_vect”这一代码段,这里存放的就是IST。可以在“_csl_intcIsrDispatch.asm”文件中看到定义。可以看到它对复位那部分代码就是以Reserve处理的。而且声明了“.align 1024”,以1k字节的边界对齐。
将用户定义的IST和ISTP中的ISTB字段绑定是在CSL_intcInit()函数中实现的,其中调用的_CSL_intcIvpSet()是用汇编写的,写在“_csl_intcIntrEnDisRes.asm”文件中。
既然有了IST,那么在中断发生之后肯定就是从IST开始执行,每个中断服务函数只能包含8条指令。这8条指令是用一个宏写的,代码如下;这里的_CSL_intcpush和_CSL_intcpop也是宏,就是保护现场用的。
1 | CALLDISP .macro intr |
这个有点难理解,但结合_CSL_intcCpuIntrTable这个结构体就好理解了。这个结构体是用C写的,并且用C在“_csl_intcResource.c”中声明,在汇编中调用就在前面多了一个下划线。这个结构体的第一个成员是Uint32类型的,虽然变量名是currentVectId,但其实是用来存放函数首地址的。上面的汇编代码先用“mvkl”和“mvkh”拿到这个结构体的首地址,然后用“ldw”从结构体中找到对应的服务函数的地址。“ldw”的delay slot是4条指令,所以这个函数地址不会马上到a0,而a0会马上自增变为指向函数首地址的地址。这时再用“stw”把相应的函数的地址存到currentVectId中。到“bnop a0, 5”时,load完成,就跳转到服务函数处了。
1 | typedef struct CSL_IntcVect { |
在CSL_intcInit()函数中,_CSL_intcCpuIntrTable的几个服务函数也都初始化了,可屏蔽中断的服务函数都是_CSL_intcDispatcher(),这个函数中为了获得中断号,就是通过相对地址的关系计算的,代码如下:
1 | Uint32 intrId = (_CSL_intcCpuIntrTable.currentVectId - (Uint32)(&_CSL_intcCpuIntrTable) - 4)/4; |
然后这个_CSL_intcDispatcher()实际在调用中断服务函数时调用的是_CSL_intcEventHandlerRecord这个结构体数组中存放的中断服务函数。
CSL_intcPlugEventHandler()则是把具体的中断服务函数存到_CSL_intcEventHandlerRecord中。
CSL_intcOpen()会调用CSL_intcHookIsr(),把初始化时的_CSL_intcDispatcher()替换为复合事件的dispacher,就是在“_csl_intcCombEventDispatcher.c”文件中定义的四种函数。
如果只是用CSL_intcInit()之后的配置,也就是用_CSL_intcDispatcher()作为服务函数,那么中断只能响应那种一对一的绑定;而如果用CSL_intcOpen()之后,就可以响应那种EVT0~EVT3的复合中断,一旦有中断产生,程序会依次检查一系列的中断标识,响应多种中断。