Code Along - Chapter 6

Time Without delay()

Replace delay() with millis() so several tasks run at once, build a state machine, and write timers that survive millis() rollover.

Estimated time 90-120 minExercises passed 0 / 7

Why delay() gets in the way

delay(500) stops the whole sketch for half a second. While it waits the Uno cannot read a button, update a display or blink a second LED. With one LED that is fine; with two tasks it breaks.

The fix is to check the clock instead of waiting. millis() returns how many milliseconds have passed since the board started. Each task remembers when it last ran and acts again when enough time has passed:

  • if (millis() - lastRun >= interval) { lastRun += interval; ... }

loop() then runs thousands of times a second, and each task only does its work when it is due.

One millis clock shared by three independent tasks
One shared clock, independent tasks.
Exercise 6.2

Heartbeat message

Not started

Print alive to the Serial Monitor once every second, using the same millis() pattern - no delay(). A heartbeat like this tells you a long-running sketch has not frozen.

Sample output
alive
alive
alive
unsigned long lastBeat = 0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  // Every 1000 ms: move lastBeat on and print alive

}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 6.3

Two LEDs, two speeds

Not started

The red LED on pin 9 already blinks every 300 ms. Add a second, independent timer so the green LED on pin 10 blinks every 700 ms. With delay() this is almost impossible; with two millis() timers it is straightforward.

const int LED_A = 9;    // red, 300 ms
const int LED_B = 10;   // green, 700 ms
unsigned long lastA = 0;
bool stateA = false;

void setup() {
  pinMode(LED_A, OUTPUT);
  pinMode(LED_B, OUTPUT);
}

void loop() {
  if (millis() - lastA >= 300) {
    lastA += 300;
    stateA = !stateA;
    digitalWrite(LED_A, stateA);
  }

  // Now the same again for LED_B, every 700 ms

}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 6.4

A button that answers instantly

Not started

This sketch blinks the onboard LED and lights the green LED on pin 8 while the button on pin 2 is held. Press Run and hold the button: the green LED reacts late, because delay() blocks the button check.

Rewrite the blinking with millis() so the green LED follows the button within a few milliseconds. The test presses at 1230 ms and releases at 1800 ms.

const int BUTTON = 2;
const int BUTTON_LED = 8;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(BUTTON_LED, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);   // pressed = LOW
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);

  if (digitalRead(BUTTON) == LOW) {
    digitalWrite(BUTTON_LED, HIGH);
  } else {
    digitalWrite(BUTTON_LED, LOW);
  }
}

Turn on JavaScript to edit, run and test this code in the browser.

State machines

A state machine remembers which step it is in with a variable, and moves to the next step when a condition is met - here, when enough time has passed. Each step can last a different length of time, which a plain blink cannot do. It is how Lesson 11 builds a traffic light without delay(), and how real controllers keep many tasks responsive.

Exercise 6.5

On for 2 s, off for 1 s

Not started

Make the onboard LED stay on for 2 seconds and off for 1 second, without delay(). state remembers which half you are in: 0 is on, 1 is off.

Work out how long the current state should last, and when that time has passed, move stateStart on, switch state and write the LED.

int state = 0;                 // 0 = on, 1 = off
unsigned long stateStart = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
}

void loop() {
  // How long does this state last? on = 2000, off = 1000

  // When it is over: stateStart += duration, swap state, write the LED

}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 6.6

Traffic light state machine

Not started

Now three states. Run a traffic light on pins 10 (green), 11 (yellow) and 12 (red): green for 3 s, yellow for 1 s, red for 3 s, then back to green. showState() already switches the right LED on.

Same shape as the last exercise, but the next state after 2 is 0 again.

const int RED = 12;
const int YELLOW = 11;
const int GREEN = 10;

int state = 0;                 // 0 = green, 1 = yellow, 2 = red
unsigned long stateStart = 0;

void showState() {
  digitalWrite(GREEN, state == 0);
  digitalWrite(YELLOW, state == 1);
  digitalWrite(RED, state == 2);
}

void setup() {
  pinMode(RED, OUTPUT);
  pinMode(YELLOW, OUTPUT);
  pinMode(GREEN, OUTPUT);
  showState();
}

void loop() {
  // How long does the current state last? (yellow 1000, the others 3000)

  // When that time has passed: move stateStart on, go to the next state, showState()

}

Turn on JavaScript to edit, run and test this code in the browser.

Deeper What happens after 49 days?

millis() is an unsigned long, so after 4294967295 ms - about 49.7 days - it rolls over to 0. Code that compares future times breaks at that moment: millis() + 1000 wraps to a tiny number, the check millis() >= next is suddenly always true, and the task runs non-stop.

Subtracting past times is always safe, because unsigned subtraction wraps too: millis() - lastRun still gives the right elapsed time across the rollover.

Exercise 6.7

A timer that survives rollover

DeeperNot started

This sketch prints tick once a second. It works for 49 days... then floods the Serial Monitor. The test starts the virtual clock about one second before millis() rolls over. Rewrite the timer with the rollover-safe pattern so it keeps printing one tick per second.

Sample output
tick
tick
tick
unsigned long nextTick = 0;

void setup() {
  Serial.begin(9600);
  nextTick = millis() + 1000;
}

void loop() {
  if (millis() >= nextTick) {
    nextTick = millis() + 1000;
    Serial.println("tick");
  }
}

Turn on JavaScript to edit, run and test this code in the browser.

Try it on a real Uno

Download Two LEDs, two speeds, wire two LEDs with 330 Ω resistors on pins 9 and 10, and upload it. Then add a button on pin 2 with INPUT_PULLUP and make it change one of the intervals while the LEDs keep blinking - something delay() could never do.