Linux C++ 串口编程 详解+实例
大致步骤
Linux下的串口编程其实与Windows下无本质差别,说白了就是类似读写文件一样,对串口进行读写操作,用到的函数无非就是open,close,read,write函数等。具体的读写操作可分为下面四个步骤
- 打开串口
- 配置串口
- 读写串口
- 关闭串口
打开串口
打开串口使用的是open函数,后缀第一个表示读写,第二个表示不允许进程干涉串口的读写操作(不太明白,实测发现终端输入会导致读写出错),第三个表示非阻塞。打开串口默认都是阻塞形式,使用标志位O_NDELAY可以设置为非阻塞模式,或者使用系统函数fcntl进行设置。
fd = open(“/dev/ttyS*”, O_RDWR | O_NOCTTY | O_NDELAY);
open可以接受的标志位如下:
O_RDONLY只读模式
O_WRONLY只写模式
O_RDWR读写模式
上述三个标志位必选其一
………………………………………………………………………………………………………………………………………………….
O_APPEND每次写操作都写入文件的末尾
O_CREAT如果指定文件不存在,则创建这个文件
O_EXCL如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)
下面三个常量同样是选用的,他们用于同步输入输出
O_DSYNC等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC读(read)等待所有写入同一区域的写操作完成后再进行
O_SYNC等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
对于串口的打开操作,必须使用O_NOCTTY参数,它表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务的一个输入(比如键盘终止信号等)都会影响进程。
O_NDELAY表示不关心DCD信号所处的状态(端口的另一端是否激活或者停止)
打开串口后的句柄 “fd” 即代表了此串口,后续操作都是对句柄进行。
配置串口
串口配置主要包括波特率的配置,校验位,停止位的设置等等。具体如下:
struct termios Opt; //创建设置结构体,配置串口即改变结构体内元素的内容
tcgetattr(fd, &Opt); //保存原有的配置,保存在Opt中
tcflush(fd, TCIOFLUSH); //刷清输入输出队列
cfsetispeed(&Opt, B9600); //设置输入波特率
cfsetospeed(&Opt, B9600); //设置输出波特率
波特率可以设置115200,9600等等,值得注意的是在设置波特率的时候要在前面加B。接下来设置停止位,校验位等等,依旧使用上述创建的结构体:
Opt.c_cflag &= ~CSIZE; //屏蔽数据位
Opt.c_cflag |= CS8; // 数据位为 8 ,CS7 for 7
Opt.c_cflag &= ~CSTOPB; // 一位停止位, 两位停止为 |= CSTOPB
Opt.c_cflag &= ~PARENB; // 无校验
Opt.c_cflag |= PARENB; //有校验
Opt.c_cflag &= ~PARODD; // 偶校验
Opt.c_cflag |= PARODD; // 奇校验
接下来设置一些时间参数:
Opt.c_cc[VTIME] = 1; //设置等待时间
Opt.c_cc[VMIN] = 100; //设置最小字节数
时间参数只有在设置为阻塞模式下才能使用,VMIN代表读取的最小字节数,当VTIME参数设置为0时,只有当串口内的数据达到VMIN长度时,read函数才会进行读取操作;
当时间参数VTIME不为0时,若串口内到达的数据未到达VMIN,read函数将等待VTIME时间之后对数据进行读取;
另一种监测串口是否有数据到达的函数是select,使用此参数可以在确定串口内有数据时才对串口数据进行读写。
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
retval =select(fd+1,&rfds,NULL,NULL,NULL);
上述代码通过设置句柄rfds来对fd进行监控,并根据fd的状态,select函数将返回不同的值。其中timeout为超时等待时间,若不希望函数阻塞可以写为:
retval =select(fd+1,&rfds,NULL,NULL,&timeout);
读写串口
读函数:
res = read(fd,buf,len);
写函数:
n = write(fd, "str", len);
注意,当读写出错是函数将返回值-1。
关闭串口
关闭串口简单粗暴:
close(fd);
程序实例
bool connectDevice(){
fd = open( SerialPort, O_RDWR|/*O_NOCTTY|*/O_NDELAY);
if (-1 == fd)
{
ROS_WARN("Redsky UART:Can't Open Serial Port");
return(-1);
}
// flushing is to be done after opening. This prevents first read and write to be spam'ish.
tcflush(fd, TCIOFLUSH);
int n = fcntl(fd, F_GETFL, 0);
//恢复为阻塞模式
fcntl(fd, F_SETFL, n & ~O_NDELAY);
portSet(fd,9600,8,"None","1",false,false);
connected = true;
return 1;
}
int portSet(int m_fd, int baudrate, int databits, const std::string & parity, const std::string & stop, bool softwareHandshake, bool hardwareHandshake)
{
struct termios newtio;
// memset(&newtio, 0, sizeof(newtio));
if (tcgetattr(m_fd, &newtio)!=0)
{
//std::cerr<<"tcgetattr() 3 failed"<<std::endl;
return -1;
}
//printf("tcgetattr() 3 success!\n");
speed_t _baud=0;
switch (baudrate)
{
#ifdef B0
case 0: _baud=B0; break;
#endif
#ifdef B50
case 50: _baud=B50; break;
#endif
#ifdef B75
case 75: _baud=B75; break;
#endif
#ifdef B110
case 110: _baud=B110; break;
#endif
#ifdef B134
case 134: _baud=B134; break;
#endif
#ifdef B150
case 150: _baud=B150; break;
#endif
#ifdef B200
case 200: _baud=B200; break;
#endif
#ifdef B300
case 300: _baud=B300; break;
#endif
#ifdef B600
case 600: _baud=B600; break;
#endif
#ifdef B1200
case 1200: _baud=B1200; break;
#endif
#ifdef B1800
case 1800: _baud=B1800; break;
#endif
#ifdef B2400
case 2400: _baud=B2400; break;
#endif
#ifdef B4800
case 4800: _baud=B4800; break;
#endif
#ifdef B7200
case 7200: _baud=B7200; break;
#endif
#ifdef B9600
case 9600: _baud=B9600; break;
#endif
#ifdef B14400
case 14400: _baud=B14400; break;
#endif
#ifdef B19200
case 19200: _baud=B19200; break;
#endif
#ifdef B28800
case 28800: _baud=B28800; break;
#endif
#ifdef B38400
case 38400: _baud=B38400; break;
#endif
#ifdef B57600
case 57600: _baud=B57600; break;
#endif
#ifdef B76800
case 76800: _baud=B76800; break;
#endif
#ifdef B115200
case 115200: _baud=B115200; break;
#endif
#ifdef B128000
case 128000: _baud=B128000; break;
#endif
#ifdef B230400
case 230400: _baud=B230400; break;
#endif
#ifdef B460800
case 460800: _baud=B460800; break;
#endif
#ifdef B576000
case 576000: _baud=B576000; break;
#endif
#ifdef B921600
case 921600: _baud=B921600; break;
#endif
default:
// case 256000:
// _baud=B256000;
break;
}
cfsetospeed(&newtio, (speed_t)_baud);
cfsetispeed(&newtio, (speed_t)_baud);
/* We generate mark and space parity ourself. */
if (databits == 7 && (parity=="Mark" || parity == "Space"))
{
databits = 8;
}
switch (databits)
{
case 5:
newtio.c_cflag = (newtio.c_cflag & ~CSIZE) | CS5;
break;
case 6:
newtio.c_cflag = (newtio.c_cflag & ~CSIZE) | CS6;
break;
case 7:
newtio.c_cflag = (newtio.c_cflag & ~CSIZE) | CS7;
break;
case 8:
default:
newtio.c_cflag = (newtio.c_cflag & ~CSIZE) | CS8;
break;
}
newtio.c_cflag |= CLOCAL | CREAD;
//parity
newtio.c_cflag &= ~(PARENB | PARODD);
if (parity == "Even")
{
newtio.c_cflag |= PARENB;
}
else if (parity== "Odd")
{
newtio.c_cflag |= (PARENB | PARODD);
}
//hardware handshake
/* if (hardwareHandshake)
newtio.c_cflag |= CRTSCTS;
else
newtio.c_cflag &= ~CRTSCTS;*/
newtio.c_cflag &= ~CRTSCTS;
//stopbits
if (stop=="2")
{
newtio.c_cflag |= CSTOPB;
}
else
{
newtio.c_cflag &= ~CSTOPB;
}
// newtio.c_iflag=IGNPAR | IGNBRK;
newtio.c_iflag=IGNBRK;
// newtio.c_iflag=IGNPAR;
//software handshake
if (softwareHandshake)
{
newtio.c_iflag |= IXON | IXOFF;
}
else
{
newtio.c_iflag &= ~(IXON|IXOFF|IXANY);
}
newtio.c_lflag=0;
newtio.c_oflag=0;
newtio.c_cc[VTIME]=1;
newtio.c_cc[VMIN]=60;
// tcflush(m_fd, TCIFLUSH);
if (tcsetattr(m_fd, TCSANOW, &newtio)!=0)
{
std::cerr<<"tcsetattr() 1 failed"<<std::endl;
}
int mcs=0;
ioctl(m_fd, TIOCMGET, &mcs);
mcs |= TIOCM_RTS;
ioctl(m_fd, TIOCMSET, &mcs);
if (tcgetattr(m_fd, &newtio)!=0)
{
//std::cerr<<"tcgetattr() 4 failed"<<std::endl;
}
//hardware handshake
if (hardwareHandshake)
{
newtio.c_cflag |= CRTSCTS;
}
else
{
newtio.c_cflag &= ~CRTSCTS;
}
/* if (on)
newtio.c_cflag |= CRTSCTS;
else
newtio.c_cflag &= ~CRTSCTS;*/
if (tcsetattr(m_fd, TCSANOW, &newtio)!=0)
{
//std::cerr<<"tcsetattr() 2 failed"<<std::endl;
}
//printf("tcsetattr() 2 success!\n");
return 1;
}
int readRev(char* revBuf){
//使用select设置阻塞时间
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
timeval timeout;
timeout.tv_sec = 1; //秒
timeout.tv_usec = 0; //微秒
//int retval = select(fd+1, &rfds, NULL, NULL, &timeout);
int retval = select(fd+1, &rfds, NULL, NULL, &timeout);
if(retval <= 0){
ROS_WARN("No data receive from port, select error");
return -1;//"noData";
}
else{
int len = read(fd, revBuf, MAXBUF);
}
}