谈InitPieVectTable()函数
题目
中断向量初始化说明
习题: InitPieVectTable( )函数涉及了哪些变量,它们是如何定义、如何定位的:该函数有什么作用?要求不少于3000字:
包含文件:
1.DSP28_PieVect.h
2.DSP281x_PieVect.c
3.DSP281x_GlobalVariableDefs.c
4.DSP281x_Headers_nonBIOS. cmd
5.DSP28_DefaultIsr.h
6.DSP281x_DefaultIsr.c
奇怪的回答
变量PieVectTable是直接与存bai储器的地址相映射的PINT是指向中断类型函数的指针类型,类似int,float,它是一个指针类型。
typedef interrupt void(*PINT)(void);//定义了一个指向中断型函数的指针类型
以下结构体的元素为函数指针类型,成员为中断函数的首地址
typedef interrupt void(*PINT)(void);
// Define Vector Table:
struct PIE_VECT_TABLE {
PINT PIE1_RESERVED;
PINT PIE2_RESERVED;
PINT PIE3_RESERVED;
……..
// Non-Peripheral Interrupts:
PINT XINT13; // XINT13 / CPU-Timer1
PINT TINT2; // CPU-Timer2
// Group 1 PIE Peripheral Vectors:
PINT SEQ1INT;
PINT SEQ2INT;
…….
…….
// Group 12 PIE Peripheral Vectors:
PINT XINT3; // External interrupt
PINT XINT4;
PINT XINT5;
PINT XINT6;
PINT XINT7;
PINT rsvd12_6;
PINT LVF; // Latched overflow
PINT LUF; // Latched underflow
};
//—————————————————————————
// PIE Interrupt Vector Table External References & Function Declarations:
//
extern struct PIE_VECT_TABLE PieVectTable;
PieVectTable这个结构体变量被DSP2833x_Headers_nonBIOS.cmd映射了所有可能中断源的物理地址,即memory
map中PIE向量表所占的存储空间,其首地址为origin = 0x000D00。
中断
中断通常被定义为一个事件,该事件改变处理器执行的指令顺序。中断有两种,一种是由CPU外部产生的,对于执行中的软件来说,这种中断的发生完全是“异步”的,根本无法预料此类中断会在什么时候发生,一般由其他硬件设备产生(例如键盘中断);另一种是由CPU本身在执行程序的过程中产生的,例如X86中的“INT n”。Intel手册中分别将这两种中断称为中断和异常。Intel文档中,中断又可分为可屏蔽中断和非屏蔽中断,异常分为:故障、陷阱、异常中止和编程异常。不管是哪种中断,CPU的相应过程基本上一致,即:在执行完当前指令以后,或者在执行当前指令中途,根据中断源提供的“中断向量”,在内存中找到相应的服务程序入口并调用该服务程序。外部中断的向量是由软件或硬件设置好了的,陷阱的向量是在“自陷”指令中发出的(INT n中的n),而各种异常的向量则是CPU的硬件结构中预先规定好的。系统调用一般是通过INT指令实现的,所以也与中断密切相关。
一:问题引入
学完中断以及第十章.CMD文件等知识后,老师布置了分析初始化中断函数InitVectTabel()的任务。具体分析其变量为何?如何定义?定位在哪?该函数又有什么作用?
二:初步认识和困惑
在第二章中断学习中我们了解到了中断的响应过程。.CPU在进程正常的程序处理的时候,有时候会被要求处理更高需求级别的任务,因此不得不中断当前任务进程,进入中断服务程序。而在处理完这些额外的任务之后,还需要回到之前的任务,因此就需要在进入中断程序之前必须保存现场,以确保在主要任务被打断并完成中断程序之后,能够准确地回到之前的任务节点。这种采用中断方法处理外设的服务请求可以节省对外设的查询时间,能够大大提高CPU的效率。
而我们又知道F281x的中断系统可以分为三级,也即外设级,PIE级以及CPU级。由于CPU无法处理所有外设中断,所以引入了PIE控制器来仲裁来自各种外设中断以及外部中断的请求。PIE将许多中断源分为一组中断输入,每八个一组,一共12组也即96个外设中断处理。其每个中断有自己的中断向量,其是在芯片生产时就已经被TI定义好了存在RAM中。当CPU响应中断时,便可自动获得向量,快速响应外设。具体如下:CPU从接口电路获取中断类型,计算对应与中断向量表的位置,获取中断向量,最后将程序流程转向中断服务程序的入口地址以处理外设中断。
学到这,我们似乎明白了中断的响应机制,但有几个问题一直困扰着我。首先所谓的中断向量表是如何储存在RAM中的呢?另外我们编程使用中断时会对中断向量表初始化,用到InitPieVectTable()函数,此函数已被编写在库文件中,我们可以直接调用,而该函数又是什么作用呢?最后,该函数又是如何实现其初始化功能的呢?而由此引申,对于其他库函数,我们再引用时有该如何实现其正常运行呢?
3. 资源分配器——.CMD
我们通过做实验逐渐知道利用DSP实现功能其实就是利用很多个储存器值的修改来实现的,实际操作中,我们可以通过位域结构直接修改到位,也可以通过寄存器地址进行修改。所以我们不难想象寄存器的地址和值是一种紧密相连的关系。我们只要知道了其地址就可以对其进行值的修改。而说到这,就不得不提到.CMD文件,因为它就是描述开发工程师对物理存储器的管理、分配和使用情况。
哈弗结构将存储分为数据存储总线以及程序存储总线,由于程序和数据存储在两个分开的物理空间中,因此取址和执行能完全重叠。中央处理器首先到程序指令存储器中读取程序指令内容,解码后得到数据地址,再到相应的数据存储器中读取数据。而对存储器而言,其主要就是两类,RAM以及ROM,其区分就是是否易失以及读取数据的快慢。而.CMD就是以这两种存储器为主轴进行地址的分配展开的。
1、用户声明的整个系统里的存储器资源。无论是 DSP 芯片自带的,还是用户外扩的,凡是可以使用的、需要用到的存储器和空间,用户都要一一声明出来:有哪些存储器,它们的位置和大小。如果有些资源根本用不到,可以视为不存在,不必列出来。
2、用户如何分配这些存储器资源,即关于资源分配情况的声明。用户根据自己的需要,结合芯片的要求,把各种数据分配到适当种类、适当特点、适当长度的存储器区域。
而具体如下:在DSP281x_Headers_nonBIOS.cmd中定义如下
MEMORY
{
PAGE 0: /* Program Memory */
PAGE 1: /* Data Memory */
PIE_VECT : origin = 0x000D00, length = 0x000100 /* PIE Vector Table */
….
}
SECTIONS
{
PieVectTableFile : > PIE_VECT, PAGE =1
…
}
可以看到.CMD文件将资源分为两片Page0,Page1,Page0作为程序空间,Page1为数据空间,
在数据空间定义了PIE_VECT区域,规定了其首地址以及长度,而在SECTIONS中又定义了
PieVectTableFile在PIE_VECT中。这样,在系统资源中就已经为PieVectTableFile划分了一个地址区域用于存放后来的向量表。
至此,我们知道.CMD文件其实就是为各个存储器划分了地址空间,我们可以可以在段中按照TI规定自行定义我们的变量。
4. 中断向量和中断服务函数
那么在通过.CMD文件分配了中断向量表的空间后,中断向量表又是如何写到其中的呢?
PieVectTableFile段已在 DSP281x_GlobalVariableDefs.c 这个文件中的下面语句定义了。 其中DSP281x_GlobalVariableDefs.c文件给出了外设寄存器结构变量和数据分段分配的说明,所有基于位域结构的工程必须包括该文件。
定义如下:
#ifdef __cplusplus
#pragma DATA_SECTION(“PieVectTableFile”)
#else
#pragma DATA_SECTION(PieVectTable,”PieVectTableFile”);
#endif
struct PIE_VECT_TABLE PieVectTable;
利用pragma伪指令对PieVectTable在PieVectTableFile中进行地址分配,其中最后一句又定义了PIE_VECT_TABLE的结构体变量PieVectTable。那么问题又来了,PIE_VECT_TABLE结构又是在哪儿定义的呢?
其定义在DSP281x_Pievect.h头文件中
struct PIE_VECT_TABLE
{
PINT PIE1_RESERVED;
PINT PIE2_RESERVED;
…
}
而PINT定义如下
typedef interrupt void(*PINT)(void);
也即定义了一组中断函数的指针PINT
可以看到 PIE_VECT_TABLE 其实是一个数组,里面放着13组关于中断首地址的指针。
而在该文件的最后,extern struct PIE_VECT_TABLE PieVectTable定义了外部结构变量PieVectTable。(问题:为何要定义两次PieVectTable)这样便将PieVectTable指定到了相应的内存空间,并且可以通过其对 PIE_VECT_TABLE表进行修改。于此我们也就将所有的外设以及外部中断向量存储到了RAM中。
而我们观察这些文件,发现了另一个变量PieVectTableInit,其定义在DSP281x_Pievect.c文件中:
const struct PIE_VECT_TABLE PieVectTableInit = {
PIE_RESERVED, // Reserved space
PIE_RESERVED, //保留
PIE_RESERVED,
PIE_RESERVED,
….
}
我们很惊讶的发现其也是PIE_VECT_TABLE 类型的变量。仔细观察,其也是一个数组。
const 关键字说明这个数组里面的每个元素都是一个常量。struct PIE_VECT_TABLE 这种结构体类型又说明这个数组里面的每个元素都是一个地址,而我们分析这个数组里面的每个元素,会发现他们是默认的中断服务函数的函数名字。函数名字就代表地址。所以这个数组变量装的是中断服务函数的首地址。
而中断服务函数又是在哪儿定义的呢?
DSP281x_DefaultIsr.h头文件中定义了所有的中断服务函数,而在相对应的.c文件中又给出了每个中断函数的确切定义,比如
interrupt void PIE_RESERVED(void) // Reserved space. For test.
{
asm (“ ESTOP0”);
for(;;);
}
那么这个中断服务函数是干什么的呢?
问题:cmd给PieVectTable分配了地址,并且中断函数地址直接赋给PieVectTable,感觉这个进中断过程没有用到ieVectTableInit,PieVectTableInit是DSP28_PieVect.c中重新定义的一个PIE_VECT_TABLE型结构体,并且又用void InitPieVectTable(void)函数将PieVectTableInit和PieVectTable两个结构体对应起来。为什么要这么“多此一举”并且整个工程中再也没有见到PieVectTableInit的身影。那这个是干嘛的呢?
思考良久,我在看以前的例程时猛然发现其中奥妙。其实也算不得什么大奥义,只不过是我一开始想多了罢了。如下:
PieVectTable.TINT0= & INT_1_7;
意思是将CPUtimerO的中断服务函数调用给自定义的INT_1_7使用罢了。利用这个中断服务函数我们便可以产生一个中断,并且由此展开编程。也就是说CPU就是通过中断服务函数进入中断处理的。
至此,我们便已经解释了起初的几个问题。在PicVect.h中声明了extern struct PIE_VECT_TABLE ;而PieVectTable的定义在GlobalVariableDefs.c中,最后通过.CMD文件将PieVectTable的位置锁定在2812指定的向量空间地址,完成了将中断向量表固定在RAM中。而又通过PieVectTabelInit函数定义了中断服务函数的入口地址,以此方便我们使用中断编程。
5. 初始化中断函数InitVectTable()
最后,回到最初的起点,InitPieVecTable()函数,我们由此最终回答老师布置的问题。其在Pievect.h文件定义如下
void InitPieVectTable(void)
{
int16 i;
Uint32 *Source = (void *) &PieVectTableInit;
Uint32 *Dest = (void *) &PieVectTable;
EALLOW;
for(i=0; i < 128; i++)
*Dest++ = *Source++;
EDIS;
PieCtrlRegs.PIECRTL.bit.ENPIE = 1;
其一共定义了五个变量分别为,i;Source ;PieVectTableInit;Dest;PieVectTable;最主要的便是前面介绍的PieVectTableInit以及PieVectTable。其中PieVectTableInit指定了PIE中断服务程序入口地址,PieVectTable制定了中断向量表的地址。两个关键变量如下图所示:
对下面两句话的解释:
Uint32 *Source = (void *) &PieVectTableInit;
Uint32 *Dest = (void *) &PieVectTable;
PieVectTableInit是个指针,&PieVectTableInit就是取这个指针的内容,在这里就是取数组PieVectTableInit的第一个元素的内容,而这第一个元素的内容也是一个指针,指向一个中断服务函数的收地址。第二个的解释同理,也即取出中断向量表的地址。
这两句话的是为下面的语句做准备,下面的语句是要将从源地址开始的内容复制到中断向量表中去。
EALLOW;
for(i=0; i < 128; i++)
*Dest++ = *Source++;
EDIS;
这就是把中断服务函数指针对应到中断向量表中去。最终也就是将变量PieVectTableInit的内容拷贝到变量PieVectTable中,也就是将实际的中断服务函数入口地址拷贝到2812指定的中断向量空间。简而言之便是,CPU响应中断后,在中断向量表中得到相应中断向量,保存现场,然后通过中断向量进入中断服务函数处理中断。
最后对PIE模块使能
PieCtrlRegs.PIECRTL.bit.ENPIE = 1;
那么这个函数有什么作用呢?
其第一个作用便如前所说,初始化PIE中断向量表,将中断服务函数与中断向量相对应起来。
第二个作用就是定制中断服务函数。还是举我们实际编程所用到的例子。
在主函数中定义PieVectTable.TINT0= & INT_1_7;也即定义了一个中断函数为INT_1_7,
然后我们便可以利用这个中断函数编程:
interrupt void INT_1_7(void)
{ k+=1;
if(k==3)
{k=0;GpioDataRegs.GPFDAT.all=0xfeff;}
else
{GpioDataRegs.GPFDAT.all=0x100;}
PieCtrlRegs.PIEACK.all =0x1;
CpuTimer0Regs.TCR.all =0xf000;
}
上面便是一个简单的利用中断控制绿灯闪烁程序。我们可以从中体会到中断服务程序的妙处。
6.总结和心得
至此便对老师题目中的问题进行了一些理解和解答。总结如下:通过nonBIOS.CMD文件以及GlobalVariableDefs.c文件在内存区域为中断向量表划分了一个地址空间,在此寄存中断向量表,而通过PieVectTableInit变量定制了中断服务函数,最后通过InitVectTable()函数将中断向量和中断服务程序连接起来。处理中断过程如下:CPU响应中断后从RAM中断向量表中获得相应的中断向量并且保存现场,由此建立起与中断服务函数的联系,获取服务函数的入口并且进入,最终处理中断。通过本次实验让我更细致的了解到了.CMD文件在分配地址中起到的作用,也更好的掌握了中断是如何被初始化的以及其作用,同时加深了对于上几次实验对于头文件配置和路径设置的理解。