一直以來在串口通信方面都很信任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 ,其支持定時發送
(如果沒有這兩款工具,可以去我個人空間下載:https://gitee.com/pyzxjfree/pyfree-IotEdge/tree/master/demo-project/tool)
主機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);
}