Serial C And The Raspberry Pi
Written by Harry Fairhead   
Monday, 29 August 2016
Article Index
Serial C And The Raspberry Pi
Linux Presents A Problem
Setting up
Send and Recieve
Polling for data

Polling for data

You can set up a software interrupt to signal when there is data to be read and you can use another thread to process it, but the majority of serial applications work best with a simple polling loop and you can do the job with a blocking or non-blocking call.

For example, suppose you have just sent a command to a device and now you need to wait for it to respond. Suppose the response is a block of code always less than 100 bytes and after this has been transmitted nothing happens until the next command is sent to the device.

To do the job with a blocking call we can read one block of data at a time by setting c_cc[VTIME] and c_cc[VMIN] correctly. As we are only expecting fewer than 100 bytes we can set c_cc[VMIN] to 100 and be safe that the call to read will not return before reading all of the characters sent by the device.

Setting c_cc[VTIME]=1 means that if there is more than a one tenth of a second pause between bytes then the block is assumed complete and the read returns with the data. Of course, if the device pauses for more than 0.1s and it hasn't finished then the block will be received incomplete. However, in many cases this is unlikely and when it does happen represents an error that needs a complete retry. 

An example program using this method is:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> 
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <sys/ioctl.h>


int main(int argc, char** argv) {
 system("sudo systemctl stop
             serial-getty@ttyAMA0.service");
 int sfd = open("/dev/serial0", O_RDWR | O_NOCTTY);
 if (sfd == -1) {
  printf("Error no is : %d\n", errno);
  printf("Error description is : %s\n", strerror(errno));
  return (-1);
 };
 struct termios options;
 tcgetattr(sfd, &options);

 cfsetspeed(&options, B9600);
 cfmakeraw(&options);
 options.c_cflag &= ~CSTOPB;
 options.c_cflag |= CLOCAL;
 options.c_cflag |= CREAD;
 options.c_cc[VTIME]=1;
 options.c_cc[VMIN]=100;
 tcsetattr(sfd, TCSANOW, &options);

 char buf[] = "hello world";
 char buf2[100];
 int count = write(sfd, buf,strlen(buf));

 usleep(100000);
 int bytes;
 ioctl(sfd, FIONREAD, &bytes);
 if(bytes!=0){
  count = read(sfd, buf2, 100);
 }

 printf("%s\n\r", buf2);
 close(sfd);
 return (EXIT_SUCCESS);
}

This will work with a loopback connection and you don't need to wait for the transmitted data to be received because you have a 0.1 time out between characters that effectively waits for the data. All that matters is that at least one character has been received. The pause of 0.1 seconds before testing to see if there are any characters in the buffer is reasonable as you have the same inter-character time out.

This works well in situations where you have a send-receive command-response setup, but it has some disadvantages. The first is that your program is halted while the block of data comes in. If it has nothing else to do this acceptable.

 

An alternative is to spin the data transfer off onto a different thread, but even in this case there is also the small problem of 0.1s wasted at the end of the transmission to confirm that the block has been sent. This is often acceptable as well. 

Polling for data with non-blocking

An alternative way of doing the same job is to use a polling loop and read in the data a character at a time. The problem in this case is knowing when the block of data is complete. You can do this by arranging for the sending device to indicate the number of bytes to be transferred or by sending a flag byte to indicate the end of the block. However it is done, you have to have some way of knowing that a block is complete. 

In this example the end of the block is marked by a null byte as if it was a string. The non-blocking read is created by setting  c_cc[VTIME] =0 and c_cc[VMIN]=0 but you could do it another way. 

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


int main(int argc, char** argv) {
 system("sudo systemctl stop
        serial-getty@ttyAMA0.service");
 int sfd = open("/dev/serial0", O_RDWR | O_NOCTTY); 

 if (sfd == -1) {
  printf("Error no is : %d\n", errno);
  printf("Error description is : %s\n", strerror(errno));
  return (-1);
 };
 struct termios options;
 tcgetattr(sfd, &options);
 cfsetspeed(&options, B9600);

 cfmakeraw(&options);
 options.c_cflag &= ~CSTOPB;
 options.c_cflag |= CLOCAL;
 options.c_cflag |= CREAD;
 options.c_cc[VTIME]=0;
 options.c_cc[VMIN]=0;
 tcsetattr(sfd, TCSANOW, &options);

 
 char buf[] = "hello world";
 char buf2[100];
 char c;
 int count = write(sfd, buf,strlen(buf)+1);
 

 int i=0;
 while(1){
  count = read(sfd, &c, 1);
  if(count!=0){
    buf2[i]=c;
    i++;
    if(c==0)break;
  };
 };
 printf("%s\n\r", buf2);
close(sfd);
 return (EXIT_SUCCESS);
}

Notice that the work is done in the while loop and also notice that we don't wait for the transmitted data to be available as a block. There is no overall timeout and if the transmitting device never sends a null then the loop never ends. In practice you would need to include an overall timeout for the loop. In this example we do send the null at the end of the string and so it should end as long as there isn't a transmission problem.

Receiving A Big Block 

What if you want to receive a block that might be bigger than 511 characters but you still want an inter-character time out?

The answer is that you can write your own function that does the job. 

The algorithm is simple. Peek a the number of bytes in the serial buffer. If it has reached the maximum size that you want to deal with then read the buffer. If it hasn't then wait for the specified time out and peek a the number of characters in the buffer. If it hasn't increased the time out is up and you read as many characters that are available in the buffer. Otherwise you wait another time out period to see if more data comes in.

The function is: 

int getBlock2(char buf[], int Toutms, int maxbytes) {
    if(maxbytes>=sizeof (buf)) return -1;
    int bytes = 0;
    int oldbytes = 0;
    struct timespec pause;
    pause.tv_sec = 0;
    pause.tv_nsec = Toutms * 1000;
    for (;;) {
        oldbytes = bytes;
        if (bytes >= maxbytes) break;
        nanosleep(&pause, NULL);
        ioctl(sfd, FIONREAD, &bytes);
        if (oldbytes == bytes)break;
    }
    memset(buf, '\0', sizeof (buf));
    if (bytes >= maxbytes)bytes=maxbytes;
    int count = read(sfd, buf, bytes);
    buf[count+1] = 0;
    return count;
}

Blocking v non-blocking

This use of completely non-blocking reads is the typical way that serial port communications is handled. This is mostly because the range of behavior that you can program using blocking reads isn't appreciated. You don't just have a choice of blocking v non-blocking. You can set a timeout between characters and a minimum number of characters to wait for and a maximum to return. When you don't know how much data is going to be sent, only an upper limit to a block of data, then blocking reads are simpler and preferable as long as you set a timeout and a minimum character size.  

Notice that a blocking read doesn't load the processor because the thread is suspended while the driver waits for data. A polling blocking read, on the other hand, occupies a single core 100% of the time but it can be doing other things as well as checking for data. Using 100% of a core doesn't matter so much if you have other cores to keep the system working and it doesn't matter at all if you haven't got anything else for the core to do. 

In more complicated situations you may have to use a separate thread to poll or wait for blocked calls, but this isn't as necessary as you might think. Neither is the use of a software interrupt to service incoming data, as most transactions are of a command- response nature. 

Linux serial port handling is much more subtle than you might have imagined.

 operators

 

Banner

espbook

 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Thursday, 01 December 2016 )