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. Powershell创建数组

    在Powershell中创建数组可以使用逗号. PS C:Powershell> $nums=2,0,1,2 PS C:Powershell> $nums 2 0 1 2 对于连续的数字数 ...

  2. Android实现摇晃手机的监听

     摘自:http://blog.csdn.net/xwren362922604/article/details/8515343 监听摇晃手机的类: /**  * @author renxinwei ...

  3. 瑞柏匡丞谈中国移动app的国际进阶路

    当今3.0互联时代,已然形成了一个移动化,互动化,全球化的完整体系.瑞柏匡丞也在常年与国内外客户的交流沟通中有了自己的些许见解. 国内的移动产业的发展已然非常迅速,但也正是因为各类企业的不断崛起,能够 ...

  4. UVA11922--Permutation Transformer (伸展树Splay)

    题意:m条操作指令,对于指令 a  b 表示取出第a~b个元素,翻转后添加到排列的尾部. 水题卡了一个小时,一直过不了样例.  原来是 dfs输出的时候 忘记向下传递标记了. #include < ...

  5. DBA避坑宝典:Oracle运维中的那些事儿

    对于Oracle运维中的那些事儿,我的最终目的:不是比谁更惨,而是能够从中吸取经验和教训. 从我的理解来看,我会从下面的几个方面来进行说明DBA运维中的一些事儿. 每个部分都是非常关键的,缺一不可,而 ...

  6. 好用的QT连接

    QT属性控件项目https://github.com/lexxmark/QtnProperty比特币交易软件https://github.com/JulyIGHOR/QtBitcoinTrader导航 ...

  7. 第31讲 UI组件之 Gallery画廊控件

    第31讲 UI组件之 Gallery画廊控件 1.Gallery的简介 Gallery(画廊)是一个锁定中心条目并且拥有水平滚动列表的视图,一般用来浏览图片,并且可以响应事件显示信息.Gallery只 ...

  8. NetAnalyzer笔记 之 二. 简单的协议分析

    [创建时间:2015-08-27 22:15:17] NetAnalyzer下载地址 上篇我们回顾完了NetAnalyzer一些可有可无的历史,在本篇,我决定先不对NetAnalyzer做介绍,而是先 ...

  9. 使用PHPExcel导入导出excel格式文件

    使用PHPExcel导入导出excel格式文件  作者:zccst  因为导出使用较多,以下是导出实现过程.  第一步,将PHPExcel的源码拷贝到项目的lib下  文件包含:PHPExcel.ph ...

  10. Laravel Cheat 表 http://cheats.jesse-obrien.ca/#

    Laravel Cheat Sheet Toggle Code Comments PDF Version Github Laravel 3 Docs Laravel 4 Docs Artisan ph ...