您的位置:首页 > 编程语言

Keil C51对外设操作的编程——旧文重读

2017-07-01 14:00 246 查看
《单片机与嵌入式系统应用》有两期文章先后探讨了Keil C51对外设操作的编程,今天又读了一遍,觉得很有启发,诚如第二篇文章作者所说:“在设计中遇到问题时.一定不要被表面现象蒙蔽,不要急于解决,应该认真分析,找出问题的原因.这样才能从根本上彻底解决问题”。

先发表的文章(摘自《单片机与嵌入式系统应用》2005年10期)

C语言是当前举世公认的高效简洁而又非常贴近硬件的编程语言之一。将C语言向单片机MCS-51上的移植始于2O世纪8O年代的中后期,经过近1O年的发展,C语言克服了产生代码过长、运行速度较慢的缺点,并且由于C语言在开发速度、软件质量、结构化、可维护性等方面有着汇编语言无法比拟的优势,从而得到日益广泛的应用。KeilC51是德国Keil公司开发的单片机C语言编译系统.该软件功能完备,是目前国内技术开发人员使用最为广泛的语言之一。

在实际工作中发现,用C语言编写的对同一端口进行连续读取的程序,经Keil C51编译后执行结果往往会出错,现以8051单片机读取12位A/D MAX197为例,如图1所示。

图1中,P1.1口用于读取转换完成时A/D发出的中断信号,P1.0对读取高4位或低8位进行选择。现假定A/D 的地址为8000H,启动CH0端口工作字为40H。为得到相应的高、低位转换数据,用C语言编程如下。


unsigned char xdata MAX197 _at_ 0x8000;
sbit MAXINT= P1^1;
sbit MAXHBEN= P1^0;
……
void main()
{unsigned char up4,down8;//设置接收数据的2个变量
……
MAX197= 0X40;//启动A/D CH0口进行转换
while(MAXINT) //等待转换完成
{};
P1.0=0; //读取低8位
down8=MAX197;
P1.0=1; //读取高4位
up4=MAX197;
}


上述的程序并没有如所希望的那样分别得到高、低位数据,实际上在down8和up4中得到的都是低8位的数据。下面是上段C语言经编译后的部分代码。


41: //取低8位
42: MAXHBEN=0;
C:0x000C C290 CLR MAXHBEN(0x90.0)
43: down8=MAX197;
C:0x000E 908000 MOV DPTR,#MAX197(0x8000)
C:0x0011 E0 MOVX A,@DPTR
C:0x0012 F509 MOV 0x09,A
44: //取高4位
45: MAXHBEN=1
C:0x0014 D290 SETB MAXHBEN(0x90.0)
46: up4=MAX197;
47:
48:
C:0x0016 F5O8 MOV 0x08,A //0x08为up4
49: }


通过分析上面的程序会发现,C编译出来的程序并没有在P1.0置为高电位后再去读一次端口,而只是直接将上次读来的结果直接送给高4位变量。如果先读高位后读低位,结果会得到两个高4位数据。为证实这一点,将4条连续重复读取一个外部端口的C语言语句放在一起,编译后发现只有第一条语句被编译执行。也就是说,Keil C51对于连续重复读取同一个端口地址,在编译时进行了“特殊”处理,这一点是十分值得注意的。那么对于确实需要对同一端口进行连续读取的情况应该如何处理呢?下面介绍两种方法以供参考。


第一种方法:加延时。

延时不宜太长,特别是在对转换速度要求较高时。首先写一个延时函数:

void delay()
{unsigned char i;
for (i=0,i<=1;i++);
}


然后将延时程序放在上面两次读取的中间位置。

P1.0=0; //读取低8位
down8=MAX197:
delay();
P1.0=1; //读取高4位
up4=MAX197;


编译后的结果如下:

49: //取低8位
50: MAXHBEN=0:
C:0x000C C29O CLR MAXHBEN(0x90.0)
51: down8=MAX197;
C:0x000E 908000 MOV DPTR,#MAX197(0x8000)
C:0x0011 E0 MOVX A,@DPTR
C:0x0012 F509 MOV 0x09,A
52: delay();
53: //取高4位
C:0x0014 120029 LCALL delay(C:0029)
54: MAXHBEN = 1;
C:0x0017 D290 SETB MAXHBEN(0x90.0)
55:up4=MAX197;
56:
57:
C:0x0019 E0 MOVX A,@DPTR
C:0x001A F508 MOV 0x08,A
58: }


可以看出,在将P1.0置高后,又对端口进行了一次读写,程序正常并得到了高4位。


第二种方法:另设指针。

void main()
{unsigned char up4,down8; //设置接收数据的2个变量
unsinged char xdata *pt1;
pt1=0x8000;
……
MAX197=0X40; //启动A/D CH0口进行转换
while(MAXINT) //等待转换完成
{};
P1.0=0; //读取低8位
down8= MAX197:
P1.0=1; //读取高4位
up4=*pt1:


……

编译的结果如下:

42: //取低8位
43: MAXHBEN=0;
C:0x0010 C290 CLR MAXHBEN(0x90.0)
44: down8=MAX197;
C:0x0012 908000 MOV DPTR,#MAX197(0x8000)
C:0x0015 E0 M0VX A,@DPTR
C:0x0016 F509 MOV 0x09,A
45: MAXHBEN=1:
46: //取高4位
47:
C:0x0018 D290 SETB MAXHBEN(0x90.0)
48: up4=*pt1:
49:
50:
C:0x001A 8F82 MOV DPL(0x82),R7
C:0x001C 8E83 MOV DPH (0x83),R6
C:0x001E E0 MOVX A,@DPTR
C:0x001F F508 MOV 0x08,A


上述两种方法都很好地解决了Keil C51中不能处理对一个端口进行连续读写的问题,但如果对转换速度要求特别高,建议最好使用第二种方法。

第二篇文章(摘自《单片机与嵌入式系统应用》2006年2期)

阅读了《单片机与嵌入式系统应用》2005年第10期杂志《经验交流》栏目的一篇文章《Keil C51对同一端口的连续读取方法》(原文)后,笔者认为该文并未就此问题进行深入准确的分析 文章中提到的两种解决方法并不直接和简单。笔者认为这并非是Keil C51中不能处理对一个端口进行连续读写的问题,而是对Kei1 C51的使用不够熟悉和设计不够细致的问题,因此特撰写本文。

本文中对原文提到的问题,提出了三种不同于原文的解决方法。每种方法都比原文中提到的方法更直接和简单,设计也更规范。(无意批评,请原文作者见谅)

1 问题回顾和分析

原文中提到:在实际工作中遇到对同一端口反复连续读取,Keil C51编译并未达到预期的结果。原文作者对C编译出来的汇编程序进行分析发现,对同一端口的第二次读取语句并未被编译。但可惜原文作者并未分析没有被编译的原因,而是匆忙地采用一些不太规范的方法试验出了两种解决办法。

对此问题,翻阅Keil C51的手册很容易发现:KeilC51的编译器有一个优化设置,不同的优化设置,会产生不同的编译结果。一般情况缺省编译优化设置被设定为8级优化,实际最高可设定为9级优化:

1. Dead code elimination。

2.Data overlaying。

3.Peephole optimization。

4.Register variables。

5.Common subexpression elimination。

6.Loop rotation。

7.Extended Index Access Optimizing。

8.Reuse Common Entry Code。

9.Common Block Subroutines。

而以上的问题,正是由于Keil C51编译优化产生的。因为在原文程序中将外设地址直接按如下定义:

unsigned char xdata MAX197 _at_ 0x8000


采用_at_将变量MAX197定义到外部扩展RAM 指定地址0x8000。因此,Keil C51优化编译理所当然认为重复读第二次是没有用的,直接用第一次读取的结果就可以了,因此编译器跳过了第二条读取语句。至此,问题就一目了然了。


2 解决方法

由以上分析很容易就能提出很好的解决办法。

2.1 最简单最直接的办法

程序一点都不用修改,将Keil C51的编译优化选择设置为0(不优化)就可以了。选择project窗口的Target,然后打开“Options for Target”设置对话框,选择“C51”选项卡,将“Code Optimiztaion”中的“Level”选择为“0:Costant folding”。再次编译后,大家会发现编译结果为:

CLR MAXHBEN
MOV DPTR,#MAX197
MOVX A,@DPTR
MOV R7,A
MOV down8,R7
SETB MAXHBEN
MOV DPTR,#MAX197
MOVX A,@DPTR
MOV R7,A
MOV up4,R7


两次读取操作都被编译出来了。

2.2 最好的方法

告诉Keil C51,这个地址不是一般的扩展RAM,而是连接的设备,具有“挥发”特性,每次读取都是有意义的。可以修改变量定义,增加“volatile”关键字说明其特征:

unsigned char volatile xdata MAX197 _at_ 0x8000;


也可以在程序中包含系统头文件;“#include”,然后在程序中修改变量,定义为直接地址:


#define MAX197 XBYTE[0x8000]


这样,Keil C51的设置仍然可以保留高级优化,且编译结果中,同样两次读取并不会被优化跳过。


2 3 硬件解决方法

原文中将MAX197的数据直接连接到数据总线,而对地址总线并未使用,采用一根端口线选择操作高低字节。很简单的修改方法就是使用一根地址线选择操作高低字节即可。比如:将P2.0(A8)连接到原来P1.0连接的HBEN脚(MAX197的5脚).在程序中分别定义高低字节的操作地址:

unsigned char volatile xdata MAX197_L _at_ 0x8000;
unsigned char volatile xdata MAX197_H _at_ 0x8100;
将原来的程序:
MAXHBEN =0;
down8=MAX197;//读取低8位
MAXHBEN =1;
up4=MAX197;//读取高4位
改为以下两句即可
down8= MAX197_L;//读取低8位
up4=MAX197_H;//读取高4位


3 小结

Keil C51经过长期考验和改进以及大量开发人员的实际使用,已经克服了绝大多数的问题,并且其编译效率也非常高。对于一般的使用.很难再发现什么问题。笔者曾经粗略研究过一下Keil C51优化编洋的结果.非常佩服Keil C51设计者的智慧,一些C程序编译产生的汇编代码.甚至比一般程序员直接用汇编编写的代码还要优秀和简练 通过研读Kell C51编译产生的汇编代码.对提高汇编语言编写程序的水平都是很有帮助的。

由本文中的问题可以看出:在设计中遇到问题时.一定不要被表面现象蒙蔽,不要急于解决,应该认真分析,找出问题的原因.这样才能从根本上彻底解决问题。

附表:Keil C51中的优化级别及优化作用

级别 说明

0 常数合并:编译器预先计算结果,尽可能用常数代替表达式。包括运行地址计算。

优化简单访问:编译器优化访问8051系统的内部数据和位地址。

跳转优化:编译器总是扩展跳转到最终目标,多级跳转指令被删除。

1 死代码删除:没用的代码段被删除。

拒绝跳转:严密的检查条件跳转,以确定是否可以倒置测试逻辑来改进或删除。

2 数据覆盖:适合静态覆盖的数据和位段被确定,并内部标识。BL51连接/定位器可以通过全局数据流分析,选择可被覆盖的段。

3 窥孔优化:清除多余的MOV指令。这包括不必要的从存储区加载和常数加载操作。当存储空间或执行时间可节省时,用简单操作代替复杂操作。

4 寄存器变量:如有可能,自动变量和函数参数分配到寄存器上。为这些变量保留的存储区就省略了。

优化扩展访问:IDATA、XDATA、PDATA和CODE的变量直接包含在操作中。在多数时间没必要使用中间寄存器。

局部公共子表达式删除:如果用一个表达式重复进行相同的计算,则保存第一次计算结果,后面有可能就用这结果。多余的计算就被删除。

Case/Switch优化:包含SWITCH和CASE的代码优化为跳转表或跳转队列。

5 全局公共子表达式删除:一个函数内相同的子表达式有可能就只计算一次。中间结果保存在寄存器中,在一个新的计算中使用。

简单循环优化:用一个常数填充存储区的循环程序被修改和优化。

6 循环优化:如果结果程序代码更快和有效则程序对循环进行优化。

7 扩展索引访问优化:适当时对寄存器变量用DPTR。对指针和数组访问进行执行速度和代码大小优化。

8 公共尾部合并:当一个函数有多个调用,一些设置代码可以复用,因此减少程序大小。

9 公共块子程序:检测循环指令序列,并转换成子程序。Cx51甚至重排代码以得到更大的循环序列。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息