|
7.1 简介
中断处理对于大多数单片机应用来说都是很重要的一个方面。中断用来使软件操作与
实时发生的事件同步。当发生中断时,软件的正常执行流程被打断,调用专门的函数
来处理事件。当中断处理结束时,恢复先前的现场信息并继续正常执行流程。
dsPIC30F 器件支持多个内部和外部中断源。另外,允许高优先级中断中断任何正在处
理的低优先级中断。
MPLAB C30 编译器完全支持在C 或行内汇编代码中进行中断处理。本章将对中断处
理做一个概括介绍。
7.2 主要内容
本章讨论的主题包括:
· 编写中断服务程序 — 可以将一个或多个C 函数指定为中断服务程序,在发生中断
时调用。为了获得最好的性能,通常将长的计算或需要调用库函数的操作放在主应
用程序中。当中断事件发生很快时,这种方式可以优化性能并最大程度降低信息丢
失的可能性。
· 写中断向量 — 当发生中断时,dsPIC30F 器件使用中断向量来转移应用控制。中断
向量是程序存储器中的专用地址,指定ISR 的地址。为使用中断,应用必须在这
些地址中包含有效的函数地址。
·中断服务程序现场保护 — 为了保证从中断返回到代码后,条件状态与中断前相
同,必须保护特定寄存器的现场信息。
· 中断响应时间 — 从中断事件发生到执行ISR 第一条指令之间的时间就是中断响应
时间
·中断嵌套 — MPLAB C30 支持中断嵌套。
· 使能/ 禁止中断 — 通过两种方式使能和禁止中断源:全局和单独。
可以根据本节提供的要领,仅使用C 语言语法结构编写所有应用代码,其中包括中断
服务程序(ISR)。
7.3.1 编写中断服务程序的要领
编写ISR 的要领为:
· 不带参数并以void 返回值类型声明ISR (强制)
· 不要通过一般程序调用ISR (强制)
·不要用ISR 调用其他函数(建议)
MPLAB C30 的ISR 和任何其他C 函数一样,可以有局部变量,可以访问全局变量。
但是, ISR 需要声明为没有参数,没有返回值。这是必须的,因为ISR 作为对硬件中
断或陷阱的响应,对它的调用与一般C 程序异步(即ISR 不是按通常的方式调用的,
因此不能有参数和返回值)。
ISR 只能通过硬件中断或陷阱调用,不能通过其他C 函数调用。ISR 使用中断返回
(RETFIE)指令退出函数,而不是使用一般的RETURN 指令。不恢复现场使用
RETFIE 指令退出中断服务程序会破坏处理器资源,如status 寄存器的值。
最后,由于中断响应时间的原因,建议不要使用ISR 调用其他函数。更多信息请参阅
第7.6 节“中断响应时间” 。
7.3.2 编写中断服务程序的语法
为将C 函数声明为中断服务程序,指定函数的interrupt 属性(参见§ 2.3 中关于
__attribute__ 关键字的描述)。interrupt 属性的语法如下:
__attribute__((interrupt [(
[ save(symbol-list)]
[, irq(irqid)]
[, altirq(altirqid)]
[, preprologue(asm)]
)]
))
可以在interrupt 属性名和参数名的前后加下划线字符。因此, interrupt 和
__interrupt__ 是等价的, save 和__save__ 也是等价的。
可选的save 参数指定进入和退出ISR 时需要保护和恢复的一个或多个变量。变量名
列表包含在括号内,变量名中间用逗号分隔开。
如果不想导出全局变量的值,应该保护可能在ISR 中修改的全局变量。被ISR 修改的
全局变量应该用volatile 限定。
可选的irq 参数允许将一个中断向量对应于一个特定的中断,可选的altirq 参数允
许将一个中断向量对应于一个指定的备用中断。每个参数都需要一个括号括起来的中
断ID 号。(参阅第7.4 节“写中断向量”中的中断ID 列表。)
可选的preprologue 参数允许在生成的代码中,编译器生成的函数prologue 前插入
汇编语句。
中断
7.3.3 为中断服务程序编写代码
下面的原型声明了函数isr0 为中断服务程序:
void __attribute__((__interrupt__)) isr0(void);
由原型可以看出,中断函数必须不带参数,没有返回值。如果需要的话,编译器将保
护所有工作寄存器,以及status 寄存器和重复计数寄存器。将其他变量指定为
interrupt 属性的参数,可以保护这些变量。例如,要使编译器自动保护和恢复变量
var1 和var2,使用下面的原型:
void __attribute__((__interrupt__(__save__(var1,var2)))) isr0(void);
为请求编译器使用快速现场保护(使用push.s 和pop.s 指令),指定函数的
shadow 属性(参阅第2.3.2 节“指定函数的属性”)。例如:
void __attribute__((__interrupt__, __shadow__)) isr0(void); 7.4 写中断向量
dsPIC 器件有两个中断向量表 — 主表和备用表 — 每个表都包含62 个异常向量。
62 个异常源有主异常向量和备用异常向量与之相关联,每个向量占据一个程序字,如
表7-1 所示。当INTCON2 寄存器中的ALTIVT 位置位时,使用备用向量名。
注: dsPIC 器件复位不通过中断向量表处理。而是在器件复位时,清零dsPIC
程序计数器。 这使处理器从地址0 处开始执行。按照约定,链接描述文件
在该地址处构建GOTO 指令来转移控制到C 运行时启动模块。 |
中断向量
IRQ# | 向量函数 | 主向量名 | 备用向量名
| n/a
| 保留 | _ReservedTrap0 | _AltReservedTrap0 | n/a
| 振荡器失效陷阱 | _OscillatorFail | _AltOscillatorFail | n/a
| 地址错误陷阱 | _AddressError | _AltAddressError | n/a | 堆栈错误陷阱 | _StackError | _AltStackError
| n/a | 数学错误陷阱 | _MathError | _AltMathError
| n/a | 保留 | _ReservedTrap5 | _AltReservedTrap5
| n/a
| 保留 | _ReservedTrap6 | _AltReservedTrap6 | n/a | 保留 | _ReservedTrap7 | _AltReservedTrap7
| 0 | INT0 — 外部中断0 | _INT0Interrupt | _AltINT0Interrupt
| 1 | Interrupt IC1 — 输入捕捉1 | _IC1Interrupt | _AltIC1Interrupt
| 2
| OC1 — 输出比较1 | _OC1Interrupt | _AltOC1Interrupt | 3
| TMR1 — 定时器1 | _T1Interrupt | _AltT1Interrupt | 4 | IC2 — 输入捕捉2 | _IC2Interrupt | _AltIC2Interrupt
| 5 | OC2 — 输出比较2 | _OC2Interrupt | _AltOC2Interrupt
| 6
| TMR2 — 定时器2 | _T2Interrupt | _AltT2Interrupt | 7 | TMR3 — 定时器3 | _T3Interrupt | _AltT3Interrupt
| 8
| SPI1 — 串行外设接口1 | _SPI1Interrupt | _AltSPI1Interrupt | 9 | UART1RX — UART1 接收器 | _U1RXInterrupt | _AltU1RX | 10 | UART1TX — UART1 发送器 | _U1TXInterrupt | _AltU1TXInterrupt |
11
| ADC — ADC 转换完成 | _ADCInterrupt | _AltADCInterrupt | 12 | NVM — NVM 写完成 | _NVMInterrupt | _AltNVMInterrupt
| 13
| 从I2C 中断 | _SI2CInterrupt | _AltSI2CInterrupt | 14 | 主 I2C 中断 | _MI2CInterrupt | _AltMI2CInterrupt
| 15
| CN — 输入变化中断 | _CNInterrupt | _AltCNInterrupt | 16
| INT1 — 外部中断1 | _INT1Interrupt | _AltINT1Interrupt | 17
| IC7 — 输入捕捉7 | _IC7Interrupt | _AltIC7Interrupt | 18 | IC8 — 输入捕捉8 | _IC8Interrupt | _AltIC8Interrupt
| 19
| OC3 — 输出比较3 | _OC3Interrupt | _AltOC3Interrupt | 20
| OC4 — 输出比较4 | _OC4Interrupt | _AltOC4Interrupt | 21
| TMR4 — 定时器4 | _T4Interrupt | _AltT4Interrupt | 22
| TMR5 — 定时器5 | _T5Interrupt | _AltT5Interrupt | 23 | INT2 — 外部中断2 | _INT2Interrupt | _AltINT2Interrupt | 24
| UART2RX — UART2 接收器 | _U2RXInterrupt | _AltU2RXInterrupt | 25
| UART2TX — UART2 发送器 | _U2TXInterrupt | _AltU2TXInterrupt | 26 | SPI2 — 串行外设接口2 | _SPI2Interrupt | _AltSPI2Interrupt
| 27
| CAN1 — 组合IRQ | _C1Interrupt | _AltC1Interrupt | 28 | IC3 — 输入捕捉3 | _IC3Interrupt
| _AltIC3Interrupt | 29
| IC4 — 输入捕捉4 | _IC4Interrupt | _AltIC4Interrupt | 30 | IC5 — 输入捕捉5 | _IC5Interrupt | _AltIC5Interrupt |
31 | IC6 — 输入捕捉6 | _IC6Interrupt | _AltIC6Interrupt
| 32 | OC5 — 输出比较5 | _OC5Interrupt | _AltOC5Interrupt
| 33 | OC6 — 输出比较6 | _OC6Interrupt
| _AltOC6Interrupt | 34 | OC7 — 输出比较7 | _OC7Interrupt | _AltOC7Interrupt
| 35
| OC8 — 输出比较8 | _OC8Interrupt | _AltOC8Interrupt | 36 | INT3 — 外部中断3 | _INT3Interrupt | _AltINT3Interrupt
| 37 | INT4 — 外部中断4 | _INT4Interrupt | _AltINT4Interrupt
| 38 | CAN2 — 组合IRQ | _C2Interrupt | _AltC2Interrupt
| 39
| PWM — PWM 周期匹配 | _PWMInterrupt | _AltPWMInterrupt | 40
| QEI — 位置计数器比较 | _QEIInterrupt | _AltQEIInterrupt | 41 | DCI — CODEC 传输完成 | _DCIInterrupt
| _AltDCIInterrupt | 42
| PLVD — 低电压检测 | _LVDInterrupt | _AltLVDInterrupt | 43 | FLTA — MPWM 故障A | _FLTAInterrupt
| _AltFLTAInterrupt | 44
| FLTB — MPWM 故障B | _FLTBInterrupt | _AltFLTBInterrupt | 45 | 保留 | _Interrupt45 | _AltInterrupt45
| 46 | 保留
| _Interrupt46 | _AltInterrupt46 | 47
| 保留 | _Interrupt47 | _AltInterrupt47 | 48 | 保留 | _Interrupt48 | _AltInterrupt48
| 49 | 保留
| _Interrupt49 | _AltInterrupt49 | 50
| 保留 | _Interrupt50 | _AltInterrupt50 | 51 | 保留
| _Interrupt51 | _AltInterrupt51 | 52
| 保留 | _Interrupt52 | _AltInterrupt52 | 53 | 保留 | _Interrupt53 | _AltInterrupt53 | 响应一个中断,必须将函数的地址填充到一个向量表的恰当地址,且函数必须保护
它使用的任何系统资源。函数必须使用RETFIE 处理器指令返回到前台任务。中断函
数可用C 编写。当将一个C 函数指定为中断处理函数时,编译器将保护编译器使用的
所有系统资源,并使用恰当的指令从函数返回。编译器可以可选地用中断函数的地址
填充中断向量表。为使编译器填充指向中断函数的中断向量,按照前表命名函数。例如,如果定义了下
列函数,堆栈错误向量将自动被填充:
void __attribute__((__interrupt__)) _StackError(void);
注意下划线是放在前面的。类似地,如果定义了下面的函数,备用堆栈错误向量将自
动被填充:
void __attribute__((__interrupt__)) _AltStackError(void);
同样要注意下划线是放在前面的。
对于没有指定处理函数的所有中断向量,将自动填充一个默认的中断处理函数。默认
的中断处理函数由链接器提供,仅复位器件。应用程序也可通过用名字
_DefaultInterrupt 声明一个中断函数来提供默认的中断处理函数。
每个表中的最后九个中断向量没有预定义的硬件函数。可通过使用前表中给出的名字,
或者更适合应用的名字,填充这些向量,同时,仍使用interrupt 属性的irq 或
altirq 参数填充适当的向量入口。 例如,为指定一个函数使用主中断向量52,使用下
面的语句:
void __attribute__((__interrupt__(__irq__(52)))) MyIRQ(void);
类似地,指定一个函数使用备用中断向量52,使用下面的语句:
void __attribute__((__interrupt__(__altirq__(52)))) MyAltIRQ(void);
irq/altirq号可以为中断请求编号 45至53中的一个。如果使用了interrupt属性的irq
参数,编译器将生成外部符号名__Interruptn,其中n 为向量号。因此, C 标识符
_Interrupt45 到_Interrupt53 被编译器保留。同样,如果使用了interrupt 属性的
altirq 参数,编译器将生成外部符号 名 __AltInterruptn,其中n 为向量编号。因
此, C 标识符_AltInterrupt45 到_AltInterrupt53 被编译器保留。
7.5 中断服务程序现场保护
中断,就其本质来说,在不可预测的时刻发生。因此,被中断的代码必须能以与中断
发生时相同的机器状态继续执行。
为正确处理中断返回,中断函数的设置(prologue)代码自动在堆栈中保护编译器管
理的工作寄存器和特殊功能寄存器,以便在ISR 末尾恢复这些寄存器内容。可使用
interrupt 属性的可选save 参数指定要保护和恢复的其他变量和特殊功能寄存器。
在某些应用中,可能需要在中断服务程序中,在编译器生成的函数prologue 前插入汇
编语句。例如,在中断服务程序的入口可能需要递增一个信号。可以这样来实现:
void
__attribute__((__interrupt__(__preprologue__("inc _semaphore"))))
isr0(void);
7.6 中断响应时间
有两个因素影响中断源发生到执行ISR 代码第一条指令之间的周期数。这两个因素
是:
? 处理器处理中断时间 — 处理器识别中断并跳转到中断向量第一个地址的时间。这
个值与具体器件和所使用中断源有关,为确定这个值的大小,请参考相应器件的数
据手册。
? ISR 代码 — MPLAB C30 在ISR 中保存它使用的寄存器,这包括工作寄存器和
RCOUNT 特殊功能寄存器。而且,如果ISR 调用一个普通的函数,编译器要保存
所有的工作寄存器和RCOUNT,即使在ISR 中没有显式使用这些寄存器。必须要
保存这些寄存器,因为一般来说,编译器不知道被调用函数使用了哪些资源。
7.7 中断嵌套
dsPIC30F 器件支持中断嵌套。由于在ISR 中将处理器资源保存在堆栈中,对嵌套ISR
的编码与非嵌套中断的编码相同。通过清零INTCON1 寄存器中的NSTDIS 位(嵌套
中断禁止位)来使能中断嵌套。注意这是默认设置,因为dsPIC30F 器件在复位时是
使能嵌套中断的。在中断优先级控制寄存器(IPCn)中,为每个中断源分配了一个优
先级。如果有一个处于等待状态的中断请求(IRQ),其优先级等于或高于处理器状态
寄存器中的(ST 寄存器中的CPUPRI 字段)当前处理器优先级,处理器将响应中
断。
7.8 使能/ 禁止中断
可单独禁止或使能每个中断源。每个IRQ 的都有一个中断使能位位于中断使能控制寄
存器(IECn)中。将中断使能位置1 将使能相应的中断;将中断使能位清零将禁止相
应的中断。dsPIC 器件复位时,所有中断使能位都被清零。另外,处理器还有一个禁
止中断指令(disable interrupt instruction,DISI),可在指定的指令周期数内禁止所有
中断。
注: 陷阱,如地址错误陷阱,不能禁止。只有IRQ 是可以被禁止的。 |
可通过行内汇编可在C 程序中使用DISI 指令。例如下面的行内汇编语句:
__asm__ volatile ("disi #16");
将在源程序中这条语句的所在处发出指定的DISI 指令。采用这种方式使用DISI 的一
个缺点是, C 编程人员不能总是确定C 编译器如何将C 源代码翻译为机器指令,因此
可能难以确定DISI 指令的周期数。通过将要保护的代码放在DISI 指令对中断的操作
之间,可以解决这个问题。DISI 指令的第一条指令将周期数设置为最大值,第二条指
令将周期数设置为零。例如,
__asm__ volatile("disi #0x3FFF"); /* disable interrupts */
/* ... protected C code ... */
__asm__ volatile("disi #0x0000"); /* enable interrupts */
另一种可选方案是直接写DISCNT 寄存器,这在硬件上和DISI 指令的作用相同,但对
于C 程序具有避免使用行内汇编的优点。这是需要的,因为在函数中使用行内汇编时
编译器可能不会执行某些优化。所以可以不使用上面的指令序列,而使用
DISICNT = 0x3FFF; /* disable interrupts */
/* ... protected C code ... */
DISICNT = 0x0000; /* enable interrupts */ |
|