Linux 下用select实现串口数据读取方法

发布于:2025-08-12 ⋅ 阅读:(17) ⋅ 点赞:(0)

在 Linux 系统里,我们可以借助 selectpoll 或者 epoll 这些 I/O 多路复用机制达成串口数据读取的触发方式。这些机制能够让程序在特定文件描述符(像串口设备文件描述符)有数据可读时得到通知,进而进行数据读取操作,而不是像轮询方式那样持续调用 read 函数。

示例代码(使用 select 实现)

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>

// 设置串口波特率
void SetSpeed(int fd, int speed) {
    struct termios options;
    tcgetattr(fd, &options);
    switch (speed) {
        case 115200:
            cfsetispeed(&options, B115200);
            cfsetospeed(&options, B115200);
            break;
        // 可以添加更多波特率设置
        default:
            cfsetispeed(&options, B115200);
            cfsetospeed(&options, B115200);
            break;
    }
    tcsetattr(fd, TCSANOW, &options);
}

// 设置串口参数
void SetParity(int fd, int databits, int stopbits, char parity) {
    struct termios options;
    tcgetattr(fd, &options);

    // 设置数据位
    options.c_cflag &= ~CSIZE;
    switch (databits) {
        case 7:
            options.c_cflag |= CS7;
            break;
        case 8:
            options.c_cflag |= CS8;
            break;
        default:
            options.c_cflag |= CS8;
            break;
    }

    // 设置奇偶校验位
    switch (parity) {
        case 'n':
        case 'N':
            options.c_cflag &= ~PARENB;
            options.c_cflag &= ~PARODD;
            break;
        case 'o':
        case 'O':
            options.c_cflag |= (PARODD | PARENB);
            break;
        case 'e':
        case 'E':
            options.c_cflag |= PARENB;
            options.c_cflag &= ~PARODD;
            break;
        default:
            options.c_cflag &= ~PARENB;
            options.c_cflag &= ~PARODD;
            break;
    }

    // 设置停止位
    switch (stopbits) {
        case 1:
            options.c_cflag &= ~CSTOPB;
            break;
        case 2:
            options.c_cflag |= CSTOPB;
            break;
        default:
            options.c_cflag &= ~CSTOPB;
            break;
    }

    tcsetattr(fd, TCSANOW, &options);
}

int main(void) {
    int fd;
    unsigned char buf[300];
    unsigned short len;
    fd = open("/dev/ttyS1", O_RDWR);
    if (fd == -1) {
        perror("can't open serial\n");
        return -1;
    }

    SetParity(fd, 8, 1, 'n');
    SetSpeed(fd, 115200);

    fd_set readfds;
    struct timeval timeout;

    while (1) {
        // 清空文件描述符集
        FD_ZERO(&readfds);
        // 将串口文件描述符加入读文件描述符集
        FD_SET(fd, &readfds);

        // 设置超时时间
        timeout.tv_sec = 0;
        timeout.tv_usec = 20000;

        // 调用 select 函数等待事件发生
        int activity = select(fd + 1, &readfds, NULL, NULL, &timeout);

        if (activity < 0) {
            perror("select error");
            break;
        } else if (activity > 0) {
            // 检查是否是串口文件描述符有数据可读
            if (FD_ISSET(fd, &readfds)) {
                len = read(fd, buf, 100);
                if (len > 0) {
                    write(fd, buf, len);
                }
            }
        }
    }

    close(fd);
    return 0;
}

代码解释

  1. SetSpeed 函数:此函数用于设置串口的波特率,它借助 tcsetattr 函数来配置串口的输入和输出波特率。
  2. SetParity 函数:该函数用于设置串口的数据位、停止位和奇偶校验位,同样是通过 tcsetattr 函数来完成串口参数的配置。
  3. main 函数
    • 打开串口设备文件 /dev/ttyS1,并且设置串口参数。
    • 构建一个 fd_set 类型的文件描述符集 readfds,把串口文件描述符 fd 添加到该集合中。
    • 设定一个超时时间 timeout,防止 select 函数一直阻塞。
    • 调用 select 函数等待事件发生,若有数据可读,select 函数会返回大于 0 的值。
    • 利用 FD_ISSET 宏检查是否是串口文件描述符有数据可读,若有则调用 read 函数读取数据,然后将数据回写到串口。

通过这种方式,程序就无需持续轮询 read 函数,而是在有数据到达时才进行读取操作。