中断架构总览
说明:
- 总共有16路中断送入CPU。其中INT1~INT12的来源是ePIE,INT13和INT14是TIMER1和TIMER2.另外还有NMI和RTOSINT
- TIMER0中断与TIMER1和TIMER2不同,它不是直接送给CPU,而是先送到ePIE。
- 外部输入中断:从GPIO口经过Input X-BAR的选择后送给XINT1~XINT5,再送到ePIE
- 其他外设的中断都是先经过ePIE。
中断传播路径
说明:
- 外设中断先到达外设中断标志寄存器中锁存:PIEIFRx.y。其中,x最大有12路,y最大有16路。外设中断最多可以有12 * 16 = 192个。
- 每一个都有对应的中断使能寄存器:PIEIERx.y。最多也有192个。
- 每一组中的16个外设中断源通过“或”操作,送到PIEACK。每一组只占PIEACK的一个位。
- 当PIEACK=0时,中断可以传播全局中断标志寄存器IFR。当PIEACK=1时,传播路径断开。
- 中断应答寄存器PIEACK具备“自锁”功能。也就是说,当有中断过来的时候,PIEACK会自动断开,需要在处理完中断响应函数后由软件清除PIEACK位(写1清零)。这样可以确保同一组中的多个中断同时发生时能依次处理,而不会遗漏。
多路复用中断
INTM
最靠近CPU的是全局使能开关:INTM。是全局中断掩码(Interrupt global mask)。它是CPU核心寄存器ST1中的一个位。
这个位是个“掩码位”,不是个“使能位”。因此,当INTM=1时,屏蔽所有中断;为0时才开启全局中断。复位后的默认值是1,即关闭全局中断。
开启全局中断的方法是,将ST1.INTM位清零。使用宏定义语句:EINT
#define EINT __asm(" clrc INTM")
关闭中断是:DINT
#define DINT __asm(" setc INTM")
中断使能和中断标志
分别是IER和IFR。这两个寄存器也是属于CPU内核寄存器。
IER
中断使能寄存器。共16位。
其中,INT1~INT12用于PIE。INT13是定时器1(TIMER1)中断;INT14是定时器2(TIMER2)中断。bit14是DLOGINT;bit15是RTOSINT。
IFR
中断标志寄存器。每个位的所对应的中断与IER相同。
PIE
PIE是外设中断扩展模块(Peripheral Interrupt Expansion)
总共有12组外设中断。
PIE中断通道映射
不同的CPU型号,中断通道映射也不同。具体要查芯片手册。比如280025的PIE通道映射如下:
中断向量表
当发生中断时,CPU从中断向量表(Interrupt Vectors)中取出中断服务函数(ISR)的地址。
中断向量表保存在0x0D00 ~ 0x0EFF地址空间。
CPU中断向量表
PIE中断向量表
中断优先级
数字越小,优先级越高。
- 首先看CPU级别的中断,INT1比INT14的优先级要高。
- 再看ePIE级别的中断。比如,在INT1组内,INT1.1比INT1.16的优先级要高。
中断向量表的初始化
可以调用库函数:Interrupt_initVectorTable()。该函数先将默认的中断处理函数Interrupt_defaultHandler()的地址保存到中断向量表中。然后再更改INT_NMI中断和INT_ILLEGAL中断的响应函数。
//*****************************************************************************
//
// Interrupt_initVectorTable
//
//*****************************************************************************
void
Interrupt_initVectorTable(void)
{
uint16_t i;
EALLOW;
//
// We skip the first three locations because they are initialized by Boot
// ROM with boot variables.
//
for(i = 3U; i < 224U; i++)
{
HWREG(PIEVECTTABLE_BASE + (2U * i)) =
(uint32_t)Interrupt_defaultHandler;
}
//
// NMI and ITRAP get their own handlers.
//
HWREG((uint32_t)PIEVECTTABLE_BASE + ((INT_NMI >> 16U) * 2U)) =
(uint32_t)Interrupt_nmiHandler;
HWREG((uint32_t)PIEVECTTABLE_BASE + ((INT_ILLEGAL >> 16U) * 2U)) =
(uint32_t)Interrupt_illegalOperationHandler;
EDIS;
}
更新中断服务函数
即:注册一个中断处理函数。
//*****************************************************************************
//
//! Registers a function to be called when an interrupt occurs.
//!
//! \param interruptNumber specifies the interrupt in question.
//! \param handler is a pointer to the function to be called.
//!
//! This function is used to specify the handler function to be called when the
//! given interrupt is asserted to the processor. When the interrupt occurs,
//! if it is enabled (via Interrupt_enable()), the handler function will be
//! called in interrupt context. Since the handler function can preempt other
//! code, care must be taken to protect memory or peripherals that are accessed
//! by the handler and other non-handler code.
//!
//! The available \e interruptNumber values are supplied in
//! <tt>inc/hw_ints.h</tt>.
//!
//! \note This function assumes that the PIE has been enabled. See
//! Interrupt_initModule().
//!
//! \return None.
//
//*****************************************************************************
static inline void
Interrupt_register(uint32_t interruptNumber, void (*handler)(void))
{
uint32_t address;
//
// Calculate appropriate address for the interrupt number
//
address = (uint32_t)PIEVECTTABLE_BASE +
(((interruptNumber & 0xFFFF0000U) >> 16U) * 2U);
//
// Copy ISR address into PIE table
//
EALLOW;
HWREG(address) = (uint32_t)handler;
EDIS;
}
PIE控制寄存器
包含2个字段:PIE使能位(ENPIE)和PIE向量。
PIE使能
不使能时,从BOOTROM中读取中断向量;
使能后,从PieVectTable中取中断向量。
PIE向量
这个字段是只读的。当发生中断时,这里会保存取中断向量的位置。
由于每个中断向量的地址都是32位的(2个字),因此,中断向量地址的最低位全都是0. 在PIE控制寄存器中就把这一位省掉了(用作PIE使能位)。实际理解的时候,应把这一位补上。或者这样理解:中断向量地址PIEVECT=PIE控制寄存器PIECTRL & 0xFFFE。
实例
当发生ADCD1中断时,PIECTRL = 0x0D4B。
查看手册:
ADCD1中断为INT1.6
中断INT1.6的向量地址为:0x0D4A
中断向量地址0x0D4A + ENPIE = 0x0D4B。与实际情况刚好相符。
小结
中断处理流程
C280x多路复用的中断请求流程
步骤 | 操作 |
---|---|
步骤 1: | PIE 组内的任何外设或外部中断生成中断。 如果在外设模块内启用了这些中断,则向 PIE 模块发送中断请求。 |
步骤 2: | PIE 模块识别 PIE 组 x 内的中断 y (INTx.y) 已发出中断且已锁定适当的 PIE 中断标志位:PIEIFRx.y = 1。 |
步骤 3: | 要将中断请求从 PIE 发送到 CPU,以下两个条件必须同时为真: 3a:必须设置了正确的启用位 (PIEIERx.y = 1) 且 3b:该组 的 PIEACKx 位必须已清除。 |
步骤 4: | 如果 3a 和 3b 中的两个条件同时为真,则中断请求被发送到 CPU 且再次设置确认位 (PIEACKx = 1)。 PIEACKx 位将保持设置,直到您清除它以指示可以将来自该组的其它中断从 PIE 发送到 CPU。 |
步骤 5: | 设置 CPU 中断标志位 (CPU IFRx = 1) 以指示 CPU 级别的暂挂中断 x。 |
步骤 6 和步骤 7: | 如果已启用该 CPU 中断(CPU IER 位 x = 1 或 DBGIER 位 x = 1)且全局中断屏蔽已清除 (INTM = 0),则CPU 将为 INTx 提供服务 |
步骤 8: | CPU 识别中断,然后执行自动背景保存、清除 IER 位、设置 INTM 并清除 EALLOW。 TMS320C28x DSP CPU 和指令集参考指南(文献编号 SPRU430)中描述了 CPU 准备服务中断时所执行的所有步骤。 |
步骤 9: | CPU 然后将向 PIE 请求合适的变量。 对于多路复用中断,PIE 模块使用 PIEIERx 和 PIEIFRx 寄存器中的当前值来解码应使用哪个矢量地址。 |
可屏蔽中断的标准操作流程
流程图如下:
具体流程:
- 中断请求发送到CPU
- IFR寄存器相关的标志位置位
- 相应的中断是否使能(IER使能位是否为1)?全局中断是否使能(INTM为0)?
- 中断允许后,送到CPU处理。此时,立即清除IFR标志位。
- 清空流水线。
- 程序计数器PC指针增加(指向中断返回后的第一条指令),然后临时保存到CPU内部。
- 取中断向量。PC 中填充了相应中断向量的地址,然后从该位置获取该向量。
- 栈指针(SP)加一。准备保存中断上下文。堆栈指针 (SP) 增加 1 以准备自动上下文保存(步骤 9)。
- 执行自动上下文保存。
- 清除相应的IER位。在步骤 9 中将 IER 寄存器保存到堆栈中后,CPU 会清除与正在处理的中断对应的 IER 位。 这可以防止重新进入同一中断。 如果您想嵌套发生的中断,请让 ISR 再次设置该 IER 位。
-
设置 INTM 和 DBGM。 清除 LOOP、EALLOW 和 IDLESTAT。 所有这些位都在状态寄存器 ST1 中。 通过将 INTM 设置为 1,CPU 可以防止可屏蔽中断干扰 ISR。 如果您希望嵌套中断,请让 ISR 清除 INTM 位。 通过将 DBGM 设置为 1,CPU 可以防止调试事件干扰 ISR 中的时间关键代码。 如果您不想阻止调试事件,请让 ISR 清除 DBGM。 CPU 清除 LOOP、EALLOW 和 IDLESTAT,以便 ISR 在新的上下文中运行。
-
用获取的向量加载 PC。 PC 加载了在步骤 7 中获取的中断向量。该向量将程序控制强制给 ISR。
-
执行中断服务程序。 这是 CPU 执行您准备处理中断的程序代码的地方。
虽然在步骤 10 中自动保存了一些寄存器值,但如果 ISR 使用其他寄存器,您可能需要在 ISR 的开头保存这些寄存器的内容。 然后必须在从 ISR 返回之前恢复这些值。
如果希望 ISR 通知外设正在处理中断,可以使用 IACK 指令发送中断确认信号。 IACK 指令接受一个 16 位常量作为操作数。 有关 IACK 指令的详细说明,请参见C28x 汇编语言指令。 -
程序继续。 如果中断没有得到 CPU 的批准,则忽略该中断,程序不中断地继续运行。 如果中断被批准,则执行其中断服务程序,程序从中断处继续(在返回地址处)
中断上下文保存的寄存器
许多寄存器值会自动保存到堆栈中。
这些寄存器成对保存; 每对都保存在一个 32 位操作中。 在每个 32 位保存操作结束时,SP 会增加 2。下表显示了寄存器对及其保存顺序。 CPU 期望所有 32 位保存都由内存包装器进行偶数字对齐。 如表中所示,SP 不受此对齐方式的影响。
参考:C28x 中断上下文的保存和恢复
中断嵌套
上文已提到,在保存中断上下文时,会自动禁止全局中断,并清除当前中断的IER位。因此,默认情况下,CPU是不支持中断嵌套的。
如果想要支持中断嵌套,必须在中断服务函数中重新使能IER标志位,并打开全局中断。
具体内容请见参考文档.
参考文档
TMS320x280x DSP-系统控制和中断参考指南.pdf(中文版)
TMS320C28x CPU and Instruction Set Reference Guide (Rev. F)-spru430f.pdf
C28x Interrupt NestingC28x Interrupt Nesting
https://software-dl.ti.com/C2000/docs/c28x_interrupt_nesting/html/index.html