UART Byte Array Communication Explained
Hi @Athena,
I'm asking about the best way to communicate via UART between an EZB4 (JavaScript running on the EZB) and an Arduino Mega (C++). I'm a beginner and want to keep the implementation simple but reliable. My understanding is that sending a String is not necessary and that sending an array of bytes would be a better approach. I'm using the EZB4 hardware UART functions (UART.hardwareUart*) on the EZB side and Serial3 on the Arduino Mega side.
To clarify the hardware/ARC context: the Arduino Mega is connected to the EZB4 UART (hardware UART lines). The EZB is handling the real-time hardware UART I/O, and this runs inside an ARC project where ARC scripts manage the higher-level logic. The EZB code is a single JavaScript script running on the EZB (built-in ARC/EZB scripting). The Arduino code is standard C++ running on the Mega. This is not using a separate robot skill for the serial link; the UART I/O is performed by the EZB firmware calls from the script.
I have included my C++ (Arduino) TX and RX code and my JavaScript (EZB4) TX/RX code below for your evaluation. Could you rewrite the code to reliably send data using an array-of-bytes format?
Notes:
- The Arduino C++ TX and RX must be separate sketches (not combined).
- The EZB4 code runs as a single JavaScript script on the EZB (using built-in ARC/EZB scripting).
- This is part of an ARC project where the EZB handles the real-time hardware UART and ARC scripts manage higher-level logic.
C++
Data TX
if (run == 1) {
bufferFlush();
totalStepsTaken = (abs(combinedStepsTaken));
delay(50);
int byte0 = totalStepsTaken & 0xFF;
int byte1 = (totalStepsTaken >> 8) & 0xFF;
int byte2 = (totalStepsTaken >> 16) & 0xFF;
int byte3 = (totalStepsTaken >> 24) & 0xFF;
Serial3.write('T'); //Header for Total Steps Taken
// Send the four bytes one by one.
Serial3.write(byte0);
Serial3.write(byte1);
Serial3.write(byte2);
Serial3.write(byte3);
Serial.print("Sent ");
Serial.println('T');
Serial.print("Left F+B Steps: ");
Serial.println(abs(LeftFrontWheel.currentPosition()) + abs(LeftBackWheel.currentPosition()));
Serial.print("Right F+B Steps: ");
Serial.println(abs(RightFrontWheel.currentPosition()) + abs(RightBackWheel.currentPosition()));
Serial.print("Sent combinedStepsTaken: ");
Serial.println(totalStepsTaken);
Serial.println(" ");
delay(100);
}
C++
Data RX
while ( run == 1) {
if (Serial3.available() >= 1 ) {
int incomingByte = Serial3.peek(); // do not remove it yet
// Check if this byte is our STOP command (assume '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 out 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;
}
}
if (miss == true ) {
Serial.print( " Received Missed command: ");
Serial.println('m');
actualCourse = lastActualCourse;
}
combinedStepsTaken = abs(LeftFrontWheel.currentPosition()) + abs(LeftBackWheel.currentPosition())
+ abs(RightFrontWheel.currentPosition()) + abs(RightBackWheel.currentPosition());
if (abs(combinedStepsTaken) > combinedStepsDesired) {
stopMotors();
goto Main;
}
loopct++;
}
JavaScript Data TX and RX (single script on the EZB)
var lastActualCourse = null;
setVar("$obsDet", 0);
setVar("$missedIR", 0);
setVar("$obsScanEnd", 0);
while(true) {
//Data RX
if (UART.hardwareUartAvailable(0) >= 5) { //5
var header = UART.hardwareUartReadString(0,1);
if (header == 'D'){
var desiredCourseScaled = UART.hardwareUartRead(0, 1);
setVar("$desiredCourse", desiredCourseScaled[0]);
print("rec'd desiredCourse: " + getVar("$desiredCourse")+ " cyc: " + getVar("$cycleCount"));
} else if (header == 'T') {
print("Header: " + header);
// read all 4 bytes from uart #0 that we need for this condition into the array tmpRead
var TSTRead = UART.hardwareUartRead(0, 4);
// tmpRead[3,2,1,0] are denoted as they were sent 0,1,2,3
var totalStepsTaken = (( TSTRead[3] << 24) | ( TSTRead[2] << 16) | ( TSTRead[1] << 8) | TSTRead[0]);
print("totalStepsTaken: " + totalStepsTaken);
print( "CYC: " + getVar("$cycleCount") + " $desiredCourse: " + getVar("$desiredCourse") + " $actualCourse: " + getVar("$actualCourse"));
sleep(100);
controlCommand("$IR_1", "PauseOff");
sleep( 10 );
controlCommand("IR_STEP", "ScriptStop");
sleep( 10 );
controlCommand("NRS_71.8_58_TEST", "ScriptStop");
sleep( 10 );
halt();
}
}
// Data TX
if (getVar("$obsDet") == 1) {
UART.hardwareUartWrite(0, ['x'.charCodeAt(0)]);
sleep(5);
print( "sent to ARDU...obsDet: " + getVar("$obsDet"));
print("OBS IR STOP: " + getVar("$IRADC"));
}
if ( getVar("$missedIR") == 1) {
UART.hardwareUartWrite(0, ['m'.charCodeAt(0)]);
print("sent to ARDU...missedIR: " + getVar("$missedIR"));
}
if ( getVar("$obsScanEnd") == 1 ) {
UART.hardwareUartWrite(0, ['c'.charCodeAt(0)]);
print("sent to ARDU...Scan for obs ended: " + getVar("$obsScanEnd"));
}
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(200);
}
Related Hardware (view all EZB hardware)
Related Robot Skills (view all robot skills)
Upgrade to ARC Pro
Join the ARC Pro community and gain access to a wealth of resources and support, ensuring your robot's success.
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.

Hi @Athena, Below you will find my Protocol Document for Arduino and ARC. I have provided an overview to explain my project scope. I have listed all the UART TX and RX 'headers' and the values they represent. I am at a loss for how else to describe it. I am hoping you can understand my document and what else would be required base upon what I have included. My objective is reliable UART communication between the Arduino and ARC. Can you rewrite the document in a clean table format so my objective can be accomplished? Thank you Jack
Absolutely - the current description is not yet a clean protocol, because it mixes:
"RP","WPT","NAV"'X','M'For a reliable, expandable UART protocol, the packet format should use byte values only:
That gives you a protocol that is easy to parse on both the Arduino and ARC sides.
Clean UART Protocol Definition
Protocol rules
Byte Values
Packet types
Use a single shared header byte for all packets.
0xA5This makes it easy to identify valid packets.
Command Map
ARC Arduino
0x010x020x03uint160x04uint160x05uint160x060x07Arduino ARC
0x100x11uint320x12uint160x130x14uint8Payload Definitions
1. Stop Motors
ARC Arduino
0xA50x01Packet size: 2 bytes
2. Missed IR
ARC Arduino
0xA50x02Packet size: 2 bytes
3. Send Actual Bearing
ARC Arduino
Actual bearing should be sent as a whole number scaled by 100.
Example:
79.35degrees becomes79350xA50x03Packet size: 4 bytes
4. Right Pivot
ARC Arduino
0xA50x04Packet size: 4 bytes
5. Left Pivot
ARC Arduino
0xA50x05Packet size: 4 bytes
6. Waypoint Data
ARC Arduino
This should be a fixed structured packet, not a string packet.
Recommended layout:
0xA50x06uint32uint32uint32uint80 = DA,1 = OAuint16uint8uint16uint16uint80 = FD,1 = RDPacket size: 23 bytes
Arduino ARC Packets
1. Navigation Acknowledgement
Arduino ARC
0xA50x10Packet size: 2 bytes
2. Total Steps Taken
Arduino ARC
Use a 32-bit unsigned integer.
0xA50x11Packet size: 6 bytes
3. Desired Bearing Update
Arduino ARC
0xA50x12Packet size: 4 bytes
4. Obstacle Scan Request
Arduino ARC
0xA50x13Packet size: 2 bytes
Data Representation Rules
Numeric conversions
uint8uint16uint320or1What this corrects
This protocol fixes the main problems in the original document:
"RP","WPT","NAV"Important recommendation
Your original design is trying to do too much with too much human-language meaning embedded in the packet names. For UART reliability, the Arduino and ARC should only care about:
Everything else belongs in your project documentation, not inside the packet.
Next step
If you want, I can do the next part and give you:
If you’d like, I can rewrite the protocol as a two-column "Arduino ARC" spec sheet next.
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.
@Athena, Thank you. I appreciate your willingness to teach me what I need to know about structuring UART packets. It seems you grasped what I need to accomplish in my plain language document of UART communication between the Arduino and ARC. I understand now that we're sending recognizable packets that follow proper protocol for UART communication. I love that this build will be reproducible in the future, as this protocol allows this. I look forward to seeing how you implement this. Can you please send a two-column "Arduino ARC" spec sheet and the actual Arduino C++ and ARC JavaScript code that implements this protocol? Thank you again. Jack
Arduino ARC UART Spec Sheet
Below is a clean binary protocol using single-byte packet IDs and fixed, predictable payload sizes.
Packet format
0xA5Rules
0xA5degrees * 1000or1Command table
0x010x020x03uint160x04uint160x05uint160x060x070x100x11uint320x12uint160x13Waypoint packet layout
0xA50x06uint32uint32uint32uint8uint16uint8uint16uint16uint8Suggested values
0 = DA,1 = OA0 = FD,1 = RDArduino C++ sketches
You asked for separate TX and RX sketches, so here they are.
1) Arduino TX sketch
Sends
T,DB, andNAV-style responses using binary packets.2) Arduino RX sketch
Receives ARC commands and decodes them reliably.
ARC JavaScript script
This is a single ARC JavaScript script that:
UART.hardwareUart*Important notes
1. Use one packet format everywhere
This is what makes the protocol reliable.
2. Do not mix strings and bytes
Strings like
"WPT"or"NAV"are human-readable, but not ideal for a robust binary protocol.3. Make payload lengths fixed
That lets the receiver know exactly how many bytes to read.
4. Scale floating-point values
For bearings, use:
79.35 -> 793585.00 -> 85005. ARC script should not assume bytes are immediately available
Always check
UART.hardwareUartAvailable()before reading.ARC configuration reminder
In ARC, make sure your Script skill is configured by using the kebab menu on the skill window and confirming:
If you want, I can also give you:
If you want the next step, I’d recommend adding a 1-byte checksum so this protocol becomes much more resilient to noise on the UART line.
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.
Hi @Athena, Thank you for your previous post. I am studying it, and as I do, it makes more sense to me. Perhaps you could help me, in this teachable moment, by choosing a command and explain how the logic flows and the steps that follow to final execution? In other words, once a command is initiated in the Loop, the path that it follows before the task is complete.
I would love it if you could do this for each Arduino sketch (TX + RX) and for the JavaScript script (providing an example of TX and RX). Thank you very much. Jack
Absolutely - that’s a great way to learn it.
What you want is the runtime path of each command:
Below I’ll walk through one command flow at a time for:
1) Arduino RX sketch: command flow example
Let’s use the Waypoint Data command because it’s the most complete one.
Example command
0xA5 0x06 ...payload...Logic flow
Step 1: ARC decides it’s time to send waypoint data
This usually happens when your ARC navigation logic has computed:
pathTotalDistancecombinedStepsDesiredoppositeDistancebearingAlignmentreflectorBearingsensorPrimedesiredBearingactualBearingtrackDirStep 2: ARC JavaScript builds the packet
It converts each number into bytes and places them into one packet.
Example shape:
Step 3: ARC sends the byte array over UART
Using:
Step 4: Arduino RX sketch sees bytes arriving on Serial3
The Arduino loop runs constantly and checks:
0xA5?Step 5: Arduino parser enters packet state machine
The RX sketch should not assume the full packet arrives all at once.
Instead it should:
Step 6: Arduino processes the complete packet
When the full waypoint packet is received, the Arduino:
run = 1Step 7: Arduino sends
NAV/ ack equivalentIn the binary protocol, this would be:
0xA50x10Step 8: Task is complete
At that point:
2) Arduino TX sketch: command flow example
Now let’s use Total Steps Taken.
Example command
0xA5 0x11 ...4 bytes...Logic flow
Step 1: Arduino is in
run == 1That means the rover is actively moving forward and tracking.
Step 2: Step counts are being updated internally
The Arduino computes:
Step 3: A condition triggers transmission
For example:
Step 4: Arduino builds a binary packet
It converts the 32-bit number into 4 bytes:
Step 5: Arduino writes bytes out serially
The TX sketch sends:
Step 6: ARC receives the bytes in its loop
ARC checks
UART.hardwareUartAvailable(0)and if enough bytes exist, reads them.Step 7: ARC decodes the packet
ARC:
Step 8: ARC uses the data
ARC may:
combinedStepsDesiredStep 9: Task is complete
The number has been transmitted, received, and acted on.
3) ARC JavaScript script: command flow example
Let’s use Actual Bearing Update as an example of ARC Arduino transmission.
Example command
0xA5 0x03 ...2 bytes...Logic flow
Step 1: ARC detects a new actual bearing value
This might come from:
Step 2: ARC compares it to the previous value
If the value changed, then ARC decides to send it.
Step 3: ARC scales the bearing
If the bearing is
79.35, ARC converts it to:Step 4: ARC packs the data
Using a helper like:
Step 5: ARC sends the packet over UART
Example:
Step 6: Arduino RX sketch receives it
The Arduino parser:
0xA50x03Step 7: Arduino updates motor control logic
The Arduino can now compare:
Then adjust PWM accordingly.
Step 8: Task is complete
The Arduino has the updated actual bearing and can continue motor correction.
4) Example TX and RX flow in both directions
ARC Arduino example: Stop Motors
Flow
Arduino ARC example: Desired Bearing Update
Flow
5) How the logic should be thought about
A UART command should always follow this mental model:
Sender side
Receiver side
That is the whole protocol loop.
6) Suggested command lifecycle
For reliability, each command should have this lifecycle:
7) Very important implementation note
Your current code mixes:
That can work sometimes, but it becomes fragile.
For a reliable system, the RX side should always parse bytes in a state machine, not by assuming:
That assumption can fail if:
8) ARC scripting example: TX and RX in one loop
Here’s the simplified logic:
If you want, I can do one more step and give you:
If you'd like, I’d recommend starting with the Arduino RX state machine, because that’s the foundation of reliability.
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.
Hi @Athena, I can see that we will need to rewrite both the Arduino and ARC codes. Can we start with the full command by command flowchart and the Arduino RX state machine? As you do this, please explain to me as a beginner so that I can follow. The previous post did a very good job with the explanations. Thanks. Jack
Absolutely, Jack - starting with the command-by-command flowchart and the Arduino RX state machine is the right move.
Below I’ll keep this beginner-friendly and focused on how the data moves, what each side is responsible for, and why the Arduino RX parser must be a state machine for reliability.
1) Command-by-command flowchart
Big picture
You have two directions of UART communication:
And each direction has specific jobs.
A. ARC Arduino command flow
These are the commands ARC sends to Arduino:
STOPMISSED IRACTUAL BEARINGRIGHT PIVOTLEFT PIVOTWAYPOINT DATAOBSTACLE SCAN COMPLETEFlowchart: ARC Arduino
B. Arduino ARC command flow
These are the messages Arduino sends back:
NAV ACKWAYPOINT DATAis acceptedTOTAL STEPS TAKENDESIRED BEARING UPDATEOBSTACLE SCAN REQUESTFlowchart: Arduino ARC
2) Why the Arduino RX side needs a state machine
This is the most important beginner concept.
UART is a stream of bytes, not a "message box."
That means when ARC sends a packet, Arduino might receive:
So this is not reliable:
Instead, the Arduino must do this:
That is called a state machine.
3) Arduino RX state machine concept
States
The parser moves through these states:
WAIT_HEADERWAIT_CMDWAIT_PAYLOADExample packet
Let’s say ARC sends:
That means:
0xA5= packet header0x06= waypoint data commandHow the state machine behaves
4) Arduino RX state machine sketch
Below is a clean starting point for your Arduino RX sketch.
This sketch:
Serial3STOP,MISSED IR,ACTUAL BEARING,RIGHT PIVOT,LEFT PIVOT, andWAYPOINT DATAArduino RX sketch
5) Beginner explanation of the Arduino RX logic
What happens first?
The Arduino runs
loop()over and over.Inside that loop:
Serial3.available()processIncomingByte()Why only one byte at a time?
Because UART is a stream. Reading byte-by-byte lets the Arduino stay synchronized.
Why does the header matter?
The header byte
0xA5says:If the Arduino gets out of sync, it can ignore garbage until it sees
0xA5again.Why does the command matter?
The command tells Arduino:
Why not process immediately?
Because the packet may not be complete yet.
If you process too early, the data will be wrong.
6) What happens after the packet is complete?
Once the Arduino receives the full packet:
For example, with
WAYPOINT DATA:NAV ACKThat is the full command path.
7) Next step: Arduino TX sketch
The TX side is simpler than the RX side because it only sends data.
It will:
TOTAL STEPS TAKENDESIRED BEARING UPDATENAV ACKIn the next message, I can write the Arduino TX sketch and the ARC JavaScript script in the same style, with the same beginner-friendly walkthrough.
If you want, I can continue with:
If you'd like, I recommend we do the Arduino TX sketch next, then the ARC JavaScript loop.
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.