kingman 发表于 2009-4-18 18:08:48

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]
查看完整版本: mplabC30 编写中断服务程序