本帖最后由 枢机主教 于 2017-6-21 00:22 编辑
第二个是GPIO和按键,以前学单片机和arm的时候动用过查询的方法做过。这里不使用查询的方法,而是使用中断的方法。所以这里重点是调试了中断。
单片机只用过C语言写过,不理解什么原理。arm的中断倒是听明白的,裸机的时候在0地址上放上中断向量表,其实是一条跳转指令,是用汇编语言写的,有韦东山的教程,学起来也是很快的。到DSP上的时候,一开始直接迷茫了,看师兄以前做过的项目的CCS工程中有一个“vectors.asm”的汇编文件,论坛里搜索怎么做DSP的中断调试时候,也有一个帖子https://bbs.21ic.com/icview-331273-1-1.html,也是汇编语言,但是之前根本没有看过汇编语言,也看不懂原理是什么,不知道原理真的不是我的习惯。
看“vectors.asm”这个文件开头的注释,好像是TI官方提供的,我在pdk里搜索却也没有搜索到。所以就耐心去看文档了。看了CIC的文档,看了CorePac的文档,看了CPU&Instruction set的文档,通过pdk里面提供的关于CIC的例子,弄明白了6678的中断系统。然后我发现在TI现在提供的源码的情况下,是不需要编辑修改汇编文件的,关于中断的汇编文件已经被编译到库文件里了,甚至中断处理函数都不用加“Interrupt”这个关键字来声明,不过为了弄懂中断系统,还是有必要看汇编文件的,这个在下面解析。
下面要帖的代码没有涉及到CIC,只有CorePac INTC。
目标:使用按键通过中断控制LED,按一下灯亮,按一下灯灭
原理:通过LED的按下在GPIO0上产生下降沿,从而产生GPINTn中断事件,这里的n就是0了,所以是CorePac0处理这个中断。
先贴代码:
- /*
- * main.c
- */
- #include "stdio.h"
- #include "global.h"
- #include "ti/csl/cslr_gpio.h"
- #include "ti/csl/csl_gpio.h"
- #include "ti/csl/csl_gpioAux.h"
- #include "ti/csl/src/intc/csl_intc.h"
- #include "ti/csl/src/intc/cslr_intc.h"
- #include "ti/csl/src/intc/csl_intcAux.h"
- //定义指向CorePac的INTC的寄存器组结构体的指针
- CSL_IntcRegsOvly gIntcRegisters = (CSL_IntcRegsOvly)CSL_CGEM0_5_REG_BASE_ADDRESS_REGS;
- //定义用于在中断处理函数中取反操作的全局变量
- unsigned char flag = 0;
- //定义中断处理函数
- static void GPINT0_event_handler(void *arg)
- {
- CSL_GPIO_clearInterruptStatus((CSL_GpioHandle)0x02320000, 0); //清除GPIO中断状态
- CSL_intcEventClear((CSL_IntcEventId)90); //清除事件的状态标志位
- flag = !flag;
- }
- //定义GPIO0引脚的中断初始化函数
- void GPIO_0_int_init(void)
- {
- CSL_GpioHandle hnd;
- printf("Debug: GPIO0 interrupt configuration...\n");
- hnd = CSL_GPIO_open(0); //获取GPIO模块寄存器基地址
- CSL_GPIO_setPinDirInput(hnd, 0); //将GPIO0设置为输入
- CSL_GPIO_bankInterruptEnable (hnd, 0); //使能GPIO中断,实际是操作BINTEN寄存器
- CSL_GPIO_setFallingEdgeDetect (hnd, 0); //GPIO0在下降沿产生中断事件GPINT0
- printf("Debug: GPIO0 interrupt configuration completed\n");
- }
- void GPIO_4_LED_init(void)
- {
- CSL_GpioHandle hnd;
- printf("Debug: GPIO4 LED init...\n");
- hnd = CSL_GPIO_open(0); //获取GPIO模块寄存器基地址
- CSL_GPIO_setPinDirOutput(hnd, 4); //将GPIO4设置为输出
- printf("Debug: GPIO4 LED init completed\n");
- }
- void GPIO_4_LED_control(char state)
- {
- CSL_GpioHandle hnd;
- hnd = CSL_GPIO_open(0); //获取GPIO模块寄存器基地址
- if(state)
- {
- printf("Debug: LED01 ON...\n");
- CSL_GPIO_clearOutputData(hnd, 4);
- }
- else
- {
- printf("Debug: LED01 OFF...\n");
- CSL_GPIO_setOutputData(hnd, 4);
- }
- }
- void Core_intc_init(CSL_IntcParam vect_id, CSL_IntcEventId event_id, void (Int_Ser_routine)(void *arg))
- {
- CSL_IntcContext intcContext; //先对其初始化,然后其地址用作CSL_intcInit的参数
- CSL_IntcEventHandlerRecord EventHandler[30];//给intcContex赋值,然后在CSL_intcInit函数中使用intContex对全局变量进行赋值。这里为什么是30?
- CSL_IntcGlobalEnableState state; //用于保存被操作前的GIE的状态
- CSL_IntcParam vectId; //用来保存使用的核心的中断号
- CSL_IntcHandle hTest; //用来指向被赋值处理过的intcObj这个变量
- CSL_IntcObj intcObj;//这个结构体用于记录对应的中断ID和事件ID。
- CSL_IntcEventHandlerRecord EventRecord; //记录一个事件处理函数的函数指针和函数参数
- printf("Debug: CorePac interrupt configuration...\n");
- /* INTC module initialization */ /*那几个全局变量的初始化,ISTP寄存器赋值,给记录各中断的中断处理函数的结构体变量赋值*/
- intcContext.eventhandlerRecord = EventHandler;
- intcContext.numEvtEntries = 30;
- if(CSL_intcInit(&intcContext) != CSL_SOK)
- {
- printf("Error: CorePac interrupt initialization failed\n");
- return;
- }
- /* Enable NMIs */ /*set NMIE = 1*/
- if(CSL_intcGlobalNmiEnable() != CSL_SOK)
- {
- printf("Error: CorePac interrupt global NMI enable failed\n");
- return;
- }
- /* Enable global interrupts */ /*set GIE = 1*/
- if(CSL_intcGlobalEnable(&state) != CSL_SOK)
- {
- printf("Error: CorePac interrupt global enable failed\n");
- return;
- }
- /* 打开INTC模块 中断ID为vect_id 事件ID为event_id */ /* 对intcObj进行赋值,把事件号路由到中断号上 */
- vectId = vect_id;
- hTest = CSL_intcOpen(&intcObj, event_id, &vectId, NULL);
- if(hTest == NULL)
- {
- printf("Error: CorePac interrupt Open failed\n");
- return;
- }
- /* 注册一个回调函数,在中断发生时被调用 */
- EventRecord.handler = Int_Ser_routine;
- EventRecord.arg = 0;
- if(CSL_intcPlugEventHandler(hTest, &EventRecord) != CSL_SOK)
- {
- printf("Error: CorePac interrupt plug vent handler failed\n");
- return;
- }
- /* Enabling the events */
- if(CSL_intcHwControl(hTest, CSL_INTC_CMD_EVTENABLE, NULL) != CSL_SOK)
- {
- printf("Error: CorePac interrupt CSL_INTC_CMD_EVTENABLE command failed\n");
- return;
- }
- printf("Debug: CorePac interrupt configuration completed\n");
- }
- int main(void) {
- char local_flag = 0;
- /* 配置GPIO0的中断 */
- GPIO_0_int_init();
-
- /* 配置CorePac INTC */
- Core_intc_init(CSL_INTC_VECTID_4, (CSL_IntcEventId)90, GPINT0_event_handler);
- /* 配置GPIO4控制LED */
- GPIO_4_LED_init();
- GPIO_4_LED_control(LED_OFF);
- for( ; ; )
- {
- if(local_flag != flag){
- if(1 == flag)
- GPIO_4_LED_control(LED_ON);
- else
- GPIO_4_LED_control(LED_OFF);
- local_flag = flag;
- }
- }
- }
这个解析明天在说吧,要明白这里代码的原理,需要明白delayslot,pipeline,汇编等相关内容,解析起来肯定会比较麻烦。当时csl提供的关于中断这一块的源码我看了大概有一个多星期才看完。
20170620
TMS320C66x DSP CPU and intruction set这个文档里对delay slot的解释如下:
The number of delay slots is equivalent to the number of additional cycles required after the source operands are read from the result to be available for reading 也就是说一条指令读了源操作数之后在结果可以被读取之前需要经过的周期数,从这里可以知道,有点指令并不是一个周期就可以执行完的,需要多个周期才能执行完。在流水线中,指令执行的各个周期被编号为E1、E2、E3……
TMS320C66x DSP CPU and intruction set这个文档里对functional unit latency的解释如下:
the functional unit latency is equivalent to the number of cycles that must pass before the functional unit can start excuting the next instruction. 也就是功能单元能够执行下一条指令之前必须要经过的周期数。
为了能够使程序高速执行,CPU采用了流水线结构,取指令、解码指令和执行指令同时进行,在C66x中,这三个阶段又被细分,取指令阶段被细分为PG、PS、PW、PR,解码又被细分为DP、DC,执行部分被分为E1、E2、E3……被细分的每一部分对应一个周期。
TMS320C66x DSP CPU and intruction set这个手册中有一个图片说明了流水线的操作:
上图是每个取指包就是一个执行包的情况。从上图可以看出,一个执行包执行了E1之后开始进入E2阶段,此时下一个执行包也开始进入到E1执行。
知道了上面的这些内容后就可以理解MCSDK安装完后生成的文件夹pdk_C6678_1_1_2_6\packages\ti\csl\src\intc里面的代码了。这里的代码和中断相关,其中最重要的中断服务表就是在这里产生的。理解中断,先来看中断服务表的产生。
首先是_csl_intcIsrDispatch.asm这个文件,里面是汇编代码,用于产生DSP的中断服务表,这个文件的代码如下:
- ;/*****************************************************************************
- ; * Copyright (c) Texas Instruments Inc 2002, 2003, 2004, 2005, 2008
- ; *
- ; * Use of this software is controlled by the terms and conditions found in the
- ; * license agreement under which this software has been supplied.
- ; *****************************************************************************/
- ;/** [url=home.php?mod=space&uid=288409]@file[/url] _csl_intcIsrDispatch.asm
- ; *
- ; * [url=home.php?mod=space&uid=212281]@date[/url] 12th June, 2004
- ; * [url=home.php?mod=space&uid=187600]@author[/url] Ruchika Kharwar
- ; */
- .global __CSL_intcDispatcher
- .def __CSL_intcVectorTable
- .ref __CSL_intcCpuIntrTable
-
- RESV .macro num
- .loop num
- mvkh __CSL_intcCpuIntrTable,a4
- .endloop
- .endm
- _CSL_intcpush .macro reg
- stw reg,*b15--[1]
- .endm
- _CSL_intcpop .macro reg
- ldw *++b15[1],reg
- .endm
- CALLDISP .macro intr
- _CSL_intcpush a0
- mvkl __CSL_intcCpuIntrTable, a0
- mvkh __CSL_intcCpuIntrTable, a0
- ldw *++a0[intr + 1], a0
- nop 2
- stw a0, *-a0[intr + 1]
- _CSL_intcpop a0
- bnop a0, 5
- .endm
- .sect ".csl_vect"
- .align 1024
- __CSL_intcVectorTable:
- __CSL_intcRsv0:
- RESV 8
- __CSL_intcIsrNMI:
- CALLDISP 1
-
- __CSL_intcRsv2:
- RESV 8
-
- __CSL_intcRsv3:
- RESV 8
-
- __CSL_intcIsr4:
- CALLDISP 4
- __CSL_intcIsr5:
- CALLDISP 5
- __CSL_intcIsr6:
- CALLDISP 6
- __CSL_intcIsr7:
- CALLDISP 7
- __CSL_intcIsr8:
- CALLDISP 8
- __CSL_intcIsr9:
- CALLDISP 9
- __CSL_intcIsr10:
- CALLDISP 10
- __CSL_intcIsr11:
- CALLDISP 11
- __CSL_intcIsr12:
- CALLDISP 12
- __CSL_intcIsr13:
- CALLDISP 13
- __CSL_intcIsr14:
- CALLDISP 14
- __CSL_intcIsr15:
- CALLDISP 15
下面分段解析:
- .global __CSL_intcDispatcher
声明一个全局变量 __CSL_intcDispatcher,不过这个变量好像没什么用。
- .def __CSL_intcVectorTable
这个表示__CSL_intcVectorTable这个量在这个文件中定义,会在其他文件中使用,实际是在_csl_intcIntrEnDisRes.asm中被用于赋值给istp寄存器,也就是记录了IST的基地址。
- .ref __CSL_intcCpuIntrTable
这个表示__CSL_intcCpuIntrTable在其他文件中定义,在这里会被使用到。其实是在_csl_intc.h定义了_CSL_intcCpuIntrTable这样一个结构体变量,在汇编中前面多加一条下划线。
- RESV .macro num
- .loop num
- mvkh __CSL_intcCpuIntrTable,a4
- .endloop
- .endm
这个是一个汇编的宏定义,宏名叫RESV,宏有一个参数为num。宏的定义在.macro和.endm之间。
下面.loop和.endloop表示这两者之间的代码重复循环执行,循环执行的次数就是.loop之后的那个数字。
中间的一条就是实际的汇编指令了,就是把C语言头文件中定义的变量_CSL_intcCpuIntrTable的地址的高半字节写到a4寄存器中。这里说是地址,其实也是我的猜测,因为只有是地址的情况下,一切才解释的通,如果哪位发现有问题,请告知。
按照上面的解释,下面又是3个宏,分别是_CSL_intcpush,_CSL_intcpop,CALLDISP。
_CSL_intcpush里只有一条保存指令,作用是将这个宏的参数reg所代表的寄存器存储到b15寄存器中的值作为地址指向的内存中,把b15自身的值减4。b15应该是编译器约定使用的栈指针,这里应该是入站操作。
_CSL_intcpop里也只有一条装载指令,作用是先执行b15=b15+4,然后新的b15中的值作为地址指向的内存中的值赋给reg,这里应该是出栈操作。
CALLDISP这个宏就可有意思了,因为它和上面说到的delay slot,流水线等相关。这个宏里有8条指令,正好一个取值包,而C66x DSP的中断服务表IST中每一个表项的大小也是一个取指包的大小。CSLLDISP这个宏里的8条汇编指令是完全串行执行的,也就是说这个取指包在解码阶段会被分为8个执行包。这个宏有一个参数intr
第一条 _CSL_intcpush a0是把a0的值如栈,需要3个周期完成。
第二条和第三条的作用是把_CSL_intcCpuIntrTable这个在C语言头文件中定义的结构体变量的地址保存到a0寄存器中。这两条指令都是一个周期完成的。
第四条的作用是把a0寄存器的值加上((intr+1)×4),这个操作的结果是在下一条指令就可以获得的,指向的内存处的值又赋给a0寄存器,需要注意的是,这条指令不是一个周期就能执行完的,而是需要5个指令周期才能执行完,所以a0里面的值被更新为内存中的值时已经是5个指令周期后了,但是在这中间,下面的指令还是在执行的。
第五条是两个空操作,消耗掉两个周期。
第六条是把a0寄存器的值存储到a0寄存器中的值减去((intr+1)×4)指向的内存中去。注意,这时第四条指令还没执行完了,所以实际情况应该是给_CSL_intcCpuIntrTable.currentVectId赋值为中断函数名成员的地址
第七条,恢复寄存器a0的值,需要5个周期来完成。
第八条,跳转到a0寄存器所指向的地址处执行,同时为了解决branch delay slot的问题,还有5个无操作。这里就是跳转到了_CSL_intcCpuIntrTable这个结构体变量中的和intr这个参数相关的函数指针上了。
这里要注意a0寄存器的值的更新,并不是上面一条指令完全执行完才执行下一条指令的,所以a0寄存器的值的更新是一个值得细致分析的地方。
|