Keil C51必须注意的一些有趣特性
Keil c51号称作为51系列单片机最好的开发环境,大家一定都很熟悉。它的一些普通的特性大家也都了解,(书上也都说有)如:因为51内的RAM很小,C51的函数并不通过堆栈传递参数(重入函数除外),局部变量也不存储在堆栈中,而是存在于固定的RAM中及寄存器中。那么看一下下面的程序。
void fun1(unsigned char i)
{
…
}
正常情况参数i通过R7传入函数,那么它的实际地址在什么地方呢?就是R7吗?回答这个问题之前我们先来了解keil c51的几个有趣的特性(不考虑重入函数)。
一、 函数在调用前定义与在调用后定义产生的代码是有很大差别的(特别是在优化级别大于3级时)。(本人也不太清楚为什么,大概因为在调用前定义则调用函数已经知道被调用函数对寄存器的使用情况,则可对函数本身进行优化;而在调用后进行定义则函数不知被调用函数对寄存器的使用情况,它默认被调用函数对寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)都已经改变,因此不在这些寄存器中存入有效的数据)
二、 函数调用函数时除在堆栈中存入返回地址之外,不在堆栈中保存其它任何寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)的内容。(除非被调用函数使用了using特性)
三、 中断函数是一个例外,它会计算自身及它所调用的函数对寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)的改变,并保存相应它认为被改变了的寄存器。
四、 使用C写程序时,尽量少使用using n (n=0,1,2,3)特性。(这个特性在本人使用的过程中存在一些问题,不知算不算是一个小bug)
以下的试验都是在(环境 keil c51 v7.20)中,优化级为default下完成。
先看第一个特性问题。
例1:
void fun2(void)
{
}
void fun1(unsigned char i)
{
fun2();
while(i--);
}
它的汇编代码如下:
; void fun2(void)
RSEG ?PR?fun2?TEST
fun2:
; SOURCE LINE # 12
; {
; SOURCE LINE # 13
; }
; SOURCE LINE # 14
RET
; END OF fun2
;
; void fun1(unsigned char i)
RSEG ?PR?_fun1?TEST
_fun1:
USING 0
; SOURCE LINE # 16
;---- Variable 'i?240' assigned to Register 'R7' ----
; {
; SOURCE LINE # 17
; fun2();
; SOURCE LINE # 18
LCALL fun2
?C0003:
; while(i--);
; SOURCE LINE # 19
MOV R6,AR7
DEC R7
MOV A,R6
JNZ ?C0003
; }
; SOURCE LINE # 20
?C0005:
RET
; END OF _fun1
从中可以看到fun2()在fun1()前先定义,fun1()知道fun2()对寄存器的使用情况,知道R7没有改变,而参数i存于R7中,即i既是R7。(;---- Variable 'i?140' assigned to Register 'R7' ----)
看另一情况
void fun2(void);
void fun1(unsigned char i)
{
fun2();
while(i--);
}
void fun2(void)
{
}
汇编代码如下:
; void fun1(unsigned char i)
RSEG ?PR?_fun1?TEST
_fun1:
USING 0
; SOURCE LINE # 14
MOV i?140,R7
; {
; SOURCE LINE # 15
; fun2();
; SOURCE LINE # 16
LCALL fun2
?C0002:
; while(i--);
; SOURCE LINE # 17
MOV R7,i?140
DEC i?140
MOV A,R7
JNZ ?C0002
; }
; SOURCE LINE # 18
?C0004:
RET
; END OF _fun1
;
; void fun2(void)
RSEG ?PR?fun2?TEST
fun2:
; SOURCE LINE # 20
; {
; SOURCE LINE # 21
; }
; SOURCE LINE # 22
RET
; END OF fun2
fun2()在fun1()调用后定义,因fun1()调用fun2()时不知道fun2()对寄存器的使用情况,则认为fun2()改变了所有的寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)。因为fun1()认为fun2()改变了寄存器的值(包括R7),因此i虽然通过R7传递,但因已因调用fun2()而改变,所以不能再存在R7了,而上在RAM中额外的用一个Byte来存储。
这也就解释了在开始时的那个问题,参数i的存储是看问题而定的。
哈哈,是否很有趣呢。在节约RAM方面,这可是一个很有用的特性哦。(大家是否也为自己的节省了1Byte的RAM)
这个例子还解释了第二个特性,函数调用函数时除在堆栈中存入返回地址之外,不在堆栈中保存其它任何寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、R6、R7)的内容。函数在调用函数前,尽量不在这些寄存器中保存有效的数据,实在无法避免,则把有效数据存入固定的RAM中。
对于中断函数问题,当你看到下面的程序相差55 Byte时,不知你会怎么想的。
例2:
void OSTimeDly(void); //using 1
static void Timer0OVInt(void) interrupt 1 //using 1
{
TR0 = 0;
TH0 = 100;
TL0 = 100;
TR0 = 1;
OSTimeDly();
}
void OSTimeDly(void) //using 1
{
}
与
void OSTimeDly(void) //using 1
{
}
static void Timer0OVInt(void) interrupt 1 //using 1
{
TR0 = 0;
TH0 = 100;
TL0 = 100;
TR0 = 1;
OSTimeDly();
}
它们的汇编代码分别是,
; static void Timer0OVInt(void) interrupt 1 //using 1
RSEG ?PR?Timer0OVInt?TEST
USING 0
Timer0OVInt:
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
MOV PSW,#00H
PUSH AR0
PUSH AR1
PUSH AR2
PUSH AR3
PUSH AR4
PUSH AR5
PUSH AR6
PUSH AR7
USING 0
; SOURCE LINE # 24
; {
; TR0 = 0;
; SOURCE LINE # 26
CLR TR0
; TH0 = 100;
; SOURCE LINE # 27
MOV TH0,#064H
; TL0 = 100;
; SOURCE LINE # 28
MOV TL0,#064H
; TR0 = 1;
; SOURCE LINE # 29
SETB TR0
;
; OSTimeDly();
; SOURCE LINE # 31
LCALL OSTimeDly
; }
; SOURCE LINE # 32
POP AR7
POP AR6
POP AR5
POP AR4
POP AR3
POP AR2
POP AR1
POP AR0
POP PSW
POP DPL
POP DPH
POP B
POP ACC
RETI
; END OF Timer0OVInt
;
;
; void OSTimeDly(void) //using 1
RSEG ?PR?OSTimeDly?TEST
OSTimeDly:
; SOURCE LINE # 35
; {
; SOURCE LINE # 36
;
; }
; SOURCE LINE # 38
RET
; END OF OSTimeDly
及
; void OSTimeDly(void) //using 1
RSEG ?PR?OSTimeDly?TEST
OSTimeDly:
; SOURCE LINE # 22
; {
; SOURCE LINE # 23
;
; }
; SOURCE LINE # 25
RET
; END OF OSTimeDly
CSEG AT 0000BH
LJMP Timer0OVInt
;
; static void Timer0OVInt(void) interrupt 1 //using 1
RSEG ?PR?Timer0OVInt?TEST
USING 0
Timer0OVInt:
; SOURCE LINE # 27
; {
; TR0 = 0;
; SOURCE LINE # 29
CLR TR0
; TH0 = 100;
; SOURCE LINE # 30
MOV TH0,#064H
; TL0 = 100;
; SOURCE LINE # 31
MOV TL0,#064H
; TR0 = 1;
; SOURCE LINE # 32
SETB TR0
;
; OSTimeDly();
; SOURCE LINE # 34
LCALL OSTimeDly
; }
; SOURCE LINE # 35
RETI
; END OF Timer0OVInt
这个例子的汇编代码很好的解释了上面的特性1及3。
至于第四个特性,值得特别说明一下。看下例:
例3:
void OSTimeDly(void);
static void Timer0OVInt(void) interrupt 1 using 0
{
TR0 = 0;
TH0 = 100;
TL0 = 100;
TR0 = 1;
OSTimeDly();
}
void OSTimeDly(void) // using 0
{
}
它的汇编代码是
; static void Timer0OVInt(void) interrupt 1 using 0
RSEG ?PR?Timer0OVInt?TEST
USING 0
Timer0OVInt:
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
USING 0
MOV PSW,#00H
; SOURCE LINE # 24
; {
; TR0 = 0;
; SOURCE LINE # 26
CLR TR0
; TH0 = 100;
; SOURCE LINE # 27
MOV TH0,#064H
; TL0 = 100;
; SOURCE LINE # 28
MOV TL0,#064H
; TR0 = 1;
; SOURCE LINE # 29
SETB TR0
;
; OSTimeDly();
; SOURCE LINE # 31
LCALL OSTimeDly
; }
; SOURCE LINE # 32
POP PSW
POP DPL
POP DPH
POP B
POP ACC
RETI
; END OF Timer0OVInt
;
; void OSTimeDly(void) // using 0
RSEG ?PR?OSTimeDly?TEST
OSTimeDly:
; SOURCE LINE # 34
; {
; SOURCE LINE # 35
;
; }
; SOURCE LINE # 37
RET
; END OF OSTimeDly
此例中除了中断函数使用了using 0之外,与上例中的程序并无区别,但是汇编的代码相差却很大。此例中的汇编代码不再保存R0 ---- R7的值。(默认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 是多么的隐蔽。(这恰是使人想象不到的)
最后再来看一例
例4:
void OSTimeDly(unsigned char i);
static void Timer0OVInt(void) interrupt 1 using 1
{
TR0 = 0;
TH0 = 100;
TL0 = 100;
TR0 = 1;
OSTimeDly(5);
}
void OSTimeDly(unsigned char i) // using 0
{
while(i--);
}
汇编的结果
; static void Timer0OVInt(void) interrupt 1 using 1
RSEG ?PR?Timer0OVInt?TEST
USING 1
Timer0OVInt:
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
USING 1
MOV PSW,#08H
; SOURCE LINE # 25
; {
; TR0 = 0;
; SOURCE LINE # 27
CLR TR0
; TH0 = 100;
; SOURCE LINE # 28
MOV TH0,#064H
; TL0 = 100;
; SOURCE LINE # 29
MOV TL0,#064H
; TR0 = 1;
; SOURCE LINE # 30
SETB TR0
;
; OSTimeDly(5);
; SOURCE LINE # 32
MOV R7,#05H
LCALL _OSTimeDly
; }
; SOURCE LINE # 33
POP PSW
POP DPL
POP DPH
POP B
POP ACC
RETI
; END OF Timer0OVInt
;
; void OSTimeDly(unsigned char i) // using 0
RSEG ?PR?_OSTimeDly?TEST
_OSTimeDly:
USING 0
; SOURCE LINE # 35
;---- Variable 'i?441' assigned to Register 'R7' ----
; {
; SOURCE LINE # 36
?C0009:
; while(i--);
; SOURCE LINE # 37
MOV R6,AR7
DEC R7
MOV A,R6
JNZ ?C0009
; }
; SOURCE LINE # 38
?C0011:
RET
; END OF _OSTimeDly
注意OSTimeDly()中此处的汇编代码,
MOV R6,AR7
DEC R7
因为Timer0OVInt()函数使用的寄存器组是1 (using 1),而OSTimeDly()默认使用0寄存器组(默认使用的寄存器组是不会用代码显示改变的)。因此Timer0OVInt()调用OSTimeDly()时寄存器组仍然是1组,R7的地址是15,而AR7的地址为OSTimeDly()所使用的寄存器组中R7的地址,在0寄存器组中为7。因此当AR7为0时,这是一个死循环。
结论,使用不同寄存器组的函数(特殊情况外)不能相互调用
Keil C51必须注意的一些有趣特性的更多相关文章
- Keil C51总线外设操作问题的深入分析
阅读了<单片机与嵌入式系统应用>2005年第10期杂志<经验交流>栏目的一篇文章<Keil C51对同一端口的连续读取方法>(原文)后,笔者认为该文并未就此问题进行 ...
- Keil C51编译及连接技术
主要介绍Keil C51的预处理方法如宏定义.常用的预处理指令及文件包含指令,C51编译库的选择及代码优化原理,C51与汇编混合编程的方法与实现以及超过64KB空间的地址分页方法的C51实现. 教学目 ...
- KEIL C51代码优化详细分析
阅读了<单片机与嵌入式系统应用>2005年第10期杂志<经验交流>栏目的一篇文章<Keil C51对同一端口的连续读取方法>(原文)后,笔者认为该文并未就此问题进行 ...
- keil c51笔记
第一章 Keil C51开发系统基本知识 第一节 系统概述 Keil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统,与汇编相比,C语言在功能上.结构性.可读性. ...
- Keil C51汉字显示的bug问题
一.缘起 这两天改进MCU的液晶显示方法,采用“即编即显”的思路,编写了一个可以直接显示字符串的程序.如程序调用disstr("我是你老爸");液晶屏上就会显示“我是你老爸”. 二 ...
- KEIL、uVision、RealView、MDK、KEIL C51区别比较
KEIL uVision,KEIL MDK,KEIL For ARM,RealView MDK,KEIL C51,KEIL C166,KEIL C251 从接触MCS-51单片机开始,我们就知道有一个 ...
- Keil C51程序调试过程
用Keil C51编写程序时,经常需要调试,如果不是经常用这个软件的话,很容易忘记这些调试步骤,现在举一个例子“验证延迟函数delay()使其延迟的时间为500ms”说明. 源程序写完后,就可以调试了 ...
- Keil C51软件的使用
进入 Keil C51 后,屏幕如下图所示.几秒钟后出现编辑界 启动Keil C51时的屏幕 进入Keil C51后的编辑界面 简单程序的调试:学习程序设计语言.学习某种程序软件,最好的方法是直接操作 ...
- Keil c51现No Browse information available
keil c51 不能使用:Go to Definition of....的解决方法 最近使用keil c51 开发usb固件,当向vc一样使用Go to Definition of....时,出现警 ...
随机推荐
- 自制单片机之十七……PC与单片机RS-232串口的通讯和控制
这次我们来试着一步步的去掌握PC与单片机通过RS-232进行通讯和控制. 先说说我硬件的情况.我用的PC是个二手的IBM240小本本,十寸屏,赛扬400,机子很老了.但也有它的优点:1.串口,并口,P ...
- VC6.0 编译 gdlib 库
环境 WinXP, MSVC6.0 1 从 https://bitbucket.org/libgd/gd-libgd/downloads 下载最新版本 libgd 2 可以用 nmake 编译 w ...
- BZOJ2768: [JLOI2010]冠军调查
2768: [JLOI2010]冠军调查 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 484 Solved: 332[Submit][Status ...
- 【转】如何过滤 adb logcat 输出
原文网址:http://www.cnblogs.com/imouto/archive/2012/12/11/filtering-adb-logcat-output.html 简介: 本文介绍如何在 s ...
- C++链接器工具错误:LNK2001, LNK2019(转载)
这是归属于链接器工具错误 这一类. 无法解析的外部符号“symbol” 代码引用了链接器无法在库和对象文件中找到的内容(如函数.变量或标签). 可能的原因 代码请求的内容不存在(例如,符号拼写错误或使 ...
- hdu5024-Wang Xifeng's Little Plot
此题一开始用暴力做,后来发现斜着走的时候其实暴力不太好写,于是改用搜索写了 #include <iostream> #include <stdio.h> #include &l ...
- Fence Repair (POJ 3253)
农夫约翰为了修理栅栏,要将一块很长的木板切割成N块.准备切成的木板长度为L1.L2.L3...LN,未切割前的木板长度恰好为切割后木板长度的总和.每次切断木板时,需要的开销为这块木板的长度.例如长度为 ...
- poj 3176 Cow Bowling(dp基础)
Description The cows don't use actual bowling balls when they go bowling. They each take a number (i ...
- java 自定义鼠标图标
由于截图截不了,所以看不了图.源码如下: import java.awt.Cursor; import java.awt.Image; import java.awt.Point; import ja ...
- [IOI1999]花店橱窗布置(DP路径记录)
题目:[IOI1999]花店橱窗布置 问题编号:496 题目描述 某花店现有F束花,每一束花的品种都不一样,同时至少有同样数量的花瓶,被按顺序摆成一行,花瓶的位置是固定的,从左到右按1到V顺序编号,V ...