Birger Birger - 29 days ago 8
C++ Question

How to read from serial device in C++

I'm trying to figure out how I should read/write to my Arduino using serial communication in Linux C++. Currently I'm trying to read a response from my Arudino that I "trigger" with

echo "g" > /dev/ttyACM0"


I've tried looking at the response from my Arduino in my terminal, by using the following command:

tail -f /dev/ttyACM0


This is working as it should. Now I want to do the same thing in a C++ application. So I made this test

void testSerialComm()
{

std::string port = "/dev/ttyACM0";
int device = open(port.c_str(), O_RDWR | O_NOCTTY | O_SYNC);

std::string response;
char buffer[64];

do
{
int n = read(device, buffer, sizeof buffer);

if (n > 0) {
response += std::string(buffer);
std::cout << buffer;
}

} while (buffer[0] != 'X'); // 'X' means end of transmission

std::cout << "Response is: " << std::endl;
std::cout << response << std::endl;

close(device);

}


After a few "messages", the transmission gets a little messed up. My test application writes the response characters in random order and something's not right. I tried configuring the /dev/ttyACM0 device by this command:

stty -F /dev/ttyUSB0 cs8 115200 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts


No dice. Can someone help me understand how to communicate with my Arduino in C++?

Answer

The shown code opens /dev/ttyACM0, attempts to seek to the end of this "file", and based on the resulting file position allocates an old-fashioned, C-style memory buffer, for some strange reason, instead of using std::vector or std::string, despite the claim that this is presumably a C++ program, according to the question's tags.

The problem with this approach is that you can only seek through regular, plain, garden-variety files. /dev/ttyACM0 is not a regular file. It's a device. Although some devices are seekable, this one isn't. Which, according to the comments, you've discovered independently.

Serial port devices are readable and writable. They are not seekable. There's no such thing as "seek"ing on a serial port. That makes no sense.

To read from the serial port you just read it, that's all. The operating system does maintain an internal buffer of some size, so if some characters were already received over the serial port, the initial read will return them all (provided that the read() buffer size is sufficiently large). If you pass a 1024 character buffer, for example, and five characters were already read from the serial port read() will return 5, to indicate that accordingly.

If no characters have been read, and you opened the serial port as a blocking device, read() will block at least until one character has been read from the serial port, and then return.

So, in order to read from the serial port all you have to do is read from it, until you've decided that you've read all there is to read from it. How do you decide that? That's up to you. You may decide that you want to read only until reading a newline character. Or you may decie that you want to read only until a fixed #n number of characters have been read. That's entirely up to you.

And, of course, if the hardware is suitably arranged, and you make the necessary arrangements with the serial port device to respect the serial port control pins, and, depending on your configuration, the DCD and/or DSR pins are signaled to indicate that the serial port device is no longer available, your read() will immediately return 0, to indicate a pseudo-end of file condition on the serial port device. That's also something that you will need to implement the necessary logic to handle.

P.S. neither C-style stdio, nor C++-style iostreams will work quite well with character devices, due to their own internal buffering algorithms. When working with serial ports, using open(2), read(2), write(2), and close(2) is better. But all of the above still applies.

Comments