Hello I need advice of how correctly to change the baud rate of the UART port in the middle of the program.
To establish communication with my desired slave I need to send the so called "break field" which consists of 13-26 (MIN 11) LOW BITS, on UART as I think it's not achievable as before and after every 8 bits UART adds start & stop bit. I had an idea of achieving it by manipulating the baud-rates send break field at 9600Bauds followed by the 19200Baud speed.
But I'm unable to see difference between baudrates on the oscilloscope. So I suppose I do it in the wrong way.
#include <iostream>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <time.h>
#include <stdlib.h>
#define BAUDRATE9600 B9600
#define BAUDRATE19200 B19200
#define PORT "/dev/ttyO4"
#define _POSIX_SOURCE 1
main()
{
int fd, c;
struct termios oldtio, newtio, midtio;
fd = open(PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd <0) { perror(PORT); exit(-1); }
tcgetattr(fd, &oldtio); /* save current port settings */
/* set new port settings for canonical input processing */
midtio.c_cflag = BAUDRATE9600 | CS8 | CLOCAL | CSTOPB ;
midtio.c_iflag = IGNPAR | ICRNL;
midtio.c_oflag = 0;
midtio.c_lflag = ICANON;
newtio.c_cflag = BAUDRATE19200 | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
tcsetattr(fd, TCSANOW, &midtio);
char break[] = {0x00}; // break field (0000 0000)
c = write (fd, break, sizeof(break));
tcsetattr(fd, TCSANOW, &newtio);
char sync [] = {0x55};
char PID [] = {0xE2};
char data [] = {0xAA};
char data1 [] = {0xA5};
char check [] = {0xAF};
c = write (fd, sync, sizeof(sync));
c = write (fd, PID, sizeof(PID));
c = write (fd, data, sizeof(data));
c = write (fd, data1, sizeof(data1));
c = write (fd, check, sizeof(check));
/* restore old port settings */
tcsetattr(fd,TCSANOW,&oldtio);
}
I have tried it also by setting baudrates in different way as well:
cfsetispeed(&midtio, B9600)
cfset0speed(&midtio, B9600)
cfsetispeed(&newtio, B19200)
cfset0speed(&newtio, B19200)
I need to send the so called "break field"
If the goal is to send a "break", then you've asked an XY question with "How to change the baudrate in c/c++". You cannot generate a break condition by manipulating the baud rate. You would need a baud rate of less than 32 baud to achieve a quarter-second break.
A break condition can be sent on the serial link by using the TCSBRK command in an ioctl().
BTW your serial port initialization code does not conform to best programming practices. You should always be testing the return codes from system calls. The elements of the termios structure should not be directly assigned, but rather just the individual bits should be modified. Refer to a guide such as this.
Related
I am trying to do a serial communication application. I have a device which is running angstrom qt linux image. I need to write the serial code application for that device. Because the cross compiler is set for QT4.8.7, so it do not include the QSerialPort. So I am following this link for serial communication and I have also found many good examples on it.
Below is the code:
void MainWindow::SerialOpen(QString PORT)
{
fd = open(PORT.toStdString().c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open /dev/ttyLP0\n");
exit(1);
}
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
fcntl(fd, F_SETFL, FNDELAY);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, O_ASYNC );
tcgetattr(fd,&termAttr);
cfsetispeed(&termAttr,B9600);
cfsetospeed(&termAttr,B9600);
termAttr.c_cflag &= ~PARENB;
termAttr.c_cflag &= ~CSTOPB;
termAttr.c_cflag &= ~CSIZE;
termAttr.c_cflag |= CS8;
termAttr.c_cflag |= (CLOCAL | CREAD);
termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
termAttr.c_oflag &= ~OPOST;
tcsetattr(fd,TCSANOW,&termAttr);
qDebug("Serial Port configured....\n");
}
and for reading I am using:
void signal_handler_IO (int status)
{
char buf [100];
int n = read (fd, buf, sizeof buf);
if(n > 0)
{
buf[n] = '\0';
printf("Receive OK %s\n",buf);
}
}
I am using event based serial read. I am wiriting it in QT creator. The problem I am facing is that whenever I am sending characters like A or B or any other single char. It receives it. But when I send the complete string, it breaks automatically. Have a look at the below image:
As you can see in the image, it receives char like p l h but then I sent hello world. It breaks it and first received hello and then world. I again sent the hello world it receives the he then llo then worl and then d. Why is this showing this behaviour. Please help. Thanks.
You are receiving your data in small chunks. Try running the read function in a while loop to append all chunks into one message like so:
#include <cstring> // needed for strcat.
#include <errno.h> // needed for strerror and errno
void signal_handler_IO (int status)
{
char buf [100];
char msg[1024];
int n;
int length = 0;
while((n = read (fd, buf, (sizeof buf)-1)) > 0)
{
buf[n] = '\0';
// you can add a check here to see if (length+n) < 1024
strcat(msg, buf);
length += n;
}
if(n<0)
{
//error handling
//EDIT: adding error handling
fprintf(stderr, "Error while receiving message: %s\n", strerror(errno));
return;
}
printf("Receive OK: '%s'\n", msg);
}
The signal handling needs to be turned off for the time of reading data so that the signal handler doesn't get called for each new chunk of data. After we're done reading the handler needs to be restored.
EDIT: Thanks to #MarkPlotnick for giving a better example on masking the signal instead of turning it off (could cause race condition). Add these two lines before calling sigaction to MainWindow::SerialOpen to mask the signal so that the handler doesn't get called again while the signal is being handled:
sigemptyset(&saio.sa_mask);
sigaddset(&saio.sa_mask, SIGIO);
The size of msg is just an example, you can set it to any value that will fit the message. Also, if you're using c++ then it would be better to use std::string instead of an array for msg.
Note the (sizeof buf)-1, if you read all characters there would be no room for the '\0' at the end of the array.
ANOTHER EDIT: After our chat conversation it turned out that read is blocking when there are no more characters to read (even though open was called with O_NDELAY flag for non-blocking reading). To fix this issue one can first check how many bytes are available for reading:
while(true)
{
size_t bytes_avail;
ioctl(fd, FIONREAD, &bytes_avail);
if(bytes_avail == 0)
break;
int n = read (fd, buf, (sizeof buf)-1);
if(n<0)
{
//error handling
fprintf(stderr, "Error while receiving message: %s\n", strerror(errno));
return;
}
buf[n] = '\0';
// you can add a check here to see if (length+n) < 1024
strcat(msg, buf);
length += n;
}
QT4.8.7, so it do not include the QSerialPort
You can build QSerialPort from sources and to use it. Please read Wiki: https://wiki.qt.io/Qt_Serial_Port
I'm attempting to read from a serial port (/dev/ttyS4) on a BeagleBone Black, but I think(?) this should apply to all Linux devices in general.
Currently, I can set up minicom with a baud rate of 9600 and 8N1 data to read from the serial port correctly. However, if I attempt to directly cat /dev/ttyS4, nothing shows up in my terminal. My code also does this, and returns a Resource temporarily unavailable error, which I suspect is what is happening with the cat command.
If I run stty -F /dev/ttyS4, I get the following output (which, as far as I can tell, is consistent with my minicom settings):
speed 9600 baud; line = 0;
intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; start = <undef>; stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; flush = <undef>;
-brkint -imaxbel
-opost -onclr
-isig -iexten -echo -echoe -echok -echoctl -echoke
An interesting note is that when I have minicom open, if I start my program, minicom will stop printing anything, and stay that way even if I stop my program. I need to open the serial settings again (Ctrl-A, P) and close it for minicom to resume working (it appears that nothing was changed).
My code is as follows:
int main() {
std::cout << "Starting..." << std::endl;
std::cout << "Connecting..." << std::endl;
int tty4 = open("/dev/ttyS4", O_RDWR | O_NOCTTY | O_NDELAY);
if (tty4 < 0) {
std::cout << "Error opening serial terminal." << std::endl;
}
std::cout << "Configuring..." << std::endl;
struct termios oldtio, newtio;
tcgetattr(tty4, &oldtio); // save current serial port settings
bzero(&newtio, sizeof(newtio)); // clear struct for new settings
newtio.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
tcflush(tty4, TCIFLUSH);
tcsetattr(tty4, TCSANOW, &newtio);
std::cout << "Reading..." << std::endl;
while (true) {
uint8_t byte;
int status = read(tty4, &byte, 1);
if (status > 0) {
std::cout << (char)byte;
} else if (status == -1) {
std::cout << "\tERROR: " << strerror(errno) << std::endl;
}
}
tcsetattr(tty4, TCSANOW, &oldtio);
close(tty4);
}
Edit: I've gotten the serial port to work correctly (in python) by following Adafruit's tutorial for using python with the BeagleBone. At this point I'm certain that I'm doing something wrong; the question is what. I would much prefer using C++ over python, so it'd be great to get that working.
Your program opens the serial terminal in nonblocking mode.
int tty4 = open("/dev/ttyS4", O_RDWR | O_NOCTTY | O_NDELAY);
Nonblocking I/O, especially read operations, requires additional, special handling in a program.
Since you neglect to mention this mode, and your program has no capability to properly process this mode, this could be considered a bug.
Either remove O_NDELAY option from the open() call, or insert a fcntl(tty4, F_SETFL, 0) statement to revert back to blocking mode.
My code also does this, and returns a Resource temporarily unavailable error,
That's an EAGAIN error, which is consistent with a nonblocking read().
The man page describes this error will occur when "the file descriptor ... has been marked nonblocking (O_NONBLOCK), and the read would block".
The read() syscall "would block" because there is no data to satisfy the read request.
If you insist on using nonblocking mode, then your program must be able to cope with this condition, which is not an error but a temporary/transient status.
But blocking mode is the simpler and preferred mode of operation for typical programs in a multitasking system.
Your program should be modified as previously mentioned.
There are numerous issues with the initialization of the serial terminal.
tcgetattr(tty4, &oldtio); // save current serial port settings
The return values from the tcgetattr() and tcsetattr() syscalls are never checked for errors.
bzero(&newtio, sizeof(newtio)); // clear struct for new settings
Starting with an empty termios structure is almost always a bad idea. It may appear to work on some systems, but it is not portable code.
The proper method for initializing a termios structure is to use values from tcgetattr().
See Setting Terminal Modes Properly.
Since it is already called, all you need is newtio = oldtio to copy the structure.
newtio.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
Rather than assignment of constants, the proper method of changing these flags is to enable or disable the individual attributes.
The following should suffice for canonical mode:
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |= CS8; /* 8-bit characters */
newtio.c_cflag &= ~PARENB; /* no parity bit */
newtio.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
newtio.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
newtio.c_lflag |= ICANON | ISIG; /* canonical input */
newtio.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
newtio.c_iflag &= ~INPCK;
newtio.c_iflag |= ICRNL;
newtio.c_iflag &= ~(INLCR | IGNCR | IUCLC | IMAXBEL);
newtio.c_iflag &= ~(IXON | IXOFF | IXANY); /* no SW flowcontrol */
newtio.c_oflag &= ~OPOST;
The following is the preferred method for setting the baudrate:
cfsetospeed(&newtio, B9600);
cfsetispeed(&newtio, B9600);
If any salient attributes are left unspecified, the existing settings are used.
This can lead to erratic program behavior, e.g. sometimes it works, sometimes it doesn't.
An interesting note is that when I have minicom open, if I start my program, minicom will stop printing anything, and stay that way even if I stop my program. I need to open the serial settings again (Ctrl-A, P) and close it for minicom to resume working (it appears that nothing was changed).
The serial terminal is not intended for sharing among more than one process.
Some of the termios attributes have to be implemented in the serial device driver, which has no concept of sharing the port. The most recent termios attributes are in effect for the device.
When you execute your program after minicom has started, you are clobbering the termios attributes that minicom expects to execute with.
You are restoring the termios attributes to minicom's requirements by using its menu.
right now I'm communicating with a device over UART in C++ on a Udoo Neo. For this I'm using termios to set up the connection and write data to the device.
For this purpose I want to use hardware flow control and have set the flag (CRTSCTS) with termios.
For the hardaware flow control I've connected the device RTS line to the boards CTS and I've also checked via oscilloscope, that the device is giving me an active high, if it is not ready to read.
The problem is that I still loose bytes in the following example of just spamming the device with numbers, but the boards output says that everything was written correctly.
I thought the UART would be blocked when using HW flow control, so that no information is lost. Am I not understanding this correctly - or is there an error in the code?
Thanks for the help
const char dev[] = "/dev/ttymxc4";
int main(int argc, char **argv) {
int fd;
struct termios t; ///< control structure for a general asynchronous interface
// edited code
tcgetattr(fd, &t);
t.c_iflag &= ~(IGNBRK | BRKINT | ICRNL |
INLCR | PARMRK | INPCK | ISTRIP | IXON);
t.c_oflag = 0;
t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= (CS8 | CRTSCTS);
// edited code
t.c_cflag |= (CLOCAL | CREAD);
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
t.c_cc[VMIN] = 0;
t.c_cc[VTIME] = 0;
cfsetispeed(&t,B57600); /* normal shall be: B115200 Baud */
fd = ::open(dev, O_RDWR);
if (fd==-1) {
printf("UART: cannot open file: %s\n",dev);
return -1;
}
tcsetattr(fd,TCSANOW, &t);
// edited code
fcntl(fd, F_SETFL, 0);
int count = 0;
while (true) {
count++;
std::stringstream output;
output << count << ",";
::write(fd, output.str().c_str(), output.str().length());
printf("%d, writing: %s\n", fd, output.str().c_str());
usleep(10000);
}
return 0;
}
Referring to the links by #sawdust, the HW flow control is manipulated via
CCTS_OFLOW and CRTS_IFLOW via libc-doc
Macro: tcflag_t CCTS_OFLOW
If this bit is set, enable flow control of output based on the CTS wire (RS232 protocol).
Macro: tcflag_t CRTS_IFLOW
If this bit is set, enable flow control of input based on the RTS wire (RS232 protocol).
CNEW_RTSCTS and CRTSCTS via SerProgGuide
Some versions of UNIX support hardware flow control using the CTS
(Clear To Send) and RTS (Request To Send) signal lines. If the
CNEW_RTSCTS or CRTSCTS constants are defined on your system then
hardware flow control is probably supported. Do the following to
enable hardware flow control:
options.c_cflag |= CNEW_RTSCTS; /* Also called CRTSCTS */
Note the "Some versions..." and "...is probably supported."
On my particular cross compilation toolchain (Linaro GCC 6.5-2018.12) if I grep for these values, CRTSCTS is not documented, but defined, CCTS_OFLOW is in a lot of info files, but in no header files...
libc/usr/include/bits/termios.h:
174:# define CRTSCTS 020000000000 /* flow control */
As you have said in your comment
... I just thought this would be handled by the kernel?
I am seeing the phenomenon, that even if I add the relevant rts/cts properties in the device-tree ({rts,cts}-gpios or uart-has-rtscts), the command stty -a -F /dev/ttyS still reports back -crtscts meaning that the RTS/CTS handshake is disabled, so even without the userspace application this doesn't seem to be a trivial config. (Kernel 5.4)
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.
I have written the C program that should read UART's RxD port and display the results as soon as there is any information. To achieve this I'm using signal_handler SIGIO signal
Read program c code
#include <iostream>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
#include <string.h>
#define BAUDRATE B19200
#define PORT "/dev/ttyO4"
#define _POSIX_SOURCE 1
int fd;
void signal_handler_IO(int status);
void set_port_settings();
char buff[255];
sig_atomic_t flag=0;
using namespace std;
int main ()
{
set_port_settings();
for(;;){
if(flag !=0)
{
//printf( "sync : 0x%X\n", buff[1]);
//printf ( "PID: 0x%X\n", buff[2]);
printf ( "D0: 0x%X\n", buff[4]);
printf ( "D1: 0x%X\n", buff[5]);
printf ( "D2: 0x%X\n", buff[6]);
printf ( "D3: 0x%X\n", buff[7]);
printf ( "D4: 0x%X\n", buff[8]);
printf ( "D5: 0x%X\n", buff[9]);
printf ( "D6: 0x%X\n", buff[10]);
printf ( "D7: 0x%X\n", buff[11]);
printf ( "CHK: 0x%X\n", buff[12]);
flag = 0;
}
}
}
void signal_handler_IO(int status)
{
if(flag !=1)
{
read(fd, &buff, sizeof(buff));
flag = 1;
}
}
void set_port_settings()
{
struct termios oldtio, newtio;
struct sigaction saio;
fd = open(PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd<0) {perror(PORT); exit(-1);}
saio.sa_handler=signal_handler_IO;
sigemptyset(&saio.sa_mask);
saio.sa_flags=SA_RESTART;
sigaction(SIGIO, &saio,NULL);
fcntl (fd, F_SETOWN, getgid());
fcntl(fd, F_SETFL, FASYNC);
tcgetattr(fd, &oldtio); perror("tsgetattr");
newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD ; perror("c_cflag");
newtio.c_iflag = IGNPAR | IXON ; perror("c_iflag");
newtio.c_oflag = 0; perror("c_oflag");
newtio.c_lflag = ICANON | ISIG ; perror("c_lflag");
newtio.c_cc[VMIN]=8;perror("c_cc[VMIN]");
newtio.c_cc[VTIME]=1; perror("c_cc[VTIME]");
newtio.c_cc[VSTART]=0x55;
tcflush(fd, TCIFLUSH); perror("TCFLUSH");
tcsetattr(fd, TCSANOW, &newtio); perror("tcsetattr");
}
Problem that I have, is that when the program reads data and starts printing out the results, the information printed out is somehow correct just printed (or read in) in a wrong place.
I'm writing to the port using another C program. I've tried to do it from the same C program but was unsuccessful to write and read from the same C program. So I keep 2 shells open: on one shell I'm running write program, on another shell I'm running read program to display what it was able to read in.
Write program C code
#include <iostream>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#define BAUDRATE9600 B19200
#define PORT "/dev/ttyO4"
#define _POSIX_SOURCE 1
using namespace std;
int main() {
int fd;
char buffer[255];
struct termios oldtio, newtio;
struct sigaction saio;
fd = open(PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd<0) {perror(PORT); exit(-1);}
tcgetattr(fd, &oldtio);
newtio.c_cflag = BAUDRATE9600 | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR;
newtio.c_oflag = 0;
newtio.c_lflag = 0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &newtio);
char SYNC [] = {0x55};
char PID [] = {0x97};
char data0 [] = {0x25};
char data1 [] = {0xFF};
char data2 [] = {0x00};
char data3 [] = {0x64};
char data4 [] = {0x01};
char data5 [] = {0xFF};
char data6 [] = {0xFF};
char data7 [] = {0xFC};
char checksum [] ={0xE0};
for (;;) {
ioctl(fd, TIOCSBRK);
usleep(676); // 13 bits, 1 bit = 52us
ioctl(fd,TIOCCBRK);
usleep(260); // 5 bits
write(fd, SYNC, sizeof(SYNC));
write(fd, PID, sizeof(PID));
write(fd, data0, sizeof(data0));
write(fd, data1, sizeof(data1));
write(fd, data2, sizeof(data2));
write(fd, data3, sizeof(data3));
write(fd, data4, sizeof(data4));
write(fd, data5, sizeof(data5));
write(fd, data6, sizeof(data6));
write(fd, data7, sizeof(data7));
write(fd, checksum, sizeof(checksum));
usleep(10000);
close (fd); }
When I run both programs to check if the READ programs is working as it should, I can see that the data is read in, but is not exactly as it it's written to the port.
example of the data read in
d0: 0x7C
d1: 0x66
d2: 0x1
d3: 0xE0
d4: 0x4C
d5: 0x7C
d6: 0x8
d7: 0x60
CHK: 0x60
d0: 0x1
d1: 0xE0
d2: 0x4C
d3: 0x7C
d4: 0x8
d5: 0x60
d6: 0xFC
d7: 0x60
CHK: 060
I hope that somebody will be able to point where I have made a mistake and what should I do to be able to read from the UART port without the problem.
Problem that I have, is that when the program reads data and starts printing out the results, the information printed out is somehow correct just printed (or read in) in a wrong place.
Unfortunately, because your read program does output something, you mistakenly think there is only one problem relating to data data alignment. Data or message alignment is just one of many problems in your programs.
The read and write programs improperly initialize the serial port to an incomplete (and therefore unknown) state. As commented by #Swanand the write program prematurely closes its file descriptor.
The read program is needlessly using async I/O events to perform read() syscalls and not checking the return code. The read program then unconditionally prints out the buffer regardless of whether there is actual read data.
Using a break condition on the serial link is an unorthodox method of message separation. Since the message seems to be framed with a starting "sync" byte and a terminating "checksum" byte, this message frame should be validated by the read program to ensure that message alignment is intact.
Some of the specific errors in your code:
Lack of proper and consistent formatting.
void set_port_settings()
{
struct termios oldtio, newtio;
newtio is an automatic variable, and therefore is not initialized.
The program selectively assigns values to a few structure elements, and leaves others undefined.
These undefined elements in the termios structure passed on through the tcsetattr() syscall can lead to unpredictable and/or unreliable operation of the serial port.
You should study Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems.
newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD ; perror("c_cflag");
newtio.c_iflag = IGNPAR | IXON ; perror("c_iflag");
This is an improper method of setting termios attributes.
Refer to the mentioned guides for proper technique.
You claimed you corrected these mistakes in this question, yet here they are still.
The unconditional perror() after each assignment is wrong since this isn't a syscall.
Enabling IXON seems irrelevant since this is the read program that isn't doing any output. (IXON enables soft flow-control for output.)
newtio.c_lflag = ICANON | ISIG ; perror("c_lflag");
Enabling ICANON seems incorrect since the data from the write program is not line-formatted text but binary data. This program should be setting up raw mode instead of canonical mode.
Enabling ISIG seems incorrect since the triggering character values in VINTR, VQUIT, VSUSP, or VDSUSP are all undefined in this uninitialized termios structure.
Since the write program is creating break conditions, a signal from a break, could be generated but has unwanted side effects, i.e. flushing the queues.
The unconditional perror() after each assignment is wrong since this isn't a syscall.
tcsetattr(fd, TCSANOW, &newtio); perror("tcsetattr");
The return code from this (and all other) system call needs to be checked.
The unconditional perror() call is wrong.
I have written the C program that should read UART's RxD port and display the results as soon as there is any information. To achieve this I'm using signal_handler SIGIO signal
Your program has no direct access to the UART's RxD port.
It's the serial port driver that reads the actual data from the UART RxD register.
Since you've setup the serial port for canonical input, the data is stored in the line discipline buffer, and then copied to a system buffer.
The read() syscall in your program retrieves data from the system buffer.
The SIGIO is not speeding up the read by your program.
void signal_handler_IO(int status)
{
if(flag !=1)
{
read(fd, &buff, sizeof(buff));
flag = 1;
}
}
This signal handler is not necessary if your program performed an ordinary blocking read(). Your premise for using nonblocking I/O and SIGIO seem to be incorrect. The program is not utilizing asynchronous I/O effectively, so you might as well simplify the program.
The return code from the read() needs to be checked. Besides checking for any error condition, the return code will indicate the number of bytes that have been read.
Your read program incorrectly assumes that a complete message of at least 11 bytes is received every time, and could cause display of stale values in the main loop.
Since the read program enables neither IGNBRK or BRKINT, a break condition is received by your program as a null byte, 0x00, along with the actual message data.
The read program needs to search for and maintain message alignment using the "sync" and "checksum" bytes. Your program currently has no method at all to test for or to achieve message alignment.
A basic algorithm for performing this are in this answer
If you read/write non ascii data to files you should open with 'O_BINARY'.
As next hint:
Use your console to read or write data to see which program is faulty. Simply use 'echo' for that.
To see what your program internally reads or writes use 'strace -xfo dump your_prog' and look into the file 'dump' to see which characters was send/read from your uart.
The following instructions seems to be really senseless for me! There is no chance to get a sleep exactly to any kind of bit rates until you have a hard real time kernel and hardware. On x86 you will have deviations of more then 100ms! for a usleep if an other process is running. Every disk io will kill your timing for example.
ioctl(fd, TIOCSBRK);
usleep(676); // 13 bits, 1 bit = 52us
ioctl(fd,TIOCCBRK);
usleep(260); // 5 bits
For sending a break use
tcsendbreak()