深入了解DSP中断及其应用场景

本章节的学习来自于TMS320F28377D参考手册和研旭例程。


学习DSP的中断,使用定时器产生中断。

目录

什么是中断:

TMS320F2837xD的中断架构:

外设阶段:

PIE阶段:

CPU阶段:

配置和使用中断

处理中断:

禁用中断:

中断应用一:使用定时器触发中断:

中断应用二:两个定时器触发中断:


什么是中断:

        中断是使CPU暂停当前执行并分支到称为中断服务程序(ISR)的不同代码的信号。这是处理外围事件的有用机制,并且涉及到比寄存器轮询更少的CPU开销或程序复杂性。但是,因为中断与程序流异步,所以必须注意避免在中断和主程序代码中访问的资源上的冲突。

       中断通过一系列标志和使能寄存器传递到CPU,标志寄存器存储中断,直到它被处理。使能寄存器阻止中断的传递。当中断信号到达CPU时,CPU从称为向量表的列表中提取正确的ISR地址。放下当前的动作,跳转到ISR指向的地址工作。

TMS320F2837xD的中断架构:

        下图为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被清零,流水线中的下一条指令将在中断禁用的情况下运行。不需要软件延迟。

假如此时外设发生了中断。

  1. 中断对应组的标志位会被置一。
  2. 如果使能了这一组的中断,中断会被进一步的传递。
  3. 如果此时对应组的应答位为0,则中断进一步被传递。如果为1可能该组还有未被执行完的中断,需等待。需要注意:当完成中断时要在程序中对ACK进行清零,否则会导致后续中断无法进入。
  4. 该组的IFR被置一。
  5. 如果是能了该组的中断,中断进一步被传递。
  6. 如果INTM被置零,CPU接收中断。
  7. 在通道的 D2 或更后阶段的任何指令都要完成。早期阶段的指令被刷新。(不懂)
  8. CPU将其上下文保存在堆栈中。
  9. 清除IFR.x和 IER.xNTM设置。EALLOW 被清除。
  10. CPU从PIE中获取ISR向量。PIEIFRx.y被清除。
  11. 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都不能正常的进行中断。

到这里就明白中断的基本原理了。

物联沃分享整理
物联沃-IOTWORD物联网 » 深入了解DSP中断及其应用场景

发表评论