参考: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 <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
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 <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#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 <unistd.h>
//回环测试
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);
}
}