现在的位置: 首页 > 综合 > 正文

linux串口编程 非规范模式 read()问题

2013年10月21日 ⁄ 综合 ⁄ 共 5591字 ⁄ 字号 评论关闭

 在linux下编写终端程序时,有规范模式 ,非规范模式(原始模式特殊的非规范模式)之分。不用于终端,而是在串口这种使用情况下,一般设置为原始模式(非规范的一种特殊情况)。但用read()函数,希望从串口接收指定的数量的字符时,往往接收到的实际字符数,都与指定的不同。如本人用read()希望接收
10 bytes的数据,但实验后发现,分了几次才接收到,俩次接收2bytes ,两次接收3bytes。


查阅相关资料得知:


一般地串口的读写模式有直接模式和缓存模式,在直接模式下,串口的读写都是单字节的,也就是说一次的read或write只能操作一个字节;


        但是大部份串口芯片都支持缓存模式,缓存模式一般同时支持中断聚合和超时机制,也就是说在有数据时,当缓存满或者超时时间到时,都会触发读或写中断。写的时候可以将要操作的数据先搬到缓存里,然后启动写操作,芯片会自动将一连串的数据写出,在读的时候类似,一次读到的是串口芯片缓存里的数据。串口设备的缓存一般有限,一次能read到的最大字节数就是缓存的容量。所以串口芯片的缓存容量决定了你一次能收到的字节数。本人用一个usb转232来充当串口接收时,发现一次可以接收8个bytes。


 


        对于具体一次传输多少字节也不去追究了,总之通讯过程中无法保证一次发送的数据肯定是一次接收的,所以必须写代码 来一次一次的接收,直到接收满足预定的为止,当然在此过程中得使用select/poll来避免超时接收。


 



即从通讯的角度来说,接受方必须自己解决如何识别一个祯的问题。

(操作串口相当于操作物理层,OSI/ISO模型中的第一层,解决祯同步问题是第二层的任务,所以我们需要自己搭一个第二层。

也就是说:我们需要通过定义通讯协议,规定数据的内容自己分析什么时候收完了一次需要的数据。因为通讯过程中无法保证一次发送的数据肯定是一次接收的)


 


下面来解决识别帧的问题:


 


           不是编写终端,我们一帮都采用原始模式;进行简单的串口编程,一般设置成阻塞模式,便可以了。但是在大多数应用场合,把串口设置成阻塞模式是很不实用的,如read()时,如果没有数据发来,这程序一直会阻塞在这里(除非用多线程)。因此一般把其设置为非阻塞模式。一般是需要用串口读取指定长度的数据,但是read函数实际读取的数据长度,往往会与指定的不同,所以必须自己编写一个读写N字节数据的函数:


             很快想到用个循环,但是循环中必须有 ‘即使一直没有收到指定长度的数据但在一定时间后也必须跳出循环’的机制,否则就与阻塞模式的没有区别了(也就是让函数一直等,等到指定长度数据接收为止)。参考下APUE的程序清单14-11的readn()函数,此函数看似很好,但是它不适合用于串口的读取,因为它一旦if(nread
= read(fd, ptr, nleft) < 0) 就立刻
会跳出循环,没有丝毫的时间上的容限,而串口的接收必然没有这么快,如若波特率为1200,是比较慢的。俩个字节传输的间隔,其都会被判断为错误而跳出。当然该函数对于读写文件是非常好用的。


ssize_t             /* Read "n" bytes from a descriptor  */

readn(int fd, void *ptr, size_t n)

{

 size_t  nleft;

 ssize_t  nread;




 nleft = n;

 while (nleft > 0) {

  if ((nread = read(fd, ptr, nleft)) < 0) {

   if (nleft == n)

    return(-1); /* error, return -1 */

   else

    break;      /* error, return amount read so far */

  } else if (nread == 0) {

   break;          /* EOF */

  }

  nleft -= nread;

  ptr   += nread;

 }

 return(n - nleft);      /* return >= 0 */

}


         再次参考下APUE的tread() 和treadn()函数,这组函数结合了select函数,使得在放弃之前,有了个时间来阻塞。有了一定的时间容限。例如把select中的tv.tv_sec
= 1;这样就不会把 原本正常的俩个字节的时间间隔,误判为错误了。


ssize_t

tread(int
fd, void *buf, size_t nbytes, unsigned int timout)

{


       int                        
nfds;

       fd_set                  
readfds;

       struct
timeval  tv;

       tv.tv_sec
= timout;

       tv.tv_usec
= 0;

       FD_ZERO(&readfds);

       FD_SET(fd,
&readfds);

       nfds
= select(fd+1, &readfds, NULL, NULL, &tv);

       if
(nfds <= 0) {

             
if (nfds == 0)

                    
errno = ETIME;

             
return(-1);

       }

       return(read(fd,
buf, nbytes));

}


 ssize_t

treadn(int
fd, void *buf, size_t nbytes, unsigned int timout)


{

       size_t     
nleft;

       ssize_t    
nread;

       nleft
= nbytes;

       while
(nleft > 0) {

         
    if ((nread = tread(fd, buf, nleft, timout)) < 0) {

                    
if (nleft == nbytes)

                           
return(-1); /* error, return -1 */

                    
else

                           
break;      /* error, return amount read so far */

             
} else if (nread == 0) {

                    
break;          /* EOF */

         
    }

             
nleft -= nread;

             
buf += nread;

       }

       return(nbytes
- nleft);      /* return >= 0 */

}


实际应用如:


某个串口通信协议一帧为10个字节,linux 必须接收1帧后去解析该帧的命令。波特率1200 。在linux中必须有个读取一帧数据的函数,该函数不能‘一直等待接收10个字节’,而必须在一定时间内没有收到完整的一帧就放弃该帧,这样才能防止对方发送错误或者通信中的错误带来的问题。
利用treadn()很好的配合该思路的实现。可以定时限为10ms。如果超过10ms(可以设置长点)
这treadn()也会返回,这时判断如果实际收到的数据小于10,则丢弃即可。本人用1200的波特率,tv设置成了500us,工作的很好。


 


最后贴一个经典的串口编程基础:


 


1.串口操作需要的头文件

#include <stdio.h>         //标准输入输出定义

#include <stdlib.h>        //标准函数库定义

#include <unistd.h>       //Unix标准函数定义

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>          //文件控制定义

#include <termios.h>     //POSIX中断控制定义

#include <errno.h>        //错误号定义

2.打开串口


串口位于/dev中,可作为标准文件的形式打开,其中:

串口1 /dev/ttyS0

串口2 /dev/ttyS1

代码如下:

int fd;

fd 
= open(“/dev/
ttyS0”, O_RDWR);
if(fd == -1
)

{

    Perror(“串口1打开失败!”);

}
//
else

    
//fcntl(fd, F_SETFL, FNDELAY);

除了使用O_RDWR标志之外,通常还会使用O_NOCTTY和O_NDELAY这两个标志。

O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。

O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。

3.设置波特率


最基本的串口设置包括波特率、校验位和停止位设置,且串口设置主要使用termios.h头文件中定义的termios结构,如下:

struct termios

{

   tcflag_t  c_iflag;   //输入模式标志

   tcflag_t  c_oflag;  //输出模式标志

   tcflag_t  c_cflag;  //控制模式标志

   tcflag_t  c_lflag;   //本地模式标志

   cc_t   c_line;              //line discipline

   cc_t   c_cc[NCC];    //control characters

}

代码如下:

int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {3840019200960048002400120030038400192009600480024001200300
, };

void SetSpeed(int fd, int
 speed)

{

    
int
 i;

    
struct termios Opt;    //定义termios结构


    
if(tcgetattr(fd, &Opt) != 0)

    {

        perror(“tcgetattr fd”);

        
return
;

    }

    
for(i = 0; i < sizeof(speed_arr) / sizeof(int); i++
)

    {

        
if(speed ==
 name_arr[i])

        {

            tcflush(fd, TCIOFLUSH);

            cfsetispeed(
&
Opt, speed_arr[i]);

            cfsetospeed(
&
Opt, speed_arr[i]);

            
if(tcsetattr(fd, TCSANOW, &Opt) != 0
)

            {

                perror(“tcsetattr fd”);

                
return
;

            }

            tcflush(fd, TCIOFLUSH);

        }

    }

}

注意tcsetattr函数中使用的标志:

TCSANOW:立即执行而不等待数据发送或者接受完成。

TCSADRAIN:等待所有数据传递完成后执行。

TCSAFLUSH:Flush input and output buffers and make the change

4.设置数据位、停止位和校验位


以下是几个数据位、停止位和校验位的设置方法:(以下均为1位停止位)

8位数据位、无校验位:

Opt.c_cflag &= ~PARENB;

Opt.c_cflag &= ~CSTOPB;

Opt.c_cflag &= ~CSIZE;

Opt.c_cflag |= CS8;

7位数据位、奇校验:

Opt.c_cflag |= PARENB;

Opt.c_cflag |= PARODD;

Opt.c_cflag &= ~CSTOPB;

Opt.c_cflag &= ~CSIZE;

Opt.c_cflag |= CS7;

7位数据位、偶校验:

Opt.c_cflag |= PARENB;

Opt.c_cflag &= ~PARODD;

Opt.c_cflag &= ~CSTOPB;

Opt.c_cflag &= ~CSIZE;

Opt.c_cflag |= CS7;

7位数据位、Space校验:

Opt.c_cflag &= ~PARENB;

Opt.c_cflag &= ~CSTOPB;

Opt.c_cflag &= ~CSIZE;

Opt.c_cflag |= CS7;

代码如下:

int SetParity(int fd, int databits, int stopbits, int parity)

{

    
struct
 termios Opt;

    
if(tcgetattr(fd, &Opt) != 0
)

    {

        perror(
"tcgetattr fd"
);

        
return
 FALSE;

    }

   Opt.c_cflag 
|= (CLOCAL 

抱歉!评论已关闭.