单片机C语言中断1
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
单片机C语言中断1
字号:大中小
interrupt中断的关键字,n是中断号提供中断程序的入口地址。
0-INT0 1-T0 2-INT1 3-T1 4-串行中断5-T2
直接访问寄存器和端口
定义
sfr P0 0x80
sfr P1 0x81
sfr ADCON; 0xDE
sbit EA 0x9F
操作
ADCON = 0x08 ; /* Write data to register */
P1 = 0xFF ; /* Write data to Port */
io_status = P0 ; /* Read data from Port */
EA = 1 ; /* Set a bit (enable all interrupts) */
在使用了interrupt 1 关键字之后,会自动生成中断向量
在ISR中不能与其他"后台循环代码"(the background loop code) 共享局部变量
因为连接器会复用在RAM中这些变量的位置,所以它们会有不同的意义,这取决于当前使用的不同的函数
复用变量对RAM有限的51来将很重要。
所以,这些函数希望按照一定的顺序执行而不被中断。
timer0_int() interrupt 1 using 2
{
unsigned char temp1 ;
unsigned char temp2 ;
executable C statements ;
}
"interrupt"声明表示向量生成在(8*n+3),这里,n就是interrupt参数后的那个数字
这里,在08H的代码区域生成LJMP timer0_int 这样一条指令
'using' 告诉编译器在进入中断处理器去切换寄存器的bank。
这个"contet"切换是
为中断处理程序的局部变量提供一个新鲜的寄存器bank 最快的方式。
对时序要求严格的程序,是首选的stack寄存器(保存寄存器到stack)方式。
注意:同样优先级别的中断可以共享寄存器bank,因为他们每次将中断没有危险
如果在timer1 的中断函数原型中使用USING 1, 寄存器的pushing将被MOV to PSW 切换寄存器bank 所替换。
不幸的是,当一个中断入口被加速时。
用在入口的直接寄存器寻址将失败。
这是因为C51没有告诉寄存器bank已经改变。
如果不工作的寄存器将被使用,如果没有其他函数被调用,优化器.....
Logically, with an interrupt routine, parameters cannot be passed to it or returned. When the interrupt occurs, compiler-inserted code is run which pushes the accumulator, B,DPTR and the PSW (program status word) onto the stack. Finally, on exiting the interrupt routine, the items previously stored on the stack are restored and the closing "}" causes a RETI to be used rather than a normal RET.
逻辑上,一个中断服务程序,不能传递参数进去,也不可返回值。
当中断发生时,编译器插入的代码被运行,它将累加器,B,DPTR和PSW(程序状态字)入栈。
最后,在退出中断程序时,预先存储在栈中被恢复。
最后的"}"结束符号
将插入RETI到中断程序的最后,
为了用Keil‘C’语言创建一个中断服务程序(ISR),利用interrupt 关键词和正确的中断号声明一个static void 函数。
Keil‘C’编译器自动生成中断向量,以及中断程序的进口、出口代码。
Interrupt 函数属性标志着该函数为ISR。
可用using 属性指定ISR使用哪一个寄存器区,这是可选的。
有效的寄存器区范围为1到3。
中断源的矢量位置
中断源Keil中断编号矢量地址
最高优先级 6 0x0033
外部中断0 0 0x0003
定时器0溢出 1 0x000B
外部中断1 2 0x0013
定时器1溢出 3 0x001B
串口 4 0x0023
定时器2溢出 5 0x002B
DMA 7 0x003B
硬件断点8 0x0043
JTAG 9 0x004B
软件断点10 0x0053
监视定时器12 0x0063
1.
函数在调用前定义与在调用后定义产生的代码是有很大差别的(特别是在优化级别大于3级时)。
(本人也不太清楚为什么,大概因为在调用前定义则调用函数已经知道被调用函数对寄存器的使用情况,则可对函数本身进行优化;而在调用后进行定义则函数不知被调用函数对寄存器的使用情况,它默认被调用函数对寄存器(ACC、B、DPH、DPL、PSW、R0、R1、R2、R3、R 4、R5、, R6、R7)都已经改变,因此不在这些寄存器中存入有效的数据)
2.
函数调用函数时除在堆栈中存入返回地址之外,不在堆栈中保存其它任何寄存器(ACC、B、DPH、DPL、PSW、R0、R1、R2、R3、R 4、R5、, R6、R7)的内容。
(除非被调用函数使用了using特性)
3.
中断函数是一个例外,它会计算自身及它所调用的函数对寄存器(ACC、B、DPH、DPL、PSW、R0、R1、R2、R3、R 4、R5、, R6、R7)的改变,并保存相应它认为被改变了的寄存器。
4.不知算不算是一个小bug)
默认keil c51中的函数使用的是0寄存器组,当中断函数使用using n时,n = 1,2,3或许是对的,但n=0时,程序就已经存在了bug(只有中断函数及其所调用的函数并没有改变R0 ---- R7的值时,这个bug不会表现出来))
一个结论是,在中断函数中如果使用了using n,则中断不再保存R0----R7的值。
当n相同时,这个存在的bug 是多么的隐蔽。
(这恰是使人想象不到的)
使用不同寄存器组的函数(特殊情况外)不能相互调用
using"关键字告诉编译器切换register bank
如果中断程序不重要,using关键字能忽略。
如果一个函数被从中断程序调用,而此中断强制使用using 当编译一个被调用的函数时,编译器必须告诉它
1)
在函数前必须用伪指令
#pragma NOAREGS
在进入函数
#pragma RESTORE
或者
#pragmas AREGS
这样就不会使用"绝对地址定位"
2)
#pragma REGISTERBANK(n)
用这个指定告诉当前使用的bank
用NOAREGS指令移除MOV R7,AR7
中断服务例程
/* Timer 0 Overflow Interrupt Service Routine */
timer0_int() interrupt 1 USING 1 {
unsigned char temp1 ;
unsigned char temp2 ;
/* executable C statements */
}
被调用的函数
#pragma SAVE // Rember current registerbank
void func(char x) { // Called from interrupt routine
// with "using1"
/* Code */
}
#pragma RESTORE // Put back to original registerbank
如果中断服务例程使用了USING,被中断服务例程调用的函数一定要
REGISTERBANK(n)
一个被ISR调用的函数也可能被后台程序调用
为了函数"reentrant"(可重入)
8051 系列MCU 的基本结构包括:32 个I/O 口(4 组8 bit 端口);两个16 位定时计数器;全双工串行通信;6 个中断源(2 个外部中断、2 个定时/计数器中断、1 个串口输入/输出中断),两级中断优先级;128 字节内置RAM;独立的64K 字节可寻址数据和代码区。
中断发生后,MCU 转到 5 个中断入口处之一,然后执行相应的中断服务
处理程序。
中断程序的入口地址被编译器放在中断向量中,中断向量位于程序代码段的最低地址处,注意这里的串口输入/输出中断共用一个中断向量。
8051的中断向量表如下:
中断源中断向量上电复位0000H
外部中断0 0003H
定时器0 溢出000BH
外部中断1 0013H
定时器1 溢出001BH
串行口中断0023H
定时器2 溢出002BH
interrupt 和using 都是C51 的关键字。
C51 中断过程通过使用interrupt 关键字和中断号(0 到31)来实现。
中断号指明编译器中断程序的入口地址中断序号对应着8051中断使能寄存器IE 中的使能位,对应关系如下:
IE寄存器C51中的8051的
的使能位中断号中断源
IE.0 0 外部中断0
IE.1 1 定时器0 溢出
IE.2 2 外部中断1
IE.3 3 定时器1 溢出
IE.4 4 串口中断
IE.5 5 定时器2 溢出
有了这一声明,编译器不需理会寄存器组参数的使用和对累加器A、状态寄存器、寄存器B、数据指针和默认的寄存器的保护。
只要在中断程序中用到,编译器会把它们压栈,在中断程序结束时将他们出栈。
C51 支持所有 5 个8051 标准中断从0 到4 和在8051 系列(增强型)中多达27 个中断源。
using 关键字用来指定中断服务程序使用的寄存器组。
用法是:using 后跟一个0 到3 的数,对应着4 组工作寄存器。
一旦指定工作寄存器组,默认的工作寄存器组就不会被压栈,这将节省32 个处理周期,因为入栈和出栈都需要 2 个处理周期。
这一做法的缺点是所有调用中断的过程都必须使用指定的同一个寄存器组,否则参数传递会发生错误。
因此对于using,在使用中需灵活取舍。
8051 系列MCU 的基本结构包括:32 个I/O 口(4 组8 bit 端口);两个16 位定时计数器;全双工串行通信;6 个中断源(2 个外部中断、2 个定时/计数器中断、1 个串口输入/输出中断),两级中断优先级;128 字节内置RAM;独立的64K 字节可寻址数据和代码区。
中断发生后,MCU 转到 5 个中断入口处之一,然后执行相应的中断服务
处理程序。
中断程序的入口地址被编译器放在中断向量中,中断向量位于程序代码段的最低地址处,注意这里的串口输入/输出中断共用一个中断向量。
8051的中断向量表如下:
中断源中断向量
上电复位0000H
外部中断0 0003H
定时器0 溢出000BH
外部中断1 0013H
定时器1 溢出001BH
串行口中断0023H
定时器2 溢出002BH
interrupt 和using 都是C51 的关键字。
C51 中断过程通过使用interrupt 关键字和中断号(0 到31)来实现。
中断号指明编译器中断程序的入口地址中断序号对应着8051中断使能寄存器IE 中的使能位,对应关系如下:
IE寄存器C51中的8051的
的使能位中断号中断源
IE.0 0 外部中断0
IE.1 1 定时器0 溢出
IE.2 2 外部中断1
IE.3 3 定时器1 溢出
IE.4 4 串口中断
IE.5 5 定时器2 溢出
有了这一声明,编译器不需理会寄存器组参数的使用和对累加器A、状态寄存器、寄存器B、数据指针和默认的寄存器的保护。
只要在中断程序中用到,编译器会把它们压栈,在中断程序结束时将他们出栈。
C51 支持所有5 个8051 标准中断从0 到4 和在8051 系列(增强型)中多达27 个中断源。
using 关键字用来指定中断服务程序使用的寄存器组。
用法是:using 后跟一个0 到3 的数,对应着 4 组工作寄存器。
一旦指定工作寄存器组,默认的工作寄存器组就不会被压栈,这将节省32 个处理周期,因为入栈和出栈都需要 2 个处理周期。
这一做法的缺点是所有调用中断的过程都必须使用指定的同一个寄存器组,否则参数传递会发生错误。
因此对于using,在使用中需灵活取舍。
关于using:
您在文中说明“这一做法的缺点是所有调用中断的过程都必须使用指定的同一个寄存器组”是不是这个意思?
举个例子来说:
定义一个函数
void func(unsigned char i) {
...
if(++i==0x12) {
...
}
...
}
有如下一个中断函数
void int_0(void) interrupt 0 using 1 {
....
}
在默认状态下,func使用寄存器组0(BANK0),那么当int_0调用func时是否存在当传递参数时会造成参数传递错误?
谢谢!
如果在中断服务函数ISR 中使用寄存器,那么必须处理好using 的使用问题:
1、中断服务函数使用using 指定与主函数不同的寄存器组(主函数一般使用Register bank 0)。
2、中断优先级相同的ISR 可用using 指定相同的寄存器组,但优先级不同的ISR 必须使用不同的寄存器组,在ISR 中被调用的函数也要使用using 指定与中断函数相同的寄存器组。
3、如果不用using 指定,在ISR 的入口,C51 默认选择寄存器组0,这相当于中断服务程序的入口首先执行指令:
MOV PSW #0
这点保证了,没使用using 指定的高优先级中断。
可以中断使用不同的寄存器组的低优先级中断。
4、使用using 关键字给中断指定寄存器组,这样直接切换寄存器组而不必进行大量的PUSH 和POP 操作,可以节省RAM空间,加速MCU 执行时间。
寄存器组的切换,总的来说比较容易出错,要对内存的使用情况有比较清晰的认识,其正确性要由你自己来保证。
特别在程序中有直接地址访问的时候,一定要小心谨慎!至于“什么时候要用到寄存器组切换”,一种情况是:当你试图让两个(或以上)作业同时运行,而且它们的现场需要一些隔离的时候,就会用上了。
在ISR 或使用实时操作系统RTOS 中,寄存器非常有用。
寄存器组使用的原则:
1、8051 的最低32 个字节分成4 组8 寄存器。
分别为寄存器R0 到R7。
寄存器组由PSW 的低两位选择。
在ISR 中,MCU 可以切换到一个不同的寄存器组。
对寄存器组的访问不可位寻址,C51 编译器规定使用using 或禁止中断的函数(#pragma disable)均不能返回bit 类型的值。
3、在ISR 中调用其它函数,必须和中断使用相同的寄存器组。
当没用NOAREGS 命令做明确的声明,编译器将使用绝对寄存器寻址方式访问函数选定(即用using 或REGISTERBANK 指定)的寄存器组,当函数假定的和实际所选的寄存器组不同时,将产生不可预知的结果,从而可能出现参数传递错误,返回值可能会在错误的寄存器组中。
举一例子:当需要在中断内和中断外调用同一个函数,假定按照程序的流程控制,不会出现函数的递归调用现象,这样的调用会不会出现问题?若确定不会发生重入情况,则有以下两种情况:
1、如果ISR 和主程序使用同一寄存器组(主程序缺省使用BANK 0,若ISR 没有使用using 为其指定寄存器区,则缺省也使用BANK 0),则不需其他设置。
2、如果ISR 和主程序使用不同的寄存器组(主程序缺省使用BANK 0,ISR 使用using 指定了其他BANK),则被调用函数必须放在:
#pragma NOAREGS
#pragma AREGS
控制参数对中,指定编译器不要对该函数使用绝对寄存器寻址方式;或者也可在Options->C51,选中“Don't use absolute register accesses”,使所有代码均不使用绝对寄存器寻址方式(这样,执行效率将稍有降低)。
不论以上的哪一种情况,编译器均会给出重入警告,需手工更改OVERLAY 参数,做重入说明。
3、还有一种办法:如果被调用函数的代码不是很长,还是将该函数复制一份,用不同的函数名代替,这种情况适合ROM有足够多余的空间。
因此,对using关键字的使用,如果没把握,宁可不用,交给编译系统自己去处理好了。
interrupt xx using y
跟在interrupt 后面的xx 值得是中断号,就是说这个函数对应第几个中断端口,一般在51中
0 外部中断0
1 定时器0
2 外部中断1
3 定时器1
4 串行中断
其它的根举相应得单片机有自己的含义,实际上c载编译的时候就是把你这个函数的入口地址方到这个对应中断的跳转地址
using y 这个y是说这个中断函数使用的那个寄存器组就是51里面一般有4个r0 -- r7寄存器,如果你的终端函数和别的程序用的不是同一个寄存器组则进入中断的时候就不会将寄存器组压入堆栈返回时也不会弹出来节省代码和时间。