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....时,出现警 ...
随机推荐
- 使用NUnit进行项目的单元测试
using System; using System.Collections; using System.Collections.Generic; using NUnit.Framework; nam ...
- github如何删除一个repository(仓库)
昨天有人问我如何删除自己创建的一个项目,我浏览了一下github网站,确实不太容易找到删除功能.这里介绍一下啊如何在github上删除一个repository 一:首先点击进入你要删除的reposit ...
- Android文件下载(实现断点续传)
本文将介绍在android平台下如何实现多线程下载,大家都知道,android平台使用java做为开发语言,所以java中支持的多线程下载方式在android平台下都支持,其中主要有两种方式可以实现多 ...
- /sbin/ifup: configuration for eth0 not found解决
/sbin/ifup: configuration for eth0 not found. Usage: ifup <device name> 那么应该是在/etc/sysconfig/n ...
- hdu4623:crime 数学优化dp
鞍山热身赛的题,也是去年多校原题 题目大意: 求n个数的排列中满足相邻两个数互质的排列的数量并取模 当时的思路就是状压dp.. dp[i][state] state用二进制记录某个数是否被取走,i ...
- thinkphp连接mysql5.5版本数据库
//数据库配置信息 'DB_TYPE' => 'mysqli', // 数据库类型 'DB_HOST' => 'localhost', // 服务器地址 'DB_NAME' => ' ...
- python进阶之路之文件处理
Python之文件处理 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !imp ...
- RMAN-configure命令
在Oracle 10g中的配置情况 使用RMAN>show all; 可以显示出RMAN 配置参数为: CONFIGURE RETENTION POLICY TO REDUNDANCY 1; # ...
- 使用DataReader
string sqlStr="select * from tb_news"; SqlCommand myCmd=new SqlCommand(sqlStr,myConn); myC ...
- Chinese_PRC_CI_AS and SQL_Latin1_General_CP1_CI_AS类型错误
在编写存储过程时,经常会用到临时表,而且往往会使用临时表与正式表相关联. 当临时表与正式表关联的字段是字符类型的时候,因为临时表创建的字符类型会默认数据库的编码,而正式表的字符编码可能不同,那么这个时 ...