欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux下串口编程流程介绍

程序员文章站 2022-03-13 17:25:19
...


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

Linux下串口编程流程介绍

一、串口编程中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 数据位掩码位
CSTOPB 2位停止位
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()控制终端

Linux下串口编程流程介绍
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。
    Linux下串口编程流程介绍

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

Linux下串口编程流程介绍
Linux下串口编程流程介绍

//函数原型
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);
相关标签: 嵌入式 linux