0

I am trying to stablish a serial communication channel between my computer and my arduino. When I look at ArduinoIDE, I get a perfect message sent from arduino - 3 identical numbers. Now, I am trying to create a c++ app to read that data on a computer running Ubuntu, however I get a lot of garbage on the string. I have been reading and searching all around without success. Could any one help me find the source of my problem?

Code:

SerialComm.h:

#ifndef SERIALCOMM_HPP
#define SERIALCOMM_HPP

#include <fstream>
#include <string>

#include <stdio.h> // standard input / output functions
#include <string.h> // string function definitions
#include <unistd.h> // UNIX standard function definitions
#include <fcntl.h> // File control definitions
#include <errno.h> // Error number definitions
#include <termios.h> // POSIX terminal control definitionss

class SerialComm {
public:

    SerialComm() noexcept {
    }

    virtual ~SerialComm() noexcept {
        tcsetattr(fd, TCSANOW, &port_settings);
        close(fd);
    }

    void begin(std::string port, speed_t baudrate);
    std::string read_data();

private:
    int fd;
    speed_t _baudrate;
    std::string _port;
    static constexpr int BUFFER_SIZE = 256;
    char buffer[BUFFER_SIZE];
    termios port_settings;
};

SerialComm.cpp

#include "SerialComm.hpp"

#include <iostream>

using namespace std;

void SerialComm::begin(string porta, speed_t baudrate) {
    _port = porta;
    _baudrate = baudrate;

    // abre a porta
    fd = open(_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);

    if (fd == -1) {
        printf((string("Unable to open port ") + _port).c_str());
    } else {
        fcntl(fd, F_SETFL, 0);
        printf("port is open.\n");
    }

    cfsetispeed(&port_settings, _baudrate); // set baud rates
    cfsetospeed(&port_settings, _baudrate);

    port_settings.c_cflag &= ~PARENB; // set no parity, stop bits, data bits
    port_settings.c_cflag &= ~CSTOPB;
    port_settings.c_cflag &= ~CSIZE;
    port_settings.c_cflag |= CS8;

    tcsetattr(fd, TCSANOW, &port_settings); // apply the settings to the port   
}

string SerialComm::read_data() {
    int state = 1;

    while (true) {
        state = read(fd, buffer, BUFFER_SIZE);
        if (state > 0) 
        {
            return string( buffer );
        }
    }
}

main.ccp

int main(int argc, char* argv[])
{

    SerialComm serial;

    serial.begin("/dev/ttyACM0", B115200);

    for(auto i = 0; i < 100; ++i)
    {
        cout << serial.read_data() << endl;
    }
}

serial.ino:

double sinal = 0;

void setup()
{
  Serial.begin( 115200 );
}

void loop()
{
  sinal = analogRead( A0 ) * ( 5.0 / 1024.0 );
  Serial.print( "$" );
  Serial.print( sinal, 5 );
  Serial.print( "," );
  Serial.print( sinal, 5 );
  Serial.print( "," );
  Serial.print( sinal, 5 );
  Serial.print( "#\n" );
}

Arduino IDE output:

$2.24121,2.24121,2.24121#
$2.24609,2.24609,2.24609#
$2.24121,2.24121,2.24121#
$2.24121,2.24121,2.24121#
$2.24609,2.24609,2.24609#

computer output:

$2.24609,2.24?�̯m#
$2.
09375#
$2.2412109375,2.2412109937500#
$2.2460937500,2.2460937500,2.2460937500#
375#
$2.2460937500,2.2460937500,2.2460937500#
$2.
375,2.2412109375#
$2.241210937937500#
$2.2460937500,2.2460937500,2.2460937500#

PS: the above was the prettiest output I could get.

Deanie
  • 2,316
  • 2
  • 19
  • 35
Fred
  • 417
  • 6
  • 14

2 Answers2

4

Besides the problem with the unterminated string buffer, you also can't say that you will receive a complete message in a single call to read. Instead you have to read in a loop until you have the end of the message (the newline you send).

This of course present you with another problem, that you can receive the end of one message, and the beginning of the next message in the same read call. This means you have to save the beginning of the next message, and put it in the buffer before next call to read.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
2

I think this is your error:

string SerialComm::read_data() {
    int state = 1;
    int receivedbyte = 0;  // never used!

    while (true) {
        state = read(fd, buffer, BUFFER_SIZE);
        if (state > 0) 
        {
            return string( buffer );
        }
    }
    buffer[receivedbyte + 1] = '\0';  // never reached!  And "off-by-one" if it were...
}

This might work better:

string SerialComm::read_data() {
    int receivedbyte = 0;

    while (true) {
        receivedbyte = read(fd, buffer, BUFFER_SIZE - 1);
        if (receivedbyte > 0) 
        {
            buffer[receivedbyte] = '\0';
            return string( buffer );
        }
    }
}

That should eliminate any garbage that you were seeing due to unterminated strings. That said, to get nice newline-terminated strings, you probably need an outer loop to look for those boundaries and divide the stream properly that way.

One way to do that might be this: Declare a string received in your class to hold all buffered input that hasn't yet been returned to the caller. Then, rewrite read_data() as follows:

string SerialComm::read_data()
{
    while (true)
    {
        size_t pos = received.find_first_of('\n', 0);
        if (pos != string::npos)
        {
            string result = received.substr(0, pos);
            received.erase(0, pos);
            return result;
        }

        int receivedbytes;
        do
        {
            receivedbytes = read(fd, buffer, BUFFER_SIZE - 1);
        } while (receivedbytes == 0);

        if (receivedbytes < 0)
            abort();  // error... you might want to handle it more cleanly, though

        buffer[receivedbytes] = 0;
        received += string( buffer );
    }
}            

If you'd like a version that returns an empty string when there no complete line to see, as opposed to waiting forever for data, you can use this version of code. Note: If there's buffered data with no terminating newline, it will hold onto it until it does see the terminating newline. You might want to add a separate flush method to make that data visible.

string SerialComm::read_data()
{
    while (true)
    {
        size_t pos = received.find_first_of('\n', 0);
        if (pos != string::npos)
        {
            string result = received.substr(0, pos);
            received.erase(0, pos);
            return result;
        }

        int receivedbytes = read(fd, buffer, BUFFER_SIZE - 1);

        if (receivedbytes < 0)
            abort();  // error... you might want to handle it more cleanly, though

        if (receivedbytes == 0)
            return string();  // nothing to see yet

        // Add received data to buffer and loop to see if we have a newline yet.           
        buffer[receivedbytes] = 0;
        received += string( buffer );
    }
}            
Joe Z
  • 17,413
  • 3
  • 28
  • 39
  • Your code worked like magic! Once! I tried to run a second time, and it just loops forever. – Fred Nov 10 '13 at 20:18
  • I've written a few programs like this in the past. :-) Not talking to Arduinos, specifically, but interfacing to other embedded systems. Most recently, I've interfaced an Intellivision video game unit to a PC via a PIC microcontroller and a USB serial link. – Joe Z Nov 10 '13 at 20:20
  • 1
    Minor tweak above: Use `received.erase()` to remove characters from the `received` string. Should be somewhat more efficient. – Joe Z Nov 10 '13 at 20:24
  • I edited my comment above. Unfortunately your code have a tendency of falling in infinite loops or read nothing, but that was the closest I got into a solution, so thank you. – Fred Nov 10 '13 at 20:27
  • You can modify it to have a timeout, or return an empty string if there's no complete line buffered in `received` and nothing read by `read`. I will put a non-blocking version above that returns an empty string in that case. – Joe Z Nov 10 '13 at 20:35
  • I checked the code, pos have a tendency to have a huge value, and that's what's causing the bug, perhaps. – Fred Nov 10 '13 at 20:40
  • `string::npos` is a very large value, and it indicates no newline was found in the buffer. That's expected, and not a problem. The "hang" you're likely seeing is due to the inner `while` loop waiting for data that hasn't arrived. If you're getting dropped data between the Arduino and the PC, then perhaps it's a flow control problem. That would look like a "hang" on the PC side, but really it's waiting for data that got dropped because of no flow control. – Joe Z Nov 10 '13 at 20:42
  • 1
    BTW, here's a more comprehensive Linux serial IO class I wrote for my PIC based projects awhile back. It handles non-blocking bidirectional byte-oriented communication. It requires you to call a `flush` function in some cases, to ensure all sent data gets written. If you want to cut the incoming data into newline separated chunks, you'll have to build on top of this. Link: http://spatula-city.org/~im14u2c/linux_serial_io/ – Joe Z Nov 10 '13 at 20:59