一直以来在串口通信方面都很信任libctb,其对serial/gpib通信支持win/linux,的确好用,但最近实现arm的串口通信时发现ctb还是有点复杂,因此想到构建一个相对简单的串口通信接口,主要依赖于linux的termios.h,termios 结构是在POSIX规范中定义的标准接口,通过设置termios类型的数据结构中的值和函数调用,就可以对终端接口进行控制。更多描述在命令行中man termios 查看。
下面给出本人实践过的接口全源码及测试样例,希望对大家有所帮助。
uart_linux.h
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef _UARTC_H_
#define _UARTC_H_
/***********************************************************************
*copyright 2020-04-06, pyfree
*
*File Name : uart_linux.h
*File Mark :
*Summary :
*linux uart data gather
*
*Current Version : 1.00
*Author : pyfree
*FinishDate :
*
*Replace Version :
*Author :
*FinishDate :
************************************************************************/
#include <termios.h>
namespace pyfree
{
/*
* 打开串口
* @param fd {int } 文件指针
* @param name {char* } 串口名,如"/dev/ttyS*"
* @return {int} 返回<1,异常;返回>0,为fd
*/
int uart_open(int &fd,const char *name);
/*
* 串口配置
* @param fd {int } 文件指针
* @param baude {int } 波特率
* @param c_flow {int } 流标识
* @param bits {int } 数据位
* @param parity {char } 奇偶位
* @param stop {int } 停止位
* @return {int} 返回=-1,异常;返回=0,成功,为读取大小
*/
int uart_config(int fd,int baude,int c_flow, int bits, char parity, int stop);
/*
* 串口读取数据
* @param fd {int } 文件指针
* @param r_buf {char* } 缓存指针
* @param lenth {int } 缓存大小
* @param time_out_ms {等待时间 } 毫秒,默认1000
* @return {int} 返回<=0,异常;返回>0,成功
*/
int uart_read(int fd, char *r_buf, int lenth, int time_out_ms=1000);
/*
* 串口写入数据
* @param fd {int } 文件指针
* @param r_buf {char* } 内容指针
* @param lenth {int } 大小
* @return {int} 返回<=0,异常;返回>0,成功,为写入大小
*/
int uart_write(int fd, char *r_buf, int lenth, int time_out_ms=1000);
/*
*用于清空输入、输出缓冲区
* @param fd {int } 文件指针
* @param model {int } 模式,有三种取值 TCIFLUSH(用于清空输入缓冲区) TCOFLUSH(用于清空输出缓冲区) TCIOFLUSH(用于清空输入输出缓冲区)
* @return {int}
*/
int uart_clear(int fd,int model);
/*
* 关闭串口
* @param fd {int } 文件指针
* @return {int}
*/
int uart_close(int fd);
};
#endif
uart_linux.cpp
#include "uart_unix.h"
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>
#include <termios.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
int pyfree::uart_open(int &fd,const char *name)
{
//检测串口路径是否存在
assert(name);
//以读写形式、不将此终端作为此进程的终端控制器、非阻塞的形式打开串口
fd = open(name,O_RDWR|O_NOCTTY|O_NDELAY);
if(fd == -1)
{
char e_buf[64]={0};
sprintf(e_buf,"uart open %s Failed!",name);
perror(e_buf);
return -1;
}
//设置串口非阻塞,因为这里是以非阻塞形式打开的,第三个参数为0,返回值:成功返回0,失败返回-1,失败原因存入errno
if(fcntl(fd,F_SETFL,0)<0)
{
perror("fcntl Failed!");
return -1;
}
return fd;
};
int pyfree::uart_config(int fd,int baude,int c_flow, int bits, char parity, int stop)
{
struct termios uart;
//用于获取termios结构体属性。成功返回0,失败返回非0
if(tcgetattr(fd,&uart)!=0)
{
perror("tcgetattr Failed!");
return -1;
}
switch(baude)
{
case 4800:
cfsetispeed(&uart,B4800);//设置输入波特率
cfsetospeed(&uart,B4800);//设置输出波特率
break;
case 9600:
cfsetispeed(&uart,B9600);
cfsetospeed(&uart,B9600);
break;
case 19200:
cfsetispeed(&uart,B19200);
cfsetospeed(&uart,B19200);
break;
case 38400:
cfsetispeed(&uart,B38400);
cfsetospeed(&uart,B38400);
break;
default:
fprintf(stderr,"UnkNown baude!");
return -1;
}
switch(c_flow)
{
case 'N':
case 'n':
uart.c_cflag &= ~CRTSCTS;//不进行硬件流控制
break;
case 'H':
case 'h':
uart.c_cflag |= CRTSCTS;//进行硬件流控制
break;
case 'S':
case 's':
uart.c_cflag |= (IXON | IXOFF | IXANY);//进行软件流控制
break;
default:
fprintf(stderr,"UnkNown c_cflag");
return -1;
}
switch(bits)
{
case 5:
uart.c_cflag &= ~CSIZE;//屏蔽其他标志位
uart.c_cflag |= CS5;//数据位为5位
break;
case 6:
uart.c_cflag &= ~CSIZE;
uart.c_cflag |= CS6;
break;
case 7:
uart.c_cflag &= ~CSIZE;
uart.c_cflag |= CS7;
break;
case 8:
uart.c_cflag &= ~CSIZE;
uart.c_cflag |= CS8;
break;
default:
fprintf(stderr,"UnkNown bits!");
return -1;
}
switch(parity)
{
case 'n':
case 'N':
uart.c_cflag &= ~PARENB;//PARENB:产生奇偶校验
uart.c_cflag &= ~INPCK;//INPCK:使奇偶校验起作用
break;
case 's':
case 'S':
uart.c_cflag &= ~PARENB;
uart.c_cflag &= ~CSTOPB;//使用两位停止位
break;
case 'o':
case 'O':
uart.c_cflag |= PARENB;
uart.c_cflag |= PARODD;//使用奇校验
uart.c_cflag |= INPCK;
uart.c_cflag |= ISTRIP;//使字符串剥离第八个字符,即校验位
break;
case 'e':
case 'E':
uart.c_cflag |= PARENB;
uart.c_cflag &= ~PARODD;//非奇校验,即偶校验
uart.c_cflag |= INPCK;
uart.c_cflag |= ISTRIP;
break;
default:
fprintf(stderr,"UnkNown parity!\n");
return -1;
}
switch(stop)
{
case 1:
uart.c_cflag &= ~CSTOPB;//CSTOPB:使用两位停止位
break;
case 2:
uart.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"UnkNown stop!\n");
return -1;
}
uart.c_oflag &= ~OPOST;//OPOST:表示数据经过处理后输出
if(tcsetattr(fd,TCSANow,&uart)<0)//激活配置,失败返回-1
{
return -1;
}
uart.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG );//使串口工作在原始模式下
uart.c_cc[VTIME] = 0;//设置等待时间为0
uart.c_cc[VMIN] = 1;//设置最小接受字符为1
tcflush(fd,TCIFLUSH);//清空输入缓冲区
if(tcsetattr(fd,TCSANow,&uart)<0)//激活配置
{
perror("tcgetattr Failed!");
return -1;
}
return 0;
}
int pyfree::uart_read(int fd, char *r_buf, int lenth, int time_out_ms/*=1000*/)
{
fd_set rfds;
struct timeval time;
ssize_t cnt = 0;
/*将读文件描述符加入描述符集合*/
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
/*设置超时为*/
time.tv_sec = (time_t)(time_out_ms/1000);
time.tv_usec = (long)1000*(time_out_ms%1000);
/*实现多路IO*/
int ret = select(fd+1, &rfds ,NULL, NULL, &time);
switch (ret) {
case -1:
fprintf(stderr,"select error!\n");
break;
case 0:
fprintf(stderr, "time over!\n");
break;
default:
cnt = read(fd, r_buf, lenth);
if(cnt == -1)
{
fprintf(stderr, "read Failed!\n");
return -1;
}
return cnt;
}
return ret;
}
int pyfree::uart_write(int fd, char *r_buf, int lenth, int time_out_ms/*=1000*/)//串口写入数据
{
fd_set rfds;
struct timeval time;
ssize_t cnt = 0;
/*将读文件描述符加入描述符集合*/
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
/*设置超时为*/
time.tv_sec = (time_out_ms/1000);
time.tv_usec = 1000*(time_out_ms%1000);
/*实现多路IO*/
int ret = select(fd+1, &rfds ,NULL, NULL, &time);
switch (ret) {
case -1:
fprintf(stderr,"select error!\n");
break;
case 0:
fprintf(stderr, "time over!\n");
break;
default:
cnt = write(fd, r_buf, lenth);
if(cnt!=lenth)
{
fprintf(stderr, "write Failed!\n");
return -1;
}
return cnt;
}
return ret;
}
int pyfree::uart_clear(int fd,int model)
{
assert(fd);
int ret = tcflush(fd,model);
if(ret<0){
fprintf(stderr, "tcflush Failed!\n");
}
return ret;
}
int pyfree::uart_close(int fd)
{
assert(fd);//assert先检查文件描述符是否存在
close(fd);
return 0;
}
demo_test,如果想在虚拟的linux系统测试,有无实物设备,方案选择如下:
采用"VSPD虚拟串口.zip"串口工具安装并创建一个串口对COM4<->COM5
采用"sscom.exe"工具模拟设备端,打开并设置其端口选择COM5,19200 8 N 1 ,其支持定时发送
(如果没有这两款工具,可以去我个人空间下载:pyfree-IotEdge: c++开发的一套物联网边缘服务系统,当前主要包含采集和调度两个子系统,采集子系统用于设备的供电、态势、IT资源等信息采集,支持串口、网口、蓝牙等采集接口,以及采集数据的分析、中转、级联;调度子系统通过可定制的定期、定时、轮询、条件、启停等任务策略以及可视化监控终端实现设备态势的自动化调度与运维 - Gitee.com)
主机win系统,安装VMware,在该工具创建虚拟机,安装linux系统,
指定使用物理串行端口,选择COM4(前提需要创建虚拟串口对) 则对应虚拟linux系统/dev/ttyS0
#include "uart_unix.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd;
int ret;
char r_buf[256];
bzero(r_buf,256);
fd = pyfree::uart_open(fd, "/dev/ttyS0");//选择的是/dev/ttyS0串口
if(fd == -1)
{
fprintf(stderr,"open Failed!\n");
exit(EXIT_FAILURE);
}
fprintf(stderr,"open success!\n");
if(pyfree::uart_config(fd,19200,'N',8,'N',1) == -1)
{
fprintf(stderr,"configure Failed!\n");
exit(EXIT_FAILURE);
}
fprintf(stderr,"config success!\n");
pyfree::uart_clear(fd,TCIOFLUSH);
while (1) {
ret = pyfree::uart_read(fd,r_buf,256,1000);
if(ret == -1)
{
fprintf(stderr, "uart_read Failed!\n");
exit(EXIT_FAILURE);
}
if(ret>0){
printf("buf:%s\n", r_buf);
// ret = pyfree::uart_write(fd,r_buf,strlen(r_buf));
// if(ret == -1)
// {
// fprintf(stderr, "uart_write Failed!\n");
// exit(EXIT_FAILURE);
// }
}
}
ret = pyfree::uart_close(fd);
if(ret == -1)
{
fprintf(stderr, "close Failed!\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}