代码之家  ›  专栏  ›  技术社区  ›  J.Panek

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

  •  0
  • J.Panek  · 技术社区  · 8 年前

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

    通信工作正常,直到微控制器开始每10毫秒发送一次消息。此时,微控制器的发送队列将满并溢出。然后他向PC发送一个错误消息(这就是我如何知道的,微控制器的发送队列被溢出,而不是PC的发送队列。

    在PC程序的嵌入式Linux版本之前,微控制器用DOS版本的PC程序运行,而不会造成错误。在DOS中,单个字节直接从串行端口读取和写入(Linux中没有内核缓冲区),因为大多数C代码是可移植到Linux的,所以我试图复制Linux中串行端口读写的DOS行为,以保持其余的进程处理这些单个字节。

    我打开并初始化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);
         }
    

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

    编辑: 这是流量控制的设置,没有。

    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
        }
    }
    

    这个函数的基本功能是检查读写位置和写位置 data 如果两个位置不重叠且大于 +1 彼此远离。

    当另一个函数需要它调用的环缓冲区中的字节时 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;
    }
    

    菲福克斯

    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() 就是在某个时候减慢你的速度,并开始减慢从缓冲区的读取速度,这再次阻塞了线程的调用。也许我错了。目前,我有点不知道究竟是什么样的错误,甚至如何解决这个问题。

    每一个好的建议都是值得赞赏的。

    1 回复  |  直到 8 年前
        1
  •  1
  •   wkz    8 年前

    你确定你需要一个先进先出的申请?串行驱动程序很可能在内核中已经有相当大的缓冲区(通常是一个页面,通常是4KB)。如果这足够,你可以通过简单地简化实现。 GetByte 对串行设备执行非阻塞读取。

    如果你想坚持这个设计,考虑重新设计读循环,一次读取字节多于字节。现在,每读取一个字节就需要两个系统调用。

    你总是在更改PID 3的调度类。这可能不是你想要的。而且,这仅仅意味着一旦字节在内核内部缓冲区中登陆,线程就会运行。 poll 返回)。如果通过在工作队列上运行作业,将字节从硬件FIFO转移到缓冲区,并且工作队列运行在 SCHED_OTHER (大多数人都这样做),更改线程的调度程序将不会产生所需的效果。经典的优先级反转问题。您可能想检查由特定的板使用的内核驱动程序。但是,如果你在 read 这应该不是什么大问题。

    如果这段代码曾经在smp系统上使用过,那么您最想保护 阅读 和 write 带有锁的指针,因为它们不是原子更新的。线程很难修复,您考虑过使用事件循环吗?有点像 libev .