【问题标题】:UART not reading as much data as I would likeUART没有读取我想要的那么多数据
【发布时间】:2016-08-03 11:40:51
【问题描述】:

我正在尝试编写一个函数来读取通过 UART 发送的一大块数据。我正在使用在 RaspberryPi 模型 B 上运行的 Raspbian Jessie,但我想在 openwrt 上使用这个 C 代码(带有任何必要的修订)。到目前为止,这就是我写的。

标题:

#ifndef __UART_LIB__
#define __UART_LIB__

#include <stdlib.h>     //Errors, etc
#include <unistd.h>     //Used for UART
#include <fcntl.h>      //Used for UART
#include <termios.h>    //Used for UART

#include <sys/types.h>  //These includes are for timeout
#include <sys/stat.h>   
#include <fcntl.h>
#include <sys/select.h> //
#include <sys/ioctl.h>

#define BITS_PER_PACKAGE_ 11
#define WAIT_PROLONGATION_CONSTANT_ 1.1f

//Some values used by default, left for the user to change if needed
unsigned int BAUD_ ;
unsigned int NUM_BITS_  ;
char *UART_PATH_ ;
unsigned int MAX_SIZE_ ;
unsigned int OPEN_FLAG_ ;
time_t TIMEOUT_SEC_ ;
suseconds_t TIMEOUT_USEC_ ;
struct timeval WAIT_CONSTANT_ ;

int open_conf_UART_() ;
int read_UART_(int uart_filestream, char** dest, int max_len) ;

#endif

.c 文件:

#include "uartlib.h"

unsigned int BAUD_ = B115200 ;
unsigned int NUM_BITS_ = CS8 ;
char *UART_PATH_ = "/dev/ttyAMA0" ;
unsigned int MAX_SIZE_ = 128 ;
unsigned int OPEN_FLAG_ = O_RDWR ;
time_t TIMEOUT_SEC_ = 5 ;
suseconds_t TIMEOUT_USEC_ = 0 ;


int open_conf_UART_()
{
    int indicator, old_fl;
    int uart_filestream ;
    struct termios options ;

    // Opening the port in a read/write mode
    uart_filestream = open(UART_PATH_, OPEN_FLAG_ | O_NOCTTY );
    if (uart_filestream < 0)
    {
        // Unable to open the serial port, so produce an error and halt
        return -1;
    }

    // Configuring the options for UART

    // Retrieve the options and modify them. 
    indicator = tcgetattr(uart_filestream, &options);
    if(indicator < 0)
    {   
        // Unable to get the attributes
        close(uart_filestream);
        return -1;
    }

    // I found a question on stackoverlow where the answer said that VTIME and VMIN will be ignored unless I 
    // switch the FNDELAY flag off
    old_fl = fcntl(uart_filestream, F_GETFL);
    if(old_fl < 0)
    {
        return -1;
    }
    old_fl &= ~FNDELAY;
    fcntl(uart_filestream, old_fl);

    //Setting the options
    options.c_cflag = CRTSCTS | BAUD_ | NUM_BITS_ | CLOCAL | CREAD ;
    options.c_iflag = 0;
    options.c_oflag = 0;
    options.c_lflag = 0;

    //I want the uart to wait 1/10 of a second between bytes at most
    options.c_cc[VTIME] = 1;
    options.c_cc[VMIN] = 0;

    // Flushing the file stream (the input and the output area)
    indicator = tcflush(uart_filestream, TCIOFLUSH);
    if(indicator < 0)
    {   
        // Unable to flush
        close(uart_filestream);
        return -1;
    }


    // Setting the options for the file stream. 
    indicator = tcsetattr(uart_filestream, TCSANOW, &options);
    if(indicator < 0)
    {   
        // Unable to set the attributes
        close(uart_filestream);
        return -1;
    }
    return uart_filestream;
}

int read_UART_(int uart_filestream, char** dest, int max_len)
{
    int indicator;
    int buffer_length;

    indicator = tcflush(uart_filestream, TCIFLUSH);
    if(indicator < 0)
    {   
        // Unable to flush
        return -1;
    }

    //Do the actual reading
    buffer_length = read(uart_filestream, (void*)(*dest), max_len);
    if(indicator < 0)
    {
        return -1;
    }
    else
    {
        // Returning number of read bytes
        return buffer_length;
    }   
    // Both branches of the if statement above have return, so this will not be reached
}

所以,当我尝试读取超过 8 个字节时,消息会被截断为 8 个字节。 正如我所读到的,将 VTIME 设置为某个值允许两个字节之间的时间间隔最多那么长。 我不确定发生了什么,但我怀疑 read() 调用在数据接收完成之前读取了缓冲区。 我的愿望是读取一块未定义大小的数据。我还在读取之前使用了带有超时的 select() 以确保程序不会完全阻塞。 我阅读了很多关于这个主题的论坛主题、stackoverflow 问题、指南等,但似乎没有一个对我的解决方案有帮助。 那么,谁能解释这里发生了什么?有没有可能做我想做的事?

请注意,我删除了一些代码(我还编写了一个写入 UART 端口的函数),所以这里可能有一些多余的包含、全局变量等。

【问题讨论】:

  • So, when I try to read more than 8 bytes, the message gets truncated to 8 bytes UART 有一个固定大小的接收缓冲区(如果需要,还有流量控制)。你应该在你的程序中做你自己的缓冲。
  • 发布的代码显示没有调用int read_UART_(int uart_filestream, char** dest, int max_len),因此没有正确调用的证据。
  • 你确定数据是整块发送的吗?你用示波器测试过传输吗?旁注:传递给函数的 max_len 值是什么?
  • 第二个 if(indicator &lt; 0) in read_UART_() 没有任何作用。
  • IIRC raspberryPi 有一个 3 线(模拟)uart,所以不能有硬件流控制。 (不过,XON/XOFF 可能是可能的)

标签: c linux uart


【解决方案1】:

所以,我解决了我的问题。我仍然无法刷新(当系统刚刚启动并且在我打开程序之前有信号时,缓冲区中有一些旧内容,在我看来)。 我使用了这个假设:包裹将以较小的突发量到达,并且它们将相隔 TIMEOUT_BYTE_ 时间量。如果到期,我认为包裹已经结束。 另外,我有一个初始等待数据的超时时间,但我认为这是特定情况。

头文件:

#ifndef UART_LIB_
#define UART_LIB_

#include <stdlib.h>     //Errors, etc
#include <unistd.h>     //Used for UART
#include <fcntl.h>      //Used for UART
#include <termios.h>    //Used for UART
#include <sys/types.h>  //These includes are for timeout
#include <sys/select.h> //Used for select(), etc

//Some values used by default, left for the user to change if needed

//Used to set up the baud rate
unsigned int BAUD_ ;

//Used to indicate number of bits in one backage 
unsigned int NUM_BITS_  ;

//Path to the UART device
char *UART_PATH_ ;

//Flag for opening the device
unsigned int OPEN_FLAG_ ;

//Timeout for answer from the other side
time_t TIMEOUT_SEC_ ;
suseconds_t TIMEOUT_USEC_ ;

//Time interval between two bursts of data inside the package
suseconds_t TIMEOUT_BYTE_ ;

int open_conf_UART_(void) ;
int read_UART_(int uart_filestream, char* dest, int max_len) ;

#endif

源文件:

#include <errno.h>
#include "uartlib.h"

unsigned int BAUD_ = B38400 ;
unsigned int NUM_BITS_ = CS8 ;
char *UART_PATH_ = "/dev/ttyAMA0" ;
unsigned int OPEN_FLAG_ = O_RDWR ;
time_t TIMEOUT_SEC_ = 2 ;
suseconds_t TIMEOUT_USEC_ = 0 ;

// This needs to be finely tuned
suseconds_t TIMEOUT_BYTE_ = 5000;


int open_conf_UART_()
{
    // Variable section
    int indicator;
    int uart_filestream ;
    struct termios options ;

    // Opening the port in a read/write mode
    uart_filestream = open(UART_PATH_, OPEN_FLAG_ | O_NOCTTY | O_NONBLOCK);
    if (uart_filestream < 0)
    {
        // Unable to open the serial port, so produce an error and halt
        return -1;
    }

    // Configuring the options for UART

    // Flushing the file stream (the input and the output area)
    indicator = tcflush(uart_filestream, TCIOFLUSH);
    if(indicator < 0)
    {   
        // Unable to flush
        close(uart_filestream);
        return -1;
    }

    // Retrieve the options and modify them. 
    indicator = tcgetattr(uart_filestream, &options);
    if(indicator < 0)
    {   
        // Unable to get the attributes
        close(uart_filestream);
        return -1;
    }

    // Setting the options
    cfmakeraw(&options);
    options.c_cflag |= BAUD_ | NUM_BITS_ | CREAD;


    // Setting the options for the file stream. 
    indicator = tcsetattr(uart_filestream, TCSANOW, &options);
    if(indicator < 0)
    {   
        // Unable to set the attributes
        close(uart_filestream);
        return -1;
    }
    return uart_filestream;
}

int read_UART_(int uart_filestream, char* dest, int max_len)
{
    // Variable section
    int indicator;
    int buffer_length;
    char *tmp_dest;
    fd_set set;
    struct timeval timeout, init_timeout;

    while(1)
    {
        // Reseting the set and inserting the uart_filestream in it
        FD_ZERO(&set);
        FD_SET(uart_filestream, &set);

        // Setting the time for initial contact
        init_timeout.tv_sec = TIMEOUT_SEC_ ;
        init_timeout.tv_usec = TIMEOUT_USEC_ ;

        // Waiting for the first contact. If this times out, we assume no contact.
        indicator = select(uart_filestream + 1, &set, NULL, NULL, &init_timeout);
        if(indicator < 0)
        {
            if(errno == EINTR)
            {
                // Try again
                continue;
            }
            return -1;
        }
        else if(indicator == 0)
        {   // Timeout has occurred
            return -2;
        }
        else
        {
            break;
        }
    }

    // This section means that there is something to be read in the file descriptor
    buffer_length = 0 ;
    tmp_dest = dest ;

    // The first select is redundant but it is easier to loop this way.
    while(buffer_length < max_len)
    {
        // select changes the timeval structure so it is reset here
        timeout.tv_sec = 0;
        timeout.tv_usec = TIMEOUT_BYTE_;

        // Reinitialize the sets for reading
        FD_ZERO(&set);
        FD_SET(uart_filestream, &set);

        // Wait for the file descriptor to be available or for timeout
        indicator = select(uart_filestream+1, &set, NULL, NULL, &timeout);

        if(indicator < 0)
        {   
            if(errno == EINTR)
            {
                // Try again
                continue;
            }

            // This indicates an error
            return -1;
        }
        else if(indicator == 0)
        {
            // This indicates a timeout; We assume that the transmission is over once first timeout is reached
            return buffer_length;
        }

        // There's been a select that didn't time out before this read
        indicator = read(uart_filestream, (void*)tmp_dest, max_len - buffer_length);
        if(indicator < 0)
        {
            if(errno == EINTR)
            {
                // If the call was interrupted, try again
                continue;
            }

            // If it was any other condition, the read is corrupt.
            return -1;
        }
        else if(indicator == 0)
        {
            // If, somehow, EOF was reached
            break;
        }

        // Change the necessary values
        buffer_length += indicator ;
        tmp_dest += indicator; 

    }
    // Both branches of the if statement above have return, so this will not be reached
    // but a warning is generated 
    return buffer_length;
}

void flush_buffer_UART_(int uart_filestream)
{
    char c;
    while(read(uart_filestream, &c, 1) > 0);
}

我知道这不是这里的主题,但是如果有人知道如何解决刷新问题,请回复。 此外,非常欢迎任何建设性的批评。

附:我也有一个 write_UART() 函数,但我认为没有必要发布它,因为它代表没有问题(用示波器测量,后来用 echo 尝试。Echo 无法给我同样的信息)。

编辑:Flush 已被引入,然后与源文件合并。还没有弄清楚它是否有效。

【讨论】:

  • if(indicator &lt; 0) { return -1; } 从 select() 返回 -1 不是必然错误,它可能是一个中断的系统调用,检查 errno(就像你在 read( )) 来选择是退出还是重试。
  • while(buffer_length &lt; max_len) 这个循环应该 select() 之外。如果 select() 表明 fd 可读,则允许 read() 一次
  • @joop 我同意关于指标的评论,会解决这个问题!我不确定你在说什么关于循环。如果端口上有“初始接触”,则第一个选择返回 >0,从那里开始,我读取了突发。我不能只读一读。尝试过,第一个选择在 UART 缓冲区已满(在我的情况下为 8 个字节)后立即返回,甚至不尝试读取到达的其余部分。这就是为什么这是一个循环。
  • 您应该在第二次选择之前再次初始化 fd 集 FD_ZERO(&amp;set); FD_SET(uart_filestream, &amp;set);。 (这是人们在 select 周围使用循环的原因之一)
  • @joop 谢谢,有点忘记了。我将尽快编辑帖子以介绍所有更改
猜你喜欢
  • 1970-01-01
  • 2021-03-05
  • 1970-01-01
  • 2019-07-08
  • 2016-10-23
  • 1970-01-01
  • 1970-01-01
  • 2019-05-17
  • 1970-01-01
相关资源
最近更新 更多