Linux串口编程,实现不定长收发数据包

参考:Linux下串口通信详解

https://blog.csdn.net/specialshoot/article/details/50707965

https://blog.csdn.net/specialshoot/article/details/50709257

一、需求:

需要利用串口对两台设备进行数据交互。

要求:数据包大小不定。能够实现阻塞读取每一个数据包。粘包,丢包问题在解析数据包中处理。

二、设计


为了实现不定长接收数据包,利用了 struct termios的两个成员属性: 

newtio.c_cc[VTIME]  = inTimeout;

newtio.c_cc[VMIN]     = inReadLen;

在串口编程模式下,open未设置O_NONBLOCK或O_NDELAY的情况下。
c_cc[VTIME]和c_cc[VMIN]影响read函数的返回阻塞时长。
VTIME定义等待的时间,单位是百毫秒(通常是一个8位的unsigned char变量,取值不能大于cc_t)。
VMIN定义了要求等待的最小字节数,这个字节数可能是0。
如果VTIME取0,VMIN定义了要求等待读取的最小字节数。函数read()只有在读取了VMIN个字节的数据或者收到一个信号的时候才返回。
如果VMIN取0,VTIME定义了即使没有数据可以读取,read()函数返回前也要等待几百毫秒的时间量。这时,read()函数不需要像其通常情况那样要遇到一个文件结束标志才返回0。
如果VTIME和VMIN都不取0,VTIME定义的是当接收到第一个字节的数据后开始计算等待的时间量。如果当调用read函数时可以得到数据,计时器 马上开始计时。
    如果当调用read函数时还没有任何数据可读,则等接收到第一个字节的数据后,计时器开始计时。函数read可能会在读取到VMIN个字节 的数据后返回,也可能在计时完毕后返回,
    这主要取决于哪个条件首先实现。不过函数至少会读取到一个字节的数据,因为计时器是在读取到第一个数据时开始计时 的。
如果VTIME和VMIN都取0,即使读取不到任何数据,函数read也会立即返回。同时,返回值0表示read函数不需要等待文件结束标志就返回了。


为了实现长时间无数据时返回无数据利用了select机制:


1、应用层接口:

打开-配置-使用,妥妥的。

直接上代码:

uart.h

应用层代码采用C++的类封装,实现了配置,阻塞非诸塞IO接口。具体实现参考下面函数实现。

#ifndef _UART_H__
#define _UART_H__
/********************************************************************************************************
【类名】UART
【描述】实现阻塞方式不定长读取数据。不定长阻塞方式写入数据
【属性】
     devFile:GPIO对应的设备文件。
     fd:GPIO设备文件的文件描述符。
【函数】
     UART:	UART类构造函数,打开对应设备文件
     cfg:				配置UART
     write:			写串口
     read:			读串口
********************************************************************************************************/
#include "common.h"
#include 
#include 
#include 
#include 
#include 
class UART
{
public:
    UART(char* cnDevFile);
    ~UART();
    int cfg(int inSpeed,int inBits,int inEvent ,int inStop, int inReadLen, int inTimeout);
    int mwrite(char* pcnBuf, int inLen);
    int mread(char* pcnBuf, int inLen);
    int mselect(int inTimeoutMs);
    int mflush(void);
private:
    int mFd;
    fd_set mRd;
    struct timeval mTimeout;
};

uart.c

具体函数的实现

/********************************************************************************************************
【文件】uart.cpp
【描述】串口读写控制:适用于linux串口设备平台
【时间】2018-6-29
********************************************************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#include 
#include "uart.h"

/********************************************************************************************************
【函数名】getBaudrate
【功  能】返回对应波特率宏定义
【参  数】baudrate			波特率大小
【返回值】波特率宏定义
********************************************************************************************************/
static speed_t getBaudrate(int baudrate)
{
    switch(baudrate) {
    case 0: return B0;
    case 50: return B50;
    case 75: return B75;
    case 110: return B110;
    case 134: return B134;
    case 150: return B150;
    case 200: return B200;
    case 300: return B300;
    case 600: return B600;
    case 1200: return B1200;
    case 1800: return B1800;
    case 2400: return B2400;
    case 4800: return B4800;
    case 9600: return B9600;
    case 19200: return B19200;
    case 38400: return B38400;
    case 57600: return B57600;
    case 115200: return B115200;
    case 230400: return B230400;
    case 460800: return B460800;
    case 500000: return B500000;
    case 576000: return B576000;
    case 921600: return B921600;
    case 1000000: return B1000000;
    case 1152000: return B1152000;
    case 1500000: return B1500000;
    case 2000000: return B2000000;
    case 2500000: return B2500000;
    case 3000000: return B3000000;
    case 3500000: return B3500000;
    case 4000000: return B4000000;
    default: return -1;
    }
}
/********************************************************************************************************
【函数名】UART
【功  能】UART的构造函数,阻塞方式打开一个串口设备文件
【参  数】devFile	:表示UART对应的设备文件
【返回值】无
********************************************************************************************************/
UART::UART(char* devFile)
{
/*
    先以非阻塞方式打开一个串口设备文件:
    O_RDWR:可读可写
    O_NOCTTY:不以终端设备方式打开
    O_NDELAY:非阻塞方式读,无数据时直接返回0
*/
    mFd=open(devFile,O_RDWR | O_NOCTTY | O_NDELAY);
    if (mFd > 0)
    {
/*恢复串口为阻塞状态*/
        if(fcntl(mFd, F_SETFL, 0)<0)
            printf("fcntl failed!\n");
        else
            printf("fcntl=%d\n",fcntl(mFd, F_SETFL,0));
    }else{
        printf("Can't open %s\n",devFile);
        exit(1);
    }
}
UART::~UART()
{
    close(mFd);
}
/********************************************************************************************************
【函数名】UART::cfg
【功  能】配置UART工作参数
【参  数】inSpeed  :串口波特率
         inBits   :数据位
         inEvent  :奇偶校验位
         inStop   :停止位
         inReadLen: 阻塞方式一次读取字节最大长度
         inTimeout:阻塞超时等待时长,单位:inTimeout*100ms
【返回值】返回0表示配置成功;否则表示配置失败
********************************************************************************************************/
int UART::cfg(int inSpeed,int inBits,int inEvent ,int inStop, int inReadLen, int inTimeout)
{
    struct termios newtio,oldtio;
    if ( tcgetattr (mFd,&oldtio)  !=  0 ){
        perror("Setup Serial");
        return -1;
    }
    bzero( &newtio, sizeof( newtio ) );
    newtio.c_cflag |=  CLOCAL|CREAD;
    newtio.c_cflag &= ~CSIZE;
    switch(inBits){
//设置数据位
    case 7:
        newtio.c_cflag |= CS7;
        break;
    case 8:
        newtio.c_cflag |= CS8;
        break;
    }
//设置奇偶校验位
    switch(inEvent){
    case 'O':
        newtio.c_cflag |=PARENB;
        newtio.c_cflag |=PARODD;
        newtio.c_iflag |=(INPCK|ISTRIP);
        break;
    case 'E':
        newtio.c_iflag |=(INPCK|ISTRIP);
        newtio.c_cflag |=PARENB;
        newtio.c_cflag &=~PARODD;
        break;
    case 'N':
        newtio.c_cflag &=~PARENB;
        break;
    }
//设置波特率
    cfsetispeed(&newtio, getBaudrate(inSpeed));
    cfsetospeed(&newtio, getBaudrate(inSpeed));
//停止位设置
    if(inStop==1){
        newtio.c_cflag &= ~CSTOPB;
    }else if(inStop==2){
        newtio.c_cflag |= CSTOPB;
    }
/* 阻塞读取字节设置:
    每读取到inReadLen个字节后read函数返回,
    或者是在接收到不够inReadLen字节时,
    等待时长超过inTimeout*100ms时函数返回
 */
    newtio.c_cc[VTIME]  = inTimeout;
    newtio.c_cc[VMIN] 	= inReadLen;
    tcflush(mFd,TCIFLUSH);

    if((tcsetattr(mFd,TCSANOW,&newtio))!=0){
        perror("Set uart error");
        return -1;
    }

    printf("Set uart done\n");
    return 0;
}
int UART::mflush(void)
{
    return tcflush(mFd,TCIFLUSH);
}
/********************************************************************************************************
【函数名】UART::write
【功  能】往串口设备发送数据
【参  数】pcnBuf :数据缓冲区
         inLen	:数据缓冲区长度
【返回值】返回0表示写入成功;否则表示写入失败
********************************************************************************************************/
int UART::mwrite(char *pcnBuf, int inLen)
{
    int nw;
    nw = write(mFd, pcnBuf, inLen) ;
    if (nw == inLen)
    {
        return 0;
    }else
    {
        printf("Error write inLen = %d,nw = %d\n",inLen,nw);
        return -1;
    }return -1;
}

/********************************************************************************************************
【函数名】UART::read
【功  能】往串口设备读取数据
【参  数】pcnBuf :数据缓冲区
         inLen  :数据缓冲区长度
【返回值】返回0表示读取成功;否则读取失败
********************************************************************************************************/
int UART::mread(char *pcnBuf, int inLen)
{
    int nr;
    nr = read(mFd, pcnBuf, inLen);
    tcflush(mFd,TCIFLUSH);
    if (nr > inLen)
    {
        return 0;
    }else
    {
        printf("Error read inLen = %d,nw = %d\n",inLen,nr);
        return -1;
    }return -1;
}

/********************************************************************************************************
【函数名】UART::mselect
【功  能】以select机制等待数据
【参  数】inTimeoutMs :等待时长
【返回值】0:表示等待超时,-1:执行select失败,>0:数据可读
********************************************************************************************************/
int UART::mselect(int inTimeoutMs)
{
    int retval;
    FD_ZERO(&mRd);
    FD_SET(mFd,&mRd);
    mTimeout.tv_sec = inTimeoutMs/1000;
    mTimeout.tv_usec = (inTimeoutMs%1000)*1000;

    retval = select(mFd+1, &mRd, NULL, NULL, &mTimeout);
    return (retval);
}

测试程序:main.cpp 

测试:短接TX,和RX引脚,实现回显。然后

#include "uart.h"
#include 
//回环测试
int main(void)
{
	int retval;
	UART *mCom = new UART("/dev/ttymxc1");
	char str[] = "This is a example project"
	char recBuf[50];
	
    mCom->cfg(115200,8,'N',1,50,1);
    while(1)
    {
        retval = mCom->mwrite(str,strlen(str)+1);
        if (retval == 0)
        {
            retval = mCom->mselect(1000);
            if (retval > 0)
            {
                retval = mCom->mread(recBuf,50);
                if (retval == 0)
                {
                    printf("Rec Str = %s\n",recBuf);
                }else
                {
                    printf("Read error:%d",retval);
                }
            }else
            {
                printf("Timeout:%d",retval);
            }
        }else
        {
            printf("Write error:%d",retval);
        }
        sleep(1);
    }
}

 

你可能感兴趣的:(Linux设备)