# Arduino serial command parsing for robots

> Parse serial commands on an Arduino robot without blocking: why Bluetooth cars lag, a simple command format, an sscanf parser and a link-loss failsafe.

Canonical: https://roboticsengineering.in/arduino-serial-command-parsing-bluetooth-robot/ | Published: 2026-11-04 | Updated: 2026-11-04 | Author: jaadudigital

**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

| Style | Example | Notes |
| --- | --- | --- |
| Single letter | F, B, L, R, S | Easiest; good for a first robot, but no speed value |
| Letter and number | F200 | Direction plus speed; parse the number after the letter |
| Named command and values | DRIVE 200 -150 | Left and right speed; clear to read and extend |
| Framed with a checksum | $DRIVE,200,-150\*3F | For 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](https://roboticsengineering.in/components/hc-05-bluetooth-module/), and for a full car build see the [Bluetooth robot car guide](https://roboticsengineering.in/bluetooth-controlled-robot-car-arduino/).

```
// 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](https://roboticsengineering.in/arduino-uno-pinout-for-robotics/) 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.*
