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必须注意的一些有趣特性的更多相关文章

  1. Keil C51总线外设操作问题的深入分析

    阅读了<单片机与嵌入式系统应用>2005年第10期杂志<经验交流>栏目的一篇文章<Keil C51对同一端口的连续读取方法>(原文)后,笔者认为该文并未就此问题进行 ...

  2. Keil C51编译及连接技术

    主要介绍Keil C51的预处理方法如宏定义.常用的预处理指令及文件包含指令,C51编译库的选择及代码优化原理,C51与汇编混合编程的方法与实现以及超过64KB空间的地址分页方法的C51实现. 教学目 ...

  3. KEIL C51代码优化详细分析

    阅读了<单片机与嵌入式系统应用>2005年第10期杂志<经验交流>栏目的一篇文章<Keil C51对同一端口的连续读取方法>(原文)后,笔者认为该文并未就此问题进行 ...

  4. keil c51笔记

    第一章 Keil C51开发系统基本知识 第一节 系统概述 Keil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统,与汇编相比,C语言在功能上.结构性.可读性. ...

  5. Keil C51汉字显示的bug问题

    一.缘起 这两天改进MCU的液晶显示方法,采用“即编即显”的思路,编写了一个可以直接显示字符串的程序.如程序调用disstr("我是你老爸");液晶屏上就会显示“我是你老爸”. 二 ...

  6. KEIL、uVision、RealView、MDK、KEIL C51区别比较

    KEIL uVision,KEIL MDK,KEIL For ARM,RealView MDK,KEIL C51,KEIL C166,KEIL C251 从接触MCS-51单片机开始,我们就知道有一个 ...

  7. Keil C51程序调试过程

    用Keil C51编写程序时,经常需要调试,如果不是经常用这个软件的话,很容易忘记这些调试步骤,现在举一个例子“验证延迟函数delay()使其延迟的时间为500ms”说明. 源程序写完后,就可以调试了 ...

  8. Keil C51软件的使用

    进入 Keil C51 后,屏幕如下图所示.几秒钟后出现编辑界 启动Keil C51时的屏幕 进入Keil C51后的编辑界面 简单程序的调试:学习程序设计语言.学习某种程序软件,最好的方法是直接操作 ...

  9. Keil c51现No Browse information available

    keil c51 不能使用:Go to Definition of....的解决方法 最近使用keil c51 开发usb固件,当向vc一样使用Go to Definition of....时,出现警 ...

随机推荐

  1. 自制单片机之十六……将文字或图形转成LCD上使用的C51字模数据

    这一讲说说如何用取模软件将图形转成数据吧,有很多人反复问我这个问题,我就再罗嗦下吧! 取字模的软件有很多款.有的只能将文字转成字模数据,有的既可将文本文字转字模也能将图片转成点阵数据.在这里我就介绍一 ...

  2. 单例模式 - OK

    单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点. 一.单例模式 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象.一个最好的办法就是,让 ...

  3. Linux企业级项目实践之网络爬虫(6)——将程序设计成为守护进程

    在linux或者unix操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进程.为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定的方式配置系统. ...

  4. 【转】 ubuntu12.04更新源 官网和163等

    原文网址:http://blog.csdn.net/zhangliang_571/article/details/8813999 分类: LINUX 摘要: 本文列出ubuntu 12.04 LTS更 ...

  5. 2015第22周一Web性能测试工具及IE扩展区别

    在高性能web测试工具推荐http://www.jb51.net/article/23034.htm中发现了dynaTrace 感觉很不错,不但可以检测资源加载瀑布图,而且还能监控页面呈现时间,CPU ...

  6. Linux生成core文件、core文件路径设置

    在Linux下产生并调试core文件 先看看我用的是个什么机器: $ uname -aLinux dev 2.4.21-9.30AXsmp #1 SMP Wed May 26 23:37:09 EDT ...

  7. 在Spring aop中的propagation的7种配置的意思

    <tx:method name="find*" read-only="true" propagation ="NOT_SUPPORTED&quo ...

  8. hdu 4586 Play the Dice(概率dp)

    Problem Description There is a dice with n sides, which are numbered from 1,2,...,n and have the equ ...

  9. pyqt搜索指定信息 github处找到,谢谢这位朋友的帮助了

    def tabunqi(self,text):    #第一遍添加之后,不提示,当第二次添加相同的数据时,就提示下    text1=str(text)    items = self.downwid ...

  10. C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

    模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一 ...