1
votes

I'm creating a class for Serial communication between an embedded system and a C++ application running in a Linux environment. Therefore I used the termios API for Linux, which is described here.

The constructor will open the device's serial port. In my case that is 'ttyUSB0' for the arduino microcontroller that I used. Next it will set the baudrate and other port options.

I also added functions to read or write data on the serial port. Because read is a blocking function (doesn't return untill data is received or if timed out) I added a function that checks whether or not there are bytes available, which you can should before calling 'Read()'.

After making a test-case, reading seemed to work fine. The function 'Available()' does indeed return the number of bytes that are available. They are printed to the console after reading them.

However due to some unknown reason my write function does not work, even though I 'believe' that I followed the steps from the guide correctly. I made a test-case for the write function: the arduino should blink it's built-in led once it receives a correct message. A message is correct when it begins with the begin-mark '#' and ends with the end-mark '$'.

When I send a correct message with the testing tool putty or with arduino's serial monitor, the led will blink. But that doesn't happen when I send the message via my own write function.

The arduino has other built-in leds that indicate data on the RX and TX pins. These leds do actually light up once I send data from my own write function but the blink function in my test-case is never called. I then checked if any bytes were read at all, but the arduino's 'Serial.available()' never returns a value higher than 0 when the data is sent from my own write function.

I think the bug is either in the write function itself or in the configuration of the serial port. Soo far I'm failing to figure this out. Does anyone have any experience or knowledge about this or have any tips on how I should approach this problem?

Thanks in advance,

Dirk

Linux code:

main.cpp

#include "serial.h"
#include <iostream>

using namespace std;

int main()
{
    //TEST CASE FOR WRITING DATA
    Serial serial("/dev/ttyUSB0");
    serial.Write("#TEST$"); 

    //TEST CASE FOR READING DATA
    /*while (true)
    {
        char message[100];
        char * ptr = NULL;
        while (serial.Available() > 0)
        {
            char c; 
            serial.Read(&c);
            switch(c)
            {
            case '#':
                ptr = message;
                break;
            case '$':
                if (ptr != NULL)
                {
                    *ptr = '\0';
                }
                std::cout << "received: " << message << std::endl;
                ptr = NULL;
                break;
            default:
                 if (ptr != NULL)
                {
                    *ptr = c;
                    ptr++;
                }
                break;
            }
        }
    }*/
    return EXIT_SUCCESS;
}

Serial.h

#ifndef SERIAL_H
#define SERIAL_H

#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <string>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

class Serial
{
    private:
        int fd;
    public:
        Serial(std::string device);

        ~Serial()
        {
            close(fd);
        };     

        int Available();
        void Read(char * buffer, int amountOfBytes);
        void Read(char * bytePtr);
        int Write(std::string message);
};

#endif

Serial.cpp

#include "serial.h"
#include <stdexcept>
#include <string.h>

Serial::Serial(std::string device)
{   
    // Open port
    fd = open(device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0)
    {
        throw std::runtime_error("Failed to open port!");
    }

    // Config
    struct termios config;

    tcgetattr(fd, &config);

    // Set baudrate
    cfsetispeed(&config, B9600);
    cfsetospeed(&config, B9600);

    // 9600 8N1
    config.c_cflag &= ~PARENB;
    config.c_cflag &= ~CSTOPB;
    config.c_cflag &= ~CSIZE;
    config.c_cflag |=  CS8;

    // Disable hardware based flow control
    config.c_cflag &= ~CRTSCTS;

    // Enable receiver
    config.c_cflag |= CREAD | CLOCAL;                               

    // Disable software based flow control
    config.c_iflag &= ~(IXON | IXOFF | IXANY);

    // Termois Non Cannoincal Mode 
    config.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); 

    // Minimum number of characters for non cannoincal read
    config.c_cc[VMIN]  = 1;

    // Timeout in deciseconds for read
    config.c_cc[VTIME] = 0; 

    // Save config
    if (tcsetattr(fd, TCSANOW, &config) < 0)                        
    {
        close(fd);
        throw std::runtime_error("Failed to configure port!");
    }

    // Flush RX Buffer
    if (tcflush(fd, TCIFLUSH) < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to flush buffer!");
    }
}

int Serial::Available()
{
    int bytes = 0;
    if (ioctl(fd, TIOCINQ, &bytes) < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to check buffer!");
    }
    return bytes;
}

void Serial::Read(char * buffer, int amountOfBytes)
{
    if (read(fd, buffer, amountOfBytes) < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to read bytes!");
    }
}

void Serial::Read(char * bytePtr)
{
    return Serial::Read(bytePtr, 1);
}

int Serial::Write(std::string message)
{
    int length = message.size();
    if (length > 100)
    {
        throw std::invalid_argument("Message may not be longer than 100 bytes!");
    }

    char msg[101];
    strcpy(msg, message.c_str());

    int bytesWritten = write(fd, msg, length);

    if (bytesWritten < 0)
    {
        close(fd);
        throw std::runtime_error("Failed to write bytes!");
    }

    return bytesWritten;
}

Arduino code

void setup() 
{
    Serial.begin(9600);
    pinMode(LED_BUILTIN, OUTPUT);
}

void loop() 
{
    //TEST-CASE FOR WRITING DATA
    /*Serial.print("#TEST$");
    delay(1000);*/

    //TEST-CASE FOR READING DATA
    char message[100];
    char * ptr = NULL;
    while (Serial.available() > 0)
    {
        char c = Serial.read();
        switch(c)
        {
        case '#':
            ptr = message;
            break;
        case '$':
            if (ptr != NULL)
            {
                *ptr = '\0';
            }
            ptr = NULL;
            int messageLength = strlen(message);
            Blink();
            break;
        default:
            if (ptr != NULL)
            {
              *ptr = c;
              ptr++;
            }
            break;
        }
    }
}

void Blink()
{
    digitalWrite(LED_BUILTIN, HIGH);
    delay(1000);
    digitalWrite(LED_BUILTIN, LOW);
    delay(1000);
}
1

1 Answers

2
votes

Solved. The 'open' function sends a signal on the serial port which the arduino interprets as a signal to reboot. I fixed the issue by disabling auto-reset.

Alternatively you could add two seconds of delay after saving the config.

This issue applies specifically for arduino microcontrollers.