- uart串口通信概念
- 数据结构
- termios作用与设置
- 串口读写实现
UART串口通信概念
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作uart,是一种异步收发传输器,uart作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。
数据结构
起始位 :表示传输字符的开始
数据位 :数据位的个数可以设置为4,5,6,7,8,构成一个字符。
奇偶校验位: 数据位加上奇偶校验位后,逻辑“1”的数量为偶数则为偶校验。为奇数则为奇校验。在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不是真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
停止位:它是一帧数据的结束标志。用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
空闲位:没有数据传输时,为逻辑1
传输方向:数据从高位(msb)开始传输还是从低位(lsb)开始传输
termios作用与设置
函数描述了用于控制异步通信端口的通用终端接口。参见Linux API termios描述
引用头文件
#include "termios.h"
结构体:
struct termios {
tcflag_t c_iflag; /* 输入模式标志*/
tcflag_t c_oflag; /* 输出模式标志*/
tcflag_t c_cflag; /* 控制模式标志*/
tcflag_t c_lflag; /* 区域模式标志或本地模式标志或局部模式*/
cc_t c_line; /* 行控制line discipline */
cc_t c_cc[NCCS]; /* 控制字符特性*/
};
c_iflag : 控制终端输入方式
参数 | 参数说明 |
---|---|
IGNBRK | 忽略BREAK键输入 |
BRKINT | 如果设置了IGNBRK,BREAK键输入将被忽略 |
IGNPAR | 忽略奇偶校验错误 |
PARMRK | 标识奇偶校验错误 |
INPCK | 允许输入奇偶校验 |
ISTRIP | 去除字符的第8个比特 |
INLCR | 将输入的NL(换行)转换成CR(回车) |
IGNCR | 忽略输入的回车 |
ICRNL | 将输入的回车转化成换行(如果IGNCR未设置的情况下) |
IUCLC | 将输入的大写字符转换成小写字符(非POSIX) |
IXON | 允许输入时对XON/XOFF流进行控制 |
IXANY | 输入任何字符将重启停止的输出 |
IXOFF | 允许输入时对XON/XOFF流进行控制 |
IMAXBEL | 当输入队列满的时候开始响铃 |
IUTF9 | 当输入是UTF8时,能够允许字符擦除在加工模式下正确执行 |
c_oflag:输出模式标志,控制终端输出方式
参数 | 参数说明 |
---|---|
OPOST | 处理后输出 |
OLCUC | 将输入的小写字符转换成大写字符(非POSIX) |
ONLCR | 将输入的NL(换行)转换成CR(回车)及NL(换行) |
OCRNL | 将输入的CR(回车)转换成NL(换行) |
ONOCR | 第一行不输出回车符 |
ONLRET | 不输出回车符 |
OFILL | 发送填充字符以延迟终端输出 |
OFDEL | 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符为NUL |
NLDLY | 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s) |
CRDLY | 回车延迟,取值范围为:CR0、CR1、CR2和 CR3 |
TABDLY | 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3 |
BSDLY | 空格输出延迟,可以取BS0或BS1 |
VTDLY | 垂直制表符输出延迟,可以取VT0或VT1 |
FFDLY | 换页延迟,可以取FF0或FF1 |
c_cflag:控制模式标志,指定终端硬件控制信息
参数 | 参数说明 |
---|---|
CBAUD | 波特率(4+1位)(非POSIX) |
CBAUDEX | 附加波特率(1位)(非POSIX) |
CSIZE | 数据位长度,取值范围为CS5、CS6、CS7或CS8 |
CSTOPB | 设置两个停止位 |
CREAD | 使用接收器 |
PARENB | 使用奇偶校验 |
PARODD | 对输入使用奇偶校验,对输出使用偶校验 |
HUPCL | 关闭设备时挂起 |
CLOCAL | 忽略调制解调器线路状态 |
CRTSCTS | 使用RTS/CTS流控制 |
c_lflag:本地模式标志,控制终端编辑功能
参数 | 参数说明 |
---|---|
ISIG | 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号 |
ICANON | 使用标准输入模式 |
XCASE | 在ICANON和XCASE同时设置的情况下,终端只使用大写。 |
ECHO | 显示输入字符 |
ECHOE | 如果ICANON同时设置,ERASE将删除输入的字符 |
ECHOK | 如果ICANON同时设置,KILL将删除当前行 |
ECHONL | 如果ICANON同时设置,即使ECHO没有设置依然显示换行符 |
ECHOPRT | 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX) |
TOSTOP | 向后台输出发送SIGTTOU信号 |
c_cc[NCCS]:控制字符,用于保存终端驱动程序中的特殊字符,如输入结束符等
NCCS | 参数说明 |
---|---|
VINTR | Interrupt字符 |
VEOL | 附加的End-of-file字符 |
VQUIT | Quit字符 |
VTIME | 非规范模式读取时的超时时间 |
VERASE | Erase字符 |
VSTOP | Stop字符 |
VKILL | Kill字符 |
VSTART | Start字符 |
VEOF | End-of-file字符 |
VSUSP | Suspend字符 |
VMIN | 非规范模式读取时的最小字符数 |
注意:控制符VTIME和VMIN之间有复杂的关系。VTIME定义要求等待的时间(百毫米,通常是unsigned
char变量),而VMIN定义了要求等待的最小字节数(相比之下,read函数的第三个参数指定了要求读的最大字节数)。
如果VTIME=0,VMIN=要求等待读取的最小字节数,read必须在读取了VMIN个字节的数据或者收到一个信号才会返回。
如果VTIME=时间量,VMIN=0,不管能否读取到数据,read也要等待VTIME的时间量。
如果VTIME=时间量,VMIN=要求等待读取的最小字节数,那么将从read读取第一个字节的数据时开始计时,并会在读取到VMIN个字节或者VTIME时间后返回。
如果VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回
tcsetattr函数用于设置终端的相关参数。
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
参数fd为打开的终端文件描述符
参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数optional_actions可以取如下的值:
参数 | 参数说明 |
---|---|
TCSANOW | 不等数据传输完毕就立即改变属性。 |
TCSADRAIN | 等待所有数据传输结束才改变属性。 |
TCSAFLUSH | 清空输入输出缓冲区才改变属性。 |
返回值 | 参数说明 |
---|---|
EBADF | 非法的文件描述符。 |
EINTR | tcsetattr函数调用被信号中断。 |
EINVAL | 参数optional_actions使用了非法值,或参数termios中使用了非法值。 |
ENCTTY | 非终端的文件描述符。 |
tcgetattr获取与fd终端关联的termios
int tcgetattr(int fd, struct termios *termios_p);
tcsetattr重新设置与fd终端关联的termios
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
设置波特率
int cfsetispeed(struct termios *termios_p, speed_t speed); //输入波特率
int cfsetospeed(struct termios *termios_p, speed_t speed); //输出波特率
常见配置:
8位数据位、无校验位:
c_cflag &= ~PARENB;
c_cflag &= ~CSTOPB;
c_cflag &= ~CSIZE;
c_cflag |= CS8;
7位数据位、奇校验:
c_cflag |= PARENB;
c_cflag |= PARODD;
c_cflag &= ~CSTOPB;
c_cflag &= ~CSIZE;
c_cflag |= CS7;
7位数据位、偶校验:
c_cflag |= PARENB;
c_cflag &= ~PARODD;
c_cflag &= ~CSTOPB;
c_cflag &= ~CSIZE;
c_cflag |= CS7;
7位数据位、Space校验:
c_cflag &= ~PARENB;
c_cflag &= ~CSTOPB;
c_cflag &= ~CSIZE;
c_cflag |= CS7
串口读写实现
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>
#include <termios.h>
#include <cerrno>
#include <cstdio>
#include <ctime>
#include <ctime>
#include <libgen.h>
#include <thread>
#include <cerrno>
//串口设备路径
#define DEV_PATH "/dev/ttyS2"
typedef void (*SerialDataReport)(size_t len, uint8_t* data);
struct SerialDevice{
SerialDataReport dataReportFunc = nullptr;
int fd = -1;
bool readStop = false;
};
static SerialDevice myDevice;
/**
* 设置波特率
* @param fd 文件句柄
* @return 设置结果 true / false
*/
static int setSpeed(int fd){
struct termios options{};
tcgetattr(fd, &options);
tcflush(fd, TCIOFLUSH);
cfsetispeed(&options, B38400);
cfsetospeed(&options, B38400);
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
options.c_oflag &= ~OPOST;
int status = tcsetattr(fd, TCSANOW, &options);
if(status != 0){
return 0;
}
tcflush(fd, TCIOFLUSH);
return 1;
}
/**
* 设置串口数据位,停止位和校验位
* @param fd 串口设备文件描述符
* @return 设置结果 true / false
*/
static int setParity(int fd){
struct termios options{};
if(tcgetattr(fd, &options) != 0){
return 0;
}
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8; //8bit
options.c_cflag &= ~PARENB; //无校验位
options.c_iflag &= ~INPCK;
options.c_cflag &= ~CSTOPB; //stop 1bit
tcflush(fd, TCIFLUSH);
options.c_cc[VTIME] = 150;
options.c_cc[VMIN] = 0;
if(tcsetattr(fd, TCSANOW, &options) != 0){
return 0;
}
return 1;
}
static void serialReading(){
int _select_ret;
uint8_t _buf[256];
struct timeval _dy_time{};
fd_set _fd_set;
int fd = myDevice.fd;
if(fd < 0){
return;
}
while(!myDevice.readStop){
FD_ZERO(&_fd_set);
FD_SET(fd, &_fd_set);
_dy_time.tv_sec = 5;
_dy_time.tv_usec = 0;
memset(_buf, 0, 256);
_select_ret = select(fd + 1, &_fd_set, nullptr, nullptr, &_dy_time);
if(_select_ret <= 0){
continue;
}
ssize_t _len = read(fd, _buf,256);
if(_len > 0){
if(myDevice.dataReportFunc){
myDevice.dataReportFunc(_len, _buf);
}
}
}
}
static int startSerial(SerialDataReport report){
int fd = open(DEV_PATH, O_RDWR);
if(fd == -1){
printf("open device [%s] failed: %s\n", DEV_PATH, strerror(errno));
return 0;
}
if(!setSpeed(fd) || !setParity(fd)){
return false;
}
myDevice.fd = fd;
myDevice.dataReportFunc = report;
myDevice.readStop = false;
std::thread read_t(serialReading);
read_t.detach();
printf("Serial Device[%s] open successes", DEV_PATH);
return 1;
}
static void closeSerial(){
myDevice.readStop = true;
}
static int write(size_t len, uint8_t* data){
if(myDevice.fd > 0){
ssize_t _len = write(myDevice.fd, data, len);
printf("write data len %zu", _len);
return _len;
}
return -1;
}