用keil开发51单片机程序的两个技巧

🏠 首页 《无线电》杂志 2005年 🔗 第9期 🔗 第37页 分类:电脑·单片机·通信 🔗 温正伟 🔗

在C语言程序中嵌入汇编语言

C语言虽然在使用上有很多优点,但通常要解决一些速度或时效上的瓶颈问题,汇编语言就有优势了。两者的结合使用可以相互取长补短,使编程更加灵活。Keil的C编译器支持混合编程,那么如何在C语句中嵌入汇编语句呢?

在Keil中嵌入汇编语句的方法很简单。如图1所示,在C文件中要嵌入汇编的地方用“#pragma asm”和“#pragma endasm”分隔开来,这样编译时Keil就知道这中间的一段是汇编了。

图1
图1 🔍原图 (441×170)

在加入汇编的文件中,还要设置编译该文件的选项,见图2。

图2
图2 🔍原图 (441×170)

然后在图3的状态选中Generate Assembler SRC File (生成汇编SRC文件)和Assemble SRC File(封装汇编文件)。选上这两项就可以在C程序中嵌入汇编语句了。设置后在文件图示中将显示三个红色的小方块。

图3
图3 🔍原图 (234×315)

为了能对汇编进行封装,还要在项目中加入相应的封装库文件。在笔者的项目中编译模式是小模式,所以选用C51S.LIB,使用者可以根据不同的编译要求选用不同的库文件,C51S.LIB是最常用的。加入到项目中的方法见图4、图5, 这些库文件在Keil安装目录下的LIB目录中,加好后就可以顺利编译了(注:笔者只在7.0以上版本使用过)。

图4
图4 🔍原图 (234×315)
图5
图5 🔍原图 (440×272)

精确定时

在应用中使用语句延时往往很难达到预期的效果,51芯片中有两个定时器/计数器可以使用,在很多情况下可以使用它们来得到精确的定时值,下面介绍笔者的一些经验。

图6
图6 🔍原图 (400×253)

先假设一个硬件项目,要求在P1.1上输出0.5Hz的方波,要求精确到1~5μs,首先可以得知高低电平各占1秒的时间。最容易想到的思路是用循环来实现,但精度要求高,这个方法实现不了,只有用定时器了。选用12M的晶体,确定使用定时器0的模式1,这个模式下定时器0是16位定时器,也就是最大定时值为FFFFH,12M晶体的每个定时周期为1μs,也就是最多可以定时FFFFH×1μs=65535μs,显然用最大的值也无法一次定时为1秒,那么我们可以把1秒分几十次,再用一个变量去计数,这里分20ms,50次定时中断后就刚好是1秒。也许你很快就可以算出20ms定时中断的定时值 ,FFFFH-20ms/1个机器周期的时间=FFFFH-20ms/1μs=B1DFH。这时可以写下如下的程序:

/调试信息:晶体12M 编译优化级别8级

#include <at89x51.h>

unsigned int TimeCom=0;

void main(void)

€?€?{

EA = 1; //允许CPU中断

ET0 = 1; //定时器0中断打开

TMOD = 0x1; //设定时器0为模式1,16位模式

TH0=0xB1;

TL0=0xDF; //设定时值为20000μs

TR0 = 1; //开始定时

while(1);}

void KeyAndDis_Time0(void) interrupt 1

{TH0=0xB1;

TL0=0xDF; //设定时值

TimeCom++;

if (TimeCom>49)

{P1=~P1;

TimeCom=0;}}

首先我们先调试每20ms中断时的精度,设定调试晶振为12M,编译优化为8级后,先在中断中设定一个断点,再运行,这时可以记下每次中断的时间。

第一次为0.02047100(见图7),第二次为0.04048300,第三次为0.06049500……

可以看出,每中断一次会比定时值长了12μs。为什么会这样呢?查看中断程序的汇编源码可得知,原来出入堆栈要花掉一些时间,如图8所示。

那么我们是不是只要在定时值中减去12μs就行了?原本20000μs这时要减去12μs,得出新的值为B1EBH。这时我们可以把程序作如下更改:

/调试信息:晶体12M 编译优化级别8级

#include <at89x51.h>

unsigned int TimeCom=0;

void main(void)

{EA = 1; //允许CPU中断

ET0 = 1; //定时器0中断打开

TMOD = 0x1; //设定时器0为模式1,16位模式

TH0=0xB1;

TL0=0xDF; //设定时值为20000μs

TR0 = 1; //开始定时

while(1);}

void KeyAndDis_Time0(void) interrupt 1

€?€?{

TH0=0xB1;

TL0=0xEB; //调整定时值

TimeCom++;

if (TimeCom>49)

{P1=~P1;

TimeCom=0;}}

再次调试后得到的运行时间如下:第一次为 0.02047100,第二次为 0.04047100,第三次为 0.06047100……

可以看出,从第一次后每次可以保证20ms中断一次了(这里没有考虑第一次的时间)。现在我们可以来看看每秒的情况。设断点,看每次到达秒计数的时间:第一次为1.00048300(见图9),第二次为2.00048300……第359次为359.00048300……

可以看出,这个程序已符合设计的要求。当然这里的讨论只是在软环境下的理想晶振频率下实现的,在现实中会因晶振偏差等因素而造成误差。同时这样的方法在短小简单的程序中比较适用,应用在多中断或复杂的程序中同样也会造成一定的偏差,这时就要用更好的程序去调整定时值。有兴趣的朋友可以到本刊网站技术交流区或笔者的网站 http://www.cdle.net与更多的爱好者一起讨论Keil UV2在C51开发中的技巧和经验。

文/温正伟