I am having an issue reading from a serial port on a Beaglebone Black using a C++ script I've written. The script sends commands to and receives responses from an Adafruit FONA GSM/GPS device and works, except that there is a long delay between sending the commands and actually receiving any bytes back from the device (I have to put a 1 sec delay between the write and read commands in order to get the response from the device). However, when I use the minicom serial terminal emulator there is no perceptible delay between sending the command and receiving the response. I'm guessing it has to do with how I am opening the serial port, but I don't know what else I can change. Right now I have the settings set to raw input and output modes with no line control or echoing, but still unable to reduce the response time. Any help or ideas are more than welcome and appreciated! Thanks!
CPP file:
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/ioctl.h>
#include <linux/serial.h>
#include <termios.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <stdio.h>
#include "Fona_control.h"
Fona_control::Fona_control(void)
{
begin();
}
void Fona_control::get_gps(void)
{
printf("Reading GPS\n");
unsigned char gpsbuff[250];
memset(gpsbuff,'\0',250);
sleep(0.1);
int bytes_a = 0;
int n_write = write(fona_fd,GPS_GET_DATA,sizeof(GPS_GET_DATA)-1);
sleep(1);
ioctl(fona_fd,FIONREAD,&bytes_a);
printf("Bytes avail: %i\n",bytes_a);
int n_read = read(fona_fd,gpsbuff,bytes_a);
printf("Buffer: %s\n",gpsbuff);
printf("Bytes read: %i\n",n_read);
return;
}
void Fona_control::begin(void)
{
printf("FONA Beginning\n");
struct termios oldtio, newtio;
struct serial_struct serinfo;
// Load the pin configuration
/* Open modem device for reading and writing and not as controlling tty
because we don't want to get killed if linenoise sends CTRL-C. */
fona_fd = open(FONA_DEVICE, O_RDWR | O_NOCTTY | O_NDELAY);
if (fona_fd < 0) { perror(FONA_DEVICE); exit(-1); }
bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings */
/* BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
CRTSCTS : output hardware flow control (only used if the cable has
all necessary lines. See sect. 7 of Serial-HOWTO)
CS8 : 8n1 (8bit,no parity,1 stopbit)
CLOCAL : local connection, no modem contol
CREAD : enable receiving characters */
cfsetspeed(&newtio,BAUDRATE_Fona);
newtio.c_cflag |= ( CLOCAL | CREAD );
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |= CS8;
newtio.c_cflag &= ~PARENB;
newtio.c_cflag &= ~CRTSCTS;
newtio.c_cflag &= ~CSTOPB;
newtio.c_cc[VMIN] = 1;
newtio.c_cc[VTIME] = 1;
ioctl (fona_fd, TIOCGSERIAL, &serinfo);
serinfo.flags |= 0x4000;
ioctl (fona_fd, TIOCSSERIAL, &serinfo);
/* setup for non-canonical mode */
//newtio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | ISTRIP | INLCR | INPCK | ICRNL | IXON | IGNCR);
newtio.c_iflag = 0;
/* Set line flags */
//newtio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
newtio.c_lflag = 0;
/* Raw output
newtio.c_oflag &= ~(OCRNL | ONLCR | ONLRET | ONOCR | ONOEOT| OFILL | OLCUC | OPOST); */
newtio.c_oflag = 0;
/* now clean the modem line and activate the settings for the port */
tcflush(fona_fd, TCIOFLUSH);
if(tcsetattr(fona_fd,TCSANOW,&newtio) < 0)
{
printf("Error Setting FONA Serial Port Attributes!");
exit(0);
};
/* terminal settings done, now send FONA initialization commands*/
sleep(1);
unsigned char buffer[50];
int bytes_avail = 0;
int n_write = 0;
int n_read = 0;
int cnt = 0;
memset(buffer,'\0',50);
tcflush(fona_fd, TCIOFLUSH);
while(strstr((char *)buffer,"OK") == NULL && cnt < 5)
{
memset(buffer,'\0',50);
n_write = write(fona_fd,FONA_AT,sizeof(FONA_AT)-1);
sleep(1);
ioctl(fona_fd,FIONREAD,&bytes_avail);
printf("BA: %i\n",bytes_avail);
if(bytes_avail > 0)
{
n_read = read(fona_fd,buffer,bytes_avail);
printf("%s\n",buffer);
}
sleep(1);
cnt++;
}
sleep(1);
n_write = write(fona_fd,"+++",3);
bytes_avail = 0;
sleep(1);
ioctl(fona_fd,FIONREAD,&bytes_avail);
printf("BA2: %i\n",bytes_avail);
n_read = read(fona_fd,buffer,bytes_avail);
printf("%s",buffer);
printf("AT Accepted\n");
sleep(1);
tcflush(fona_fd, TCIOFLUSH);
unsigned char buffer1[50];
memset(buffer1,'\0',50);
int n = write(fona_fd,FONA_ECHO_OFF,sizeof(FONA_ECHO_OFF)-1);
printf("Writ: %i\n",n);
bytes_avail = 0;
sleep(1);
ioctl(fona_fd,FIONREAD,&bytes_avail);
printf("BA2: %i\n",bytes_avail);
n = read(fona_fd,buffer1,bytes_avail);
printf("%s",buffer1);
memset(buffer1,'\0',50);
sleep(1);
n = write(fona_fd,GPS_POWER_ON,sizeof(GPS_POWER_ON)-1);
printf("Writ: %i\n",n);
bytes_avail = 0;
sleep(1);
ioctl(fona_fd,FIONREAD,&bytes_avail);
printf("BA2: %i\n",bytes_avail);
n = read(fona_fd,buffer1,bytes_avail);
printf("%s\n",buffer1);
memset(buffer1,'\0',50);
sleep(1);
n = write(fona_fd,FONA_SMS_TYPE,sizeof(FONA_SMS_TYPE)-1);
printf("Writ: %i\n",n);
bytes_avail = 0;
sleep(1);
ioctl(fona_fd,FIONREAD,&bytes_avail);
printf("BA2: %i\n",bytes_avail);
n = read(fona_fd,buffer1,bytes_avail);
printf("%s\n",buffer1);
sleep(1);
}
H File:
#ifndef _fona_control_H
#define _fona_control_H
#include <stdint.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#define FONA_DEVICE "/dev/ttyO5" //Beaglebone Black serial port
//#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define BAUDRATE_Fona B115200 // Change as needed, keep B
/* Define FONA AT Commands */
#define FONA_AT "AT\r\n"
#define FONA_ECHO_OFF "ATE0\r\n"
#define FONA_CMD_REPEAT "A/\r\n"
#define FONA_NO_ECHO "ATE0\r\n"
#define FONA_PIN_CHECK "AT+CPIN?\r\n"
#define FONA_PIN_SEND "AT+CPIN=1234\r\n"
#define FONA_SMS_TYPE "AT+CMGF=1\r\n"
/* Define FONA GPS AT Commands */
#define GPS_POWER_ON "AT+CGNSPWR=1\r\n"
#define GPS_POWER_OFF "AT+CGNSPWR=0\r\n"
#define GPS_GET_DATA "AT+CGNSINF\r\n"
/* Define FONA GPS NMEA Commands */
#define PMTK_CMD_HOT_START "AT+CGNSCMD=0,"$PMTK101*32"\r\n"
#define PMTK_CMD_WARM_START "AT+CGNSCMD=0,"$PMTK102*31"\r\n"
#define PMTK_CMD_COLD_START "AT+CGNSCMD=0,"$PMTK103*30"\r\n"
#define PMTK_SET_NMEA_5HZ "AT+CGNSCMD=0,"$PMTK220,200*2C"\r\n"
#define PMTK_SET_BAUD_38400 "AT+CGNSCMD=0,"$PMTK251,38400*27"\r\n"
#define PMTK_SET_WAAS "AT+CGNSCMD=0,"$PMTK301,2*2E"\r\n"
#define PMTK_SET_SBAS "AT+CGNSCMD=0,"$PMTK313,1*2E"\r\n"
#define PMTK_NMEA_TYPES "AT+CGNSCMD=0,"$PMTK314,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0*29"\r\n"
#define PMTK_STANDY_MODE "AT+CGNSCMD=0,"$PMTK161,0*28"\r\n" //Send any byte to exit standby mode
class Fona_control {
public:
void begin(void);
void get_gps(void);
Fona_control(void); // Constructor when using HardwareSerial
uint8_t fix_status, fix_mode, sats, sats_used, glo_sats, cn0;
uint8_t month, day, minute;
uint32_t year;
double seconds, latitude, longitude, speed, course, hdop, vdop, pdop, hpa, vpa;
int fona_fd;
private:
};
#endif
1 second sleeps are murderous and I can assure you minicom is not doing them. It will be waiting for data to come in, rather than polling, then displaying it. Serial data is slow. Every character sent at 9600 baud takes about a millisecond to arrive. at 115.2k baud, you're moving a character in a little less than 85 microseconds. Your Beaglebone, on the other hand, is working in nanoseconds, so if you don't wait before reading, the data won't be there yet. That said, you should have that "OK" in less than 1 ms and waiting a whole second is vast overkill.
Consider blocking and waiting for the three bytes needed for "OK"
while(strstr((char *)buffer,"OK") == NULL && cnt < 5)
{
memset(buffer,'\0',50);
n_write = write(fona_fd,FONA_AT,sizeof(FONA_AT)-1);
n_read = read(fona_fd,buffer,3);
cnt++;
}
This can block forever if the device never responds so you need a timeout
The easiest catch-all timeout mechanism I can think of off the top of my head is something like this:
int retry= 5;
while (retry)
{
fd_set readfs; // file descriptor set used by select
struct timeval timeout;
FD_ZERO(&readfs); // clear file descriptor set
FD_SET(fona_fd, &readfs); // set our port as the one item in the set
timeout.tv_sec = 1;
timeout.tv_usec = 0;
if (select(fona_fd+1, &readfs, NULL, NULL, &timeout) !=0)
{
rval = read(fona_fd, buffer, sizeof(buffer)-1);
// will stop reading after configurable gap between bytes read
buffer[rval] = '\0'; // NULL terminate the buffer or it ain't a string
// If not a string, results of strstr are undefined
if (strstr((char *)buffer,"OK") != NULL)
{
break;
}
}
retry--;
}
Documentation on select
. It's very very cool function. Not as cool as epoll, but easier to find tutorials for. If you do a lot of select
give epoll
a good look over.
Select in this case will wait for data for 1 second before giving up. If it finds data, the program will enter the if
body and read
will read until some user configurable number of character-times have passed since the last character was received or the buffer is full.
We then null terminate the data in the buffer so we can use string handling routines on it. This also eliminates the need to memset
the buffer. If the buffer contains "OK", we exit the loop.
Otherwise the we decrement the retry counter and loop around to see if there are more retries.