8051单片机上C语言的扩展(中断的使用及使用关键字)
直接访问寄存器和端口
定义
特殊寄存器 P0 0x80
P1 0x81
sfr ADCON; 0xDE
sbit EA 0x9F
操作
ADCON = 0x08;
P1=0xFF;
io_状态=P0;
EA=1;
使用中断 1 关键字后,会自动生成中断向量。
您不能将 ISR 中的局部变量与其余的“后台循环代码”共享,因为链接器将重用这些变量在 RAM 中的位置,因此它们将根据当前使用的不同函数具有不同的含义。对于 RAM 有限的 51 来说,重用变量非常重要。因此,这些函数应该按一定顺序执行而不会被中断。
void timer0_int(void) 中断 1 使用 2
无符号字符 temp1;
无符号字符 temp2;
可执行 C 语句;
“中断”声明表向量生成在(8*n+3)处,其中n为中断参数后面的数字。这里在08H的代码区生成一条指令LJMP timer0_int。
“using” 告诉编译器在进入中断例程时切换寄存器组。这种“上下文”切换是
为中断例程的本地数据提供新的寄存器组,对于时间要求极高的例程来说,这比堆叠寄存器更可取。请注意,相同优先级的中断可以共享一个寄存器组,因为它们不存在相互中断的风险。
“using” 告诉编译器在进入中断处理程序时切换寄存器组。此“contet”切换是为中断处理程序的局部变量提供新寄存器组的最快方法。对于时间关键型程序,堆栈寄存器(将寄存器保存在堆栈上)方法是首选方法。
注意:相同优先级的中断可以共享寄存器组,因为它们不会相互中断。
如果在 timer1 中断函数原型中添加 USING 1,则寄存器的推送将被简单的 MOV 到 PSW 替换,以切换寄存器组。不幸的是,虽然中断入口速度加快了,但 sys_interp 入口使用的直接寄存器寻址失败了。这是因为 C51 尚未被告知寄存器组已更改。如果没有使用工作寄存器并且没有调用其他函数,优化器将消除切换寄存器组的代码。
如果在timer1中断函数原型中使用1,则寄存器推送将被MOV到PSW取代,以切换寄存器组。
不幸的是,当加速中断入口时,入口处使用的直接寄存器寻址将失败。这是因为 C51 未被告知寄存器组已更改。如果将使用不起作用的寄存器,并且如果没有调用其他函数,则优化器...
从逻辑上讲,对于中断例程,不能向其传递或返回参数。发生中断时,将运行编译器插入的代码,该代码将累加器、B、DPTR 和 PSW(程序状态字)推送到堆栈上。最后,在退出中断例程时,先前存储在堆栈上的项将恢复,并且结束的“}”会导致使用 RETI,而不是正常的 RET。
从逻辑上讲,中断服务例程不能向其中传递参数,也不能返回值。当发生中断时,将执行编译器插入的代码,该代码将累加器、B、DPTR 和 PSW(程序状态字)推送到堆栈上。最后,在退出中断例程时,恢复堆栈上先前存储的值。最后的“}”结束符号将 RETI 插入到中断例程的末尾。要在 Keil C 语言中创建中断服务例程 (ISR),请使用中断关键字和正确的中断号声明一个静态 void 函数。Keil C 编译器会自动生成中断向量,以及中断例程的导入和导出代码。Interrupt function 属性将函数标记为 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、R4、R5、、R6、R7),所以这些寄存器中不会存放任何有效数据)
2. 函数调用函数时,除了返回地址外,堆栈中不会保存任何其他寄存器(ACC、B、DPH、DPL、PSW、R0、R1、R2、R3、R4、R5、R6、R7)的内容。(除非被调用函数使用了 using 特性)
3、中断函数是个例外,它计算自己以及自己调用的函数对寄存器(ACC、B、DPH、DPL、PSW、R0、R1、R2、R3、R4、R5、、R6、R7)所做的改变,并保存它认为改变的相应寄存器。
4. 用 C 写程序时,尽量避免使用 using n (n=0,1,2,3) 特性。(这个特性在我使用过程中出现了一些问题,不知道是不是一个小 Bug)
默认情况下Keil C51中的函数都使用寄存器组0,当中断函数使用使用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”关键字告诉编译器切换寄存器组。
如果中断例程不重要,则可以省略 using 关键字。如果从中断例程调用函数,并且中断例程强制使用 using,则编译器必须在编译被调用函数时告知它。
1)函数前必须使用伪指令
#pragma NOAREGS
进入函数时
#pragma 恢复
或者
#pragmas AREGS
这将不使用“绝对地址定位”
2)#pragma REGISTERBANK(n)
使用此规范来告诉当前正在使用的银行
使用 NOAREGS 指令删除 MOV R7,AR7
中断服务程序
void timer0_int(void) 中断 1 使用 1
无符号字符 temp1;
无符号字符 temp2;
被调用的函数
#pragma SAVE //记住当前寄存器组
#pragma REGISTERBANK(1) //Tel C51 当前寄存器组的基地址。
无效函数(char x)
// 从中断例程调用
// 使用“using1”
#pragma RESTORE // 放回原始寄存器组
如果中断服务程序使用了using,那么中断服务程序调用的函数必须为REGISTERBANK(n)。 ISR调用的函数也可能被后台程序以函数“可重入(reentrant)”的形式调用。8051系列MCU的基本结构包括:32个I/O端口(4组8位端口);两个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 中的使能位,对应关系如下:
8051的IE寄存器C51
中断号 中断源 使能位
--------------------------------
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 支持 8051 系列(增强型)中从 0 到 4 的所有五个 8051 标准中断和最多 27 个中断源。
using关键字用于指定中断服务程序使用的寄存器组。用法为:using后面跟着0~3的数字,分别对应4组工作寄存器。一旦指定了工作寄存器组,默认的工作寄存器组就不会被压入堆栈,这样可以节省32个处理周期,因为压栈和出栈都需要2个处理周期。这种方式的缺点是所有中断调用过程都必须使用同一个指定的寄存器组,否则参数传递会失败。因此,需要灵活选择using。