Education › Robotics › Stage 2: Sense and move

Programming the board

The Arduino sketch model: GPIO, PWM, timing, serial and debugging your first programs.

Beginner→Intermediate ~34 min read Module 5 of 16

Firmware is the program that runs on your microcontroller — the reflexes of the robot. This lesson takes you past the blink into how real robot code is structured: the setup-and-loop model, reading inputs and driving outputs, generating analog-like signals with PWM, timing things without freezing the robot, and using the serial link to see what your code is actually doing. Get these right and your robot becomes responsive instead of stuttery.

After this module you can
  • Structure a sketch with setup() and a non-blocking loop()
  • Read digital and analog inputs and drive digital and PWM outputs
  • Replace delay() with millis()-based timing so the robot stays responsive
  • Use the serial link to debug what the firmware is seeing and doing

The setup-and-loop model

Every Arduino sketch has two functions. setup() runs once when the board powers up — you configure pins and start the serial link here. loop() runs forever afterwards, as fast as the board can go, and it is where the sense–think–act cycle lives. The mental model to hold: setup() is 'get ready', loop() is 'the robot's heartbeat'. Everything the robot does happens because loop() runs thousands of times a second, each pass reading inputs and updating outputs.

Pins are configured with pinMode(pin, MODE). An output pin drives something (OUTPUT); an input pin reads something (INPUT, or INPUT_PULLUP to use the board's internal resistor so a floating button reads a clean HIGH until pressed). Getting the mode wrong is a common bug: a pin left as input cannot drive an LED, and an input without a pull-up floats and reads random noise.

Reading inputs, driving outputs

There are two flavours of each direction. Digital is on/off: digitalRead(pin) returns HIGH or LOW, and digitalWrite(pin, HIGH/LOW) sets a pin fully on or off. Analog in reads a varying voltage: analogRead(pin) returns a number from 0 to 1023 representing 0 V to the board's reference, which is how you read a potentiometer, a light sensor, or a distance sensor's analog output. There is no true analog *out* on most boards — instead you use PWM (next section) via analogWrite.

  • digitalRead(pin) → HIGH or LOW; read a button or a digital sensor.
  • digitalWrite(pin, HIGH) → drive an LED, a relay, or a motor-driver enable.
  • analogRead(pin) → 0–1023; read a potentiometer or analog sensor.
  • analogWrite(pin, 0-255) → PWM 'analog' output on a PWM-capable pin.

PWM — fake analog for speed and brightness

A digital pin can only be fully on or fully off, so how do you dim an LED or run a motor at half speed? Pulse-width modulation (PWM) switches the pin on and off very fast, and the fraction of time it spends on — the *duty cycle* — sets the effective level. Fifty per cent duty is half brightness or half speed. On Arduino, analogWrite(pin, value) sets PWM where value 0 is off, 255 is full, and 127 is about half. Only certain pins support it (marked with a ~).

This is exactly how you control motor speed through a driver: you send the driver a PWM signal, and it delivers that fraction of the motor's power. Brightness, speed, and servo-like control all come from PWM, so it is one of the most-used tools in robot firmware.

Timing without freezing the robot

The single biggest beginner firmware mistake is delay(). delay(1000) stops the *entire* program for a second — the robot cannot read its sensors, react to a wall, or do anything else while it waits. For a blink that is fine; for a robot it is fatal, because a robot must keep sensing while it acts. The professional pattern is non-blocking timing with millis(), which returns the milliseconds since power-on. Instead of waiting, you check whether enough time has passed and act if so, letting the rest of the loop keep running.

cpp
// Non-blocking blink: the loop stays free to do other work.
const int LED = 13;
unsigned long lastToggle = 0;
const unsigned long INTERVAL = 500;  // ms
bool on = false;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  unsigned long now = millis();
  if (now - lastToggle >= INTERVAL) {   // time to flip?
    on = !on;
    digitalWrite(LED, on ? HIGH : LOW);
    lastToggle = now;
  }
  // ...read sensors and drive motors here every pass, no waiting...
}
Watch out

If your robot 'ignores' its sensors for a moment, look for a delay(). While delay() runs, nothing else happens — no sensor reads, no motor updates. Convert timed behaviour to the millis() pattern and the robot becomes responsive.

Serial: your window into the firmware

You cannot see what a microcontroller is thinking — unless it tells you. Serial.begin(9600) in setup() opens a channel to your computer, and Serial.println(value) prints to the Serial Monitor. This is the most important debugging tool in robotics: when a robot misbehaves, printing the sensor values shows you what it is *actually* sensing, which is almost always different from what you assumed. Print the distance, the button state, the motor command — and the bug usually reveals itself.

A good habit is to structure the loop clearly: read all sensors into variables, compute the decision, then drive the outputs — and print the key values while developing. Keeping sense, think and act as distinct phases each loop makes the code readable and the robot predictable.

Hands-on practice

Build a responsive input-to-output sketch

  1. Wire a potentiometer to an analog pin and an LED (with resistor) to a PWM pin.
  2. In loop(), read the potentiometer with analogRead and map its 0–1023 range to 0–255.
  3. Use analogWrite to set the LED brightness from that value, so turning the knob dims the LED.
  4. Add Serial.println of the raw and mapped values, and watch them in the Serial Monitor.
  5. Add a blink on a second LED using the millis() pattern, and confirm the knob still responds instantly while it blinks.
Cheat sheet

Programming the board — at a glance

Main things to focus on

  • setup() runs once to configure; loop() runs forever as the robot's heartbeat.
  • pinMode sets a pin as INPUT, INPUT_PULLUP or OUTPUT.
  • digitalRead/Write for on-off; analogRead for a voltage; analogWrite for PWM.
  • PWM's duty cycle fakes analog output for brightness and motor speed.
  • Use millis() not delay() so the robot keeps sensing while it acts.
  • Serial.println is your primary debugging tool — print what the robot senses.

Pins

pinMode(p, OUTPUT)configure a pin to drive a load
pinMode(p, INPUT_PULLUP)read a button with the internal resistor
digitalRead(p)returns HIGH or LOW
digitalWrite(p, HIGH)turn a pin fully on

Analog & PWM

analogRead(p)0–1023 from a varying voltage
analogWrite(p, 0-255)PWM output; duty cycle sets the level
map(x, 0,1023, 0,255)rescale one range to another
~ pinsthe pins that support PWM

Timing

millis()ms since power-on; use for non-blocking timing
now - last >= intervalthe non-blocking 'has time passed?' check
unsigned longthe type for millis() values so they don't overflow
delay(ms)blocks everything — avoid in robot loops

Serial

Serial.begin(9600)open the channel in setup()
Serial.println(v)print a value and newline for debugging
Serial Monitorthe window that shows the prints

Common pitfalls

  • Using delay() in a robot loop, so it stops sensing while it waits.
  • Leaving an input pin floating instead of using INPUT_PULLUP, giving random reads.
  • Calling analogWrite on a non-PWM pin, which does nothing useful.
  • Debugging blind instead of printing the sensor values that reveal the real behaviour.
  • Mixing up analogRead's 0–1023 scale with analogWrite's 0–255 scale.
Quiz

Check your understanding

5 questions · 4 to pass · answers are explained as you go. Your best score is saved on this device only.

Progress and quiz scores are saved in this browser only. Back up or restore on the hub.

Was this lesson useful? Tell me what to improve →