Serial programming RS485 - c++

I have been tasked with the implementation of the ModBus protocol over a RS485 2-wire system. (Actually it's three wires, A/B and GND).
ModBus is not the point though, but the step before that...simple I/O over the interface.
I am using the FTDI USB-RS485 converter to connect a Linux host (not interchangeable) to a Windows host (interchangeable with another Linux host, though I'd like to avoid that)
The encoding is supposed to be 19200, 8, n, 1.
But it just doesn't seem to work.
I don't have the exact code handy, but on Linux I am doing this:
int fd = open("/dev/ttyS3", O_RDWR | O_CTTY);
if(fd == -1) return "Error while opening the port";
Next, I configure the port.
struct termios tty;
tcgetattr(fd, &tty);
cfsetispeed(&tty, B19200);
cfsetospeed(&tty, B19200);
tty.c_cflag = CS8; //Empties the cflags and sets the character width.
tty.c_cflag |= (CLOCAL | CREAD); //Sets 'recommended' options.
tty.c_lflag = 0;
tty.c_iflag = 0;
tty.c_oflag = 0;
tcgetattr(fd, TCSANOW, &tty);
Parity and Flow Control are currently not planned, since the final result will connect to a low level board, where I need to take care of the signals myself. Furthermore there aren't any wires, which would allow 'unfettered communication'. (After all I don't want an XON/XOFF character to limit the byte range I can transmit)
All of these function go through properly and the data is set.
On Windows, I open the serial port like this:
DCB SP;
HANDLE hSerial = CreateFile("COM6", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if(hSerial == INVALID_HANDLE_VALUE) return "Error while opening the port";
GetCommState(hSerial, &SP);
Parity is disabled, as well as flow control. Byte size is set to 8.
Edit:
Since it has been asked, here is my code for the baudrate on Windows (from memory)
SP.DCBlength= sizeof(SP);
SP.BaudRate = 19200;
SP.Parity = NOPARITY;
SP.StopBits = ONESTOPBIT;
SetCommState(hSerial, &SP);
Again, all of these functions run flawlessly.
Now, for the test case that's giving me a major headache.
On the Linux host, I create a byte buffer of 256 bytes size.
This buffer is filled with the character values from 0-255...and then sent over the wire with write.
At the same time, the other side is waiting with 'ReadFile' for data to arrive.
With this configuration, for both the 'other Linux host', as well as for the Windows host, 256 Bytes arrive...however it's NOT the numbers from 0-255, but something 00 06 etc.
I can get the Linuxhost to work, when I'm setting all members of the termios structure to 0 before setting the options I actually want. I'm guessing, it's because of the control characters...however if I do that, the Windows host either receives only 4 of 256 bytes.
As I said, unfortunately I don't have the code handy. If anyone has any idea from what point I could tackle this, I'd be very grateful. I will post more code, once I have access to it again.
How I'm implementing the read operation:
DWORD nBytes = 0;
char Buffer[256], *ptr = Buffer;
int Rem = 256;
while(Rem) {
ReadFile(hSerial, ptr, Rem, &nBytes, 0);
Rem -= nBytes;
ptr += nBytes;
}
//Evaluate Buffer
To be noted, I did set the timeouts, but can't remember the exact values.
Edit: Since I now have access to my work place again, here's the actual (current) code.
const char *InitCOM(const char *TTY) {
struct termios tty;
hSerial = open(TTY, O_RDWR | O_NOCTTY | O_NDELAY);
if(hSerial == -1) return "Opening of the port failed";
fcntl(hSerial, F_SETFL, 0);
if(tcgetattr(hSerial, &tty) != 0) return "Getting the parameters failed.";
if(cfsetispeed(&tty, B19200) != 0 || cfsetospeed(&tty, B19200) != 0) return "Setting the baud rate failed.";
//CFlags
//Note: I am full aware, that there's an '=', and that it makes the '&=' obsolete, but they're in there for the sake of completeness.
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; //8-bit characters
tty.c_cflag |= (CLOCAL | CREAD);und erlaubt 'Lesen'.
tty.c_cflag &= ~(PARENB | PARODD);
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
//Input Flags
tty.c_iflag &= ~IGNBRK;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
//Local Flags
tty.c_lflag = 0;
//Output Flags
tty.c_oflag = 0;
//Control-Characters
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 5;
if(tcsetattr(hSerial, TCSAFLUSH, &tty) != 0) return "Setting the new parameters failed";
return NULL;
}
As for the actual sending/receiving code:
int main(int argc, char* argv[]) {
#if defined FOR_PC
const char *err = InitCOM("/dev/ttyUSB0");
#else
const char *err = InitCOM("/dev/ttyS3");
#endif
if(err) printf("Error while initalizing: %s ErrNum: %d\n", err, errno);
else {
/*unsigned char C[256]; //Original code with the array
int nBytes;
#ifdef FOR_PC
int Rem = 256, ReqCount = 0;
unsigned char *ptr = C;
while(Rem > 0) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
nBytes = read(hSerial, ptr, Rem);
if(nBytes > 0) {
Rem -= nBytes;
ptr += nBytes;
++ReqCount;
}
}
printf("Number of received Bytes: %d in %d sends.\n\n", 256 - Rem, ReqCount);
for(int i = 0; i < 256; ++i) {
printf("%02X ", C[i]);
if((i%32) == 31) printf("\n");
}
#else
for(int i = 0; i < 256; ++i) C[i] = i;
nBytes = write(hSerial, C, 256);
printf("\nWritten Bytes: %d\n", nBytes);
#endif*/
//Single-Byte Code
unsigned char C = 0x55;
#ifdef FOR_PC
while(true) { //Keeps listening
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
read(hSerial, &C, 1);
printf("Received value 0x%02X\n", C);
}
#else
write(hSerial, &C, 1); //Sends one byte
#endif
close(hSerial);
}
return 0;
}
As for the Oscilloscope: I've tested both directions with sending. Both did their job quite admirably.
The signal of 0x55 is a constant Up/Down at the length of 50 microseconds (as it should, so setting the baud rate is no problem either).
So is there anything in my 'receive' code I'm doing wrong? Is the 'select' wrong?

Are you also setting the proper baud rate on the Windows side?
Use an oscilloscope to check the actual data present on the wire(s). Debugging serial communications is what oscilloscopes were invented for. Almost. :)

Your read function could easily explode. If you are near the end of the buffer, and you read more than the amount to fill it, you will copy past the end of the buffer, overwriting the stack.
On the Linux sending side, you should look into "raw mode", e.g., cfmakeraw(). This way you will not be bothered by the system "helping" you (like adding CR when you send a newline -- really screws up the binary data...). There's a way to do that in microsoft, but I forget how.

on windows side, did you set the DCBLength field in your DCB struct?
dcb.DCBlength = sizeof(dcb);

Related

program hangs while sending serial data in VxWorks

I'm communicating with a Automatic Land Navigation System via serial wherein I'll be issuing commands to it and will get in return the required response from it. Since the messages are terminated by \r\n just like in GPS (NMEA standard) I'm opening the serial port in Line Mode. The problem I'm facing is if I try to send data at a faster rate the program is getting hanged. For time being I'm flushing both the input and output buffers as a workaround of this issue. Does anybody have a better solution. This is how I'm opening my serial port:
#include <iostream>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sioLib.h>
#include <tyLib.h>
int fd = -1;
void recvthread() {
fd = open("/tyCo/2", O_RDWR | O_NOCTTY, 0);
if (fd != -1) {
int nRes = ioctl(fd, FIOBAUDRATE, 9600);
if (nRes == -1)
perror("Error in setting Baudrate for /tyCo/2");
int options = ioctl(fd, FIOGETOPTIONS, 0);
if (options == -1)
options = 0;
options |= CREAD | CLOCAL;
options &= ~CSIZE;
options |= CS8;
options &= ~STOPB;
options &= ~PARENB;
options |= OPT_LINE;
nRes = ioctl(fd, FIOSETOPTIONS, options);
if (nRes == -1)
perror("Error in setting options for /tyCo/2");
char buff[512];
while (true) {
// receive data
int len = read(fd, buff, 10);
// process the received data
}
}
}
int main() {
std::thread t(recvthread);
sleep(5);
char buff[512];
while (true) {
int len = write(fd, buff, 56);
std::this_thread::sleep_for(std::chrono::milliseconds(250LL));
}
}
I use Curtiss Wright 185 board which has xmc 371 serial card as an accessory for which I use tews serial driver.

No data sent over serial port in linux c++

TL;DR - I am attempting serial communication with Arduino with code that I found here and nothing gets sent over (Arduino programmed to respond, and I checked that it does with its serial monitor)
Hi there,
I was looking for a way to send information over to an Arduino Mega (2560) unit over linux serial port by C++.
I came across the following solution: Solution
I'm using this guy's code for write (I'm able to read data from the arduino) and use the same parameters (they work, as I'm able to receive data from the Ardunio).
I programmed my Arduino to send "Hi" over serial whenever it sees at least 1 bit of information, and checked it worked through the Arduino IDE Serial Monitor.
Yet when running the C++ code, the arduino doesn't respond. Do anyone might have idea why?
Full disclosure - I inserted #Lunatic999's code to a class so I can make an instance of it for my needs of the code.
fd = open(portNameC, O_RDWR | O_NOCTTY | O_SYNC); //open port ("opens file")
Serial Parameters:
struct termios tty;
struct termios tty_old;
memset (&tty, 0, sizeof tty);
/* Error Handling */
if ( tcgetattr ( fd, &tty ) != 0 ) {
std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
}
/* Save old tty parameters */
tty_old = tty;
/* Set Baud Rate */
cfsetospeed (&tty, (speed_t)B19200);
cfsetispeed (&tty, (speed_t)B19200);
/* Setting other Port Stuff */
tty.c_cflag &= ~PARENB; // Make 8n1
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
tty.c_cflag &= ~CRTSCTS; // no flow control
tty.c_cc[VMIN] = 1; // read doesn't block
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
/* Make raw */
cfmakeraw(&tty);
/* Flush Port, then applies attributes */
tcflush( fd, TCIFLUSH );
if ( tcsetattr ( fd, TCSANOW, &tty ) != 0) {
std::cout << "Error " << errno << " from tcsetattr" << std::endl;
}
Write (this code I put inside a function which I call)
unsigned char cmd[] = "INIT \r";
int n_written = 0,
spot = 0;
do {
n_written = write( fd, &cmd[spot], 1 );
spot += n_written;
} while (cmd[spot-1] != '\r' && n_written > 0);
Arduino code:
bool dataRecieved = false;
int ledpin = 13;
void setup() {
pinMode(ledpin, OUTPUT);
digitalWrite(ledpin, HIGH);
Serial.begin(19200);
}
void loop() {
while(!dataRecieved)
{
digitalWrite(ledpin,HIGH);
if (Serial.available() > 0)
{
dataRecieved = true;
}
}
digitalWrite(ledpin,LOW);
delay(1000);
digitalWrite(ledpin,HIGH);
delay(1000);
Serial.println("hi");
}
Turns out it was an arduino problem all along. I needed to apply some usleep to let arduino bootload

Receiving information from serial port in while loop

I'm reading information from a serial port using this code:
struct termios tio;
memset(&tio, 0, sizeof(tio));
// Open serial port in mode `8N1', non-blocking
tio.c_cflag = CS8 | CREAD | CLOCAL;
tio.c_cc[VMIN] = 1;
tio.c_cc[VTIME] = 10;
int fd = open("/dev/ttyACM1", O_RDONLY);
cfsetospeed(&tio, B9600);
cfsetispeed(&tio, B9600);
tcsetattr(fd, TCSANOW, &tio);
unsigned char byte = '0';
// check for input from arduino
while (!quit)
{
keyboardInput(quit);
read(fd, &byte, 1);
if ((byte == '1' || quit)
{
oldByteDoor = '1';
break;
}
}
where keyboardInput(quit) sets quit to true when the close button of the window is pressed.
If nothing is in the serial port it gets stuck at read(fd, &byte, 1) forever.
How can I prevent this?
Thanks

linux c flush serial buffer after writing

Simple problem. When I write to /dev/ttyS1, it does not flush it immediately. This is probably something to do with my initialization of serial port... But I can not figure it out! My code is like this:
#define BAUDRATE B115200
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define FALSE 0
#define TRUE 1
volatile int STOP = FALSE;
void signal_handler_IO(int status); /* definition of signal handler */
int wait_flag = TRUE; /* TRUE while no signal received */
int main()
{
int fd, c, res;
struct termios oldtio, newtio;
struct sigaction saio; /* definition of signal action */
char buf[255];
/* open the device to be non-blocking (read will return immediatly) */
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
perror(MODEMDEVICE);
exit(-1);
}
/* install the signal handler before making the device asynchronous */
saio.sa_handler = signal_handler_IO;
//saio.sa_mask = 0;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO, &saio, NULL);
/* allow the process to receive SIGIO */
fcntl(fd, F_SETOWN, getpid());
/* Make the file descriptor asynchronous (the manual page says only
O_APPEND and O_NONBLOCK, will work with F_SETFL...) */
fcntl(fd, F_SETFL, FASYNC);
tcgetattr(fd, &oldtio); /* save current port settings */
/* set new port settings for canonical input processing */
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
newtio.c_cc[VMIN] = 1;
newtio.c_cc[VTIME] = 0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &newtio);
int i = 0;
/* loop while waiting for input. normally we would do something
useful here */
while (1) {
printf(".\n");
usleep(100000);
//tcflush(fd, TCOFLUSH);
//tcflush(fd, TCIOFLUSH);
i++;
if (i == 20) {
// ------------------ write to serial ----------------------
int n = write(fd, "ATZ\n", 4);
if (n < 0)
fputs("write() of 4 bytes failed!\n", stderr);
}
/* after receiving SIGIO, wait_flag = FALSE, input is available
and can be read */
if (wait_flag == FALSE ) {
res = read(fd, buf, 255);
buf[res] = 0;
printf(":%s:%d\n", buf, res);
wait_flag = TRUE; /* wait for new input */
}
}
/* restore old port settings */
tcsetattr(fd, TCSANOW, &oldtio);
}
void signal_handler_IO(int status) {
printf("received SIGIO signal.\n");
wait_flag = FALSE;
}
Any idea?
You have enabled CTS hardware flow control by setting CRTSCTS. That means that if the if the CTS input to the UART is not active, the UART should refrain from transmitting.
You might want to have a look through the termios man page in general.
I do not think this is a solution, but adding:
tcflush(fd, TCIOFLUSH); // clear buffer
to clear the buffer seem to do the trick. However, there should be some more elegant and proper solution.
Since "flush" can mean either clear buffer or wait until all bytes are sent, here's quotation from https://linux.die.net/man/3/tcdrain
tcdrain() waits until all output written to the object referred to by fd has been transmitted.
tcflush() discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of queue_selector
So,
tcdrain(fd); // Wait until transmission ends
tcflush(fd, TCOFLUSH); // Clear write buffer

C++ Serial Communication

I'm running Ubuntu 9.10 and I seem to be having trouble with termios.
So I can start minicom open the serial port at 57600 Baud, 8N1, no hardware or software flow control and it works great. I type in #17 5 and my device responds. When I try to set up my serial port in my C++ code I don't get a response. I know that the software is communicating to the port because an led turns on.
Here is my main:
int main(void)
{
int fd; /* File descriptor for the port */
fd = open("/dev/keyspan1", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
/*
* Could not open the port.
*/
perror("open_port: Unable to open /dev/ttyS0 - ");
}
else
fcntl(fd, F_SETFL, 0);
/*****************************CHANGE PORT OPTIONS***************************/
struct termios options;
/*
* Get the current options for the port...
*/
tcgetattr(fd, &options);
/*
* Set the baud rates to 57600...
*/
cfsetispeed(&options, B57600);
cfsetospeed(&options, B57600);
/*
* Enable the receiver and set local mode...
*/
options.c_cflag |= (CLOCAL | CREAD);
/*
* Set the new options for the port...
*/
tcsetattr(fd, TCSANOW, &options);
/***********************************END PORT OPTIONS***********************/
int n;
n = write(fd, "#17 5 \r", 7);
if (n < 0)
fputs("write() of 8 bytes failed!\n", stderr);
char buff[20];
sleep(1);
n = read(fd, buff, 10);
printf("Returned = %d\n", n);
close(fd);
return(0);
}
Any suggestions would be appreciated. Thanks.
You probably need to initialize your terminal to raw mode. I suggest you use cfmakeraw() to initialize the term options structure. Among others, cfmakeraw will make sure that flow control is disabled, parity checks are disabled, and input is available character by character.
cfmakeraw is non-Posix. If you're concerned with portability, look up the cfmakeraw manpage for the settings it is making.