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. VC 隐藏托盘图标

    苦苦寻找的隐藏托盘图标的方法,今天终于搞定,献给大家! #include <atlbase.h> #include <atlconv.h> #include <CommC ...

  2. UVA514 Rails

     铁轨 PopPush城市有一座著名的火车站.这个国家到处都是丘陵.而这个火车站是建于上一个世纪.不幸的是,那时的资金有限.所以只能建立起一条路面铁轨.而且,这导致这个火车站在同一个时刻只能一个轨道投 ...

  3. redis 学习笔记一

    找了半天,发觉还是redis的源码看起来比较舒服.所以决定今年把redis的源码读一遍顺便做个读书笔记.好好记录下.话说现在越来不越不愿意用脑袋来记录东西,喜欢靠note来记.话说这样不爱用脑会不会过 ...

  4. iOS7 UI适配教程

    最近写了点iOS7适配的文章,请指正 ios6to7 1 ios6to7 2

  5. hdu 1695 GCD(欧拉函数+容斥)

    Problem Description Given 5 integers: a, b, c, d, k, you're to find x in a...b, y in c...d that GCD( ...

  6. mysql 中文乱码的解决方法

    添加或修改my.ini 配置文件,设置编码字符为utf8 ,默认为latin1,见红色字体 [mysql]# 设置mysql客户端默认字符集default-character-set=utf8 [my ...

  7. 获取某个文件夹中所有txt文件

    <?php // 获取文件夹中的所有txt文件名 $dir = "D:/a"; //这里输入其他路径 $handle = opendir($dir."." ...

  8. Win32多线程编程(3) — 线程同步与通信

      一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线 ...

  9. OLE-DB 操作excel 基本

    1 方法用例   *&---------------------------------------------------------------------* *& 本程序总结了常 ...

  10. Stm32高级定时器(一)

    Stm32高级定时器(一) 1 定时器的用途 2 高级定时器框图 3 时基单元 4 通道 1 定时器的用途 已知一个波形求另一个未知波形(信号长度和占空比) 已知波形的信号长度和占空比产生一个相应的波 ...