为什么单片机中断优先级的中断系统不能直接用函数的调用直接实现,为什么非得加上一个interrupt?

君,已阅读到文档的结尾了呢~~
中断函数的使用中断函数,函数的,中断函数的
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
中断函数的使用
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口11578人阅读
C/C++(10)
&C语言在8051单片机上的扩展(interrupt、using关键字的用法)
直接访问寄存器和端口
sfr P0 0x80
sfr&P1 0x81
sfr&ADCON; 0xDE
sbit EA&&0x9F
ADCON = 0x08;&&
P1 = 0xFF;&&&
io_status = P0 ;
EA = 1;&&&
&&&&&&& 在使用了interrupt 1 关键字之后,会自动生成中断向量
&&&&&&& 在 ISR中不能 与其他 &后台循环代码&(the background loop code) 共享局部变量,因为连接器会复用 在RAM中这些变量的位置 ,所以它们会有不同的意义,这取决于当前使用的不同的函数复用变量对RAM有限的51来讲很重要。所以,这些函数希望按照一定的顺序执行 而不被中断。
void timer0_int(void) interrupt 1 using 2
&unsigned char temp1;
&unsigned char temp2;
&executable C
&&&&&&& &interrupt&声明表向量生成在(8*n+3),这里,n就是interrupt参数后的那个数字这里,在08H的代码区域 生成LJMP timer0_int这样一条指令。
&&&&&&& &using& tells the compiler to switch register banks on entry to an interrupt routine. This &context& switch is the fastest way of
providing a fresh registerbank for an interrupt routine's local data and is to be preferred to stacking registers for very time-criticalroutines. Note that interrupts of the same priority can
share a register bank, since there is no risk that they will interrupt each other.
&&&&&&& &using& 告诉编译器 在进入中断处理器 去切换寄存器的bank。这个&contet&切换是为中断处理程序的局部变量提供一个新鲜的寄存器bank最快的方式。对时序要求严格的程序,是首选的stack寄存器(保存寄存器到stack)方式。
&&&&&&& 注意:同样优先级别的中断可以共享寄存器bank,因为他们每次将中断没有危险。
&&&&&&& If a USING 1 is added to the timer1 interrupt function prototype, the pushing of registers is replaced by a simple MOV to PSW to switch registerbanks. Unfortunately, while the interrupt
entry is speeded up, the direct register addressing used on entry to sys_interp fails. This is because C51 has not yet been told that the registerbank has been changed. If no working registers are used and no other function is called, the optimizer eliminiates
teh code to switch register banks.
&&&&&&&如果在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.使用C写程序时,尽量少使用using n (n=0,1,2,3)特性。(这个特性在本人使用的过程中存在一些问题,不知算不算是一个小bug)
&&&&&&& 默认keil c51中的函数使用的是0寄存器组,当中断函数使用using n时,n = 1,2,3或许是对的,但n=0时,程序就已经存在了bug(只有中断函数及其所调用的函数并没有改变R0 ---- R7的值时,这个bug不会表现出来)
&&&&&&& 一个结论是,在中断函数中如果使用了using n,则中断不再保存R0----R7的值。由此可以推论出,一个高优先级的中断函数及一个低优先级的中断函数同时使用了using n,(n = 0,1,2,3)当n相同时,这个存在的bug 是多么的隐蔽。(这恰是使人想象不到的)
&&&&&&& 使用不同寄存器组的函数(特殊情况外)不能相互调用&using&关键字告诉编译器切换register bank
&&&&&&& 如果中断程序不重要,using关键字能忽略。如果一个函数被从中断程序调用,而此中断强制使用using,当编译一个被调用的函数时,编译器必须告诉它
1)在函数前 必须用伪指令
#pragma NOAREGS
在进入函数
#pragma RESTORE
#pragmas AREGS
这样就不会使用 &绝对地址定位&
2)#pragma REGISTERBANK(n)
用这个指定告诉当前使用的bank
用NOAREGS指令 移除 MOV R7,AR7
中断服务例程
void timer0_int(void) interrupt 1&using 1
&unsigned char temp1 ;&&
&unsigned char temp2 ;&&
被调用的函数
#pragma SAVE // Rember current registerbank
#pragma REGISTERBANK(1)&&&& // Tel C51 base address of current registerbank.
void func(char x)
// Called from interrupt routine&&&&&&
// with &using1&&&
#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 都是C5 的关键字。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,在使用中需灵活取舍。
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:61857次
排名:千里之外
转载:20篇
(1)(1)(1)(4)(1)(12)单片机中断系统_C语言中文网
&&/&&&&/&&&&/&&
中断的产生背景
请设想这样一个场景:此刻我正在厨房用煤气烧一壶水,而烧开一壶水刚好需要 10 分钟,我是一个主体,烧水是一个目的,而且我只能时时刻刻在这里烧水,因为一旦水开了,溢出来浇灭煤气的话,有可能引发一场灾难。但就在这个时候呢,我又听到了电视里传来《天龙八部》的主题歌,马上就要开演了,我真想夺门而出,去看我最喜欢的电视剧。然而,听到这个水壶发出的&咕嘟&的声音,我清楚:除非等水烧开了,否则我是无法享受我喜欢的电视剧的。
这里边主体只有一个我,而我要做的有两件事情,一个是看电视,一个是烧水,而电视和烧水是两个独立的客体,它们是同时进行的。其中烧水需要 10 分钟,但不需要了解烧水的过程,只需要得到水烧开的这样一个结果就行了,提下水壶和关闭煤气只需要几秒的时间而已。所以我们采取的办法就是:烧水的时候,定上一个闹钟,定时 10 分钟,然后我就可以安心看电视了。当 10 分钟时间到了,闹钟响了,此刻水也烧开了,我就过去把煤气灭掉,然后继续回来看电视就可以了。
这个场景和单片机有什么关系呢?
在单片机的程序处理过程中也有很多类似的场景,当单片机正在专心致志的做一件事情(看电视)的时候,总会有一件或者多件紧迫或者不紧迫的事情发生,需要我们去关注,有一些需要我们停下手头的工作去马上去处理(比如水开了),只有处理完了,才能回头继续完成刚才的工作(看电视)。这种情况下单片机的中断系统就该发挥它的强大作用了,合理巧妙的利用中断,不仅可以使我们获得处理突发状况的能力,而且可以使单片机能够&同时&完成多项任务。
&定时器中断的应用
在第五章我们学过了定时器,而实际上定时器一般用法都是采取中断方式来做的,我是故意在第五章用查询法,就是使用 if(TF0==1)这样的语句先用定时器,目的是明确告诉同学们,定时器和中断不是一回事,定时器是单片机模块的一个资源,确确实实存在的一个模块,而中断,是单片机的一种运行机制。尤其是初学者们,很多人会误以为定时器和中断是一个东西,只有定时器才会触发中断,但实际上很多事件都会触发中断的,除了&烧水&,还有&有人按门铃&,&来电话了&等等。
标准 51 单片机中控制中断的寄存器有两个,一个是中断使能寄存器,另一个是中断优先级寄存器,这里先介绍中断使能寄存器,如表 6-1 和表 6-2 所示。随着一些增强型 51 单片机的问世,可能会有增加的寄存器,大家理解了我们这里所讲的,其它的通过自己研读数据手册就可以理解明白并且用起来了。
表 6-1 IE&&中断使能寄存器的位分配(地址 0xA8、可位寻址)
表 6-2 IE&&中断使能寄存器的位描述
总中断使能位,相当于总开关
定时器&2&中断使能
串口中断使能
定时器&1&中断使能
外部中断&1&使能
定时器&0&中断使能
外部中断&0&使能
中断使能寄存器 IE 的位 0~5 控制了 6 个中断使能,而第 6 位没有用到,第 7 位是总开关。总开关就相当于我们家里或者学生宿舍里的那个电源总闸门,而 0~5 位这 6 个位相当于每个分开关。那么也就是说,我们只要用到中断,就要写 EA = 1 这一句,打开中断总开关,然后用到哪个分中断,再打开相对应的控制位就可以了。
我们现在就把前面的数码管动态显示的程序改用中断再实现出来,同时数码管显示抖动和&鬼影&也一并处理掉了。程序运行的流程跟图 6-1 所示的流程图是基本一致的,但因为加入了中断,所以整个流程被分成了两部分,秒计数和转换为数码管显示字符的部分还留在主循环内,而动态扫描部分则移到了中断函数内,并加入了消隐的处理。下面来看程序:
#include &reg52.h&
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
unsigned char LedBuff[6] = { //数码管显示缓冲区,初值 0xFF 确保启动时都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
unsigned char i = 0;
//动态扫描的索引
unsigned int cnt = 0;
//记录 T0 中断次数
void main(){
unsigned long sec = 0;
//记录经过的秒数
//使能总中断
ENLED = 0;
//使能 U3,选择控制数码管
ADDR3 = 1;
//因为需要动态改变 ADDR0-2 的值,所以不需要再初始化了
TMOD = 0x01;
//设置 T0 为模式 1
TH0 = 0xFC;
//为 T0 赋初值 0xFC67,定时 1ms
TL0 = 0x67;
//使能 T0 中断
while (1){
if (cnt &= 1000){
//判断 T0 溢出是否达到 1000 次
//达到 1000 次后计数值清零
//秒计数自加 1
//以下代码将 sec 按十进制位从低到高依次提取并转为数码管显示字符
LedBuff[0] = LedChar[sec%10];
LedBuff[1] = LedChar[sec/10%10];
LedBuff[2] = LedChar[sec/100%10];
LedBuff[3] = LedChar[sec/1000%10];
LedBuff[4] = LedChar[sec/10000%10];
LedBuff[5] = LedChar[sec/];
/* 定时器 0 中断服务函数 */
void InterruptTimer0() interrupt 1{
TH0 = 0xFC;
//重新加载初值
TL0 = 0x67;
//中断次数计数值加 1
//以下代码完成数码管动态扫描刷新
P0 = 0xFF;
//显示消隐
switch (i){
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0];
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1];
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2];
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3];
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4];
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5];
大家可以先把程序抄下来,编译下载到单片机里运行,看看实际效果。是否可以看到,近乎完美的显示效果经过我们的努力终于做成功了。下面我们还要再来解析一下这个程序。
在这个程序中,有两个函数,一个是主函数,一个是中断服务函数。主函数 main()我们就不用说了,重点强调一下中断服务函数,它的书写格式是固定的,首先中断函数前边 void表示函数返回空,即中断函数不返回任何值,函数名是 InterruptTimer0(),这个函数名在符合函数命名规则的前提下可以随便取,我们取这个名字是为了方便区分和记忆,而后是 interrupt这个关键字,一定不能错,这是中断特有的关键字,另外后边还有个数字 1,这个数字 1 怎么来的呢?我们先来看表 6-3。
表 6-3 中断查询序列
外部中断&0
外部中断&1
这个表格同样不需要大家记住,需要的时候过来查就可以了。我们现在看第二行的 T0中断,要使能这个中断那么就要把它的中断使能位 ET0 置 1,当它的中断标志位 TF0 变为 1时,就会触发 T0 中断了,那么这时就应该来执行中断函数了,单片机又怎样找到这个中断函数呢?靠的就是中断向量地址,所以 interrupt 后面中断函数编号的数字 x 就是根据中断向量得出的,它的计算方法是 x*8+3=向量地址。当然表中都已经给算好放在第一栏了,我们可以直接查出来用就行了。到此为止,中断函数的命名规则我们就都搞清楚了。
中断函数写好后,每当满足中断条件而触发中断后,系统就会自动来调用中断函数。比如我们上面这个程序,平时一直在主程序 while(1)的循环中执行,假如程序有 100 行,当执行到 50 行时,定时器溢出了,那么单片机就会立刻跑到中断函数中执行中断程序,中断程序执行完毕后再自动返回到刚才的第 50 行处继续执行下面的程序,这样就保证了动态显示间隔是固定的 1ms,不会因为程序执行时间不一致的原因导致数码管显示的抖动了。
6.1 6.2 6.3 6.4 6.5 6.6 6.7}

我要回帖

更多关于 单片机定时器中断 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信