mplabC30 编写中断服务程序
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 _AltIC5Interrupt31IC6 — 输入捕捉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 */
页:
[1]