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. 策略模式 - OK

    策略模式(Strategy):它定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的 ...

  2. logstash 安装zabbix插件

    <pre name="code" class="html">[root@xxyy yum.repos.d]# yum install ruby Lo ...

  3. hdu1573:数论,线性同余方程组

    题目大意: 给定一个N ,m 找到小于N的  对于i=1....m,满足  x mod ai=bi  的 x 的数量. 分析 先求出 同余方程组 的最小解x0,然后 每增加lcm(a1...,am)都 ...

  4. BeyondCompare两个文件中同一行字符长度不一致的文件对比,比如pi文件对比(xjl456852原创)

    假设有两个文件,里面存放的数字都只有一行,但长度不一样,对比时会有问题 示例文件: 对比示例如图: 左边的pi的字符串比较长,右边的比较短. 右边的pi的值不是从开始的第一个位置对比的,这样的情况是有 ...

  5. #python基础学习模块:marshal 对象的序列化

    #标准库地址:https://docs.python.org/2/library/marshal.html"""有时候,要把内存中一个对象持久化保存磁盘或者序列化二进制流 ...

  6. Laravel-路由-控制器

    (慕课网_轻松学会Laravel-基础篇_天秤vs永恒老师) 一.基础路由 二.多请求路由 三.参数路由 四.路由别名 生成url可以使用别名 五.路由群组 六.路由输出视图 七.控制器参数绑定

  7. 数学之路(3)-机器学习(3)-机器学习算法-PCA

    PCA 主成分分析(Principal components analysis,PCA),维基百科给出一个较容易理解的定义:“PCA是一个正交化线性变换,把数据变换到一个新的坐标系统中,使得这一数据的 ...

  8. Linux编程环境介绍(1) -- linux的历史

    1. linux是什么? "Hello everybody out there using minix——I'm doing a (free) operating system"  ...

  9. hdu2767 Proving Equivalences --- 强连通

    给一个图,问至少加入�多少条有向边能够使图变成强连通的. 原图是有环的,缩点建图,在该DAG图上我们能够发现,要使该图变成强连通图必须连成环 而加入�最少的边连成环,就是把图上入度为0和出度为0的点连 ...

  10. 【Android 应用开发】 ActionBar 样式详解 -- 样式 主题 简介 Actionbar 的 icon logo 标题 菜单样式修改

    作者 : 万境绝尘 (octopus_truth@163.com) 转载请著名出处 : http://blog.csdn.net/shulianghan/article/details/3926916 ...