Linux下串口编程流程介绍


  串口接口简称串口,也称串行通信接口(通常为COM接口),串行接口( Serial Interface)是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线可实现双向通信,根据通信的方向可分为单工、半双工和全双工三种。在Linux 下标准的串口节点名为 /dev/ttyS* ,如果是USB转串口,则为/dev/ttyUSB*:

在这里插入图片描述

一、串口编程中struct termios结构体

  在串口编程中有一个很重要的结构体——struct termios,通过该结构体我们可对串口的属性(输入输出)进行控制:

struct termios
{
       tcflag_t c_iflag;           //输入模式标志
       tcflag_t c_oflag;           //输出模式标志
       tcflag_t c_cflag;           //控制模式标志
       tcflag_t c_lflag;           //本地模式标志
       cc_t    c_cc[NCCS];         //控制字符
}

  其中tcflag_t定义为:

typedef unsigned int tcflag_t

  在该结构体中c_cflag输入模式标志最为重要,可设置波特率、数据位、校验位、停止位。设置波特率需要在前加上B(B9600,B115200),然后对需要设置的位进行"与","或"操作,其中标志所代表的意义如下:

  • c_cflag控制模式标志
位成员代表含义
CBAUD波特率掩码位
EXTA外部时钟
EXTB外部时钟
CSIZE数据位掩码位
CSTOPB2位停止位
CREAD接收使能
PARENB奇偶校验位使能
PARODD使用奇校验
CLOCAL忽略终端状态行
CRTSCTS硬件流控制使能位

通常情况下CLOCAL和CREAD两个选项总是被打开,这两个选项可保证你的程序不会变成端口的所有者,而端口所有者需要去处理发散性控制和挂断信号,同时还保证串行接口驱动会读取输入的数据字节。

  • c_iflag输入模式标志
位成员代表含义
IGNBRK忽略BREAK键输入
BRKINT如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断
IGNPAR忽略奇偶校验错误
PARMRK标识奇偶校验错误
INPCK允许输入奇偶校验
ISTRIP去除字符的第8个比特
INLCR将输入的NL(换行)转换成CR(回车)
IGNCR忽略输入的回车
ICRNL将输入的回车转化成换行(如果IGNCR未设置的情况下)
IUCLC将输入的大写字符转换成小写字符(非POSIX)
IXON允许输入时对XON/XOFF流进行控制
IXANY输入任何字符将重启停止的输出
IXOFF允许输入时对XON/XOFF流进行控制
IMAXBEL当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置
  • c_oflag输出模式标志
位成员代表含义
OPOST处理后输出
OLCUC将输入的小写字符转换成大写字符(非POSIX)
ONLCR将输入的NL(换行)转换成CR(回车)及NL(换行)
OCRNL将输入的CR(回车)转换成NL(换行)
ONOCR第一行不输出回车符
ONLRET不输出回车符
OFILL发送填充字符以延迟终端输出
OFDEL以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘\0’)(非POSIX)
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_lflag本地模式标志
位成员代表含义
ISIG当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
ICANON使用标准输入模式
ECHO显示输入字符
ECHOE如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词
ECHOK如果ICANON同时设置,KILL将删除当前行
ECHONL如果ICANON同时设置,即使ECHO没有设置依然显示换行符
ECHOPRT如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
TOSTOP向后台输出发送SIGTTOU信号

二、使用串口流程

1、打开串口

comport->fd = open(comport->dev_name,O_RDWR|O_NOCTTY|O_NDELAY) ;

  openz函数除了普通参数外还有O_NOCTTY|O_NDELAY:

  • O_NOCTTY:通知Linux系统,这个程序不会成为这个端口的控制终端;
  • O_NDELAY:通知系统不管县DCD信号所线处的状态。

2、串口配置流程

a、tcgetattr() 与 tcsetattr()控制终端

在这里插入图片描述
tcgetattr()

  • 参数
    int fd:打开串口文件后,获取到的文件描述符;
    struct termios &termios_p: termios 类型的结构体,包含在 <termios.h> 头文件中,这里需要传地址或指针;

  • 功能:获取对应文件描述符相应串口的原始属性,保存在第二个参数中,通常获取串口需要对原始信息进行备份,在程序退出前需要修改回来,一边继续使用串口;

  • 返回值:成功返回0,失败返回-1。

tcsetattr()

  • 参数
    int fd: 要设置属性的文件描述符
    int optional_actions: 设置属性时,可以控制属性生效的时刻,optional_actions可以取下面几个值:
    TCSANOW: 立即生效;
    TCADRAIN: 改变在所有写入fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。(当前输出完成时将值改变);
    TCSAFLUSH :改变在所有写入fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)。
    *termios termios_p: 用来设置的串口属性的结构体指针,对串口的termios配置好后,传入函数即可。

  • 功能:设置终端参数的函数;

  • 返回值:成功返回0,失败返回-1。
    在这里插入图片描述

b、cfsetispeed() 与 cfsetospeed()设置波特率

在这里插入图片描述
在这里插入图片描述

//函数原型
static void set_baudrate (struct termios *opt, unsigned int baudrate)
{
	cfsetispeed(opt, baudrate);
	cfsetospeed(opt, baudrate);
}

参数:

  • opt:通过结构体设置串口通信属性,这是指向该结构体的指针;
  • baudrate:串口没有内部时钟所以为异步通信,为了通信双方达到收发消息统一,需要设置通信双方的波特率相同。

实现如下:

int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,B38400, B19200, B9600, B4800, B2400, B1200, B300, };

int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400,19200, 9600, 4800, 2400, 1200, 300, };
/* 设置波特率 */
int set_baudrate(struct termios *opt,int baudrate)
{
    int i;
    for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {
        if(baudrate == name_arr[i])
        {
            if(cfsetispeed(opt,baudrate) < 0)
            {
                printf("cfsetispeed failure:%s\n", strerror(errno));
                return -1;
            }

            if(cfsetospeed(opt,baudrate) < 0)
            {
                printf("cfsetospeed failure:%s\n", strerror(errno));
                return -2;
            }
        }
    }
    return 0;
}

c、使用掩码设置数据位

/* 设置数据位 */
void set_databit(struct termios *opt,int databit)
{
    opt->c_cflag |= (CLOCAL|CREAD ); //cLOCAL本地连接模式,CREAD开启串行数据接收
    opt->c_cflag &= ~CSIZE; //字符长度,取值范围为CS5、CS6、CS7或CS8
    switch(databit){
        case 5:
            opt->c_cflag |= CS5;
            break;
        case 6:
            opt->c_cflag |= CS6;
            break;
        case 7:
            opt->c_cflag |= CS6;
            break;
        case 8:
            opt->c_cflag |= CS8;
            break;
        default:
            opt->c_cflag |= CS8;
            break;
    }

}

d、使用c_cflag和c_iflag设置奇偶校验

/* 设置校验位 */
void set_parity(struct termios *opt,char parity)
{
    switch(parity){
        case 'n':
        case 'N':
             opt->c_cflag &= ~PARENB; //使用奇偶校验
             break;
        case 'e':
        case 'E':
             opt->c_cflag |= PARENB;
             opt->c_cflag &= ~PARODD;   //对输入使用奇偶校验,对输出使用偶校验
             opt->c_cflag |= (INPCK | ISTRIP); //允许输入奇偶校验|去除字符第8比特
             break;
        case 'o':
        case 'O':
             opt->c_cflag |= (PARENB | PARODD); //使用输出奇偶校验
              opt->c_cflag |= (INPCK | ISTRIP); //              允许输入奇偶校验|去除字符第8比特
              break;
        default:
              opt->c_cflag &= ~PARENB; //默认使用奇偶校验
              break;
    }
}

e、设置停止位

/* 设置停止位 */
void set_stopbit(struct termios *opt, int stopbit)
{
    switch(stopbit)
    {
        case 2:
            opt->c_cflag |= CSTOPB;  //设置两个停止位
            break;
        default:
            opt->c_cflag &= ~CSTOPB;  //停止位为1,则要清楚CSTOPB
            break;
    }
}

f、设置最少字符和等待时间

  对于接收字符和等待时间没有特别要求时可设为0:

    //串口超时设置
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 0;

3、读写数据

  写数据使用write系统调用,成功时会返回写入的字节数,失败时会返回-1,例如:

rv = write(fd, buf, buf_size);
if (rv < 0)
	printf("write data failed!\n", strerror(errno));

  读串口数据我们需要考虑到一个问题就是timeout超时问题,当端口在raw data mode操作模式下,那么read系统调用将返回从串口输入缓冲区中实际得到的字节数,如果没有数据可读,那么该系统调用将会被阻塞(block)直到有数据为止,如果超过一定时间仍然没有数据可读,那么将返回一个错误(读错误)。

rv = read(comport->fd, buf, buf_size);
    if(rv < 0)
    {
        printf("Read data from dev failure:%s\n", strerror(errno));
        return rv;
    }

  我们也可通过fcntl()函数使read()
没有数据可读的情况下立即返回而不是被阻塞:

fcntl(fd, F_SETFL, FNDELAY);
//FNDELAY选项在read无数据可读立即返回0,实际调用如下:
fcntl(fd, F_SETFL, 0);

4、关闭串口

  关闭串口就是关闭该串口下的文件描述符:

close(comport->fd);
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页