C51单片机串口驱动设计的常见问题解析

接口/总线/驱动

1114人已加入

描述

1、C51串口的弊端。

C51的串口收发程序相信大家都很熟悉了,在hello.c里面有很简单的例程,不知 道大家有没有注意到hello.c里面有一句很不显眼的语句“TI = 1;” 当你在初始化串口的时候如果你不让TI = 1的话,相信你看到你的数据永远都发不出去,debug里运行stop会看到程序实际上是进行到了while(!TI);的语句处进入死循环了。

深 入一点的看,可以在keil/c51/lib下发现putchar函数的原文件,和许多软件串口驱动一样printf()都是反复调用putchar() 来实现的,所以putchar函数是我们进入死循环的症结。putchar函数很简单,在其中有一个最小实现方式,我就以这个简单的例子来解释。

char putchar(char c){

while(!TI);

TI = 0;

return(SBUF = c);

}

很 显然,C51中缺省的putchar函数是靠查询并等待TI这个标志位来实现串口发送的,也就是说,在putchar函数中确实发送了所有的数据,但是每 发送一个BYTE前都等待了一段时间。这就不难理解为什么在初始化串口的时候必须把TI置位了,无非是想让发第一个数据的时候让putchar函数能顺利 执行。

注意,这里有一个问题出现了,我们可以把UART理解为一个独立的外设,在一次数据装订后就应该交给UART自动完成数据收发,也 就是说宝贵的CPU时间应该不在这里浪费掉,所以我们可以做出这样一个结论C51的putchar函数其实是有弊端的,它在等待TI置位时大大占用了 CPU时间。

2、刨根问底

为什么C51这么别有用心的设计这样一个基础的函数来实现收发呢,为什么必须用TI来支持这个判断,我在写程序的时候发现了一点,其实就是51中UART的一点特性。

51 的UART可以理解为一个自动的串行输出外设,每对SBUF写一个数据就会触发UART的一次串行输出操作,即在定时器分频的基础上逐步移初所有数据位 (包括启始为和结束位等等),移出速度是靠定时器溢出时间来来度量的,所以对于MCU来说这个时间一般都比较长。因此如果在定时器还没有溢出的时候再对 SBUF写数据的话会重新引起这个新数据的发送。这样如果你写

while(1)

SBUF = ‘a’;

其实是没有任何意义的,发出的肯定是乱码。

由于以上的原因我们就可以看出TI确实是上次发送的结束和下次发送的开始的结点。C51也是利用了这样一个特性来实现自己的函数。

3 改进的PUTCHAR函数

缓 冲区是连接告诉设备和低速设备的接口,我们的串口收发其实就是MCU的高速和UART的低速的协同工作,所以我们应该设计一个缓冲区作为数据的暂存位置, 当设备发送数据的时候如果UART正在忙于发上一个数据那么就应该把数据存在BUFF里面,而如果UART不忙了就应该把数据从BUFF里面顺序读出并发 送。

这个正好符合队列的概念,我就设计了一个循环队列来实现这个功能。而在

putchar函数就应该设计成

void putchar(char c)

{

if(UART不忙)

直接发送数据到SBUF

else

把数据写到BUFF里

}

而中断函数则应该写为

ISR(){

……

if(TI){

TI = 0;

if(BUFF里面还有数据)

取下一个数据并发送;

}

新的问题又出现了,什么是UART忙,他与TI的关系如何,是不是TI = 0就是UART忙?

前两个问题先不说,最后一个问题的答案很显然是“no!”,从最极端的角度来看,上电后UART就是空闲,TI也应该等于0!

上面的几个问题从另一个角度也可以得到答案,这里有一点点哲理的问题,一个物品一般只能完成一件事情,既然TI已经作为上次发送的结束和下次发送的开始的结点那么它应该不是作为UART忙的标致。

4、最后的设计概要

从OS的角度来看UART是一种资源,对于我们的程序我们把SBUF看做它的载体,所以对于高速和低速设备的同步问题我们应该引入互斥量来实现对这个资源占用情况的标志。所以我设计的串口驱动里写了一个mutex_sbuf来实现这个功能。

后面的事情就简单了

void putchar(char c)

{

if(mutex_sbuf == 0){

EA = 0

mutex_sbuf = 1;

SBUF = c;

EA = 1

}

else

把数据写到SBUF里面

}

ISR()

{

……

if(TI){

TI = 0;

mutex_sbuf = 0;

if(BUFF里面还有数据){

mutex_sbuf = 1;

取下一个数据并送;

}

}

}

写到这里说的差不多了,没兴致了:( 以后我把程序贴上来供大家参考

希望大家能把我的程序优化一下,我现在的这个版本的driver是用纯C写的,对ROM的占用太大了。以后我会用ASM来改写部分代码。

而且还有一个问题就是C51对于指针的使用很麻烦,程序很容易跑飞,我的代码还不是足够的清晰,因为就是指针的乱跑,所以我在必要的函数里面加了指针类型限定,但是我发现如果都加限定的话反而也会飞。

过两天我会放上来希望大家能一起把这个写好。

写得不错,但我不倾向采用中断发送。因为如果采 用中断发送的话,需要一个发送缓冲区,缓冲区设多大?只设一个字节的话,那么调用putchar的时候是不是先得判断缓冲区非空,如果不空则printf 一类的函数仍然需要等待。缓冲区设很大的话,有两个问题,一是51本来内存就小,二仍然是需要判断缓冲区空不空。考虑再三,还是用的查询发送。到底采用查 询或中断发送,可能要根据自己的需求来选择。

另外,KeilC写的不见得就比asm写的占空间大;c指针跑飞的原因,大部分是指针越界,比如申请了5字节内存,使用了第6个,把别的变量冲掉了等等,不在于你是否加cast,也就是说与强制类型转换无关,要加强对查表等索引指针的检查,确保指针不越界。

确实是这样的,根据项目需要吧,我现在只是想写一个模块出来给大家参考:)

代码量大是因为我用了循环队列,对于buff的操作几乎是透明的,所以几乎不用考虑,还有一点因为我用的是RD2的芯片,所以有ERAM,我对内存的考虑就稍微少了一些。

如果不用中断的话就要写一个scheduler来实现,我想以后要写一个调度器的实现方式,不过我现在不知道怎么模拟串口收数据,好象这个问题比较麻烦,斑竹有没有什么好的想法?

于这个问题我想还是有必要讨论一下的,你可能认为我对buff的校验不到位,所以产生了越界,但是我想问为什么有了casting以后就可以不越界了呢

我对C51不是很熟悉,但是我觉得症结可能在于函数嵌套过多,虽然结构明晰了,但是堆栈不够用了。

另外一个需要注意的是为什么总是跑到IDATA里面,这个我也不解:)

我所有的buff都是在XDATA中的,而且我用了MALLOC函数,但我又用的是compact mode

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分