active buzzer/Using it/08. Beeps without delay()
Using it · 08 of 9

Beeps without delay()

A beep with delay() stops the whole sketch until it ends. Here a pattern is a list of on and off times, and a small function called on every pass of loop() steps through it by watching millis(), so the sketch keeps reading the serial port while the buzzer plays. Four patterns, from a 60 ms chirp to a four-beep alarm.

Why not delay()

The first beep used delay(200). While a delay runs, the sketch does nothing else: it does not read a button, check a sensor or answer the serial port. For one beep a second that does not matter. For an alarm that has to stop when somebody presses a key, it does.

The fix is to stop waiting and start checking. A pattern becomes a list of times, on first. The sketch remembers which time it is on and when that step began, and on every pass of loop() it asks one question: has this step's time run out? If not, it goes back to its other work. If so, it moves to the next step and switches the buzzer to match.

Beeps without delay()
Pattern
Beeps
2
Lasts
240 ms
Now
waiting
Two short beeps with a gap: distinct from one, without being longer.

Play a pattern and watch the ticks under it: each is a pass of loop(), and they keep coming while the buzzer beeps. On a real board there are thousands of them per pattern, and the sketch prints the count.

Four patterns

KeyPatternTimes, msMeant as
oOK60a key taken, a job done
dDouble80, 80, 80something to notice
eError500something failed
aAlarm150, 100, 150, 100, 150, 100, 150come and look

These are choices, not rules, and they are easy to change: edit the lists. Even positions are on, odd ones off, and a 0 ends the list. Keep each beep to 50 ms or more. The oscillator inside the buzzer has to start before it makes a sound, and its datasheet does not say how long that takes.

How it works

play() points pattern at a list, sets the step to 0, notes the time, and turns the buzzer on, because every pattern starts with a beep.

updateBuzzer() does nothing if no pattern is playing, or if millis() - stepStart has not reached the current step's time. Otherwise it moves to the next step. A 0 there ends the pattern and turns the buzzer off; any other number starts a new step, on if it is at an even position and off if odd.

millis() - stepStart is written that way round on purpose. millis() counts up and wraps back to 0 after about 49 days, and the subtraction of two unsigned numbers still gives the right gap across the wrap.

Everything else the sketch does goes where loops++ is, and as long as none of it waits, the beeps keep time.

The code

buzzer_patterns.ino

No library. Each pattern is a list of times in ms, on first, ended by 0. play() starts one; updateBuzzer(), called on every pass of loop(), moves it on when its time is up. Type o, d, e or a in the Serial Monitor.

/*
  Active Buzzer - beeps without delay()                 TK36 / /p/tk36

  Wiring. Count from the square pad on the TinkerBlock board, parts
  up, header at the bottom:

    GND    -> GND
    VCC    -> 5V: an Uno's 5V, the 5V pin of an ESP32 or ESP32-S3
              board on USB, a Pico's VBUS (3V3 works, quieter)
    NC     -> nothing   (unconnected on the board)
    SIGNAL -> GPIO 4 on an ESP32-S3 or ESP32, D9 on an Uno,
              GP15 on a Raspberry Pi Pico

  Arduino IDE
    Tools > Board                 your board, e.g. ESP32S3 Dev Module
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    Serial Monitor at 115200, line ending of your choice.
    No library needed.

  Type o, d, e or a in the Serial Monitor and press Enter.
*/

// The pin SIGNAL is wired to.
// Uno: 9. ESP32: 4. ESP32-S3: 4. Pico: 15.
const int BUZZER_PIN = 4;

// On and off times in ms, on first. A 0 ends the list.
const unsigned int patOk[] = {60, 0};
const unsigned int patDouble[] = {80, 80, 80, 0};
const unsigned int patError[] = {500, 0};
const unsigned int patAlarm[] = {150, 100, 150, 100, 150, 100, 150, 0};

const unsigned int *pattern = nullptr;  // what is playing, if anything
int step = 0;                           // which time in the list
unsigned long stepStart = 0;            // when that step began
unsigned long loops = 0;                // passes of loop() meanwhile

void play(const unsigned int *p) {
  pattern = p;
  step = 0;
  loops = 0;
  stepStart = millis();
  digitalWrite(BUZZER_PIN, HIGH);       // every pattern starts on
}

// Called on every pass of loop(). Never waits.
void updateBuzzer() {
  if (pattern == nullptr) return;
  if (millis() - stepStart < pattern[step]) return;

  step++;
  stepStart = millis();
  if (pattern[step] == 0) {             // end of the list
    digitalWrite(BUZZER_PIN, LOW);
    pattern = nullptr;
    Serial.print("done; loop() ran ");
    Serial.print(loops);
    Serial.println(" times while it played");
    return;
  }
  // Even steps are on, odd steps are off.
  digitalWrite(BUZZER_PIN, step % 2 == 0 ? HIGH : LOW);
}

void setup() {
  pinMode(BUZZER_PIN, OUTPUT);          // first: no pull-down on SIGNAL
  digitalWrite(BUZZER_PIN, LOW);
  Serial.begin(115200);
  Serial.println("Type o, d, e or a and press Enter.");
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    if (c == 'o') play(patOk);
    if (c == 'd') play(patDouble);
    if (c == 'e') play(patError);
    if (c == 'a') play(patAlarm);
  }

  updateBuzzer();
  loops++;          // anything else the sketch does goes here
}

When a pattern ends the sketch prints how many times loop() ran while it played, which is the point: thousands of passes, where a delay() version would have made one. It compiles for an ESP32-S3 and an Uno.

When it does not work

A pattern plays once and then my typing is ignored.

Something else in loop() is waiting: a delay(), a while loop, a blocking read. updateBuzzer() only moves the pattern on when loop() calls it, so anything that stops loop() also stops the pattern and the serial check. Keep every step of the sketch short.

The OK chirp is barely a click.

60 ms is short, and the buzzer's own oscillator has to start inside it. The datasheet gives no start-up time, so the book keeps every beep to 50 ms or more. If yours sounds clipped, make it 80 or 100 ms.

Nothing happens when I type a letter.

Check the Serial Monitor is at 115200 and that you pressed Enter or Send: the letter only reaches the board when the line is sent. Only lowercase o, d, e and a do anything; anything else, the line ending included, is ignored.

Where this goes next

Six symptoms and the part each one points at.

When it does not beep

Edit this page — content/books/active-buzzer/beeps-without-delay.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Active Buzzer

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum