0

I have an Arduino code that outputs strings coming from some sensors over the USB. These are the strings:

Sensor 1 F1 410nm : 0 F2 435nm : 290 F3 445nm : 0 F4 460nm : 211 F5 480nm : 0 F6 515nm : 0 F7 535nm : 262 F8 555nm : 0 F9 590nm : 0 F10 610nm : 263 F11 630nm : 0 F12 645nm : 262 F13 680nm : 0 F14 715nm : 181 F15 730nm : 201 F16 780nm : 0 F17  860nm  : 262 F18 940nm : 211;
Sensor 0 F1 410nm : 0 F2 435nm : 171 F3 445nm : 0 F4 460nm : 213 F5 480nm : 0 F6 515nm : 0 F7 535nm : 228 F8 555nm : 0 F9 590nm : 0 F10 610nm : 150 F11 630nm : 0 F12 645nm : 200 F13 680nm : 0 F14 715nm : 159 F15 730nm : 204 F16 780nm : 0 F17  860nm  : 213 F18 940nm : 228;

Each string is terminated by the character ";".

Now, I wrote a simple C++ program that runs on Ubuntu and reads from serial and outputs data. Arduino is connected via USB to the computer where I have to run the C/C++ program. The problem is that it prints characters are they arrive and randomly, like this:

Sensor 0 F1 410nm : 0 F2 435nm : 272 F3 445nm : [ INFO] [1652193464.896782509]: Sensor 0 F1 410nm : 0 F2 435nm : 272 F3 445nm : 0 F4 460nm : 178 F5 480nm : 0 F6 515nm : 0 F7 53[ INFO] [1652193464.900888273]: 0 F4 460nm : 178 F5 480nm : 0 F6 515nm : 0 F7 53 5nm : 298 F8 555nm : 0 F9 590nm : 0 F10 610nm : 170 F11 630nm : 0 F12 645nm : 237 F13 680nm : 0 F14 715nm : 297 F15 730nm : 289 F16 780nm : 0 F17 860nm : 178 F18 940nm : 298;

while I would like to read all the characters till the ";" and then print the full string.

How can I do this in a smart way?

I can also format the string from Arduino by adding a character before each string, for example:

:String1_with_values;

in order to be able to know when the string starts and when it finishes. Do you think it would be a good solution to improve it?

This is my current full code:

#include "ros/ros.h"
#include "std_msgs/String.h"
// C library headers
#include <iostream> 
#include <stdio.h>
#include <string.h>

// Linux headers
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()

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

 ros::init(argc, argv, "talker");
 ros::NodeHandle n;
 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

  ros::Rate loop_rate(10);

  // Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
  int serial_port = open("/dev/ttyACM0", O_RDWR);

  // Create new termios struc, we call it 'tty' for convention
  struct termios tty;

  // Read in existing settings, and handle any error
  if(tcgetattr(serial_port, &tty) != 0) {
      printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
      return 1;
  }

  tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
  tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
  tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size 
  tty.c_cflag |= CS8; // 8 bits per byte (most common)
  tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
  tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

  tty.c_lflag &= ~ICANON;
  tty.c_lflag &= ~ECHO; // Disable echo
  tty.c_lflag &= ~ECHOE; // Disable erasure
  tty.c_lflag &= ~ECHONL; // Disable new-line echo
  tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
  tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
  tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

  tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
  tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
  // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)
  // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)

  tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
  tty.c_cc[VMIN] = 0;

  // Set in/out baud rate to be 9600
  cfsetispeed(&tty, B115200);
  cfsetospeed(&tty, B115200);

  // Save tty settings, also checking for error
  if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
      printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
      return 1;
  }
unsigned char read_buf[800];
int count = 0;
 while (ros::ok())
  {
    int n = read(serial_port, read_buf, sizeof(read_buf) - 1);
    if (n < 0) {
        /* handle errno condition */
        return -1;
    }
    read_buf[n] = '\0';
    std::cout << read_buf ;

std_msgs::String msg;

    std::stringstream ss;
    ss << read_buf;
    msg.data = ss.str();

    ROS_INFO("%s", msg.data.c_str());

    chatter_pub.publish(msg);

    ros::spinOnce();

    loop_rate.sleep();
    ++count;

}

  close(serial_port);
  return 0; // success
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
Marcus Barnet
  • 2,083
  • 6
  • 28
  • 36
  • This is clearly C++, as is the whole Arduino platform. There is nothing called "C/C++", they are quite different languages nowadays. Don't use the C tag. – Lundin May 10 '22 at 14:49
  • OK, thank you! However, I included it because I'm open to solution based on only C instead of C++. – Marcus Barnet May 10 '22 at 14:50
  • You are using a C++ compiler so you don't have that option. – Lundin May 10 '22 at 14:50
  • Also executing `return 0` from main() on a microcontroller will cause your program to crash and burn... – Lundin May 10 '22 at 14:53
  • Please, read carefully the topic, my program in running on Ubuntu on a x86 pc and it just have to read data sent by Arduino via USB. It doesn't run on Arduino board! – Marcus Barnet May 10 '22 at 14:56
  • I have that option, I just need to use gcc instead of g++ when I compile the code on Ubuntu. The topic is not related to a code running on Arduino, but on a x86 pc. – Marcus Barnet May 10 '22 at 14:57
  • So your question is not at all about Arduino, you just happened to have one connected to a Linux PC which the question is actually about? – Lundin May 10 '22 at 14:58
  • Yes, I want to read data coming from Arduino by using a C/C++ code running on Ubuntu. Arduino is connected via USB to Ubuntu. I mentioned Arduino since I can modify the sketch in order to change the strings format if this can help to improve the C/C++ program. – Marcus Barnet May 10 '22 at 15:02
  • I edited the topic to clarify this. – Marcus Barnet May 10 '22 at 15:03
  • "*Each string is terminated by the character ";".*" -- Are you sure it's not a line that is terminated with some combination of '\n' and '\r' characters? Have you heard of canonical mode, which will scan the incoming text for you, and have the **read()** syscall return a line of input instead of some quanity of bytes? See https://stackoverflow.com/questions/57152937/canonical-mode-linux-serial-port – sawdust May 12 '22 at 19:35

1 Answers1

2

The comment on your source says:

"// Wait for up to 1s (10 deciseconds), returning as soon as any data is received."

Since a transmission over a serial line takes some time, you will in most cases receive the message in multiple parts. (Look up "baudrate" to learn about transmission times.) How many characters one part contains, depends on many factors, and commonly you cannot foresee it.

Change your reading algorithm to a simple finite state machine that collects received characters until the end-of-message marker is received. A primitive solution would scan for it.

the busybee
  • 10,755
  • 3
  • 13
  • 30
  • "*Change your reading algorithm ... until the end-of-message marker is received*" -- Why bother using raw mode when the OS can make this so easy by reading full lines for you? – sawdust May 12 '22 at 21:53
  • I did not exclude this possibility. If there is an OS function that provides this, why not? It uses the mentioned simple FSM. – the busybee May 13 '22 at 11:49