Arduino serial command parsing for robots

Why Bluetooth robots lag, how the Arduino serial timeout works, and a non-blocking command parser with a simple format and a link-loss failsafe.

Reference
RE-3802
Published
Read
4 min

About the author · Sources, testing and AI policy

A Bluetooth or Wi-Fi robot is controlled by text commands arriving over a serial link, and badly written parsing is why such robots lag, jump or ignore half the commands. This guide shows how to read commands without blocking, how the Arduino timeout works, and a short command format that is easy to parse.

Status: written from the Arduino Serial reference. The sketches are untested outlines and no latency measurements of our own are included.

The simple way and its catch

The easy method is Serial.readStringUntil('\n'), which collects characters until a newline. The Arduino reference says it ends early if it times out, and returns what it has read. The timeout set with Serial.setTimeout() defaults to 1000 ms and also governs parseInt(), parseFloat(), readString() and readBytesUntil(). So if a phone app sends a command without the terminator, your robot can stall for up to a second inside that call, with the motors still running the last command. That is the classic cause of a laggy Bluetooth car.

Safer: build the line yourself, without blocking

Read one character per pass of loop() only when Serial.available() says one is waiting, and add it to a buffer. When you see the end-of-line character, process the buffer. Nothing waits, so the control code keeps running.

// Untested outline.
const byte MAX_LEN = 24;
char line[MAX_LEN];
byte len = 0;

void readSerial() {
  while (Serial.available() > 0) {
    char c = Serial.read();
    if (c == '\n') {
      line[len] = 0;
      handleCommand(line);
      len = 0;
    } else if (c != '\r' && len < MAX_LEN - 1) {
      line[len++] = c;
    }
  }
}

void loop() {
  readSerial();
  // control code runs every pass
}

Using a fixed char array rather than the String class avoids heap fragmentation on small boards, and the length check stops a long garbage line overflowing the buffer.

Choose a command format that is easy to parse

StyleExampleNotes
Single letterF, B, L, R, SEasiest; good for a first robot, but no speed value
Letter and numberF200Direction plus speed; parse the number after the letter
Named command and valuesDRIVE 200 -150Left and right speed; clear to read and extend
Framed with a checksum$DRIVE,200,-150*3FFor noisy links; more work to write

Whatever you choose, document it in a comment so you and a phone-app builder agree on it.

Parsing “DRIVE left right”

// Untested outline.
void handleCommand(char *s) {
  if (strncmp(s, "DRIVE ", 6) == 0) {
    int left, right;
    if (sscanf(s + 6, "%d %d", &left, &right) == 2) {
      left = constrain(left, -255, 255);
      right = constrain(right, -255, 255);
      setMotors(left, right);
      lastCommand = millis();
    }
  } else if (strcmp(s, "STOP") == 0) {
    setMotors(0, 0);
  }
}

Check that sscanf returned 2 before using the values, constrain them to the range your motor driver accepts, and ignore anything that does not match. parseInt() returns 0 if it reads no digits before the timeout, so a missing number silently becomes a zero command; checking the result yourself avoids that.

Add a failsafe

If the link drops, the last command stays active. Record the time of each valid command and, in loop(), stop the motors if none has arrived for a set time, say 500 ms. The value is a starting point; choose it from how often your app sends. For a wired example see the HC-05 module page, and for a full car build see the Bluetooth robot car guide.

// Untested outline.
unsigned long lastCommand = 0;
const unsigned long FAILSAFE_MS = 500;

void loop() {
  readSerial();
  if (millis() - lastCommand > FAILSAFE_MS) setMotors(0, 0);
}

Common mistakes

  • The phone app sends \r\n and the sketch only looks for \n, leaving a stray \r in the command.
  • Baud rate mismatch between the module and Serial.begin(); garbled text is the sign.
  • Using the same hardware serial port for debugging and for the Bluetooth module on an Uno, which clashes. See the Uno pinout guide for the pins.
  • No failsafe, so a lost connection leaves the robot driving.
  • Trusting values from the phone without constraining them.

A test plan

  • Open the Serial Monitor and type commands by hand with newline line endings.
  • Send a command without a newline and confirm the robot does not freeze.
  • Send garbage and a very long line and confirm nothing crashes.
  • Switch off the sender and confirm the motors stop after the failsafe time.
  • Test with wheels off the ground first, and record what you changed.

Sources: Arduino language reference for Serial.setTimeout(), Serial.readStringUntil() and Serial.parseInt(). Last reviewed November 2026. Written, checked and maintained by Jignesh Panchal.

Pass it onWhatsAppLinkedIn

Keep reading

Continue learning

Free: the final-year project pack

A shortlist with BOMs, a report outline that matches the usual chapter scheme, a costing sheet and 40 viva questions. Built for the February–April submission season.

The Build Note

One short email when something is worth your bench time.

A new build sheet, a part price that moved, or a mistake worth avoiding. No daily drip, no recycled news.

One short email when a build or a price is worth your time. Unsubscribe in one click.