您的位置:首页 > 其它

【串口通信二】串口通信使用的一点心得

2011-08-15 09:47 281 查看
串口是我的最爱,成本低,容易实现,连接简单方便。在我设计的硬件中,无一不配置一个串口,在主要功能完成之后,我会在计算机上再编写一个终端软件。这个软件可不只用来监控,我用这个软件完全控制硬件的所有功能,直到能够监测到硬件尽可能多的状态信息,只要一看这些信息,我就能知道硬件的工作状态如何,故障可能发生在哪里。

近来发现论坛里提问频率较高、问题也五花八门的问题之一就是串口通信问题,因此想在这里写点心得,也只是一点心得,对于网上很容易查到的信息,这里就不谈了。

问题一:缓冲区大小的问题。

现在绝大多数CPU都包含串口功能,同时也为串口配置了FIFO缓冲区,对于FIFO缓冲区的使用存在着些误区。

单就串口来说,通信也分为很多方式,缓冲区大小影响较大的有两种:突发通信和连续的数据流通信。突发通信的主要特点是:数据量小、持续时间短、通信发生时间不确定,通常的设备状态监控就属于这一类型。相对的数据流通信的特点是:数据量大、持续时间长等,如图像通信、音频采集等。

对于突发通信,应该不使用FIFO缓冲区,或者把FIFO大小设置为1。因为在如果把缓冲区大小设置为应该接收的数据的大小,那么发生通信错误如丢失数据时就不会产生中断,接收方会认为未发生通信事件,而发送方只能靠超时认为通信中断。这和接收到数据并判断出通信数据发生错误的性质是不同的,如果接收到数据我们就可以判断出发生错误的原因,如果接收不数据,那么怎么判断,硬件连接?FIFO设置?软件错误?软件存在隐藏的bug?所以对于突发通信不应该使用FIFO。

对于数据流通信,由于数据量大,如果还是用单字节产生中断的方式来接收,那CPU就会疲于处理串口中断事件,而且这种情况下单字节是无法作数据处理的,而使用FIFO缓冲区,一次接收多个字节后再产生中断就会节约CPU时间用于数据处理,这里就不多说了。

问题二:串口通信软件的编写。

好的硬件配上好的软件才能有好的效果,我发现有些编写了几年软件的人串口通信软件也写不好,我这里就把这几年编写串口通信软件的一点小小的心得拿出来分享。

我们常用的TCP/IP协议是一种分层的协议,在编写串口通信软件时也可以借鉴这种分层协议的编程思想。另外对于协议的使用,不同的人有不同的看法,区别较大,这里只介绍相当于物理层和协议层之间的数据链路层程序。

由于没有使用FIFO功能,就要在内存中划出一块内存作为发送缓冲区,由发送中断服务程序把数据发送到接收方。代码如下:

// 定义发送缓冲区大小,可以根据系统RAM的大小的数据量来定义

#define U0TX_BUF_SIZE 1024

// 定义发送缓冲区

char u0tx_buf[U0TX_BUF_SIZE];

// 定义串口发射忙标志,当串口正在发送数据且发送缓冲区不空时为1

char u0tx_busy_bit;

// 缓冲区中数据长度

unsigned int u0tx_length;

// 发送缓冲区数据指针,指向下一个发送的数据

unsigned int u0tx_buf_pointer;

// 准备发送数据

// data是要发送的数据

// length数据的长度

char u0tx_send_packet( char *data, int length )

{

int i;

// u0tx_busy_bit为1时表示串口正在使用,正在使用则退出,

// 避免后续的数据覆盖正在发送的数据

if( u0tx_busy_bit==1 )

{

// 返回0x01表示未能进入串口发送操作

return 0x01;

}

// 把要发送的数据转移到发送缓冲区

for( i=0; i<length; i++ )

u0tx_buf[i] = data[i];

// 数据准备完毕,把u0tx_busy_bit置1,表示正在使用串口

u0tx_busy_bit = 1;

// 设置发送缓冲区中数据的长度

u0tx_length = length;

// 把数据缓冲区的指针指到第一个数据

u0tx_buf_pointer = 0;

// 为了保证程序的可移植性,这里封装了一个串口发送中断触发标志,

// 设置这个标志后,立即进入串口发送中断服务程序

set_u0tx_Int_flag();

// 返回0x00表示成功进行了一次串口发送操作

return 0x00;

}

// 串口发送中断服务程序,逐一把发送缓冲区中的数据发送出去

__interrupt void u0tx_Int_handler( void )

{

// 检查数据是否发送完毕,还有数据则发送另一个字节数据

if( u0tx_buf_pointer < u0tx_length )

{

// 把数据缓冲区指针指向的数据移到发送寄存器

TXBUF0 = u0tx_buf[u0tx_buf_pointer];

// 调整数据缓冲区指针,指向下一个数据

u0tx_buf_pointer++;

}

else

// 数据发送完毕,把u0tx_busy_bit置0

u0tx_busy_bit = 0;

}

在接收端,由于没有使用FIFO功能,同样要在内存中划出一块内存作为接收缓冲区,由接收中断服务程序把数据存入接收缓冲区。这个接收缓冲区是一个先入先出的环形队列,要根据CPU的运行速度和数据流量的大小来设置,太大会浪费内存资源,太小则会出现追尾现象,造成数据丢失。具体代码如下:

#define U0RX_BUF_SIZE 128

// 定义接收缓冲区

char u0rx_buf[U0RX_BUF_SIZE];

// 串口接收缓冲区有数据标志,当接收缓冲区不空时为1

char u0rx_Flag;

// 数据有效标志,因为使用的流通信协议,且包括全范围值,所有要用标志指出数据是否可用

char u0rx_data_valid_flag;

// 接收缓冲区入队列指针,指向下一个存储单元

unsigned int u0rx_in_pointer;

// 接收缓冲区出队列指针,指向下一个存储单元

unsigned int u0rx_out_pointer;

// 串口接收中断服务程序,把接收的数据逐一存入接收缓冲区中

// 在这里使用的是覆盖旧数据模式,可以根据情况加入丢弃新数据的代码

__interrupt void u0rx_Int_handler( void )

{

// 把接收到的数据存入接收缓冲区

u0rx_buf[ u0rx_in_pointer ] = RXBUF0;

// 入队列指针加1,指向下一个存储单元

u0rx_in_pointer ++;

// 如果入队列指针超出了缓冲区尾地址,则指向首地址

if( u0rx_in_pointer>=U0_RX_BUFF_SIZE )

u0rx_in_pointer = 0;

// 标志置1,表示接收缓冲区中有数据

RX0_Flag = 1;

}

// 从串口接收缓冲区中读取一个字节数据

char u0rx_get_data(void)

{

// 临时变量,保存从接收缓冲区中读取的数据

char c;

// 如果出队列指针不等于入队列指针且有效,表示接收缓冲区在有数据,否则为空

if( (u0rx_out_pointer != u0rx_in_pointer) && (u0rx_out_pointer < U0RX_BUFF_SIZE) )

{

// 读取一个字节的数据

c = u0rx_buf[u0rx_out_pointer];

// 出队列指针加1,指向下一个存储单元

u0rx_out_pointer ++;

// 如果出队列指针超出了缓冲区尾地址,则指向首地址

if( u0rx_out_pointer>=U0_RX_BUFF_SIZE )

u0rx_out_pointer = 0;

// 数据有效标志置1

u0rx_data_valid_flag = 1;

// 如果出队列指针等于入队列指针,表示接收缓冲区已空

if( u0rx_out_pointer==u0rx_in_pointer )

u0rx_Flag = 0;

// 返回读取的数据

return c;

}

else

{

// 标志置1,表示接收缓冲区为空

u0rx_Flag = 0;

// 数据有效标志清零

u0rx_data_valid_flag = 0;

// 返回0xff,无意义

return 0xff;

}

}

这部分代码主要是针对无操作系统的单片机软件编写的,但我用的是流通信协议,所以在C#中也经常这样使用。

这是我这几年使用串口的一点心得,如有不当之处欢迎留言。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: