Keil C51必须注意的一些有趣特性

发布者:梦中徐来最新更新时间:2016-12-15 来源: eefocus关键字: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必须注意的一些有趣特性

上一篇:一种基于C51单片机的非抢占式的操作系统架构
下一篇:基于RS485总线的温湿度监控系统设计

推荐阅读最新更新时间:2024-03-16 15:25

keil 中调用printf 的使用 在软件调试下 serial windows 下显示打印信息
1.软件编程内容 printf 要包含C的头文件#include stdio.h 标准的输入输出; printf 在调用的时候要先重写(不知道是不是重写)putchar 函数,在单片机中用下面的函数, char putchar(char s) { SBUF=s; while(TI==0) { _nop_(); } TI=0; } 在stm32 中用要写fputc 函数 int fputc(int ch, FILE *f) { while((USART1- SR&0X40)==0);//循环发送,直到发送完毕 USART1- DR = (u8) ch; r
[单片机]
<font color='red'>keil</font> 中调用printf 的使用 在软件调试下 serial windows 下显示打印信息
Keil创建新的STM32工程以及CortexM3的位带操作
  上周实验课照例很水,首先是准备工作没做好,J-Link的驱动没装好,而且由于机房电脑本身的问题好多机子无法正确装驱动,或者在进入keil后会弹出莫名错误、闪退等情况,方老师说得好,当我们浪费时间再做这些事情的时候(浪费时间很大程度上是因为机房电脑造成的),好一点的学校早就在写程序了。这么多时间已经浪费了,还有多少能剩下来看代码进而理解它呢?   从新建一个工程开始学习,再加上上周实验课的位带操作相关内容,有需要的同学可以看看,也希望指正相关错误:) 1.新建工程   在keil中新建一个基于51的工程挺简单,不过新建一个STM32工程要复杂一些,多了一些步骤,需要建立更详细的工程目录,导入一些CMSIS(Cortex Mic
[单片机]
<font color='red'>Keil</font>创建新的STM32工程以及CortexM3的位带操作
Keil c51的应用及特性解析
Keil c51号称作为51系列单片机最好的开发环境,大家一定都很熟悉。它的一些普通的特性大家也都了解,(书上也都说有)如:因为51内的RAM很小,C51的函数并不通过堆栈传递参数(重入函数除外),局部变量也不存储在堆栈中,而是存在于固定的RAM中及寄存器中。那么看一下下面的程序。 void fun1(unsigned char i) { } 正常情况参数i通过R7传入函数,那么它的实际地址在什么地方呢?就是R7吗?回答这个问题之前我们先来了解keil c51的几个有趣的特性(不考虑重入函数)。 一、函数在调用前定义与在调用后定义产生的代码是有很大差别的(特别是在优化级别大于3级时)。(本人也不太清楚为什么,大概因为在
[单片机]
keil MDK编译器警告和错误详解
工作后从单片机转成ARM,刚开始用ADS1.2编译器,用了一段时间,因为我接手的项目的老程序正是用ADS编译的,部门也大都在用.在学单片机的时候用的是keil c51编译器,ads和这个编译器在易用性上真是无法比较.后来渐渐知道keil已经被arm公司收购,现在keil MDK成为了arm官方编译器,所以决定重新投奔keil,利用平时的时间,将原程序重新用mdk编译.mdk的优点就没必要说了,在这里把平时遇到的编译器给出的警告和错误信息给出详解,希望给初学者一点帮助,发现错误,需要补充的欢迎留言. 1.warning: #550-D: variable “d” was set but never used 描述:变量’d’定义
[单片机]
关于keil MDK 的配置文件Configuration Wizard
在keil MDK自带的启动代码有一个优势就是可视化的配置选项,就是Configuration wizard选项,这样就省去了我们手动输入配置的问题。比如下面对于Memory的设计的Configuration界面: 而在启动代码中的这个Configuration的程序为: // e Watchdog Timer Setup // h Watchdog Timer Control Register (WTCON) // o1.8..15 Prescaler Value 0-255 // o1.5 Watchdog Timer Enable // o1.3..4 Clock Divis
[单片机]
关于<font color='red'>keil</font> MDK 的配置文件Configuration Wizard
如何在C51文件和汇编语言文件中相互调用?
如何在C51文件和汇编语言文件中相互调用对方文件中的函数? 答:汇编语言文件中对于函数的调用方法,与调用汇编语言中的函数一样,如: LCALL DISPLAY 在C语言文件中调用汇编语言中的函数,必须先声明再调用。声明格式如下: extern 返回值类型 函数名(参数表); 例如: extern unsigned char right_shift ( char, char );
[单片机]
Hash查找法在Keil C51中的实现
摘要:散列(hash)是一种重要的存储方法,也是一种常见的查找方法。它是指在记录的存储位置和它的关键字之间建立一个确定的对应关系。本文以射频卡门禁控制器为例,说明用射频卡卡号作为关键字,用Hash查找法确定此卡能否开门,并给出对应的Keil C51程序。 单片机应用系统中,经常要涉及到数据的存储和查找。以射频卡门禁系统为例,见图1。系统由51系列单片机、射频卡(RF卡)读卡电路、存储单元24C256、继电器等部分组成。其基本原理为:用户刷卡后,RF卡读卡电路读出卡号,通过I/O口线送入单片机。单片机收到卡号后,在存储单元中查找此卡是否允许开门。如允许,则命令继电器动作,打开电磁门锁:如不允许,则返回。 iframe id="
[单片机]
Hash查找法在<font color='red'>Keil</font> <font color='red'>C51</font>中的实现
Keil C51中变量的使用
引言 8051内核单片机是一种通用单片机,在国内占有较大的市场份额。在将C语言用于51内核单片机的研究方面,Keil公司做得最为成功。由于51内核单片机的存储结构的特殊性,Keil C51中变量的使用与标准C有所不同。正确地使用变量,有利于获得高效的目标代码。下面详细介绍Keil C51中变量的使用方法。 1 CPU存储结构与变量的关系 变量都需要有存储空间,存储空间的不同使得变量使用时的工作效率也不同。 标准C的典型运行环境是8086(含IA-32系列)内核,其存储结构是CPU内部有寄存器,外部有存储器,寄存器的访问速度大大高于存储器的访问速度。在标准C中,不加特别定义的变量是放在存储器中的,使用register可以
[工业控制]
<font color='red'>Keil</font> <font color='red'>C51</font>中变量的使用
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

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

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