How to fix serial-port transmission error? - c++

I am trying to send some chars via serial port. The problem is that if I send A (ASCII 65) I receive something else (ASCII 225). Any letter or string that I send I receive something else.
Here is my code:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
int open_port(void)
{
int port;
port = open("/dev/ttySAC3", O_RDWR | O_NOCTTY | O_NDELAY);
if (port == -1){
perror("open_port: Unable to open /dev/ttySAC3 - ");
}else{
fcntl(port, F_SETFL, 0);
}
return (port);
}
int main()
{
int port,n;
char str = 'A';
struct termios specs;
port = open_port();
tcgetattr(port, &specs);
specs.c_cflag = (CLOCAL | CREAD );
specs.c_oflag = (OPOST | CR3| CS8);
cfsetospeed(&specs,B9600);
tcsetattr(port,TCSANOW,&specs);
n = write(port, &str, 1);
if (n<0) {
printf("\nError");
}
close(port);
return(0);
}
I measured using an oscilloscope and that is the data leaving the device, so it is not a reading issue on my PC.
I searched the web for the last 2 days and can't figure out what I am doing wrong. Some help would be appreciated.

In case you get 226 for a "'B'" then I'd say the uP sends a 'b' and uses 7 data bits and uses parity.
From N Alex's comment below I conclude:
My above assumption was wrong.
He could have simply added a proper initialisation to the termios structure:
struct termios specs = {0};

Baud rate is one parameter, there are also number of data bits, stop bits, parity and flow control (RTS/CTS, none, etc). These parameters must be identical on both sides, provided the hardware on both sides support them. If this is the case, then the cable Rx, Tx must connect Rx(side A) to Tx(side B) and Tx(side A) to Rx(side B).
This is the minimum requirement if we ignore ground and grid for interference, and flow control pins.
The next consideration is distance. RS232 cannot go very far on standard cables due to interference from nearby static emitters (cellphones, motors, etc.)
The final, and imho hardest is that RS232 has no error correction. If some bits are changed during transmission, then the other side can only check if the packet is correct up to a very low degree, but it cannot correct it. For this, you'll need on both sides an error correction protocol, or at the very least a ACK/NAK mechanism in place.
Hope this helps.

Related

I want to receive multiple data from arduino to raspberry pi using I2C

Thank you to whoever is kind enough to look into this question.
I want to receive multiple data from arduino to raspberry pi using I2C.
I can obtain 1 data from arduino, but once I move to more than one data, it fails to do so.
I have tried multiple methods so far, and I found this method to work the best to obtain data from Arduino.
My previous attempt in obtaining data from arduino is as follows:
I want to read from Arduino using I2C using Raspberry Pi
Raspberry Pi's terminal response has weird font that cannot be recognized
Which are all solved by now.
Got Massive Help from link below
https://area-51.blog/2014/02/15/connecting-an-arduino-to-a-raspberry-pi-using-i2c/
Arduino Code
#include <Wire.h>
#define echoPin 7
#define trigPin 8
int number=0;
long duration;
long distance;
void setup()
{
//Join I2C bus as slave with address 8
Wire.begin(8);
//Call SendData & Receive Data
Wire.onRequest(SendData);
//Setup pins as output and input to operate ultrasonic sensor
Serial.begin(9600);
pinMode(echoPin,INPUT);
pinMode(trigPin,OUTPUT);
}
void loop ()
{
digitalWrite(trigPin,LOW);
delayMicroseconds(2);
digitalWrite(trigPin,HIGH);
delayMicroseconds(2);
digitalWrite(trigPin,LOW);
duration=pulseIn(echoPin,HIGH);
distance=duration/58.2;
Serial.print(distance);
Serial.println(" cm");
}
void SendData()
{
Wire.write(distance);
Wire.write("Why No Work?");
Wire.write(distance);
}
C++ Code
//Declare and Include the necessary header files
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
//Define Address of Slave Address
#define ADDRESS 0x08
//Eliminate the Used of std in the future
using namespace std;
static const char *devName="/dev/i2c-1";
int main(int argc, char **argv)
{
//Check to see if C++ works
cout<<"Hello, World!\n";
cout<<"I2C: Connecting"<<endl;
int file;
if ((file = open(devName, O_RDWR))<0)
{
fprintf(stderr, "I2C: Failed to access");
exit(1);
}
if (ioctl(file, I2C_SLAVE, ADDRESS)<0)
{
cout<<"Failed to Access"<<endl;
}
char buf[0];
char dd;
for (int i=0; i<100;i++)
{
read(file,buf, 3);
float distance= (int) buf[0];
dd= buf[1];
float dist=(int) buf[2];
cout<<distance<<endl;
usleep(10000);
cout<<"doh"<<endl;
cout<<dd<<endl;
cout<<dist<<endl;
}
return 0;
}
What I would expect from the c++ code would be as follows
15
doh
Why No Work?
15
But I get
15
doh
weird font can't be recognized
255
Wire.write(distance);
wants to write a long onto the I2C bus. For an Arduino This is 32 bits, 4 bytes, of data. I'm not sure exactly what wire.write does because the documentation I can find is substandard to the point of being garbage, but the documentation looks like it's going to send exactly 1 of the 4 bytes you wanted to send. In order to send more than one byte, it looks like you need to use the array version: Wire.write(&distance, sizeof (distance));, but even this may not be sufficient. I'll get back into that later.
Wire.write("Why No Work?");
writes a null-terminated string (specifically a const char[13]) onto the I2C bus. I don't know arduino well enough to know if this also sends the terminating null.
so
Wire.write(distance);
Wire.write("Why No Work?");
Wire.write(distance);
needed to write at least 4 + 12 + 4 bytes onto the I2C bus. and probably only wrote 1 + 12 + 1.
On the Pi side,
read(file,buf, 3);
read out 3 bytes. This isn't enough to get the whole of distance, let alone the array of characters and second write of distance. You need to read all of the data you wrote, at least 20 bytes.
In addition,
char buf[0];
defines an array of 0 length. There isn't much you can do with it as there is no space to store anything here. It cannot hold 3 characters, let alone the 20 or 21 necessary. read of 3 bytes wrote into invalid memory and the program can no longer be counted on for sane results.
This means that at best
float distance= (int) buf[0];
dd= buf[1];
float dist=(int) buf[2];
got only one byte of the four bytes of distance and it's dumb luck that the result was the same as expected. dd got exactly one character, not the whole string, and this is turning out to be nonsense because of one of the preceding mistakes. dist is similarly garbage.
To successfully move data from one machine to another, you need to establish a communication protocol. You can't just write a long onto a wire. long doesn't have the same size on all platforms, nor does it always have the same encoding. You have to make absolutely certain that both sides agree on how the long is to be written (size and byte order) and read.
Exactly how you are going to do this is up to you, but here are some pointers and a search term, serialization, to assist you in further research.

C++ Serial port reading issue: does ioctl(FIONREAD) set a wrong value?

I am facing a very strange problem, which I wasn't able to solve. I want to read (just read) data collected and sent by a micro-controller via usb as serial port (FTDI) on Mac Os X using c++. The the size of one complete data-sequence is always exactly 10 bytes. However I was using the following code to read the data:
Imported libraries:
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
Code:
void init(){
serial = open(port.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); // 0_RDONLY ?
struct termios options;
//set opt to 115200-8n1
cfsetspeed(&options, B115200);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
tcsetattr(serial, TCSANOW, &options);
if (serial < 0){
//Error
}else{
//run loop
}
}
void serial_loop(){
long bytes_read;
int bytes_available;
unsigned char msg[10];
while(1){
do{
usleep(1000);
ioctl(serial, FIONREAD, &bytes_available);
}while(bytes_available < 10); //wait for the sequence to complete
bytes_read = read(serial, msg, 10);
//do some parsing here
}
}
This code worked a few days ago but now it isn't anymore. The data is reaching the computer perfectly according to the Terminal -> screen -command. I checked the port-file-name which is still correct and the port is opened successfully as well.
I narrowed down my issue to the ioctl-command FIONREAD which doesn't write the correct number to the bytes_available-var (anymore).
It did work, and I believe, I didn't change anything in the code.
Do you see any problem that could cause this issue?
Are there any dangerous passages in my code?
Thank you for your help, I'm really stuck here...
EDIT:
Thanks to the feedback, I was able to get it running again. Here is the current code:
int serial;
void init(){
serial = open(port.c_str(), O_RDWR | O_NOCTTY); //removed 0_NONBLOCK
struct termios options;
//set opt to 115200-8n1
cfsetspeed(&options, B115200);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //Non-canonical
options.c_cc[VMIN] = 1; //block read until at least 1 byte was recieved
options.c_lflag = 0;
tcsetattr(serial, TCSANOW, &options);
if (serial < 0){
//Error
}else{
//run loop
}
}
void serial_loop(){
int datalength = 10;
long bytes_read = 0;
int bytes_in_msg = 0;
unsigned char buf[datalength];
unsigned char msg[datalength];
do{
bytes_read = read(serial, buf, datalength-bytes_in_msg);
usleep(1000);
if (bytes_read>0){
memcpy(&msg[bytes_in_msg], &buf, bytes_read);
}
bytes_in_msg += bytes_read;
}while(bytes_in_msg < datalength);
//do some parsing here
}
}
This works, but is there anything left, that could be problematic?
Thank you for your support!
This code worked a few days ago but now it isn't anymore.
Fickle program behavior usually indicates improper or incomplete initialization.
Your termios initialization only configures the baudrate and character framing, and everything else is left to chance.
See Setting Terminal Modes Properly
and Serial Programming Guide for POSIX Operating Systems.
Your revised code still has not properly resolved this issue.
The code never initializes the termios structure options by calling the tcgetattr() function.
For sample code see my answer to How to open, read, and write from serial port in C?
options.c_lflag = 0;
That is not considered the proper way of assigning a termios element.
options.c_cc[VMIN] = 1;
Non-canonical mode requires definition of both the VMIN and VTIME entries.
Your code uses whatever garbage value that exists at the location for VTIME.
See Linux Blocking vs. non Blocking Serial Read.
FIONREAD which doesn't write the correct number to the bytes_available-var (anymore).
Negative descriptions, i.e. what does not occur, are not as helpful or specific as descriptions of what does occur.
So what kind of values are you getting back?
Why do you think it is "wrong"?
More importantly, why aren't you checking the return value from each syscall, especially this ioctl() that you think is giving you problems?
Most likely is that the ioctl() failed, did not update your bytes_available variable, and returned an error code.
Instead of first checking for a good return, your code unconditionally uses the returned argument.
Another answer that critiques your code is misleading. Flow control can be disabled. Your code is broken because it doesn't do proper initialization and is not checking for error returns, not because the comm link is "deadlocked".
In your revised code:
bytes_read = read(serial, buf, datalength-bytes_in_msg);
usleep(1000);
A sleep or delay before and/or after a blocking read is superfluous.
Your code is broken. No protocol can allow both the reader to wait for the writer and the writer to wait for the reader. If it did, deadlock could result.
Your do/while loop refuses to read any data until the writer writes all 10. However, serial ports permit the writer to refuse to write any more data until the reader reads what it has already written. So it cannot also permit a reader to refuse to read any more data until the writer writes more.
You cannot use FIONREAD to wait for more bytes to be written because the writer may also be waiting for you. Instead, read the bytes as they become available, thus ensuring you unblock the writer. Accumulate them in a buffer and break when you have the number you need.

Serial communication - I'm having trouble turning an incoming char array into an int

I'm trying to receive a number from an Arduino as an integer in C++. The full code is below:
#define STRICT
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "Serial.h"
#include <boost\lexical_cast.hpp>
enum { EOF_Char = 27 };
int __cdecl _tmain(int /*argc*/, char** /*argv*/)
{
CSerial serial;
LONG lLastError = ERROR_SUCCESS;
// Attempt to open the serial port (COM4)
lLastError = serial.Open(_T("COM4"), 0, 0, false);
// Setup the serial port (9600,8N1, which is the default setting)
lLastError = serial.Setup(CSerial::EBaud9600, CSerial::EData8, CSerial::EParNone, CSerial::EStop1);
// Register only for the receive event
lLastError = serial.SetMask(CSerial::EEventBreak |
CSerial::EEventCTS |
CSerial::EEventDSR |
CSerial::EEventError |
CSerial::EEventRing |
CSerial::EEventRLSD |
CSerial::EEventRecv);
// Use 'non-blocking' reads, because we don't know how many bytes
// will be received. This is normally the most convenient mode
// (and also the default mode for reading data).
lLastError = serial.SetupReadTimeouts(CSerial::EReadTimeoutNonblocking);
// Keep reading data, until an EOF (CTRL-Z) has been received
bool fContinue = true;
do
{
// Wait for an event
lLastError = serial.WaitEvent();
// Save event
const CSerial::EEvent eEvent = serial.GetEventType();
// Handle data receive event
if (eEvent & CSerial::EEventRecv)
{
// Read data, until there is nothing left
DWORD dwBytesRead = 0;
char szBuffer[101];
do
{
// Read data from the COM-port
lLastError = serial.Read(szBuffer, sizeof(szBuffer) - 1, &dwBytesRead);
if (dwBytesRead > 0)
{
// Finalize the data, so it is a valid string
szBuffer[dwBytesRead] = '\0';
// Display the data
printf("%s", szBuffer);
// Check if EOF (CTRL+'[') has been specified
if (strchr(szBuffer, EOF_Char))
fContinue = false;
}
} while (dwBytesRead == sizeof(szBuffer) - 1);
}
} while (fContinue);
// Close the port again
serial.Close();
return 0;
}
I have my Arduino constantly sending out the number 51. This code works fine and consistently displays "51". However, I want an int to manipulate in C++.
First I added
std::stringstream str(szBuffer);
int tester;
str >> tester;
printf("My number is: %d\n", tester+1);
right after
printf("%s", szBuffer);
A typical result looks like:
51My number is: 52
51My number is: 52
51My number is: 52
51My number is: 52
51My number is: 52
5My number is: 6
1My number is: 2
After doing it perfectly 5 or 6 times, the output always separates the incoming digits once or twice in a row (I haven't been able to find a specific pattern yet, but it's always 5-6 and 1-2).
My other attempt was to use the boost library:
int tester = boost::lexical_cast<int>(szBuffer);
printf("My number is: %d\n", tester);
right after
printf("%s", szBuffer);
and I get the same result (1-2 errors after 5-6 correct ones). I don't think the Arduino is sending bad data, since just a
printf("%s", szBuffer);
will never deviate from the number it's supposed to be. Could the conversion be messing up the receiving of data? Thanks.
EDIT: The Arduino code is:
void setup() {
Serial.begin(9600); // same as in your c++ script
}
void loop() {
Serial.print(51);
delay(1000);
}
With serial ports, there is no mechanism where a transmitter can inform a receiver how many bytes were transmitted as a block. I.e. there's no "hidden" marker where Serial.print(51); tells the receiver that it sent two characters as one number. You have to add some kind of indication (spaces, commas, line ends, initial byte counts, whatever) to your serial protocol.
Because of this, the number of characters you get from serial.Read depends on the number of characters you asked it to read (the second parameter) and how many characters are in the serial port's receive buffer, whichever is smaller. Most of the time, it seems the Arduino sends both digits before you call serial.Read, but sometimes it only gets one out in time... and the second is read the next time through the loop.
So let's assume you decided to use line ends to separate your numbers. All you have to do on the Arduino end is change to Serial.println(51);. The receive end is a little more complex.
I don't know what your serial library has in it. Most have some kind of "read line" function, and you would just replace the serial.Read call with something like:
serial.Readline(szBuffer, sizeof(szBuffer) - 1);
and it will take care of null-terminating the output. If it doesn't take care of null-termination, you'll need to find the line end and change it to a \0 yourself. From this point on, your code will work fine, because the serial.Readline function will block until it gets the whole line.
If you don't have a "read line" or at least a "read until this character" function, it's a bit harder. You have to repeatedly call serial.Read, moving through your buffer, until you see the line end character. Further, you run the risk of reading part or all of the next line, so you can't just discard all the data you read when you're done reading the number; you have to move teh data in the buffer so the next line's data (and further) is at the start of the buffer.
If you're using Boost (are you? it has no CSerial that I see), it looks like it has a read_until function. This takes three parameters: the stream you're reading from, a stream buffer to store the data in, and something to stop on. In this case, the stream buffer for storage is the one in your std::stringstream:
std::stringstream buffer;
size_t chars = boost::asio::read_until(serial, buffer.rdbuf(), '\n');
if(chars == 0) return;
int tester;
buffer >> tester;
printf("My number is: %d\n", tester+1);

C++ sending an int over serial to arduino that is using Serial.parseInt();

Firstly, the problem:
I cannot find a away to send 2bytes of data via serial to an arduino (that uses Serial.parseInt()) from a windows C++ console app.
The code follows: (it is stripped down for clarity, but the problem still presents itself in a larger version that checks all serial setup function calls are successful and so on)
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
int main()
{
//setup
HANDLE hSerial;
DCB dcbSerialParams = {0};
COMMTIMEOUTS timeouts = {0};
hSerial = CreateFileA("COM3", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
GetCommState(hSerial, &dcbSerialParams);
dcbSerialParams.BaudRate = CBR_19200;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
SetCommState(hSerial, &dcbSerialParams);
//the data to send:
//char dataToSend = '5';
//unsigned int dataToSend = 125;
//(LPVOID)
unsigned short dataToSend = 125;
DWORD bytes_written;
//now we send it
WriteFile(hSerial, &dataToSend, sizeof(dataToSend), &bytes_written, NULL);
fprintf(stderr, "%d bytes written\n", bytes_written);
CloseHandle(hSerial);
Sleep(3000);
return 0;
}
The above code will not give the results I'm after, using unsigned int I get all zeros (I'm looking at the data as bits in the arduino), I need 2bytes only so i try unsigned short, same thing, all zeros - however when I try char, I will see the binary representation of 0-9 on the arduino, but not for any other ascii values (I thought I was close to a solution then! i.e. I could just cast the 'int' as it's two bytes in ascii...).
Also tried the '(LPVOID)' cast (no idea what it is but it was mentioned in another thread).
The kicker is that I have an LCD connected on the arduino and using the arduino IDE serial monitor and with that I can send any int value I want and it will work as intended (using 'no line ending').
A little context re. why using parseint>> I'm trying to avoid using anything more than 2bytes as 15bits of data is all I need. Arduino has 2byte ints so I figure I can use that data type to send the info (and code/decode the parts I need using bitwise operations at either end). I have no issue with this (that part at least is working).
The arduino code is as simple as:
if (Serial.available()) {servoData = Serial.parseInt();}
As mentioned, I have no issues with this using the arduino serial monitor.
I've tried flipping endianess, but aren't sure I went about it correctly - the 2byte version was:
dataToSend = (dataToSend << 8) | ((dataToSend >> 8) & 0x00ff);
Something to do with serial, line endings, variable types?
Any hints appreciated :)

Writing STRINGS to serial port in C++ linux

I know this question is scattered all over the internet, but still, nothing is getting me completely there yet. I want to write data to a serial port in C++ (linux) for a a Propeller board. Program works fine when taking input from the console, but when I write strings to it always return: ERROR - Invalid command from the device. I tried creating array of char with Hex values then it worked. here's a working code, below. But how will i be able to just provide a string variable of command and send it to the serial port? perhaps, how do you I convert it to hex values if it's the only way? Thanks everyone
note: the loop is to use user input from console. What i need is a way to send a string variable to the serial port.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
int main(int argc,char** argv){
struct termios tio;
struct termios stdio;
int tty_fd;
fd_set rdset;
unsigned char c='D';
printf("Please start with %s /dev/ttyS1 (for example)\n",argv[0]);
memset(&stdio,0,sizeof(stdio));
stdio.c_iflag=0;
stdio.c_oflag=0;
stdio.c_cflag=0;
stdio.c_lflag=0;
stdio.c_cc[VMIN]=1;
stdio.c_cc[VTIME]=0;
tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // make the reads non-blocking
memset(&tio,0,sizeof(tio));
tio.c_iflag=0;
tio.c_oflag=0;
tio.c_cflag=CS8|CREAD|CLOCAL; // 8n1, see termios.h for more information
tio.c_lflag=0;
tio.c_cc[VMIN]=1;
tio.c_cc[VTIME]=5;
tty_fd=open(argv[1], O_RDWR | O_NONBLOCK);
cfsetospeed(&tio,B115200); // 115200 baud
cfsetispeed(&tio,B115200); // 115200 baud
tcsetattr(tty_fd,TCSANOW,&tio);
//char str[] = {'V','E','R','\r'};
//the above str[] doesn't work although it's exactly the same as the following
char str[] = {0x56, 0x45, 0x52, 0x0D};
write(tty_fd,str,strlen(str));
if (read(tty_fd,&c,1)>0)
write(STDOUT_FILENO,&c,1);
while (c!='q')
{
if (read(tty_fd,&c,1)>0) write(STDOUT_FILENO,&c,1); // if new data is available on the serial port, print it out
if (read(STDIN_FILENO,&c,1)>0)
if(c!='q')
write(tty_fd,&c,1); // if new data is available on the console, send it to the serial port
}
close(tty_fd);
}
I'm happy to solve my own solution but yet disappointed to not have seen the trivial matter much sooner. char by default are signed in c++, which makes it holding the range -128 to 127. However, we are expecting the ASCII values which are 0 to 255. Hence it's as simple as declaring it to be unsigned char str[] and everything else should work. Silly me, Silly me.
Still, Thank you everyone for helping me!!!
Are you sure you should end with '\r'? When entering text from console the return key will result in a '\n' character (on Linux) and not '\r'
Also error checking is missing on most functions (open(), fcntl(), etc.). Maybe one of these functions fail. To find out how to check for errors read the man page (for example man 2 open for the open() command. In case of open() the man page explains it returns -1 when it could not open the file/port.
After your edit you wrote:
char str[] = {0x56, 0x45, 0x52, 0x0D};
write(tty_fd,str,strlen(str));
which is wrong. strlen expects a '\0' terminated string which str is obviously not so now it sends your data and whatever there is in memory until it sees a '\0'. You need to add 0x00 to your str array.