学习DSP的中断,使用定时器产生中断。
目录
什么是中断:
TMS320F2837xD的中断架构:
外设阶段:
PIE阶段:
CPU阶段:
配置和使用中断
处理中断:
禁用中断:
中断应用一:使用定时器触发中断:
中断应用二:两个定时器触发中断:
什么是中断:
中断是使CPU暂停当前执行并分支到称为中断服务程序(ISR)的不同代码的信号。这是处理外围事件的有用机制,并且涉及到比寄存器轮询更少的CPU开销或程序复杂性。但是,因为中断与程序流异步,所以必须注意避免在中断和主程序代码中访问的资源上的冲突。
中断通过一系列标志和使能寄存器传递到CPU,标志寄存器存储中断,直到它被处理。使能寄存器阻止中断的传递。当中断信号到达CPU时,CPU从称为向量表的列表中提取正确的ISR地址。放下当前的动作,跳转到ISR指向的地址工作。
TMS320F2837xD的中断架构:
C28x CPU有14个外设中断线。其中两个(INT13和INT14)分别直接连接到CPU定时器1和2。其余12个通过增强型外设中断扩展模块(ePIE或缩写版PIE)连接到外设中断信号,PIE将多达十六个外设中断复用到每个CPU中断线中。它还扩展向量表,以允许每个中断具有自己的ISR。这允许CPU支持大量的外设。
中断的路径分为三个阶段:外设阶段,PIE阶段,CPU阶段。由上面的中断结构可知,并不是所有的中断都存在这三个阶段,比如说INT13和INT14不通过外设和PIE直接到达了CPU,所以他只有CPU阶段。
外设阶段:
外设产生信号传输大到GPIO中,通过配置GPIO接收中断信号。例如当我按下按钮GPIO接收到一个上升沿,这个上升沿就是判定为中断信号。
PIE阶段:
PIE为每个外设中断信号提供单独的标志和使能寄存器位,这些标志和寄存器根据其相关的CPU中断进行分组。每个PIE组都有一个16位使能寄存器(PIEIERx) ,一个16位标志寄存器(PIEIFRx)和PIE应答寄存器(PIEACKx)。
当CPU接收到中断时,它从PIE中获取ISR的地址。PIE返回已标记和已启用的在向量组中最低编号通道的向量。当多个中断请求时,这为较低编号的中断提供了更高的优先级。如果没有中断被标志和使能,PIE返回通道1的向量。除非软件在中断传递时改变PIE的状态,否则不会发生这种情况。
简言之就是当外设中断产生后,相应的PIE标志寄存器就会置位,只有配置好相应的PIE使能位中断才会进一步被传递,当对应的PIEACK为0时中断就进入了CPU。
CPU阶段:
与PIE类似, CPU为其每个中断提供标志和使能寄存器位。有一个使能寄存器(IER)和一个标志寄存器(IFR),两者都是内部CPU寄存器。还有一个全局中断屏蔽,由ST1寄存器中的INTM位控制。可以使用.CPU的SETC指令设置和清除该掩码。在C代码中, controlSUITE的DINT和EINT宏可用于此目的。对IER和INTM的写操作是内核操作。特别是,如果INTM被清零,流水线中的下一条指令将在中断禁用的情况下运行。不需要软件延迟。
假如此时外设发生了中断。
- 中断对应组的标志位会被置一。
- 如果使能了这一组的中断,中断会被进一步的传递。
- 如果此时对应组的应答位为0,则中断进一步被传递。如果为1可能该组还有未被执行完的中断,需等待。需要注意:当完成中断时要在程序中对ACK进行清零,否则会导致后续中断无法进入。
- 该组的IFR被置一。
- 如果是能了该组的中断,中断进一步被传递。
- 如果INTM被置零,CPU接收中断。
- 在通道的 D2 或更后阶段的任何指令都要完成。早期阶段的指令被刷新。(不懂)
- CPU将其上下文保存在堆栈中。
- 清除IFR.x和 IER.xNTM设置。EALLOW 被清除。
- CPU从PIE中获取ISR向量。PIEIFRx.y被清除。
- CPU分支到ISR
配置和使用中断
第一次上电时。默认是不开启中断的。PIEIER和IER会被置零,INTM被置一。
要启用外设中断,请执行以下步骤:
1. 全局禁止中断(DINT或SETC INTM)。
2. 通过设置 PIECTRL 寄存器的 ENPIE 位来使能 PIE。
3. 将每个中断的 ISR 向量写入 PIE 向量表中的相应位置,可在 Table3-2 中找到。
4,为每个中断设置相应的PIEIERx位。PIE组和通道分配可以在Table 3-2中找到。
5,为包含已启用中断的任何PIE组设置CPU IER位。
6. 在外设中使能中断。
7,全局使能中断(EINT或CLRC INTM) 。
步骤 4 不适用于直接连接到 CPU 的 Timer1 和 Timer2 中断。
处理中断:
在中断服务程序中需要执行以下操作:
ISR与正常功能类似,但必须执行以下操作:
1. 保存和恢复某些 CPU 寄存器(如果使用)的状态。
2. 清除中断组的PIEACK位。
3. 使用IRET 指令返回。
如果函数使用interrupt关键字定义, TMS320C28xC编译器会自动处理需求1和3。用户代码必须手动清除中断组的PIEACK位,这通常在ISR结束时完成。如果PIEACK位未被清零, CPU将不会从该组接收任何进一步的中断。这不适用于不通过PIE的Timer1和Timer2中断。
禁用中断:
要禁用所有中断,请通过DINT或SETC INTM设置CPU的全局中断掩码。在设置INTM 或修改IER 后不需要添加NOP下一个指令将在中断禁用时执行。可以使用PIEIERx寄存器禁止各个中断,但必须注意避免竞争条件。如果在PIEIER写入完成时中断信号已经传播,则它可能到达 CPU 并触发伪中断条件。为避免这种情况,请使用以下过程:
1,全局禁止中断(DINT或SETC INTM)。
2. 清除中断的 PIEIER 位。
3. 等待5个周期,以确保任何传播中断已到达CPUIFR寄存器。
4. 清除中断PIE组的CPUIFR位。
5. 清除中断PIE组的PIEACK位。
6. 全局使能中断(EINT或CLRC INTM)。
可以使用 CPU IER 寄存器禁止中断组。这不会引起竞争条件,因此不需要特殊的过程。
PIEIFR位永远不能在软件中清零,因为读/修改/写操作可能会导致传入中断丢失。清除PIEIFR位的唯一安全方法是让CPU接受中断。以下过程可用于绕过正常ISR:
1. 全局禁止中断(DINT 或SETC INTM)。
2. 修改PIE向量表,将PIEIFR位的中断向量映射到空ISR。此ISR将仅包含中断指令(IRET)的返回。
3. 禁止外设寄存器中的中断。
4. 全局使能中断(EINT或CLRC INTM)。
5. 等待空ISR处理请求的中断。
6. 全局禁止中断。
7. 修改PIE向量表,将中断向量映射回其原始ISR.
8. 清除中断PIE组的PIEACK位。
9. 全局启用中断。
中断应用一:使用定时器触发中断:
void main(void)
{
//Step 1.初始化系统控制:锁相锁、看门狗、使能外围时钟
InitSysCtrl();
//Step 2. 初始化GPIO
InitGpio();
GPIO_Setup();
//Step 3. 清除所有中断并初始化PIE向量表
//禁用CPU中断
DINT;
//将PIE控制寄存器初始化为其默认状态。默认状态是禁用所有PIE中断并清除标志。
InitPieCtrl();
//禁用CPU中断并清除所有CPU中断标志:
IER = 0x0000;
IFR = 0x0000;
//初始化中断向量表
InitPieVectTable();
//关联ISR函数。
EALLOW; //这是写入EALLOW保护寄存器所必需的
PieVectTable.TIMER0_INT = &cpu_timer0_isr;
EDIS;
InitCpuTimers(); //本例中只初始化Cpu0 Timers
//ConfigCpuTimer括号里第一个是选择定时器,第 2个是定时器频率,单位是MHZ,第三个是设置定时器周期,单位是 us。
ConfigCpuTimer(&CpuTimer0, 200, 500000);
//为了确保精确的计时,请使用只写指令来写入整个寄存器。
//因此,如果ConfigCpuTimer和InitCpuTimers(在F2837xS cputimervars h中)中的任何配置位被更改,则下面的设置也必须更新。
CpuTimer0Regs.TCR.all = 0x4000; //启动CPU定时器,并且定时器减至0时请求中断。
//Step 5. 用户特定代码,启用中断:
//使能与CPU- timer 0相连的CPU Iint1
IER |= M_INT1;
//IER |= M_INT13;
//IER |= M_INT14;
//使能PIE中的TIMERO:组1中断7
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
//启用全局中断和更高优先级的实时调试事件:
EINT; // 启用全局中断INTM
ERTM; // 启用全局实时中断DBGM
//Step 6. 空闲循环:
while(1)
{
}
}
__interrupt void cpu_timer0_isr(void)
{
CpuTimer0.InterruptCount++;
GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO1 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO2 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO3 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO4 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO5 = 1;
// Acknowledge this interrupt to receive more interrupts from group 1
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
开发板中的现象就是LED闪烁,用示波器测量LED的电平,翻转时间正好是500ms。中断计数每500ms增加一次。
中断应用二:两个定时器触发中断:
我们再添加一个TIMER1中断。令CPU定时器1每间隔1s进入一次中断。
void main(void)
{
//Step 1.初始化系统控制:锁相锁、看门狗、使能外围时钟
InitSysCtrl();
//Step 2. 初始化GPIO
InitGpio();
GPIO_Setup();
//Step 3. 清除所有中断并初始化PIE向量表
//禁用CPU中断
DINT;
//将PIE控制寄存器初始化为其默认状态。默认状态是禁用所有PIE中断并清除标志。
InitPieCtrl();
//禁用CPU中断并清除所有CPU中断标志:
IER = 0x0000;
IFR = 0x0000;
//初始化中断向量表
InitPieVectTable();
//关联ISR函数。
EALLOW; //这是写入EALLOW保护寄存器所必需的
PieVectTable.TIMER0_INT = &cpu_timer0_isr;
PieVectTable.TIMER1_INT = &cpu_timer1_isr;
EDIS;
InitCpuTimers(); //本例中只初始化Cpu0 Timers
//ConfigCpuTimer括号里第一个是选择定时器,第 2个是定时器频率,单位是MHZ,第三个是设置定时器周期,单位是 us。
ConfigCpuTimer(&CpuTimer0, 200, 500000);
ConfigCpuTimer(&CpuTimer1, 200, 1000000);
//为了确保精确的计时,请使用只写指令来写入整个寄存器。
//因此,如果ConfigCpuTimer和InitCpuTimers(在F2837xS cputimervars h中)中的任何配置位被更改,则下面的设置也必须更新。
CpuTimer0Regs.TCR.all = 0x4000; //启动CPU定时器,并且定时器减至0时请求中断。
CpuTimer1Regs.TCR.all = 0x4000; //启动CPU定时器,并且定时器减至0时请求中断。
//Step 5. 用户特定代码,启用中断:
//使能与CPU- timer 0相连的CPU Iint1
IER |= M_INT1;
IER |= M_INT13;
//IER |= M_INT14;
//使能PIE中的TIMERO:组1中断7
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
//启用全局中断和更高优先级的实时调试事件:
EINT; // 启用全局中断INTM
ERTM; // 启用全局实时中断DBGM
//Step 6. 空闲循环:
while(1)
{
}
}
__interrupt void cpu_timer0_isr(void)
{
CpuTimer0.InterruptCount++;
GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO1 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO2 = 1;
// GpioDataRegs.GPATOGGLE.bit.GPIO3 = 1;
// GpioDataRegs.GPATOGGLE.bit.GPIO4 = 1;
// GpioDataRegs.GPATOGGLE.bit.GPIO5 = 1;
// Acknowledge this interrupt to receive more interrupts from group 1
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
__interrupt void cpu_timer1_isr(void)
{
CpuTimer1.InterruptCount++;
// GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1;
// GpioDataRegs.GPATOGGLE.bit.GPIO1 = 1;
// GpioDataRegs.GPATOGGLE.bit.GPIO2 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO3 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO4 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO5 = 1;
// Acknowledge this interrupt to receive more interrupts from group 1
//PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
INT13是直接连接到CPU上的,不需要在PIE中使能(想使能也找不到位置),也不需要在最后清PIEACK。如果timer0不在PIE中使能和在中断中清除PIEACK都不能正常的进行中断。
到这里就明白中断的基本原理了。