I´m using ubuntu as host, and C++ as language.
I´m communicating to a serial device that uses line by line commands. For that purpose I opted for a canonical mode of operation and my termios flags are:
fd = open(portName.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
struct termios config = { 0 };
config.c_iflag = IGNPAR | ICRNL;
config.c_oflag = 0;
config.c_lflag = ICANON;
config.c_lflag &= ~(ECHO | ECHONL | IEXTEN | ISIG);
config.c_cc[VINTR] = 0;
config.c_cc[VQUIT] = 0;
config.c_cc[VERASE] = 0;
config.c_cc[VKILL] = 0;
config.c_cc[VEOF] = 4;
config.c_cc[VTIME] = 0;
config.c_cc[VMIN] = 0;
config.c_cc[VSWTC] = 0;
config.c_cc[VSTART] = 0;
config.c_cc[VSTOP] = 0;
config.c_cc[VSUSP] = 0;
config.c_cc[VEOL] = 0;
config.c_cc[VREPRINT] = 0;
config.c_cc[VDISCARD] = 0;
config.c_cc[VWERASE] = 0;
config.c_cc[VLNEXT] = 0;
config.c_cc[VEOL2] = 0;
if(tcsetattr(fd, TCSAFLUSH, &config) < 0)
{
std::cout << "Error configuring serial." << std::endl;
}
And a simple read is done as:
char buffer[1024]; // 1024 bytes
read(fd, &buffer[0], 1024);
My problem is that if I loose communication (ie: when the connected device powers off), my read statement hangs and the program gets blocked.
I need to imlpement a timeout where it would return with an error after some seconds. As far as I know, VTIME and VMIN has no effect on canonical reads.
Help appreciated on how to enable timeout for such a read command.
Related
I have success writing code in Windows and performing serial port communication. But not in Linux so far.
Here is the code I wrote:
I have used Picocom to verify that I can communicate over the serial ports via my (USB to NULL Modem to USB) cable.
Setting the code to None blocking yields "errno: 11 Resource temporarily unavailable" on both client and server."
When I set to Blocking, it hangs in code at the Read File method. If I comment that line out it runs... but not getting data.
Server sends packets continuous.
Client receives packets continuous.
Problem seems to be bytes aren't received at the client. They are sent, however at the server.
CLIENT:
RECV(1611912): NumChars: 0 String:
RECV(1611913): NumChars: 0 String:
RECV(1611918): NumChars: 0 String:
RECV(1611919): NumChars: 0 String:
RECV(1611920): NumChars: 0 String:
SERVER:
SENT(106775): NumChars: 10 String: 0123456789
SENT(106776): NumChars: 10 String: 0123456789
SENT(106777): NumChars: 10 String: 0123456789
SENT(106778): NumChars: 10 String: 0123456789
SENT(106779): NumChars: 10 String: 0123456789
to run:
g++ -o sp serialport.cpp
client: ./sp /dev/ttyS0 c
server: ./sp /dev/ttyS4 s
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
class C_SerialPort
{
private:
int giFD;
char gpcDevice[100] = {0};
public:
//------------------------------------------------
//
//------------------------------------------------
int GetError(char* pcMessage = (char*)"")
{
char pcError[100] = {0};
sprintf(pcError, " (%s): errno: %d %s\n", pcMessage, errno, strerror(errno));
if(errno > 0)
{
printf("%s", pcError);
}
return errno;
}
//------------------------------------------------
//
//------------------------------------------------
int Connect()
{
return Connect((char*)"/dev/ttyS0");
//return Connect((char*)"/dev/ttyUSB0");
}
//------------------------------------------------
//
//------------------------------------------------
int Connect(char *pcDevice)
{
strcpy(gpcDevice, pcDevice);
struct termios s_TA;
// Open the serial port
giFD = open(gpcDevice, O_RDWR | O_NOCTTY | O_NDELAY | O_FSYNC );
if(giFD < 0)
{
printf("open_port: Unable to open %s\n%s", gpcDevice, strerror(errno));
printf("EXITING...\n");
return 1;
}
else
{
printf("Connect Device: %s\n", gpcDevice);
}
// get attributes
if(tcgetattr(giFD, &s_TA) != 0)
{
GetError((char*)"tcgetattr");
printf("EXITING...\n");
return 1;
}
// clear terminalAttributes data
//memset(&s_TA, 0, sizeof(struct termios));
if(0)
{
// 57600 bauds; 8 bits per word; Ignore modem control lines; Enable receiver.
s_TA.c_cflag = B57600 | CS8 | CLOCAL | CREAD;
// Ignore framing errors and parity errors.
s_TA.c_iflag = IGNPAR | ONLCR;
//Enable implementation-defined output processing.
s_TA.c_oflag = OPOST;
// min time; min bytes to read
s_TA.c_cc[VTIME] = 1;
s_TA.c_cc[VMIN] = 1;// none zero blocks
}
else
{
cfsetospeed(&s_TA, B9600);
cfsetispeed(&s_TA, B9600);
s_TA.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
s_TA.c_cflag &= ~CSIZE;
s_TA.c_cflag |= CS8; /* 8-bit characters */
s_TA.c_cflag &= ~PARENB; /* no parity bit */
s_TA.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
s_TA.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
/* setup for non-canonical mode */
s_TA.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
s_TA.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
s_TA.c_oflag &= ~OPOST;
/* fetch bytes as they become available */
s_TA.c_cc[VMIN] = 1; // 0 1
s_TA.c_cc[VTIME] = 1; // 0 1
}
//int iVal = fcntl(giFD, F_SETFL, 0); // set blocking?
//printf("file status = 0x%x\n", iVal);
//GetError((char*)"fcntl");
// Set the port to our state
if (tcsetattr(giFD, TCSANOW, &s_TA) != 0)
{
GetError((char*)"tcsetattr");
printf("EXITING...\n");
return 1;
}
// flushes data written but not transmitted.
// flushes data received but not read.
tcflush(giFD, TCOFLUSH);
tcflush(giFD, TCIFLUSH);
printf("CONNECTION OK\n");
//return giFD;
return 0;
}
//------------------------------------------------
//
//------------------------------------------------
void Disconnect(void)
{
close(giFD);
printf("nPort 1 has been CLOSED and %d is the file descriptionn", giFD);
GetError((char*)"Disconnect");
}
//------------------------------------------------
//
//------------------------------------------------
int SendArray(unsigned char *buffer, int len)
{
int n = write(giFD, buffer, len);
// error catch
if(n < 0)
GetError((char*)"write");
return n;
}
//------------------------------------------------
//
//------------------------------------------------
int GetArray (unsigned char *buffer, int len)
{
int n = 0;
int len2 = BytesToRead();
n = read(giFD, buffer, len2); // this line is an issue? with settings?
// error catch
//if(n < 0)
// GetError((char*)"read");
return n;
}
//------------------------------------------------
//
//------------------------------------------------
void Clear()
{
tcflush(giFD, TCIFLUSH);
tcflush(giFD, TCOFLUSH);
}
//------------------------------------------------
//
//------------------------------------------------
int BytesToRead()
{
int iBytes = 0;
ioctl(giFD, FIONREAD, &iBytes);
//printf("Byte2Read: %d\n", iBytes);
// error catch
GetError((char*)"BytesToRead");
return iBytes;
}
};
//------------------------------------------------
//
//------------------------------------------------
int main(int argc, char const *argv[])
{
// device
char *pcDevice = (char*)argv[1];
printf("init:device:%s\n", pcDevice);
// connection type
char cConnType = argv[2][0];
printf("ConnectionType:%c\n", cConnType);
// instantiate SerialPort
C_SerialPort c_SP;
// connect
int iReturn = c_SP.Connect(pcDevice);
if(iReturn != 0)
{
printf("EXITING...\n");
return 1;
}
// clear buffer
c_SP.Clear();
printf("clear\n");
printf("prior...\n");
// main loop
while(1)
{
int iSleep_ms = 200;
usleep(iSleep_ms);
char pcArray[100] = {0};
int iNumChars = 0;
if(cConnType == 's')
{
static long lCount = 0;
// Send
strcpy(pcArray, "0123456789");
iNumChars = c_SP.SendArray((unsigned char*)pcArray, 10);
if(iNumChars > 0)
{
printf("SENT(%ld): NumChars: %d String: %s\n", lCount, iNumChars, pcArray);
}
lCount++;
}
if(cConnType == 'c')
{
static long lCount = 0;
// Receive
iNumChars = c_SP.GetArray((unsigned char*)pcArray, sizeof(pcArray));
if(iNumChars > 0)
{
printf("RECV(%ld): NumChars: %d String: %s\n", lCount, iNumChars, pcArray);
}
else
{
//printf("RECV: NumChars: %d String: %s\n", iNumChars, pcArray);
}
lCount++;
}
}
c_SP.Disconnect();
return 0;
}
You have four major bugs:
Your GetArray function ignores the return value of BytesToRead. It should read the lesser of the buffer size or the number of bytes available.
Your code has no ability to sanely handle the case where BytesToRead returns zero.
You ignore the return value of GetArray. When you print pcArray, you do not tell printf how many characters to output. So how is it supposed to know what to print?
You overwrite the returned values from tcgetattr by calling memset.
The issue is the serial port to communicate through.
The /dev/ttyS0 and /dev/ttyS4 are the serial ports after the USB in the NULL modem cable hook up. But to communicate I had to use /dev/ttyUSB0 and /dev/ttyUSB1
I'm making frames with the bytes that I read from a controller.
The first 100-200 frames are good, but after that I start to get bad frames. I checked 10000 times that my class' parsing of frames is good. That's why I'm asking if there is maybe a problem with reading from the device.
This is my code:
int ConnectDevice::getBytes() {
DCB dcb;
BYTE Byte;
DWORD dwBytesTransferred;
DWORD dwCommModemStatus;
HANDLE hPort = CreateFile(
chosePort().c_str(),
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (!GetCommState(hPort, &dcb)) {
std::cout.flush();
system("cls");
getBytes();
}
dcb.BaudRate = 115200;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
COMMTIMEOUTS timeout;
timeout.ReadIntervalTimeout = 50;
timeout.ReadTotalTimeoutConstant = 50;
timeout.ReadTotalTimeoutMultiplier = 50;
timeout.WriteTotalTimeoutConstant = 50;
timeout.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(&Byte, &timeout);
if (!SetCommState(hPort, &dcb))
return false;
while (1) {
SetCommMask(hPort, EV_RXCHAR | EV_ERR);
if (_deviceProtocol->IsFrameReady != 1) {
WaitCommEvent(hPort, &dwCommModemStatus, 0);
if (dwCommModemStatus & EV_RXCHAR)
ReadFile(hPort, &Byte, 1, &dwBytesTransferred, NULL);
else if (dwCommModemStatus & EV_ERR)
return 0x101;
_parseFrame->ParseGCSFrame(Byte);
} else if (_deviceProtocol->IsFrameReady == 1) {
_deviceProtocol->IsFrameReady = 0;
_kalmanFilter->displayKalman();
}
}
return 0;
}
I wrote a class to handle data coming from a module to the serial port.
The goal is having the port always listening and when it's needed transmit some messages.
The module communicates using hexadecimal tokens.
Here is my code for the receiving part:
int serial::serial_receive (string &s_buffer, int &rcp)
{
struct pollfd fds[1];
fds[0].fd = ptr;
fds[0].events = POLLIN ;
int pollrc = poll( fds, 1, 1000);
if (pollrc < 0)
{
perror("poll");
}
else if( pollrc > 0)
{
if( fds[0].revents & POLLIN )
{
char buff[1024];
ssize_t rc = read(ptr, buff, sizeof(buff) );
if (rc > 0)
{
s_buffer = buff;
rcp = rc;
}
else {cout << "error reading fd";}
}
}
return pollrc;
}
In my test main:
using namespace std;
char test[] = {0xAA,0x34,0x00,0x22};
int main(void) {
stringstream ss;
const char* mybuff;
string serial_buffer;
int rcl;
serial mymodule ("/dev/ttymxc5",115200, 0); //port configuration
ss << std::hex << setfill('0');
mymodule.serial_send(test,4); //send method
usleep(2000);
mymodule.serial_receive(serial_buffer,rcl); //receive method
mybuff = serial_buffer.c_str();
for (int i = 0; i < 8; ++i)
{
ss << std::setw(2) << static_cast<unsigned>(mybuff[i]);
}
cout << ss.str() << endl;
return 0;
}
My serial port configuration is:
int serial::serial_set()
{
struct termios options;
if (tcgetattr (ptr, &options) != 0)
{
return EXIT_FAILURE;
}
cfsetospeed (&options, B115200);
cfsetispeed (&options, B115200);
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8;
options.c_iflag &= ~IGNBRK;
options.c_lflag = 0;
options.c_oflag = 0;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 5;
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~(PARENB | PARODD);
options.c_cflag |= serial_parity;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CRTSCTS;
if (tcsetattr (ptr, TCSANOW, &options) != 0)
{
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Please note that ISTRIP bit is set as 0, not to lose the 8th bit.
The resoult i get is "almost" correct.
I'm aspecting a AA B4 04 01 00 00 00 94 sequence but instead i get a AA B4 04 01 00 A8 4A F0 sequence.
This mistake is repeatable, every time I send given command to the module.
Do you possible have some ints or suggestions ?
After some thinking, I found out that i can't simply cast string = char*.
I used a string method instead to build my string:
std::string check(reinterpret_cast<char*>(buff), 8);
Where buff is the char array to convert and 8 is the number of elements.
Thanks to Neil Butterworth who pointed out the unsafe casting.
I need to send binary data over a serial port, without any bytes getting reinterpreted as control characters along the way. I'm currently setting up my serial port as follows:
#include <windows.h>
// open serial port
HANDLE hSerial;
hSerial = CreateFile ("COM1", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
// get serial parameters
DCB dcbSerialParams = {0};
dcbSerialParams.DCBlength = sizeof (dcbSerialParams);
if (!GetCommState(hSerial, &dcbSerialParams)) {
cout << "error getting state\n";
exit(0);
}
// set serial params
dcbSerialParams.BaudRate = CBR_115200;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if (!SetCommState (hSerial, &dcbSerialParams)) {
cout << "error setting parameters\n";
exit(0);
}
// set time outs
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 10;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 10;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts (hSerial, &timeouts)) {
cout << "problem setting timeout values\n";
exit(0);
} else cout << "timeouts set\n";
When I issue ReadFile commands, I can get and display bytes from 0 to 255 with no problem. but I'm having no such luck with WriteFile. Is there a way to explicitly set a binary write mode?
EDIT
Ok, here's some more info. I have a windows machine and a linux single board computer hooked up through serial, the code above on the windows side is followed by:
unsigned char temp = 0;
bool keepReading = true;
while (keepReading) {
DWORD dwBytesRead = 0;
ReadFile (hSerial, &temp, 1, &dwBytesRead, NULL);
if (1 == dwBytesRead) cout << (unsigned int) temp << " ";
if (255 == temp) keepReading = false;
}
cout << endl;
bool keepWriting = true;
char send = 0;
while (keepWriting) {
DWORD dwBytesWritten = 0;
WriteFile (hSerial, &send, 1, &dwBytesWritten, NULL);
send++;
if (256 == send) keepWriting = false;
}
My code on the linux side looks like this:
int fd = open("/dev/ttymxc0", O_RDWR | O_NOCTTY);
struct termios options;
bzero (options, sizeof(options));
options.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
options.c_iflat = IGNPAR;
options.c_oflag = 0;
options.c_lflag = ICANON;
options.c_cc[VMIN] = 1;
options.c_CC[VTIME] = 0;
tcflush (fd, TCIFLUSH);
tcsetattr (fd, ICSANOW, &options);
bool keepWriting = true;
char send = 0;
while (keepWriting) {
write (fd, &send, 1);
send++;
if (256 == send) keepWriting = false;
}
bool keepReading = true;
while (keepReading) {
char temp = 0;
int n = read (fd, &temp, 1);
if (-1 == n) {
perror ("Read error");
keepReading = false;
} else if (1 == n) {
cout << temp << " ";
}
if (256 == temp) keepReading = false;
}
cout << endl;
close(fd);
I start up the code on both machines, and the first set of while loops runs fine. The terminal on the windows side displays 0 through 255. Then it just sits there. If I output the number of bytes read on the linux side for the second set of while loops, it continually gives me 0 bytes. This would indicate a closed port normally, but I just sent a bunch of info through it so how could that be?
As Jonathan Potter mentions, most likely you don't have XON/XOFF flow control turned off. Add these lines before the call to SetCommState:
dcbSerialParams.fOutX = 0;
dcbSerialParams.fInX = 0;
Some other fields which you may need to set:
dcbSerialParams.fNull = 0;
dcbSerialParams.fDtrControl = DTR_CONTROL_DISABLE;
dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE;
I think what may be happening is that Linux is detecting a break, and resetting the port, or the fact that canonical mode is set is messing it up. Try these settings in addition to what you have already:
options.c_iflag |= IGNBRK;
options.c_iflag &= ~BRKINT;
options.c_iflag &= ~ICRNL;
options.c_oflag = 0;
options.c_lflag = 0;
Alright so I figured it out, rather, a co-worker did. On the linux side, in the file /etc/inittab I had to comment out the line:
T0:23:respawn:/sbin/getty -L ttymxc0 115200 vt100
This was grabbing the serial port in a way that made it unusable for receiving bytes. I now see the expected output.
I need to send binary data over a serial port, without any bytes getting reinterpreted as control characters along the way. I'm currently setting up my serial port as follows:
#include <windows.h>
// open serial port
HANDLE hSerial;
hSerial = CreateFile ("COM1", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
// get serial parameters
DCB dcbSerialParams = {0};
dcbSerialParams.DCBlength = sizeof (dcbSerialParams);
if (!GetCommState(hSerial, &dcbSerialParams)) {
cout << "error getting state\n";
exit(0);
}
// set serial params
dcbSerialParams.BaudRate = CBR_115200;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if (!SetCommState (hSerial, &dcbSerialParams)) {
cout << "error setting parameters\n";
exit(0);
}
// set time outs
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 10;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 10;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts (hSerial, &timeouts)) {
cout << "problem setting timeout values\n";
exit(0);
} else cout << "timeouts set\n";
When I issue ReadFile commands, I can get and display bytes from 0 to 255 with no problem. but I'm having no such luck with WriteFile. Is there a way to explicitly set a binary write mode?
EDIT
Ok, here's some more info. I have a windows machine and a linux single board computer hooked up through serial, the code above on the windows side is followed by:
unsigned char temp = 0;
bool keepReading = true;
while (keepReading) {
DWORD dwBytesRead = 0;
ReadFile (hSerial, &temp, 1, &dwBytesRead, NULL);
if (1 == dwBytesRead) cout << (unsigned int) temp << " ";
if (255 == temp) keepReading = false;
}
cout << endl;
bool keepWriting = true;
char send = 0;
while (keepWriting) {
DWORD dwBytesWritten = 0;
WriteFile (hSerial, &send, 1, &dwBytesWritten, NULL);
send++;
if (256 == send) keepWriting = false;
}
My code on the linux side looks like this:
int fd = open("/dev/ttymxc0", O_RDWR | O_NOCTTY);
struct termios options;
bzero (options, sizeof(options));
options.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
options.c_iflat = IGNPAR;
options.c_oflag = 0;
options.c_lflag = ICANON;
options.c_cc[VMIN] = 1;
options.c_CC[VTIME] = 0;
tcflush (fd, TCIFLUSH);
tcsetattr (fd, ICSANOW, &options);
bool keepWriting = true;
char send = 0;
while (keepWriting) {
write (fd, &send, 1);
send++;
if (256 == send) keepWriting = false;
}
bool keepReading = true;
while (keepReading) {
char temp = 0;
int n = read (fd, &temp, 1);
if (-1 == n) {
perror ("Read error");
keepReading = false;
} else if (1 == n) {
cout << temp << " ";
}
if (256 == temp) keepReading = false;
}
cout << endl;
close(fd);
I start up the code on both machines, and the first set of while loops runs fine. The terminal on the windows side displays 0 through 255. Then it just sits there. If I output the number of bytes read on the linux side for the second set of while loops, it continually gives me 0 bytes. This would indicate a closed port normally, but I just sent a bunch of info through it so how could that be?
As Jonathan Potter mentions, most likely you don't have XON/XOFF flow control turned off. Add these lines before the call to SetCommState:
dcbSerialParams.fOutX = 0;
dcbSerialParams.fInX = 0;
Some other fields which you may need to set:
dcbSerialParams.fNull = 0;
dcbSerialParams.fDtrControl = DTR_CONTROL_DISABLE;
dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE;
I think what may be happening is that Linux is detecting a break, and resetting the port, or the fact that canonical mode is set is messing it up. Try these settings in addition to what you have already:
options.c_iflag |= IGNBRK;
options.c_iflag &= ~BRKINT;
options.c_iflag &= ~ICRNL;
options.c_oflag = 0;
options.c_lflag = 0;
Alright so I figured it out, rather, a co-worker did. On the linux side, in the file /etc/inittab I had to comment out the line:
T0:23:respawn:/sbin/getty -L ttymxc0 115200 vt100
This was grabbing the serial port in a way that made it unusable for receiving bytes. I now see the expected output.