串口通信的概念与实现

  • 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参数说明
VINTRInterrupt字符
VEOL附加的End-of-file字符
VQUITQuit字符
VTIME非规范模式读取时的超时时间
VERASEErase字符
VSTOPStop字符
VKILLKill字符
VSTARTStart字符
VEOFEnd-of-file字符
VSUSPSuspend字符
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非法的文件描述符。
EINTRtcsetattr函数调用被信号中断。
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值