在Linux下使用C/C++编写基于操作系统异步机制的串口通信程序

在Linux下使用C/C++编写基于异步机制的串口通信程序,可以通过信号驱动I/O或I/O多路复用(如epoll)实现。以下是使用信号驱动I/O的详细步骤和示例代码:

步骤说明

  1. 打开串口设备
    使用open函数以非阻塞模式打开串口设备文件(如/dev/ttyS0),设置标志O_RDWR(读写)、O_NOCTTY(不分配控制终端)和O_NONBLOCK(非阻塞)。

  2. 配置串口参数
    通过termios结构体设置波特率、数据位、停止位、校验位等参数,使用tcsetattr应用配置。

  3. 设置信号处理函数
    注册SIGIO信号的处理函数,用于异步接收数据通知。在信号处理函数中读取数据。

  4. 启用异步I/O模式
    使用fcntl设置文件描述符的属主(F_SETOWN)并启用异步模式(O_ASYNC),确保数据到达时触发信号。

  5. 主循环等待信号
    主程序进入循环等待信号,信号触发后处理数据。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

int fd; // 串口文件描述符

// SIGIO信号处理函数
void sigio_handler(int sig) {
    (void)sig; // 避免未使用参数警告
    char buffer[256];
    ssize_t bytes_read;

    // 非阻塞读取所有可用数据
    while ((bytes_read = read(fd, buffer, sizeof(buffer))) {
        if (bytes_read > 0) {
            // 输出接收到的数据(示例:直接写入标准输出)
            write(STDOUT_FILENO, "Received: ", 10);
            write(STDOUT_FILENO, buffer, bytes_read);
        } else if (bytes_read == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 无更多数据可读
                break;
            } else {
                perror("read error");
                exit(EXIT_FAILURE);
            }
        } else {
            // bytes_read为0表示EOF(通常串口不会触发)
            break;
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <serial_port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    // 1. 打开串口(非阻塞模式)
    fd = open(argv[1], O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 2. 配置串口参数
    struct termios tty;
    memset(&tty, 0, sizeof(tty));
    if (tcgetattr(fd, &tty) != 0) {
        perror("tcgetattr");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 设置波特率115200
    cfsetispeed(&tty, B115200);
    cfsetospeed(&tty, B115200);

    // 数据位:8位,无校验,1位停止位
    tty.c_cflag &= ~PARENB;       // 禁用奇偶校验
    tty.c_cflag &= ~CSTOPB;       // 1位停止位
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;           // 8位数据位
    tty.c_cflag &= ~CRTSCTS;      // 禁用硬件流控
    tty.c_cflag |= CREAD | CLOCAL; // 启用接收,忽略调制解调器状态

    // 非规范模式,禁用回显和信号
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);

    tty.c_oflag &= ~OPOST; // 原始输出模式

    // 非阻塞读取,立即返回可用数据
    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 0;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        perror("tcsetattr");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 3. 设置信号处理
    struct sigaction sa;
    sa.sa_handler = sigio_handler;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGIO); // 处理期间阻塞SIGIO
    sa.sa_flags = 0;
    if (sigaction(SIGIO, &sa, NULL) == -1) {
        perror("sigaction");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 4. 设置文件描述符属主和异步模式
    if (fcntl(fd, F_SETOWN, getpid()) == -1) {
        perror("fcntl F_SETOWN");
        close(fd);
        exit(EXIT_FAILURE);
    }

    int flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        close(fd);
        exit(EXIT_FAILURE);
    }

    if (fcntl(fd, F_SETFL, flags | O_ASYNC) == -1) {
        perror("fcntl F_SETFL");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 5. 主循环等待信号
    printf("Listening on %s. Press Ctrl+C to exit.\n", argv[1]);
    while (1) {
        pause(); // 暂停等待信号
    }

    close(fd);
    return 0;
}

编译与运行

  1. 编译代码
    保存为async_serial.c,使用以下命令编译:

    gcc async_serial.c -o async_serial
    
  2. 运行程序
    需要串口设备路径作为参数:

    sudo ./async_serial /dev/ttyS0
    
  3. 测试
    使用另一个终端或虚拟串口发送数据,例如通过echo命令写入设备文件:

    echo "Hello, Serial!" > /dev/ttyS0
    

注意事项

  • 权限问题:确保用户有权限访问串口设备(通常需加入dialout组或使用sudo)。
  • 信号安全性:信号处理函数中应仅使用异步信号安全函数,避免复杂操作。
  • 数据缓冲:实际应用中可能需要更健壮的数据缓冲机制,避免数据丢失。

此示例展示了如何利用信号驱动I/O实现异步串口通信,适用于低至中等数据速率的场景。对于高吞吐量需求,可考虑结合epoll或专用线程进行优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值