Search code examples
clinuxuart

UART not reading as much data as I would like


I am trying to write a function that reads a chunk of data sent through UART. I am using Raspbian Jessie running on a RaspberryPi model B but I wanted to use this C code (with any necessary revisions) on openwrt. So far, this is what I wrote.

Header:

#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 file:

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

So, when I try to read more than 8 bytes, the message gets truncated to 8 bytes. As I read, setting VTIME to a certain value allows the time interval between two bytes to be at most that long. I am not certain what is going on but I suspect that the read() call reads the buffer before the receiving of the data is complete. My wish is to read a chunk of data of undefined size. I also used select() with a timeout before the read to make sure the program won't block entirely. I read a lot of forum topics, stackoverflow questions, guides, etc. on this topic and none seem to help me with my solution. So, can anyone explain what is going on here? Is it possible to do what I want?

Note that I removed some of the code (I also wrote a function that writes to a UART port) so here might be some redundant includes, global variables, etc.


Solution

  • So, I solved my problem. I still can't flush (when the system is freshly booted up and there was a signal before I turned my program on, there is some old content in the buffer, as it seems to me). I used this assumption: The package will arrive in smaller bursts and they will be separated by TIMEOUT_BYTE_ amount of time. If that expires, I assume that the package is over. Also, I have a timeout for initial waiting for the data but I reckon that is situational.

    Header file:

    #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
    

    Source file:

    #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);
    }
    

    I know it is not the topic here, but if someone knows how to solve the flush issue, please respond. Also, any constructive criticism is very welcome.

    P.S. I also have a write_UART() function but I did not deem necessary to post it as it represented no problem (measured with an oscilloscope and later, tried with echo. Echo wouldn't be able to give me the same message).

    EDIT: Flush has been introduced and then merged with the source file. Still haven't figured out is it working or not.