Linux串口编程进阶
阅读原文时间:2023年07月09日阅读:3

在《Linux串口编程》编程一文中介绍了串口应用中常用的基本操作,如:串口打开关闭、串口设置、数据收发等。本篇文章主要基于常规串口操作进行了扩充,主要介绍如下操作:

  • Linux系统使用非标准波特率
  • 同步方式串口发送
  • select I/O复用串口数据读写
  • 串口参数VTIME和VMIN的作用
  • RS485串口功能应用
  • 串口同步等待Modem信号变化

与上一篇文章类似,为方便用户使用我们将以上串口操作均封装成了独立的函数,可以极大的节约开发时间。

    /**
     * libtty_setcustombaudrate - set baud rate of tty device
     * @fd: device handle
     * @speed: baud rate to set
     *
     * The function return 0 if success, or -1 if fail.
     */
    static int libtty_setcustombaudrate(int fd, int baudrate)
    {
        struct termios2 tio;

        if (ioctl(fd, TCGETS2, &tio)) {
            perror("TCGETS2");
            return -1;
        }

        tio.c_cflag &= ~CBAUD;
        tio.c_cflag |= BOTHER;
        tio.c_ispeed = baudrate;
        tio.c_ospeed = baudrate;

        if (ioctl(fd, TCSETS2, &tio)) {
            perror("TCSETS2");
            return -1;
        }

        if (ioctl(fd, TCGETS2, &tio)) {
            perror("TCGETS2");
            return -1;
        }

        return 0;
    }

Note:

  1. 使用cfsetspeed函数集无法设置非标准波特率的主要原因主要是因为系统中缺少相关的宏定义。glibc中均是通过波特率宏和实际波特率之间进行转换。
  2. 如上设置波特率的方法主要是使用了termios2结构体,将相应标志位BOTHER置为有效,然后通过ioctl传递给驱动的tty核心层。

直接使用write函数完成串口数据的发送时,在该函数返回时实际上只是把write缓冲区的数据拷贝至tty内核层的缓冲区中,当缓冲区满时write才会阻塞,此过程中串口驱动并未执行真正的发送动作。在有些场景下,我们希望等待串口发送物理上真正完成了再执行后续的操作。那么此时需要使用的函数为:

    /**
     * tcdrain() waits until all output written to the object referred to by fd has been
     * transmitted.
     */
    int tcdrain(int fd);

使用select函数实现的I/O多路转接模型,在select操作期间,I/O可以进行其他操作。在对多个设备同时使用的应用场景中应用较为普遍。比如多个串口设备,或者网络通讯中处理多个客户端。select可以具体设置每个文件描述符的条件、等待时间等,这样在函数返回时可以知道具体哪个设备已经准备好读写。

    /**
     * libtty_selectread - read data from uart
     * @fd: device handle
     * @buffer: pointer to read buffer
     * @count: read length
     *
     * The function return actual read length if success, 0 if timeout, -1 if fail.
     */
    static int libtty_selectread(int fd, char *buffer, int count)
    {
        int ret;
        fd_set rd;
        struct timeval tv;

        FD_ZERO(&rd);
        FD_SET(fd, &rd);

        tv.tv_sec = 5;
        tv.tv_usec = 0;

        ret = select(fd + 1, &rd, NULL, NULL, &tv);
        if (ret == -1) {
            perror("select(): ");
        }
        else if (ret)
            return read(fd, buffer, count);
        else {
            printf("select timeout.\n");
        }
        return ret;
    }

VTIME和VMIN常规情况下,设置为0。但是很多应用场景我们需要将二者结合起来共同控制对串口的读取行为,参数组合说明如下:

  • VMIN = 0 和 VTIME = 0 :在这种情况下,read 调用总是立刻返回。如果有等待处理的字符,read 就会立刻返回;如果没有字符等待处理,read 调用返回0,并且不读取任何字符;
  • VMIN = 0 和 VTIME > 0 :在这种情况下,只要有字符可以处理或者是经过 VTIME 个十分之一秒的时间间隔,read 调用就返回。如果因为超时而未读到任何字符,read 返回0,否则 read 返回读取的字符数目。
  • VMIN > 0 和 VTIME = 0 :在这种情况下,read 调用将一直等待,直到有 MIN 个字符可以读取时才返回,返回值是读取的字符数量。到达文件尾时返回0。
  • VMIN > 0 和 VTIME > 0 :在这种情况下,当 read 被调用时,它会等待接收一个字符。在接收到第一个字符及后续的每个字符后,一个字符间隔定时器被启动(如果定时器已经运行,则重启它)。当有 MIN 个字符可读或两个字符之间的时间间隔超过 TIME 个十分之一秒时,read 调用返回。这个功能可用于区分是单独按下了 Escape 键还是按下一个 Escape 键开始的功能组合键。但要注意的是,网络通信或处理器的高负载将使得类似这样的定时器失去作用。

部分使用RS485的应用场景或者针对特定的串口硬件,需要通过串口应用程序主动调用RS485功能开启的相关API。用法如下:

    /**
     * libtty_rs485set - rs485 set
     * @fd: file descriptor of tty device
     * @enable: 0 on disable, other on enable
     *
     * The function return 0 if success, others if fail.
     */
    int libtty_rs485set(int fd, char enable)
    {
        struct serial_rs485 rs485conf;

        if (enable)
            rs485conf.flags |= SER_RS485_ENABLED;
        else
            rs485conf.flags &= ~SER_RS485_ENABLED;

        return ioctl(fd, TIOCSRS485, &rs485conf);
    }

同步等待串口Modem信号变化是指,应用程序可以调用接口函数进入等待,直到程序中设定的Modem输入信号DCD/RI/CTS/DCD的信号变化才退出等待。常用于设备状态与特定仪器的操作同步场景。

    /**
     * libtty_tiocmwait - wiat for modem signal to changed
     * @fd: file descriptor of tty device
     *
     * The function return 0 if success, others if fail.
     */
    static int libtty_tiocmwait(int fd)
    {
        unsigned long modembits = TIOCM_DSR | TIOCM_CTS | TIOCM_CD | TIOCM_RI;

        return ioctl(fd, TIOCMIWAIT, modembits);
    }

如以上代码所示,设置等待的modem信号为所有输入信号DSR、CTS、DCD和RI,只要任意一路串口信号发生改变,则API退出。

关于Linux串口编程的介绍就到这里了,关于更多更实用的串口用法可以随时交流讨论哈~