Omron HVC-P2 (Second Camera) icon Omron HVC-P2 (Second Camera) Windows ARC plugin for Omron HVC-P/HVC-P2: Python-based face/body/hand detection, gaze, age/gender, expressions and recognition; dual-camera support. Try it →
Asked — Edited
Resolved Resolved by Athena!

Arduino Serial Communication Troubleshooting

Hi @Athena,

I'm using the following code snippet on an Arduino Mega that is communicating via UART with an EZB4:


if (Serial3.available() >= 1) {
    int incomingByte = Serial3.peek(); // Do not remove it yet
    // Check if this byte is our STOP command (assuming 'x' is the stop command)

    if (incomingByte == 'm') {
        Serial3.read();
        miss = true;
        actualCourse = desiredCourse;
    }

    if (incomingByte == 'x') {
        // Consume the byte and stop motors
        Serial3.read();
        Serial.print("Received STOP command: ");
        Serial.println('x');
        stopMotors();
        goto Main; // Jump to the Main section
    }

    if (incomingByte == 'c') {
        Serial3.read();
        Serial.print("(Received obs scan complete) command ");
        Serial.println('c');
    }
}

if (Serial3.available() >= 1) {
    // Read and check the header byte (expected to be 64)
    int headerByte = Serial3.read();

    if (headerByte == 64) { 
        int32_t b0 = Serial3.read();
        int32_t scaledValue = (b0);   
        actualCourse = scaledValue;
        Serial.print(" ..............---------------------------- rec'd actualCourse: ");
        Serial.println(actualCourse, 4); 

        lastActualCourse = actualCourse;
        initial = true;    
        miss = false;
        loopct = 0;
    }
}

The lower half of the code sometimes returns gibberish for the "actualCourse". I suspect the issue may be related to the Serial3.peek() and Serial3.available() statements before the actual Serial3.read(). Could you please review the structure of my code and suggest a solution for reliable data transfer?

Thank you,
Jack



Related Hardware (view all EZB hardware)
Synthiam ARC enables Arduino Mega 2560 control with 8 ADC, 13 PWM, 21 digital ports and 24 servos; includes firmware, shield and tutorials
USB
Servos 24
Camera
Audio
UART 3
I2C
ADC 8
Digital 21

Related Robot Skills (view all robot skills)
EZB Connection by Synthiam
Connects EZ-B I/O controllers to ARC via COM or IP (5 connections). Supports init scripts, battery monitoring, TCP/EZ-Script server and serial/I2C.
Serial Terminal by Synthiam
PC and EZB serial terminals for ARC: connect, monitor and send text/ASCII to serial devices. Supports COM, flow control, baud, DTR/RTS, hex

ARC Pro

Upgrade to ARC Pro

ARC Pro will give you immediate updates and new features needed to unleash your robot's potential!

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#1  

Good catch - the symptoms and your code both point to framing and timing issues in how you read the serial stream. A few problems to fix:

  • You sometimes read only one byte but treat it as a full value. Serial3.read() returns a single byte (or -1), but you assign it to an int32 and treat it like the full 32-bit value.
  • Calling available() in separate places and using peek() can leave you attempting to read a header/payload before the whole packet has arrived (race condition).
  • If the start/header byte gets out of sync (noise / dropped bytes), the parser will return garbage.
  • peek() doesn't consume a byte; it’s useful but you still must ensure the full packet is present before consuming.

Two reliable approaches:

  1. Binary fixed-length packets with explicit start byte + payload length + checksum (recommended for speed/efficiency).
  2. ASCII/text lines terminated by newline (easier to debug with Serial Monitor / Serial Terminal).

Below are concrete examples for both approaches and extra tips.

  1. Robust binary packet parser (start byte 0x40, 4-byte signed big-endian int payload)
  • Wait until you have the full packet before reading.
  • Discard garbage until you find the start byte.
  • Read all payload bytes atomically (use readBytes or check available >= payloadLen).
  • Optionally add checksum to detect corruption.

Example:

const uint8_t PACKET_START = 0x40; // 64
const size_t PAYLOAD_LEN = 4;      // 4 bytes => int32_t

void loop() {
  // Process as many full packets as possible
  while (Serial3.available() > 0) {
    // 1) Find start byte
    if (Serial3.peek() != PACKET_START) {
      // discard one byte of garbage and continue searching
      Serial3.read();
      continue;
    }

    // 2) Make sure full packet is available (start byte + payload)
    if (Serial3.available() < 1 + PAYLOAD_LEN) {
      // not enough bytes yet, wait for more
      break;
    }

    // 3) consume start byte
    Serial3.read();

    // 4) read payload
    uint8_t buf[PAYLOAD_LEN];
    size_t got = Serial3.readBytes(buf, PAYLOAD_LEN); // will return immediately because we checked available
    if (got != PAYLOAD_LEN) {
      // read error - handle/reset
      continue;
    }

    // 5) assemble big-endian signed 32-bit
    int32_t val = (int32_t)((uint32_t)buf[0] << 24 | (uint32_t)buf[1] << 16 | (uint32_t)buf[2] << 8 | (uint32_t)buf[3]);

    // Now use val (scale if needed)
    actualCourse = val; // or scale/convert as appropriate
    Serial.print("received actualCourse: ");
    Serial.println(actualCourse);
  }

  // ... other loop processing ...
}

Notes:

  • Replace the bit-assembly with little-endian if your sender sends little-endian (i.e., buf[0] is LSB).
  • If payload length or type differs, adjust accordingly.
  • Consider adding a one-byte checksum after payload and validate before accepting value.
  1. ASCII/line protocol (human-readable, easier debug) If you can change the sender, one of the easiest robust protocols is a newline-terminated ASCII string like: "ACT:1234\n" or just "1234\n"

Parsing example:

void loop() {
  if (Serial3.available()) {
    String line = Serial3.readStringUntil('\n'); // blocking until '\n' or timeout
    line.trim();
    if (line.length() == 0) return;

    // optional: handle single-byte commands first
    if (line == "x") {
      Serial.println("Received STOP command: x");
      stopMotors();
      goto Main;
    }

    // parse integer
    long val = line.toInt(); // simple conversion
    actualCourse = (int32_t)val;
    Serial.print("received actualCourse: ");
    Serial.println(actualCourse);
  }
}

Advantages: easy to inspect with Serial Monitor or the Serial Terminal skill, and less fragile.

Other suggestions and troubleshooting checklist

  • Ensure baud rates match on both ends and no flow-control mismatch.
  • Use the Serial Terminal skill in ARC to watch raw bytes if using USB COM: https://synthiam.com/Support/Skills/Communication/Serial-Terminal?id=16071
  • If you stay binary, add a checksum or CRC so you can detect and discard corrupted frames.
  • Keep single-purpose packet handling: handle single-byte commands (x / c / m) and full packets in the same parsing loop so they don't step on each other.
  • Avoid goto for flow control if possible - use state machine or flags; it’s harder to reason about when dealing with async serial data.

If you want a minimal state-machine parser that handles both single-byte commands and fixed binary packets, I can provide that.

Quick questions to tailor a final solution

  1. Is the EZB (sender) sending binary or ASCII? How many bytes represent actualCourse, and what endianness?
  2. What exact baud rate and flow control are you using?
  3. Can you change the sender format (e.g., add checksum or newline)?
  4. If you can, please paste a short hex dump of a few packets (e.g., from ARC Serial Terminal) so I can match the parser exactly.

If you want, I can:

  • Provide a full Arduino parser state machine that also handles commands 'x','c','m' and recovers from out-of-sync conditions.
  • Convert this to little-endian assembly if your sender is little-endian.
  • Give a simple checksum/CRC example.

References:

Tell me the sender packet format (binary vs ASCII, payload length and endianness) and I’ll produce the exact parser code you can drop in.


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
#2  

Thank you @Athena.

JavaScript snippet that sends data via UART to the Arduino:

UART.initHardwareUart(0, 115200); 
var irValue = 10;
var lastActualCourse = 0;

while (true) {

            // REFLECTOR IS MISSED
if (getVar("$lowIR") > irValue) { 
         UART.hardwareUartWrite(0, ['m'.charCodeAt(0)]);   // SEND MISSED SIGNAL TO ARDUINO MOTOR CONTROLLER
         UART.hardwareUartWrite(0, ['c'.charCodeAt(0)]);   // SEND OBS SCAN SIGNAL TO ARDUINIO MOTOR CONTROLLER
       
            //OBSTACLE DETECTED      
if (getVar("$lowIR") >= 160) {
         UART.hardwareUartWrite(0, ['x'.charCodeAt(0)]);   // SEND FULL STOP SIGNAL TO ARDUINO  MOTOR CONTROLLER
}
}

     if(getVar("$actualCourse") != lastActualCourse) { 
        var scaledValue = getVar("$actualCourse");  
        //Send as whole number to Arduino:
        var byte0 = scaledValue & 0xFF;
        var header = [64];
        // Combine the header with the 4 bytes and send via UART
        UART.hardwareUartWrite(0, header.concat([byte0]));
       lastActualCourse = getVar("$actualCourse"); 
       print(" $actualCourse:  " + getVar("$actualCourse")); 
       }   
 
sleep(10);
}

C++ snippet that receives data via UART from EZB4:

if (Serial3.available() >= 1) {
    int incomingByte = Serial3.peek(); // Do not remove it yet
    // Check if this byte is our STOP command (assuming 'x' is the stop command)

    if (incomingByte == 'm') {
        Serial3.read();
        miss = true;
        actualCourse = desiredCourse;
    }

    if (incomingByte == 'x') {
        // Consume the byte and stop motors
        Serial3.read();
        Serial.print("Received STOP command: ");
        Serial.println('x');
        stopMotors();
        goto Main; // Jump to the Main section
    }

    if (incomingByte == 'c') {
        Serial3.read();
        Serial.print("(Received obs scan complete) command ");
        Serial.println('c');
    }
}

if (Serial3.available() >= 1) {
    // Read and check the header byte (expected to be 64)
    int headerByte = Serial3.read();

    if (headerByte == 64) { 
        int32_t b0 = Serial3.read();
        int32_t scaledValue = (b0);   
        actualCourse = scaledValue;
        Serial.print(" ..............---------------------------- rec'd actualCourse: ");
        Serial.println(actualCourse, 4); 

        lastActualCourse = actualCourse;
        initial = true;    
        miss = false;
        loopct = 0;
    }
}

Of the two options you detailed in your last response, I think option (2) maybe more suitable for me.

(ASCII/line protocol (human-readable, easier debug) If you can change the sender, one of the easiest robust protocols is a newline-terminated ASCII string like: "ACT:1234\n" or just "1234\n")

Could you please provide me a snippet of JavaScript that would reliably send data, and C++ that would reliably receive data based upon option 2? Thank you Jack

Author Avatar
PRO
Canada
Twitter YouTube GitHub
#3  

Perfect - ASCII line protocol is a great choice for reliability and debugging. Below are drop-in examples you can use right away.

Summary:

  • JavaScript (ARC) sends newline-terminated ASCII lines: "ACT:<value>\n" and single-byte commands ("m","c","x") as needed.
  • C++ (Arduino Mega / Serial3) reads bytes non-blocking, handles single-byte commands immediately, buffers text until '\n' then parses "ACT:" lines into an integer (or float if you prefer).

JavaScript (ARC) - sender

  • Uses UART.initHardwareUart(0, 115200) and UART.hardwareUartWriteString to send readable lines.
  • Throttles sends and only sends when the value changes.
// ARC JavaScript - send ASCII lines and single-char commands
// init once
UART.initHardwareUart(0, 115200);

var lastActualCourse = null;

while (true) {
  // example IR checks that also send single-char commands
  var irVal = getVar("$lowIR", 0);

  if (irVal > 10) { // replace 10 with your threshold logic
    // send MISSED + OBS SCAN
    UART.hardwareUartWriteString(0, "m\n");
    UART.hardwareUartWriteString(0, "c\n");

    if (irVal >= 160) {
      // send FULL STOP
      UART.hardwareUartWriteString(0, "x\n");
    }
  }

  // send actualCourse as ASCII line when changed
  var actualCourse = getVar("$actualCourse", 0);

  if (actualCourse !== lastActualCourse) {
    // If actualCourse is an integer:
    var msg = "ACT:" + Math.round(actualCourse) + "\n";
    // If you want to preserve decimals, use:
    // var msg = "ACT:" + actualCourse.toString() + "\n";

    UART.hardwareUartWriteString(0, msg);

    lastActualCourse = actualCourse;
    print("$actualCourse:" + actualCourse);
  }

  sleep(50); // milliseconds - adjust as needed to avoid flooding the UART
}

Notes:

  • I send commands 'm', 'c', 'x' followed by '\n' to make them visible in a terminal and consistent. The Arduino code below treats them either as immediate single-byte commands or as lines.
  • Change sleep() interval if you need faster/slower updates. 50 ms is a safe starting point.
  • Use UART.hardwareUartWriteString(0, "...") - simpler than manually sending arrays of char codes.

C++ (Arduino Mega) - receiver

  • Initializes Serial3 (UART connected to EZB) at 115200.
  • Non-blocking loop: reads all available bytes, immediately handles single-char commands (x, c, m), buffers characters into rxBuf until newline, then parses "ACT:" messages.
  • Safe against buffer overflow and ignores '\r'.
// Arduino Mega - Serial3 receiver (from ARC / EZB)
// Place near top of sketch:
const unsigned int RX_BUF_SIZE = 64;
char rxBuf[RX_BUF_SIZE];
unsigned int rxIdx = 0;

int32_t actualCourse = 0;
bool miss = false;
int desiredCourse = 0; // set as appropriate

void setup() {
  Serial.begin(115200);   // debug on USB serial
  Serial3.begin(115200);  // UART to ARC/EZB
  rxIdx = 0;
  memset(rxBuf, 0, RX_BUF_SIZE);
}

void processLine(char *line) {
  // Trim leading whitespace
  while (*line == ' ' || *line == '\t') line++;

  // Debug print
  Serial.print("LINE RECV: ");
  Serial.println(line);

  // Expecting "ACT:<value>" (value can be integer or float)
  if (strncmp(line, "ACT:", 4) == 0) {
    char *p = line + 4;
    // parse int32_t
    long v = atol(p); // use atol for integers
    // If you expect floats, use: float f = atof(p);
    actualCourse = (int32_t)v;
    Serial.print("Parsed ACT => ");
    Serial.println(actualCourse);
    // handle received course (update control variables)
    // lastActualCourse = actualCourse; initial = true; etc...
  } else {
    // Unknown line - handle or ignore
    Serial.println("Unknown line format");
  }
}

void handleCommand(char cmd) {
  // handle immediate single-byte commands
  if (cmd == 'm') {
    miss = true;
    actualCourse = desiredCourse;
    Serial.println("CMD: m -> miss");
  } else if (cmd == 'x') {
    Serial.println("CMD: x -> STOP");
    stopMotors(); // implement stopMotors()
    // if you used goto Main previously, convert to flags/state machine instead
  } else if (cmd == 'c') {
    Serial.println("CMD: c -> obs scan complete");
    // handle obstacle-scan-complete
  } else {
    // other single-char commands
    Serial.print("CMD: unk ");
    Serial.println(cmd);
  }
}

void loop() {
  // Read all available bytes on Serial3
  while (Serial3.available() > 0) {
    char c = (char)Serial3.read();

    // Immediately handle newline/carriage return
    if (c == '\n') {
      if (rxIdx > 0) {
        rxBuf[rxIdx] = '\0';
        processLine(rxBuf);
        rxIdx = 0;
        rxBuf[0] = '\0';
      }
      // ignore empty lines
    } else if (c == '\r') {
      // ignore
    } else if (c == 'x' || c == 'c' || c == 'm') {
      // If these are sent alone (with or without newline), handle immediately.
      // If they might appear inside a textual line (they won't in "ACT:"), consider skipping immediate handling.
      handleCommand(c);
    } else {
      // accumulate into buffer (printable characters)
      if (rxIdx < RX_BUF_SIZE - 1) {
        rxBuf[rxIdx++] = c;
      } else {
        // buffer overflow - reset buffer and optionally log
        rxIdx = 0;
        rxBuf[0] = '\0';
        Serial.println("RX buffer overflow - cleared");
      }
    }
  }

  // your control code continues here...
  // Example: use actualCourse variable for your motor control logic
}

Important integration notes and checks

  • Baud rate must match exactly on both sides (115200 in these examples).
  • Ensure correct UART wiring (TX RX) and TTL voltage levels match (3.3V vs 5V).
  • If actualCourse can be negative or has decimals:
    • For negatives: atoi/atol will handle a leading '-' automatically.
    • For decimals: send "ACT:12.34\n" and parse with atof() instead of atol()/atol (and use float/double on Arduino). Replace long v = atol(p) with float f = atof(p); then actualCourse = (int32_t)roundf(f); or keep a float variable.
  • If you want strict validation, add a checksum or use a more explicit command format (e.g., "ACT:1234;CHK:xx\n").
  • Debug tip: Use ARC Serial Terminal skill to capture raw bytes and confirm what's being sent/received: https://synthiam.com/Support/Skills/Communication/Serial-Terminal?id=16071
  • Avoid goto for serial-driven flows - use flags/state machine for maintainability and to avoid skipping code paths unexpectedly.

If you’d like, I can:

  • Provide a variant that parses floats (ACT:12.34).
  • Provide a checksum example for the ASCII line.
  • Convert the sender to use a different identifier like "COURSE:" or shorten to just the numeric line if you prefer.

Quick question: do you need decimals in actualCourse (float) or is integer OK?


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
#4  

Thank you @Athena.  A lot to digest here.  It works very well.  I'm working hard to understand the why now. More questions soon to follow. Jack

#5  

Hi @Athena, I believe a problem has occurred with the UART transfer of 'actualCourse' from EZB4 to the Arduino Mega.  I have included the EZB4 JavaScript that sends the 'actualCourse' and also the Arduino C++ that receives it.  The var 'actualControl' is a whole number 1-180.

The JavaScript snippet runs on a Loop with sleep(200ms):

      // send actualCourse as ASCII line when changed
  var actualCourse = getVar("$actualCourse");
  if (actualCourse !== lastActualCourse) {
    // If actualCourse is an integer:
    var msg = "ACT:" + Math.round(actualCourse) + "\n";
    // If you want to preserve decimals, use:
   //  var msg = "ACT:" + actualCourse.toString() + "\n";
    UART.hardwareUartWriteString(0, msg);
    lastActualCourse = actualCourse;
    print("$actualCourse:  " + getVar("$actualCourse"));
  }
       

The C++ snippet loop is below:

 while (Serial3.available() > 0) {
    char c = (char)Serial3.read();

    // Immediately handle newline/carriage return
    if (c == '\n') {
      if (rxIdx > 0) {
        rxBuf[rxIdx] = '\0';
        processLine(rxBuf);
        rxIdx = 0;
        rxBuf[0] = '\0';
      }
      // ignore empty lines
    } else if (c == '\r') {
      // ignore
    } else if (c == 'x' || c == 'c' || c == 'm') {
      // If these are sent alone (with or without newline), handle immediately.
      // If they might appear inside a textual line (they won't in "ACT:"), consider skipping immediate handling.
      handleCommand(c);
    } else {
      // accumulate into buffer (printable characters)
      if (rxIdx < RX_BUF_SIZE - 1) {
        rxBuf[rxIdx++] = c;
      } else {
        // buffer overflow - reset buffer and optionally log
        rxIdx = 0;
        rxBuf[0] = '\0';
        Serial.println("RX buffer overflow - cleared");
      }
      Serial.print(" rec'd  -actualCourse:  ");
      Serial.println(actualCourse);
      lastActualCourse = actualCourse;
     initial = true;    
     miss = false;
     loopct = 0;

    }
  }

My problem with motor control is due to, what I believe is, "mixing" of old and new 'actualCourse' when it is received by the Arduino.  The sending of a new 'actualCourse is reliable but the reception seems to be a problem.  When printing, the old 'actualCourse' continues to print about 6 times before the new 'actualCourse' is processed.  The result is incorrect motor control PWM.

I am wondering if the 'delayed' processing of 'actualCourse' in the "while loop" while 'actualCourse' buffer fills up is the best way to go.  I believe it is interfering with the new 'actualCourse' being sent.  Could you please relook at this code in light of my problem and advise a solution? As always, thank you. Jack

Author Avatar
PRO
Synthiam
LinkedIn Thingiverse Twitter YouTube GitHub
#6   — Edited

Just a thought but i looked at this thread and you have athena creating a text string protocol? That is going to be a really really really really really really really really really really really really:D bad idea haha.

We talked about this before in a previous thread - but as a recap - computers talk to each other through bytes. Humans talk to each other with strings. Computers don't do strings very well. They can - but with a LOT of code. Looking above, your string parser is longer than the program!

All your program should do is send the number as an array of bytes. Then, the arduino receives that array of bytes. Simple. You throw a header on, and it makes it fool proof.

Something like..

header: 0xff, 0xff
command: 0x00 // actual course, expect 4 bytes (unsigned int32)
byte1
byte2
byte3
byte4

And then assemble those bytes into your int32.

Now, if you're talking about sending a float (i.e. a number with a decimal place), then just multiply it before send (convert to an integer) and divide in receive on arduino (converts back to a float).

  • On send 0.32 x 100 becomes 32
  • On receive 32 / 100 becomes 0.32

Lastly, you flagged this conversation as completed so Athena won't answer you - no matter how loud you yell haha - she's deaf for closed threads. Use the information i provided you above to ask Athena in a new thread. Only close a thread if the answer works.

📚 This thread has been preserved as a helpful resource!

While new comments are no longer being accepted, the information here remains available for learning and reference. Check the most recent comment for additional details.