Keil C51的一些有趣特性

发布者:czc天天最新更新时间:2011-05-14 来源: 侃单片机关键字:Keil  C51  特性 手机看文章 扫描二维码
随时随地手机看文章

    首先得说的是我是菜鸟,在此论坛上学了很多的东东。但是今年以来,论坛上似乎没有了去年一大帮高手讨论问题的场面了,似乎失去了往日的风光了。在此我那出我近日一些不成熟的想法,希望大家斧正。有啥不正确的,请一定告之与我。

    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的一些有趣特性

上一篇:基于STC89C516RD单片机的新型冰箱温控器系统
下一篇:基于AT89C51CC01单片机的CAN—LIN网关设计

推荐阅读最新更新时间:2024-03-16 12:35

使用Keil C51进行仿真下载的相关设置
注:编译或仿真工程必须首先安装Keil软件,以及对应的Silicon IDE驱动软件 ·Keil的安装   请到 Keil 官方网站: https://www.keil.com/c51/demo/eval/c51.htm 下载安装。 ·Keil μVision Driver 的安装   请到 Silabs 官方网站: http://www.silabs.com/products/mcu/Pages/KeilDriver.aspx 下载安装。 1、安装Keil μVision Driver驱动 2、如下图所示,对着Target1右键选择Options for Target 'Target1',然后会弹出设置窗口
[单片机]
使用<font color='red'>Keil</font> <font color='red'>C51</font>进行仿真下载的相关设置
单声道 AB 类音频功放的功能及特性说明
功能说明 NS8002 是一款 AB 类桥式输出音频功率放大器。其应用电路简单,只需极少数外围器件。输出不需要外接耦合电容或上举电容和缓冲网络。SOP8 封装,更适合用于便携系统。 NS8002 可以通过控制进入低功耗关断模式,从而减少功耗。增益带宽积高达 2.5M,并且单位增益稳定。通过配置外围电阻可以调整放大器的电压增益,方便应用。 NS8002 提供 SOP8 封装,额定的工作温度范围为-40℃至 85℃。 主要特性  输出功率: 2. 4W(RL=4Ω,THD=10%)  掉电模式漏电流小: 1uA(典型)  高电平 ShutDown  采用 SOP8 封装  外部增益可调 电压范围 3.0V—5.
[嵌入式]
单声道 AB 类音频功放的功能及<font color='red'>特性</font>说明
c51:将16进制序列码,转换为16进制数组
//将16进制序列码,转换为16进制数组。 #include reg51.h #include stdio.h #include ctype.h #define uchar unsigned char void main() { //ch 表示16进制 序列码 char ch = 0123456789ABCDEF ; //ch1 表示 16进制 字符数组 char ch1 ; char i; for(i=0;i 8;i++) { ch1 =toint(ch )*16+toint(ch ); } } /* 转换结果: ch1 =0x01; ch1 =0x23; ch1 =0x45;
[单片机]
C51 INT0方式 1键多能程序
//按键接INT0,按键实现 P1.0~P1.7逐位翻转。 #include reg52.h void delay(unsigned int x); sbit led0=P1^0; sbit led1=P1^1; sbit led2=P1^2; sbit led3=P1^3; sbit led4=P1^4; sbit led5=P1^5; sbit led6=P1^6; sbit led7=P1^7; unsigned char k; main() { IT0=1; EA=1; EX0=1; while(1) { } } void delay(unsigned int x) { unsigned int i;
[单片机]
AT89C2051+LCD1602+DS1302实时时钟设计(c51)
#include at892051.h #include intrins.h #define uchar unsigned char sbit rs=P3^0;//LCD1602控制脚 sbit rw=P3^1; sbit e=P3^2; sbit io=P3^4;//DS1302 PCI总线引脚 sbit rst=P3^5; sbit sclk=P3^3; uchar hour,minute,second,year,months,date,day;//显示时间寄存器 uchar whour,wminute,wsecond,wyear,wmonths,wdate,wday;//设制初始时间寄存器 uchar code tab
[单片机]
keil or c51 汇编调用c语言函数 容易忽视的问题
最近,在用keil 写一个小程序时,想实践一下从汇编调用 C语言函数,我们都知道C语言调用汇编函数讨论得较多,但反过来,从汇编中调用C语言的函数未见深入分析;在开始的时候,还是忽视了一个问题,就是对现场的保护和还原,以导于程序跑飞。 下面的一个小的测试用例,主要作用是:从C语言程序中调用一个用汇编写的名为int LEDFLASH(int a, int b)。并从该汇编函数中,反过来调言用C语言实现的delay()延时程序。最后的结果是:由P2口控制的LED灯出现闪烁的现象。 C语言源程序所在文件名为:user.c,C语言源程序如下: #include reg52.h void delay(int,int); in
[单片机]
C51中,怎样嵌入汇编语言程序
在C51中,怎样嵌入汇编语言程序?怎样进行处理实现混合编程? 答:其方法是用编译控制指令“#pragma src”、“#pragma asm”和“#pragma endasm”实现。编译控制命令“#pragma src”是控制编译器将C源文件编译成汇编文件,“#pragma src”要放在文件的开始;“#pragma asm”和“#pragma endasm”指示汇编语言程序的开始和结束,分别放在汇编程序段的前面和后面。 对于函数的调用方法,与调用汇编语言中的函数一样, 在C语言文件中调用汇编语言中的函数,必须先声明再调用。声明格式如下: extern 返回值类型 函数名(参数表);
[单片机]
CC2530芯片的主要特性以及它的应用领域
CC2530(无线片上系统单片机)是用于IEEE802.15.4,ZigBee和RF4CE应用的一个真正的片上系统解决方案,它能够以非常低的成本建立起一个强大的无线网络。并且CC530还结合了领先的2.4GHz的RF收发器的优良性能,是业界标准的增强型的8051单片机,所以如果了解过51单片机,要入门CC2530也是很简单的。芯片引脚图如下: 根据芯片内置闪存的容量不容,可以分为四个类型:CC2530F32/64/128/256。编号后缀分别代表:具有32KB/64KB/128KB/256KB的闪存。 CC2530芯片主要特性 1、高性能、低功耗且具有代码预取功能的8051微控制器内核 2、符合2.4GHz IEEE802.1
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved