Serial communication with Arduino ( - RX)

Hi all,
I'm trying to communicate my PC with 2 LCDs connected to Arduino.
I am using serial communication via USB.

I'm sending command from PC in following format:
"SCR" + index + byte[32] + "#" ,which is 38 byte long.

On the Rx side I use following code:

void UpdateMonitors() {
  //Get characters from Serial
  if (Serial.available() >= 38) {
      Serial.readStringUntil('R');
      int scrNum = (int)Serial.read() - '0';
      Serial.println(scrNum);
      Serial.read();//Reads ':"
      byte msg[8];
      for (int j = 0; j < 4; j++) {
        Serial.readBytes(msg, 8);
        lcd[scrNum]->createChar(byte(j), msg);
      }
      Serial.read();//Reads '#"
      //Serial.println("message read");

      lcd[scrNum]->clear();
      lcd[scrNum]->write(byte(0));
      lcd[scrNum]->write(byte(1));
      lcd[scrNum]->setCursor(0, 1);
      lcd[scrNum]->write(byte(2));
      lcd[scrNum]->write(byte(3));

      //Serial.println("LCD updated");
  }
}

When sending single command, everything works great. Both LCDs are (separately) updated.

When I tried updating both LCDs with single command: (by increasing Serial.available to 72 and adding internal loop)

void UpdateMonitors() {
  //Get characters from Serial
  if (Serial.available() >= 72) {
    for (int i = 0; i < 2; i++) {
      Serial.readStringUntil('R');
      int scrNum = (int)Serial.read() - '0';
      Serial.println(scrNum);
      Serial.read();//Reads ':"
      byte msg[8];
      for (int j = 0; j < 4; j++) {
        Serial.readBytes(msg, 8);
        lcd[scrNum]->createChar(byte(j), msg);
      }
      Serial.read();//Reads '#"
      //Serial.println("message read");

      lcd[scrNum]->clear();
      lcd[scrNum]->write(byte(0));
      lcd[scrNum]->write(byte(1));
      lcd[scrNum]->setCursor(0, 1);
      lcd[scrNum]->write(byte(2));
      lcd[scrNum]->write(byte(3));

      //Serial.println("LCD updated");
    }
  }
}

Transmitted message stopped being handled at all.(!?!?!?)

I did think it was strange that adding the loop messed it up, but same code without the loop does tend to partially work, not completely though: Second screen's data get's all scrambled, but you can see the update taking place.

Could someone assist please?

Please post a complete program not just snippets

Gladly,

#include <LiquidCrystal.h>

#define DEBUG_PRINT 1
#define DEBUG_MONITORS 0
#define DEBUG_BUTTONS_1 0
#define DEBUG_BUTTONS_2 0
#define SERIAL_BAUD 9600

void setup() {
  setupSerial();
  setupLCD();
  setupButtons();
}

//buttonArray is global up-to-date array of buttons size of BUTTON_COUNT
uint8_t buttonArray[3] = {LOW};
void loop() {
  Buttons(true);

  UpdateMonitors();
}


void setupSerial() {
  Serial.begin(SERIAL_BAUD);
#if DEBUG_PRINT
  Serial.println("Serial started");
#endif
}

//Define screen size
#define LCDColumns 16
#define LCDRows 2

//Define pins
#define First_RSPin 8 //(8 and 9)
#define First_EnablePin 6 //(6 and 7)
#define DS4 2
#define DS5 3
#define DS6 4
#define DS7 5

#define SCREENCOUNT 2
LiquidCrystal* lcd[SCREENCOUNT];

void setupLCD() {
  for (int i = 0; i < SCREENCOUNT; i++) {

    //Create LiquidCrystal instance
    lcd[i] = new LiquidCrystal(First_RSPin + i, First_EnablePin + i, DS4, DS5, DS6, DS7);
#if DEBUG_MONITORS
    Serial.println("LiquidCrystal object created successfully");
#endif

    //Begin the LCD
    lcd[i]->begin(LCDColumns, LCDRows);
    lcd[i]->setCursor(0, 0);
    String tmp = String("SCR#"); tmp += i + 1;;
    lcd[i]->print(tmp);

#if DEBUG_MONITORS
    Serial.print("writing: '");
    Serial.print(tmp);
    Serial.print("' to LCD");
    Serial.print(i);
    Serial.println(":Line0");
#endif
  }
}


void UpdateMonitors() {
  //Get characters from Serial
  if (Serial.available() >= 38) {
    //for (int i = 0; i < 2; i++) {
      Serial.readStringUntil('R');
      int scrNum = (int)Serial.read() - '0';
      Serial.println(scrNum);
      Serial.read();//Reads ':"
      byte msg[8];
      for (int j = 0; j < 4; j++) {
        Serial.readBytes(msg, 8);
        lcd[scrNum]->createChar(byte(j), msg);
      }
      Serial.read();//Reads '#"
      //Serial.println("message read");

      lcd[scrNum]->clear();
      lcd[scrNum]->write(byte(0));
      lcd[scrNum]->write(byte(1));
      lcd[scrNum]->setCursor(0, 1);
      lcd[scrNum]->write(byte(2));
      lcd[scrNum]->write(byte(3));
      delay(200);
      //Serial.println("LCD updated");
    //}
  }
}

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. There is also a parse example to illustrate how to extract numbers from the received text.

The technique in the 3rd example will be the most reliable.

You can send data in a compatible format with code like this (or the equivalent in any other programming language)

Serial.print('<'); // start marker
Serial.print(value1);
Serial.print(','); // comma separator
Serial.print(value2);
Serial.println('>'); // end marker

...R

Exactly what I was looking for.

Thanks for your answer, great tutorial!

The hardware serial buffer size is 64 bytes on mist Arduinos. Testing for 72 or more bytes will always fail.

You said:

I'm sending command from PC in following format:
"SCR" + index + byte[32] + "#" ,which is 38 byte long.

But your code is expecting the end marker to be 'R'.

Which is correct?

This is the read sequence:
When buffer has more then 38 bytes do this:

      Serial.readStringUntil('R');//data discarded
      int scrNum = (int)Serial.read() - '0';
      Serial.read();//Reads ':" - data discarded
      byte msg[8];
      for (int j = 0; j < 4; j++) {
        Serial.readBytes(msg, 8);
        lcd[scrNum]->createChar(byte(j), msg);
      }
      Serial.read();//Reads '#"-data discarded

I still don't understand why my buffer gets over-filled when using this approach.

Could someone please elaborate?

I will try out the read one-by-one until condition is meat approach, it seems more reliable.

misha782:
This is the read sequence:
When buffer has more then 38 bytes do this:

      Serial.readStringUntil('R');//data discarded

int scrNum = (int)Serial.read() - '0';

That code did not come from my Tutorial :slight_smile:

...R

That's for sure...
I'm reading and parsing simultaneously, I figured that's what the blocking functions are designated for...
I'm rewriting my code to handle the input byte by byte, as yours tutorial suggests, it seems more reliable.

misha782:
I figured that's what the blocking functions are designated for...

I don't do blocking code :slight_smile:

...R

I still don't understand why my buffer gets over-filled when using this approach.

I don't see how we can help you when we have no idea which buffer you are talking about.

Instead of trying to use the data being read, concentrate on reading it correctly, and printing what you read, so that you know you read it correctly.

Only after you know you read the data correctly should you try to use what you read.

PaulS:

we have no idea which buffer you are talking about.

I was referring to the serial buffer, of course.

reading it correctly, and printing what you read, so that you know you read it correctly.

Is exactly what I was trying to achieve, you can see in this code snippet:

void UpdateMonitors() {

  AWAIT DATA

  if (Serial.available() >= 38) {

      READ AND PARSE DATA

      //Serial.println("message read");

      USE PARSED DATA

      //Serial.println("LCD updated");
  }
}

Robin2, I tried taking your advice, but nevertheless it produces same error as before:

void setup() {
  setupSerial();
  setupLCD();
  setupButtons();
}

#define START_MARKER '<'
#define END_MARKER '>'
#define SERIAL_BUFFER 40
byte receivedBytes[SERIAL_BUFFER];
boolean isData = false;
void loop() {
  Buttons(true);

  UpdateMonitors();
}

void setupSerial() {
  Serial.begin(SERIAL_BAUD);
  Serial.println("Serial started");
}

void serialEvent() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  byte rb;

  while (Serial.available() && !isData) {
    rb = Serial.read();
    if (ndx >= SERIAL_BUFFER)
      ndx--;

    if (recvInProgress == true) {//Ask about recvInProgress
      if (rb != END_MARKER)
        receivedBytes[ndx++] = rb;
      else {
        receivedBytes[ndx] = END_MARKER;
        recvInProgress = false;
        isData = true;
        Serial.print("isData=true; Serial:");
        Serial.println(Serial.available());
      }
    }
    else if (rb == START_MARKER) {
      recvInProgress = true;
    }
  }
  }

void UpdateMonitors() {
if (isData) {
    byte chunk[8];
    int scrNum = (int)receivedBytes[3] - '0';
    Serial.print("Uploading custom char to SCR#");
    Serial.println(scrNum);

    //Start from 5th position to skip command parametteres
    for (int i = 5; receivedBytes[i] != END_MARKER; i++) {
      //Serial.println((i - 5));
      chunk[(i - 5) % 8] = receivedBytes[i];
      if ((i - 5 ) % 8 == 7) {
        lcd[scrNum]->createChar(byte((i - 5 + 1) / 8 - 1 ), chunk);

        //Serial.print(scrNum);
        //Serial.print(":");
        Serial.print((i - 5 + 1) / 8 - 1 );
      }
    }
    Serial.println();

    lcd[scrNum]->clear();
    lcd[scrNum]->write(byte(0));
    lcd[scrNum]->write(byte(1));
    lcd[scrNum]->setCursor(0, 1);
    lcd[scrNum]->write(byte(2));
    lcd[scrNum]->write(byte(3));

    isData = false;
    Serial.print("isData=false; Searial: ");
    Serial.println(Serial.available());
    }
}
    for (int i = 5; receivedBytes[i] != END_MARKER; i++) {

Ypu can NOT assume that the binary data in the array is greater than, equal to, or less than ANY value. ALL values in the range 0 to 255 could be present in the binary data.

I did solve the problem by the way. Adding delay() between the prints on the Tx side solved it.

I don't think I understand how the buffer works, please consult me.

In my understanding:

  • Buffer is always available for Rx (assuming you don't run out of space)
  • serialEvent() grabs all data from buffer, inbetween start and end markers.
  • UpdateMonitors() handles the data when data flag turns true.

During single iteration of Loop()

  • I assume buffer is getting filled during serialEvent() so after reading single command, it well may be that the next command is already in queue.
  • I assume buffer is getting filled during UpdateMonitors() so after executing single command, it well may be that the next command is already in queue.

I know I understand of the flow wrong, otherwise it would work as I expect. Could you please elaborate on this subjects?

In my understanding:

  • Buffer is always available for Rx (assuming you don't run out of space)
  • serialEvent() grabs all data from buffer, inbetween start and end markers.
  • UpdateMonitors() handles the data when data flag turns true.

Your understanding is wrong. There are two serial buffers. One holds data that comes in the RX pin, that you can read from is available() returns a positive value.

The (useless) serialEvent() function is called if there is any data in the incoming serial data buffer. There is NOTHING that requires serialEvent() to read all the data, or to leave any data unread.

PaulS:

    for (int i = 5; receivedBytes[i] != END_MARKER; i++) {

Ypu can NOT assume that the binary data in the array is greater than, equal to, or less than ANY value. ALL values in the range 0 to 255 could be present in the binary data.

You are correct!
Except if it's the END_MARKER which I deliberately placed in the binary data to indicate the end of the sequence.

PaulS:
The (useless) serialEvent() function is called if there is any data in the incoming serial data buffer. There is NOTHING that requires serialEvent() to read all the data, or to leave any data unread.

I did not understand your last comment... Sorry :-/
please try explaining with more details. From what I did understand:

SerialEvent(), is equivalent to if(Serial.Avaliable()>0){EventFunction()}.

You can see in my implementation of SerailEvent() I handle reading from buffer until END_MARKER.
Then changing the isData flag to true.
Then calling upon UpdateMonitors() to utilize the command.

I followed instructions from Robins2 tutorial in this thread.

Here's another take on receiving a serial message with a known structure.

Unfortunately, the "sketch" you provided was incomplete (SERIAL_BAUD, LCD, Buttons etc not defined) so I couldn't verify compile. Have a look and see if you can use anything.

#define SERIAL_BUFFER 40
#define FIRST_CHAR      0
#define REST_OF_MSG     1

#define MSG_CHAR_TOUT   50              //50mS character timeout -- PC finished sending data       
byte receivedBytes[SERIAL_BUFFER];


void setup() 
{
    setupSerial();
    setupLCD();
    setupButtons();
}//setup


void loop() 
{
    Buttons( true );
    
    if( RxMessage() == true )
        UpdateMonitors();
}//loop

bool RxMessage( void )
{
    bool
        bRetval;
    byte
        rxdChar;
    static byte
        stateRX = FIRST_CHAR,
        rxIdx = 0;
    static unsigned long
        timerRxTimeout = 0;

    switch( stateRX )
    {
        case    FIRST_CHAR:
            if( Serial.available() > 0 )
            {
                rxdChar = Serial.read();
                receivedBytes[rxIdx++] = rxdChar;
                timerRxTimeout = millis();
                stateRX = REST_OF_MSG;
           
            }//if
        break;

        case    REST_OF_MSG:
            if( (millis() - timerRxTimeout) > MSG_CHAR_TOUT )
            {
                bRetval = CheckMsg( rxIdx );
                rxIdx = 0;
                stateRX = FIRST_CHAR;
                return( bRetval );
                 
            }//if
            else if( Serial.available() > 0 )
            {
                rxdChar = Serial.read();
                receivedBytes[rxIdx++] = rxdChar;
                if( rxIdx == SERIAL_BUFFER )
                    rxIdx--;
                    
                timerRxTimeout = millis();
                
            }////if
        break;
        
    }//switch

    return( false );
       
}//RxMessage

bool CheckMsg( byte len )
{
    //verify length, header and trailer
    if( len != 38 )
        return false;

    if( receivedBytes[0] != 'S' ||
            receivedBytes[1] != 'C' ||
                receivedBytes[2] != 'R' )
        return false;

    if( receivedBytes[36] != '#' || 
            receivedBytes[37] != 0 )
        return false;

    return true;
    
}//CheckMsg

void setupSerial() 
{
    Serial.begin( SERIAL_BAUD );
    Serial.println("Serial started");
}

void UpdateMonitors() 
{
    byte chunk[8];
    
    int scrNum = (int)receivedBytes[3] - '0';
    
    Serial.print("Uploading custom char to SCR#");
    Serial.println(scrNum);

    //Start from 5th position to skip command parametteres
    for (int i = 5; receivedBytes[i] != '#'; i++) 
    {
        //Serial.println((i - 5));
        chunk[(i - 5) % 8] = receivedBytes[i];
        if ((i - 5 ) % 8 == 7) 
        {
            lcd[scrNum]->createChar(byte((i - 5 + 1) / 8 - 1 ), chunk);

            //Serial.print(scrNum);
            //Serial.print(":");
            Serial.print((i - 5 + 1) / 8 - 1 );
        }//if
        
    }//for
    
    Serial.println();

    lcd[scrNum]->clear();
    lcd[scrNum]->write(byte(0));
    lcd[scrNum]->write(byte(1));
    lcd[scrNum]->setCursor(0, 1);
    lcd[scrNum]->write(byte(2));
    lcd[scrNum]->write(byte(3));

}//UpdateMonitors

Except if it's the END_MARKER which I deliberately placed in the binary data to indicate the end of the sequence.

What steps are taking to assure that the same value doesn't appear elsewhere in the binary stream? None is the wrong answer.

The serialEvent() function should be called thereIsSomeSerialDataReadyToBeRead(). When the function is called, there is NO requirement that it read anything from the buffer. There is no requirement that it read all the data in the buffer. And, there is no requirement that it read only one packet of data from the buffer.

The usual assumption is that the function WILL read all the data that makes up a packet, and only that data, but assumptions and requirements are not the same thing.