c++linuxarduinoserial-porttermios

Canonical serial reading using terminos fail?


I am trying to read lines of datas comming from my arduino using serial.

My arduino code look like that : Serial3.print(Z, 2);Serial3.print(F(";"));Serial3.println(F("END\n"));

And this is my code to read the data on ubuntu :

void setup(){  
//set up serial
   tcgetattr(dueSerial, &port_options); // Get the current attributes of the Serial port
   dueSerial = open("/dev/ttyUSB0", O_RDWR | O_NONBLOCK | O_NOCTTY | O_NDELAY);
   if (dueSerial == -1) {
     reportFailure("Could not open Arduino");
   } else {
     port_options.c_cflag &= ~PARENB; // Disables the Parity Enable bit(PARENB),So No Parity
     port_options.c_cflag &= ~CSTOPB; // CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit
     port_options.c_cflag &= ~CSIZE; // Clears the mask for setting the data size
     port_options.c_cflag |= CS8; // Set the data bits = 8
     port_options.c_cflag &= ~CRTSCTS; // No Hardware flow Control
     port_options.c_cflag |= (CREAD | CLOCAL); // Enable receiver,Ignore Modem Control lines
     port_options.c_iflag &= ~(IXON | IXOFF | IXANY); // Disable XON/XOFF flow control both input & output
     port_options.c_lflag &= ~(ECHO | ECHONL | IEXTEN | ISIG); // no echo 
     port_options.c_iflag |= ICANON; //Enable canonical
     port_options.c_iflag |=   ICRNL; //map CR to NL
     //port_options.c_oflag &= ~OPOST; // No Output Processing
     //port_options.c_lflag = 0; //  enable raw input instead of canonical,
   /* 
         initialize all control characters 
         default values can be found in /usr/include/termios.h, and are given
         in the comments, but we don't need them here
       */
        port_options.c_cc[VINTR]    = 0;     /* Ctrl-c */ 
        port_options.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
        port_options.c_cc[VERASE]   = 0;     /* del */
        port_options.c_cc[VKILL]    = 0;     /* @ */
        port_options.c_cc[VEOF]     = 4;     /* Ctrl-d */
        port_options.c_cc[VTIME]    = 0;     /* inter-character timer unused */
        port_options.c_cc[VMIN]     = 0;     /* blocking read until 1 character arrives */
        port_options.c_cc[VSWTC]    = 0;     /* '\0' */
        port_options.c_cc[VSTART]   = 0;     /* Ctrl-q */ 
        port_options.c_cc[VSTOP]    = 0;     /* Ctrl-s */
        port_options.c_cc[VSUSP]    = 0;     /* Ctrl-z */
        port_options.c_cc[VEOL]     = 0;     /* '\0' */
        port_options.c_cc[VREPRINT] = 0;     /* Ctrl-r */
        port_options.c_cc[VDISCARD] = 0;     /* Ctrl-u */
        port_options.c_cc[VWERASE]  = 0;     /* Ctrl-w */
        port_options.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
        port_options.c_cc[VEOL2]    = 0;     /* '\0' */
    
     cfsetispeed( & port_options, BAUDRATE); // Set Read  Speed
     cfsetospeed( & port_options, BAUDRATE); // Set Write Speed
      tcflush(dueSerial, TCIFLUSH);
     tcflush(dueSerial, TCIOFLUSH);
     int att = tcsetattr(dueSerial, TCSANOW, & port_options);
     if (att != 0) {
       reportFailure("ERROR in Setting Arduino port attributes");
     } else {
       LOG_INFO("SERIAL DUE Port Good to Go");
     }

   }
 }

 void UART::tick() {
   //Arduino msg = "IMU;LAX;LAY;LAZ;AVX;AVY;AVZ;AY;AP;AR;END"
   //  rx_buffer[0] = '0';
     memset(&rx_buffer, '\0', sizeof(rx_buffer));
  // tcflush(dueSerial, TCIOFLUSH);
     
     rx_length = read(dueSerial, &rx_buffer,255);
 if (rx_length < 0) {
     LOG_INFO("Error reading");
 }else{
   LOG_INFO("Read %i bytes. Received message: %s", rx_length, rx_buffer);
 }
}

But when I try this code I get many lines at a time so my output look like this :

2020-11-15 09:13:09.491 INFO  packages/skeleton_pose_estimation/apps/usb/UART.cpp@87: Read 0 bytes. Received message: 
2020-11-15 09:13:09.496 INFO  packages/skeleton_pose_estimation/apps/usb/UART.cpp@87: Read 255 bytes. Received message: 0.01;0.00;0.00;0.00;0.00;0.00;0.00;END

IMU;-0.00;0.02;0.03;0.00;0.00;0.00;0.00;0.00;0.00;END

IMU;-0.00;0.02;0.03;0.00;-0.00;0.00;0.00;0.00;0.00;END

IMU;-0.00;0.02;-0.02;0.00;-0.00;0.00;0.00;0.00;0.00;END

IMU;-0.00;0.02;-0.02;-0.00;0.00;0.00;0.00;0.00;
2020-11-15 09:13:09.501 INFO  packages/skeleton_pose_estimation/apps/usb/UART.cpp@87: Read 241 bytes. Received message: 0.00;END

IMU;-0.01;-0.02;-0.01;-0.00;0.00;0.00;0.00;0.00;0.00;END

IMU;-0.01;-0.02;-0.01;-0.00;-0.00;0.00;0.00;0.00;0.00;END

IMU;-0.01;-0.02;0.03;-0.00;-0.00;0.00;0.00;0.00;0.00;END

IMU;-0.01;-0.02;0.03;0.00;0.00;0.00;0.00;0.00;0.00;END


2020-11-15 09:13:09.506 INFO  packages/skeleton_pose_estimation/apps/usb/UART.cpp@87: Read 0 bytes. Received message: 
2020-11-15 09:13:09.511 INFO  packages/skeleton_pose_estimation/apps/usb/UART.cpp@87: Read 0 bytes. Received message: 

But I want it to read only one line per read() function call. I believe that I either set a wrong parameter making the conanical mode unused or maybe it's ignoring my \n and \r but don't know why....

Please help me to find why. Thank you a ton !


Solution

  • But I want it to read only one line per read() function call. I believe that I either set a wrong parameter making the conanical mode unused ...

    Your program does not behave as expected because canonical mode is never actually set.
    The statement

     port_options.c_iflag |= ICANON; //Enable canonical
    

    is incorrect. ICANON is in the c_lflag member, and not in c_iflag.


    Your code has additional issues.

    (1) The variable dueSerial is used uninitialized:

    void setup(){  
    //set up serial
       tcgetattr(dueSerial, &port_options); // Get the current attributes of the Serial port
       dueSerial = open(...);
       ...
    

    The file descriptor needs to be obtained and validated before it can be used in the tcgetattr() call.
    The proper ordering of statements is:

    void setup(){  
       //set up serial
       dueSerial = open(...);
       if (dueSerial == -1) {
         /* abort */
       }     
       tcgetattr(dueSerial, &port_options);
       ...
    

    (2) Numerous input conversions are left unspecified in your termios initialization.
    Canonical mode enables various options to convert certain input characters, and most of these options need to be disabled for reading by a program (versus an interactive terminal).
    Typically INPCK (enable input parity checking), IUCLC (map uppercase characters to lowercase), and IMAXBEL (ring bell when input queue is full) are disabled.
    You need to review whether you also want IGNCR (preserve or ignore carriage return), INLCR (translate newline to carriage return), and ICRNL (translate carriage return to newline unless IGNCR is set) to also be disabled.

    (3) Use of nonblocking mode is questionable.
    Since you want "to read only one line per read() function call", then blocking mode is the proper way to obtain that result.
    If you insist on using nonblocking mode, then the read() syscall will always return "immediately" and may not return any data at all.