分享

串口通信的心得

 心不留意外尘 2016-06-07
http://blog.csdn.net/zd394071264/article/details/8512730
2013

串口是我的最爱,成本低,容易实现,连接简单方便。在我设计的硬件中,无一不配置一个串口,在主要功能完成之后,我会在计算机上再编写一个终端软件。这个软件可不只用来监控,我用这个软件完全控制硬件的所有功能,直到能够监测到硬件尽可能多的状态信息,只要一看这些信息,我就能知道硬件的工作状态如何,故障可能发生在哪里。
    近来发现论坛里提问频率较高、问题也五花八门的问题之一就是串口通信问题,因此想在这里写点心得,也只是一点心得,对于网上很容易查到的信息,这里就不谈了。
    问题一:缓冲区大小的问题。
    现在绝大多数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#中也经常这样使用。
    这是我这几年使用串口的一点心得,如有不当之处欢迎留言。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多