嵌入式 Linux:从串行端口读取字节太慢,无法快速传输

Embedded Linux: Reading bytes from serial port too slow for quicker transfer

我有一个微控制器和一个嵌入式 PC。这两个通过串行连接进行通信,并且都在相互接收和发送数据。这两者之间的波特率为38400。微控制器和PC具有相同的配置以确保通信(8个数据位,1个停止位,偶校验)。

在微控制器开始大约每 10 毫秒发送一次消息之前,通信工作正常。此时,单片机的发送队列已满,超过运行s。然后他向PC发送一条错误消息(我是这样知道的,微控制器的发送队列结束运行,而不是PC的。

在嵌入式 Linux 版本的 PC 程序之前,微控制器 运行 带有 DOS 版本的 PC 程序而不会导致错误。在 DOS 中,单个字节直接从串行端口读取和写入(没有像 Linux 中那样的内核缓冲区)因为大多数 C 代码都可以移植到 Linux 我尝试复制串行的 DOS 行为端口读取和写入 Linux 以保留处理这些单个字节的其余部分。

我在PC中打开并初始化串口如下。

fd_mc = open("/dev/ttyS1", O_NOCTTY | O_RDWR /*| O_NONBLOCK*/ /*| O_SYNC*/);
    if(fd_mc == -1)
    {
        perror("Could not open µc port.");
    }
    else 
    {
        struct termios tty;
        memset(&tty, 0, sizeof(tty));
        if ( tcgetattr ( fd_mc, &tty ) != 0 )
        {
            perror("Error getting termios attributes");
        }
        cfsetospeed (&tty, B38400);
        cfsetispeed (&tty, B38400);
        tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);     //raw input
        tty.c_oflag &= ~OPOST;                              //raw output

        tty.c_cflag |= PARENB;      //even parity
        tty.c_cflag &= ~PARODD;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CSIZE;
        tty.c_cflag |= CS8;

        tty.c_cflag |= (CLOCAL | CREAD);

        tcsetattr(fd_mc, TCSANOW, &tty);
     }

上面的代码是一个函数的片段,它初始化了两个串行端口(其中一个是微控制器的端口)。

编辑: 这里是流量控制的设置,有none.

tty.c_cflag &= ~CRTSCTS;                            //No hardware based flow control
tty.i_cflag &= ~(IXON | IXOFF | IXANY);             //no software based flow control

在环形缓冲区和 pollin 的帮助下,从串行端口读取发生在线程内。该线程在主循环中被调用。代码如下:

void *thread_read()
{

    struct sched_param param;
    param.sched_priority = 97;
    int ret_par = 0;
    ret_par = sched_setscheduler(3, SCHED_FIFO, &param);
    if (ret_par == -1) {
        perror("sched_setscheduler");
        return 0;
    }

    struct pollfd poll_fd[2];
    int ret;
    extern struct fifo mc_fifo, serial_fifo;
    ssize_t t;
    char c;

    poll_fd[0].fd = fd_mc;
    poll_fd[0].events = POLLIN;

    poll_fd[1].fd = fd_serial;
    poll_fd[1].events = POLLIN;

    while(1) {
        ret = poll(poll_fd, 2, 10000);

        if(ret == -1) {
            perror("poll");
        }

        if(poll_fd[0].revents & POLLIN) {
            t = read(fd_mc, &c, 1);
            if(t>0) {
                fifo_in(&mc_fifo, c);
            }
        }
        if(poll_fd[1].revents & POLLIN) {
            t = read(fd_serial, &c, 1);
            if(t>0) {
                fifo_in(&serial_fifo, c);
            }
        }
    }
    pthread_exit(NULL);
}

在主循环内调用。

pthread_t read;
pthread_create(&read, NULL, thread_read, NULL);

写入缓冲区(fifo_in)的函数是

int fifo_in(struct fifo *f, char data) 
{
    if( (f->write + 1 == f->read) || (f->read == 0 && f->write + 1 == FIFO_SIZE) ) //checks if write one before read or at the end
    {
        printf("fifo in overflow\n");
        return 0;   //fifo full
    }
    else {
        f->data[f->write] = data;
        f->write++;
//      printf("Byte in: Containing %4d\tData:\t%4d\n", BytesInReceiveBuffer(1), data); //Bytes contained in fifo of mc
        if(f->write >= FIFO_SIZE) {
            f->write = 0;
        }
        return 1;   //success
    }
}

这个函数基本上做的是检查读写位置在哪里,如果两个位置不重叠并且彼此相距超过+1,则在缓冲区内写入data .

当另一个函数需要环形缓冲区内的字节时,它会调用 GetByte 从环形缓冲区中读取字节。

获取字节

int GetByte(int port)
{
    char c;

    switch(port) {
    case 0:     //COM1
        fifo_out(&serial_fifo,&c);
        break;
    case 1:     //COM2
        fifo_out(&mc_fifo,&c);
        break;
    }
    return (int)c;
}

fifo_out

int fifo_out(struct fifo *f, char *data) {
    if(f->read == f->write) {
        printf("fifo in overfwrite\n");
        *data = 0;
        return 0;
    }
    else {
        *data = f->data[f->read];
        f->read++;
//      printf("Byte out: Containing %4d\tData:\t%4d\n", BytesInReceiveBuffer(1), *data);
        if(f->read >= FIFO_SIZE) {
            f->read = 0;
        }
        return 1;
    }
}

在 Linux 移植之前,DOS 版本中的一切都是顺序的。

吃了那一刻我最好的猜测是,read() 会减慢速度,在某些时候你会开始减慢缓冲区的读取速度,这再次阻止了线程的调用。也许我错了。目前我有点不知道这个错误到底是什么,甚至不知道如何解决这个问题。

感谢每一个好的建议。

您确定在应用程序中需要 fifo 吗?您的串行驱动程序很可能已经在内核中有相当大的缓冲区(通常是一页,通常为 4kB)。如果这就足够了,您可以通过 GetByte 对串行设备进行非阻塞读取来从根本上简化实现。

如果您想坚持这种设计,请考虑修改您的读取循环以一次读取多个字节。现在你需要为每个字节读取两个系统调用。

您一直在更改 PID 3 的调度 class。这可能不是您想要的。此外,这仅意味着一旦字节到达内核内部缓冲区(即 poll returns 时),您的线程将 运行。如果通过 运行 在工作队列上执行作业将字节从硬件 ​​FIFO 传输到缓冲区,并且该工作队列 运行s 在 SCHED_OTHER 中(大多数人这样做),则更改调度程序你的线程不会有预期的效果。经典的优先级倒置问题。您可能想要审核您的特定板使用的内核驱动程序。不过,如果您在每个 read 清空整个缓冲区,这应该不是什么问题。

如果此代码曾经在 SMP 系统上使用过,您很可能会想用锁来保护 readwrite 指针,因为它们不是自动更新的。线程很难正确处理,您是否考虑过使用事件循环来代替?类似于 libev.