• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

C programming. Using termios

Les Jones

Well-Known Member
Thread starter #1
I am trying to get a program which is designed to record data received over serial link via HC12 modules (433 mhz serial link) to behave as I want. As there is a possibility of data being lost over the wireless link I would like to get the termios receive function to time out if it does not receive data within a certain anount of time so that the program does not hang waiting for data. I am using a Raspberry Pi for the data logging. The request for data from a remote sensor is initiated by the Raspberry Pi sendin a "#" character followed by a letter. (The sensor address) The sensor then responds with a ASCII text string. I am pretty useless at "C" programming so there are probably ways the the code could be improved. The is the code in it's present state.
Code:
/*
 * 12/03/17 Code being added to write the data to a file.
 * 13/03/17 Being modified to extract raw humidity an temperature
 * 13/03/17 Being modified for voltage monitor
 * 14/03/17 Being modified to enter time between readings and number of readings.
 * 18/03/17 Serial input buffer now beinig flushed at and of read
 * with "tcflush(fd, TCIFLUSH);" instruction
 * 23/12/17 Moving TCIFLUSH) to just before sending # character.
 * 24/12/17 Added a 500 mS delay between sending station address and
 *          reading input string.This prevents it only reading the
 *          first 8 bytes of the input string.
 *          Added a 10 mS delay at the start of the main "do"
 *          loop before the "displayDecoderValues(fd);" This seems to stop the
 *          program hanging intermittently.
 * 31/12/17 Attempting to test if the first character in "serialBuff_i" is 0x00
 *          If so then request a new read of data from remote station.
 *
 * Test010b.c
 *
 *
 */

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


void writeBytes(int descriptor, int count);
void readBytes(int descriptor, int count);
void displayDecoderValues(int fd);

char serialBuff_i[30];           // Serial buffer sto store data for I/O
char serialBuff_o[30];
char outbuff[30];
char vbuff[10];
char F_name[25];           //Disk file name
int rnum = 1;
int S_time = 1;              // Time between readings
int samples = 100;           // Number of readings to be taken.
int main(int argc, char **argv)
{
   int fd;                                       // File descriptor of port we will talk to
   char *portName = "/dev/ttyAMA0";       // Name of the UART port on the Raspberry pi
   struct termios options;     
   struct termios oldtio, newtio;
   char buf[255];
                       // Port options
 
   fd = open(portName, O_RDWR | O_NOCTTY);       // Open port for read and write not making it a controlling terminal
   if (fd == -1)
   {
       perror("openPort: Unable to open port ");   // If open() returns an error
   }
   tcgetattr(fd, &options);
   cfsetispeed(&options, B9600);           // Set baud rate
   cfsetospeed(&options, B9600);                 
//   cfmakeraw(&options);
   newtio.c_lflag = 0;   //Set non canonical
   options.c_cc[VTIME] = 10;   //struct termios
   options.c_cc[VMIN] = 10;
   tcflush(fd, TCIFLUSH);
   tcsetattr(fd, TCSANOW, &options); //End of UART setup
 
   usleep(10000);       // Sleep for UART to power up and set options


   printf ("Name of file to hold readings ? ");
   scanf ("%s", F_name);

   printf ("Time between readings ? (Integer minutes.) ");
   scanf ("%d", &S_time);

   printf ("\nNumber of readings ? ");
   scanf ("%d", &samples);
     
   int a = 0; 

   do 
   {
   printf("new   ");
   usleep(10000);
   displayDecoderValues(fd);

   serialBuff_o[0] = 0x0A;
   serialBuff_o[1] = 0x0D;     
 
   writeBytes(fd, 2);
 
//   sleep(S_time * 60);
   sleep(S_time);

   a++; 
   } while (a < samples);
   close(fd);
   printf ("Exit\r\n"); 
 
   return 0;
}

void writeBytes(int descriptor, int count) {
   if ((write(descriptor, serialBuff_o, count)) == -1) {           // Send data out 
       perror("Error writing");
       close(descriptor);                       // Close port if there is an error
       exit(1);
   }
}

void readBytes(int descriptor, int count) {
   if (read(descriptor, serialBuff_i, count) == -1) {               // Read back data into buf[]
       perror("Error reading ");
       printf("Read error");
       close(descriptor);                           // Close port if there is an error
       exit(1);
   }
}


void displayDecoderValues(int fd) {
   tcflush(fd, TCIFLUSH);
   serialBuff_o[0] = ' ';       // space character (To wake up UART) 
   serialBuff_o[1] = '#';       // # character
   serialBuff_o[2] = 'D';      // Station ID                                                            // Command to return encoder values 
   writeBytes(fd, 3);

   usleep(100000);
   printf("Now about to read data \r\n");
   readBytes(fd, 12);       //

   usleep(500000);
   printf("Data read \r\n");


   if (serialBuff_i[0] ==0x00)
   {
   printf("Null character at start of input buffer \r\n");
   }
 
//   sleep(2);
   strcpy (outbuff, serialBuff_i);

   outbuff[16] = 0x00; // Write 0x00 to terminate string

   printf(" \r\n");
   printf (outbuff); //Print to screen

   vbuff[0] = outbuff[0];
   vbuff[1] = outbuff[1];
   vbuff[2] = outbuff[2];
   vbuff[3] = outbuff[3];
   vbuff[4] = outbuff[4];
   vbuff[5] = outbuff[5];
   vbuff[6] = 0x2C;   // Comma character
   vbuff[7] = 0x00;       //End of string marker

   printf ("\n%d,", rnum);

   printf (vbuff);

// Setup for writing to disk
   FILE *fp;
   int value;
   fp = fopen (F_name , "ab"); // "ab" append binary

   if (fp)
   {

   fprintf (fp,"\n%d,", rnum);
   rnum ++;

   fprintf (fp, vbuff);

   fclose (fp);   //close disk file
   }
 
   printf(" \r\n");
   printf("Next   ");

   fflush(stdout);                   // Flush output
}
When the program hangs if data is list is the line " readBytes(fd, 12); " which is just after the line "printf("Now about to read data \r\n");" (These printf lines were added to find out where the program was hanging.)

Les.
 

Pommie

Well-Known Member
Most Helpful Member
#2
Not too versed in uart programming in linux but your timeout value is set to 1 second.

Can you try setting VTIME and VMIN both to zero and add a timeout to your readBytes routine.
Code:
unsigned char readBytes(int descriptor, int count){
uint32_t timeOut;
uint8_t received=0,temp,error=FLASE;
timeOut=millis()+100;            //allow 1/10th of a second
    while(received<count and error==FALSE){
        if(temp=read(descriptor, serialBuff_i + received, count - received) == -1)
            error=TRUE;
        else
            received+=temp;
        if(millis()>timeOut)
            error=TRUE;
    }
    return(error);
}
This will at least time out but you will need to change the calling code to check for an error and resend etc.

I'm assuming that millis() exists and returns a 32 bit integer.

The above is not tested and I'm not sure if you can add a uint8 to a char pointer (serialBuff_i + received) without getting an error.

Mike.
 

Les Jones

Well-Known Member
Thread starter #3
Hi Mike,
Thanks for the suggested changes in the code. I will try them and let you know if it solves the problem. I have had lots of problems with the termios read routine. I have tried to find some good documentation on termios to try to understand the exact meaning of the parameters but failed. I find "C" difficult to understand compared with assembler. With assembler it is easy to find out exactly what each instruction does from the data sheet.

Les.
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
#4
I take it you found the linux manual..

http://man7.org/linux/man-pages/man3/termios.3.html

On reading it it looks pretty much the same as Borlands old uart library....

The timeout is primarily a "reset" so the comms can re-sync.. If your packet has 10 characters at 9600 baud your time out can be 20mS, if its too long you may miss the next packet.

I have used a timer in the past to keep the com port clear after unsuccessful transmissions.. Using a timer stops the uart functions from hanging...
 

Les Jones

Well-Known Member
Thread starter #5
Hi Ian,
I did find that document or something similar but I was looking for a better definition of timeout in the definition "VTIME Timeout in deciseconds for noncanonical read (TIME)" I did not know if it was how long was allowed between the first character received and the last character received or the the time before it was considered an error condition. I will explain what was happening. First this is the setup. The remote sensor (A PIC12F1840 and a HC12 in the garage.) which is waiting to receive a "#" character. It then waits a short time to receive its address character. (A letter "D" in this case) It then takes a voltage reading using the ADC and converts it to an ASCII text string. (This is in the form "12.128 Volts" followed by a space charcter and C/R, L/F) If it does not receive this character it goes back to waiting for a "#" character. The monitor program is running on a Raspberry Pi with an HC12 in my computer room. Also in the computer room is a PC with an HC12 running Tera Term monitoring traffic on the radio link. When the program hangs on the Raspberry Pi The PC has displayed the #D sent by the R Pi followed by the text string from the remote sensor. If any character is sent from the PC it wakes up the R Pi but it replaces the first character of the text string with the character sent from the PC. So for example 12.128 Volts is displayed on the R Pi as x2.128 Volts. I was hoping to get the readbytes routine to drop though with an error so I could send another #D to get the remote sensor to send another reading. I am going to try to get Mike's version of the readbytes routine to work.

Les.
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
#7
Me personally!!
When I do this, I send from the remote.... This means the remote can sleep.

Remote wakes... Sends ID Data.... CRLF... Back to sleep.

If you have several remotes, then set the wake time slightly different to avoid cross talk!!
 

Les Jones

Well-Known Member
Thread starter #8
Hi Mike and Ian,
Mike, I have tried to replace the "readbytes" routine in my program with your suggested routine but I can't get the line "while(received<count and error==FALSE){" to compile. My knowledge ov "C" programming is so poor that I can't see what is wrong. Mike and Ian, I think I should try going through Nigels "C" tutorials so that I have more idea about what I am trying to do. I think I must be like one of the people asking for help on some complex electronics when they don't even know ohms law.

Les.
 

Pommie

Well-Known Member
Most Helpful Member
#9
Sorry Les, my fault. I've been using VB and it crept into the above.
The line should be "while(received<count && error==FALSE){".

Mike.
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
#10
Sorry Les, my fault. I've been using VB and it crept into the above.
The line should be "while(received<count && error==FALSE){".

Mike.
I do that..... A lot!!! Now I'm using Pascal, I'm getting other stupid errors... ie ":=".. Gets on me T**Ts...
 

Les Jones

Well-Known Member
Thread starter #11
Hi Mike and Ian,
That modified version of the line of code cleared the error message the originl caused. I have now found that I can't get the "millis" function to work on the Raspberry Pi.The documentation that I found suggested that it should work with the header file "wiringPi.h" but adding that did not fix the problem. I think I will abandon this idea until I manage to learn enough about "C" programming to understand what I am doing. I did try looking at the link to Nigel Goodwin's tutorial but it is aimed at programming PICs
Thanks to you and Ian for your help.

Les.
 

Les Jones

Well-Known Member
Thread starter #12
Update on progress.
I found this link which gives a good explanation of "VMIN" and "VTIME" This enabled me to modify the program so it did not hang when data was lost ofer the radio link. It now does a retry if data is lost.
This is the modified version.
Code:
/*
 * 12/03/17 Code being added to write the data to a file.
 * 13/03/17 Being modified to extract raw humidity an temperature
 * 13/03/17 Being modified for voltage monitor
 * 14/03/17 Being modified to enter time between readings and number of readings.
 * 18/03/17 Serial input buffer now beinig flushed at and of read
 * with "tcflush(fd, TCIFLUSH);" instruction
 * 23/12/17 Moving TCIFLUSH) to just before sending # character.
 * 24/12/17 Added a 500 mS delay between sending station address and
 *          reading input string.This prevents it only reading the
 *          first 8 bytes of the input string.
 *          Added a 10 mS delay at the start of the main "do"
 *          loop before the "displayDecoderValues(fd);" This seems to stop the
 *          program hanging intermittently.
 * 31/12/17 Attempting to test if the first character in "serialBuff_i" is 0x00
 *          If so then request a new read of data from remote station.
 *
 * Test009b.c
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <wiringPi.h>
#include <time.h>


void writeBytes(int descriptor, int count);
// void readBytes(int descriptor, int count);
void displayDecoderValues(int fd);

char serialBuff_i[30];           // Serial buffer sto store data for I/O
char serialBuff_o[30];
char outbuff[30];
char vbuff[10];
char F_name[25];           //Disk file name
int rnum = 1;
int S_time = 1;              // Time between readings
int samples = 100;           // Number of readings to be taken.
//int FALSE = 0;
//int TRUE = 1;

int main(int argc, char **argv)
{
   int wiringPiSetup(void);

   unsigned int millis;

   int fd;                                       // File descriptor of port we will talk to
   char *portName = "/dev/ttyAMA0";       // Name of the UART port on the Raspberry pi
   struct termios options;       
   struct termios oldtio, newtio;
   char buf[255];
                       // Port options
   
   fd = open(portName, O_RDWR | O_NOCTTY);       // Open port for read and write not making it a controlling terminal
   if (fd == -1)
   {
       perror("openPort: Unable to open port ");   // If open() returns an error
   }
   tcgetattr(fd, &options);
   cfsetispeed(&options, B9600);           // Set baud rate
   cfsetospeed(&options, B9600);                   
   cfmakeraw(&options);
   newtio.c_lflag = 0;   //Set non canonical
   options.c_cc[VTIME] = 10;   //struct termios
   options.c_cc[VMIN] = 0;  
   tcflush(fd, TCIFLUSH);
   tcsetattr(fd, TCSANOW, &options); //End of UART setup
   
   usleep(10000);       // Sleep for UART to power up and set options


   printf ("Name of file to hold readings ? ");
   scanf ("%s", F_name);

   printf ("Time between readings ? (Integer minutes.) ");
   scanf ("%d", &S_time);

   printf ("\nNumber of readings ? ");
   scanf ("%d", &samples);
       
   int a = 0;   

   do   
   {
   printf("new   ");
   usleep(10000);
   displayDecoderValues(fd);
   

   serialBuff_o[0] = 0x0A;
   serialBuff_o[1] = 0x0D;                                                           // Command to return encoder values
   
   writeBytes(fd, 2);
   
//   sleep(S_time * 60);
   sleep(S_time);

   a++;   
   } while (a < samples);
   close(fd);
   printf ("Exit\r\n");   
   
   return 0;
}

void writeBytes(int descriptor, int count) {
   if ((write(descriptor, serialBuff_o, count)) == -1) {                           // Send data out   
       perror("Error writing");
       close(descriptor);                                                           // Close port if there is an error
       exit(1);
   }
}

 void readBytes(int descriptor, int count) {
   if (read(descriptor, serialBuff_i, count) == -1) {                               // Read back data into buf[]
       perror("Error reading ");
       printf("Read error");
       close(descriptor);                                                           // Close port if there is an error
       exit(1);
   }
}



void displayDecoderValues(int fd) {
Tryagain:
   tcflush(fd, TCIFLUSH);
   serialBuff_o[1] = '#';       // # character
   serialBuff_o[2] = 'D';      // Station ID                                                            // Command to return encoder values   
   writeBytes(fd, 3);

   usleep(100000);
   printf("Now about to read data \r\n");
   usleep(300000);
   readBytes(fd, 12);
           //
   if (serialBuff_i[0] ==0x00)
   {
   printf("Missed reading \r\n");
   goto Tryagain;
   }
//   usleep(500000);
//   printf("Data read \r\n");    

   strcpy (outbuff, serialBuff_i);
   serialBuff_i[0] = 0x00;  // clear string ???   

   outbuff[16] = 0x00; // Write 0x00 to terminate string

   printf(" \r\n");
   printf (outbuff); //Print to screen
   

   vbuff[0] = outbuff[0];
   vbuff[1] = outbuff[1];
   vbuff[2] = outbuff[2];
   vbuff[3] = outbuff[3];
   vbuff[4] = outbuff[4];
   vbuff[5] = outbuff[5];
   vbuff[6] = 0x2C;   // Comma character
   vbuff[7] = 0x00;       //End of string marker

   printf ("\n%d,", rnum);

   printf (vbuff);

// Setup for writing to disk
   FILE *fp;
   int value;
   fp = fopen (F_name , "ab"); // "ab" append binary

   if (fp)
   {

   fprintf (fp,"\n%d,", rnum);
   rnum ++;

   fprintf (fp, vbuff);

   fclose (fp);   //close disk file
   }
   
   printf(" \r\n");
   printf("Next   ");

   fflush(stdout);                                                                   // Flush output to ensure that data is displayed on the screen
}
Les.
 

Latest threads

EE World Online Articles

Loading

 
Top