Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.
Quiz Buzzer: the finished build
UnoStudent projects

Quiz buzzer system for 4 teams with lockout (Arduino Uno + TM1637 + WS2812B)

Four teams, one winner — the first press lights its colour and locks out the rest.

Difficulty
Easy
Build time
2 h
Parts cost
₹1,088 · $13
Verified — core avr 1.8.8, TM1637 1.2.0, 3 Nov 2026Compile-only

Community draft — report a mistake

Parts list

PartQtyApprox.Buy
Arduino Uno R3 (CH340 clone)1₹450Buy Arduino Uno R3 (CH340 clone)
Arcade push button 30 mm (red, green, blue, yellow)4₹180Buy Arcade push button 30 mm (red, green, blue, yellow)
Tactile push button (quizmaster OPEN)1₹3Buy Tactile push button (quizmaster OPEN)
TM1637 4-digit display module (0.56", with colon)1₹60Buy TM1637 4-digit display module (0.56", with colon)
WS2812B 8-LED stick1₹120Buy WS2812B 8-LED stick
Active buzzer module1₹25Buy Active buzzer module
330 Ω resistor + 1000 µF / 10 V capacitor1₹10Buy 330 Ω resistor + 1000 µF / 10 V capacitor
2-core wire for the team buttons (about 2 m each)4₹120Buy 2-core wire for the team buttons (about 2 m each)
Jumper wires + small project box1₹120Buy Jumper wires + small project box
Total₹1,088

Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.

Wiring

Arduino Uno R3Only the pins used are shown · 5 V logicPush button (6 mm tactile switch)Push button (6 mm tactile switch) #2Push button (6 mm tactile switch) #3Push button (6 mm tactile switch) #4Push button (6 mm tactile switch) #5TM1637 4-digit 7-segment displayWS2812B addressable RGB LED strip (NeoPixel)Active buzzer module (high-level trigger)330 Ω5VGNDD2D3D4D5D6D7D8D9D101212121212CLKDIOVCCGNDDIN5VGNDDOUT (not connected)I/OVCCGND
Every connection
Arduino Uno R3Part · pinWireNotes
D2Push button (6 mm tactile switch) · 1orangeTeam 1 (red) — internal pull-up
GNDPush button (6 mm tactile switch) · 2black
D3Push button (6 mm tactile switch) #2 · 1orangeTeam 2 (green)
GNDPush button (6 mm tactile switch) #2 · 2black
D4Push button (6 mm tactile switch) #3 · 1orangeTeam 3 (blue)
GNDPush button (6 mm tactile switch) #3 · 2black
D5Push button (6 mm tactile switch) #4 · 1orangeTeam 4 (yellow)
GNDPush button (6 mm tactile switch) #4 · 2black
D6Push button (6 mm tactile switch) #5 · 1purpleQuizmaster’s OPEN button
GNDPush button (6 mm tactile switch) #5 · 2black
D7TM1637 4-digit 7-segment display · CLKyellow
D8TM1637 4-digit 7-segment display · DIOgreen
5VTM1637 4-digit 7-segment display · VCCred
GNDTM1637 4-digit 7-segment display · GNDblack
D9WS2812B addressable RGB LED strip (NeoPixel) · DINgreenVia 330 Ω — Resistor close to the stick; 470–1000 µF across 5V/GND at the stick
5VWS2812B addressable RGB LED strip (NeoPixel) · 5Vred
GNDWS2812B addressable RGB LED strip (NeoPixel) · GNDblack
D10Active buzzer module (high-level trigger) · I/Oblue
5VActive buzzer module (high-level trigger) · VCCred
GNDActive buzzer module (high-level trigger) · GNDblack

Board quirks: All four team buttons are on PORTD (D2–D5), so the sketch reads them in one instruction. Checking them one by one with digitalRead would always favour the team checked first by a few microseconds — tiny, but an examiner will ask, and this way the answer is “none”. A team already pressing when OPEN is pressed is locked out for that question. Without this, a team could hold its button down and “win” every question the instant it opens. Long button cables (2 m or more) pick up noise. The internal pull-ups (about 35 kΩ) are weak; if you see phantom presses, add a 10 kΩ resistor from each team pin to 5V and a 100 nF capacitor across each button. The stick runs at brightness 80 of 255: plenty indoors, and 8 LEDs stay well under 500 mA on USB. Full-white at 255 can reach about 480 mA, close to the limit of a laptop port.

Code

quiz-buzzer-system.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// Quiz buzzer system — Arduino Uno + 4 team buttons + host button + TM1637 + WS2812B stick + buzzer.
// The host presses OPEN to start a question. The first team to press wins: the display shows
// "P  2", that team's two LEDs light in its colour and the buzzer sounds. Later presses are
// listed in order on the Serial Monitor. A team holding its button when the question opens is
// locked out for that question (no early pressing).
// Team buttons D2–D5 (to GND), OPEN button D6 (to GND), TM1637 CLK D7 / DIO D8,
// WS2812B DIN D9 through 330 Ω, active buzzer module I/O D10.
#include <TM1637Display.h>
#include <Adafruit_NeoPixel.h>

constexpr uint8_t HOST_PIN = 6;
constexpr uint8_t CLK = 7, DIO = 8;
constexpr uint8_t LED_PIN = 9;
constexpr uint8_t NUM_LEDS = 8;  // two per team
constexpr uint8_t BUZZER_PIN = 10;

TM1637Display display(CLK, DIO);
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

// Team colours: red, green, blue, yellow (0xRRGGBB, as Adafruit_NeoPixel::Color packs them).
const uint32_t TEAM_COLOR[4] = {0xFF0000, 0x00FF00, 0x0000FF, 0xFFB000};
const uint8_t READY[] = {SEG_E | SEG_G, SEG_A | SEG_D | SEG_E | SEG_F | SEG_G, SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,
                         SEG_B | SEG_C | SEG_D | SEG_F | SEG_G};  // "rEdy"
const uint8_t SEG_P = SEG_A | SEG_B | SEG_E | SEG_F | SEG_G;

enum State : uint8_t { IDLE, ARMED, LOCKED };
State state = IDLE;
unsigned long armedAt = 0, firstMs = 0, buzzOffAt = 0, lastShown = 0;
uint8_t lockedOut = 0;  // bit n = team n+1 was holding its button when the question opened
uint8_t answered = 0;   // bit n = team n+1 has pressed during this question
uint8_t place = 0;
bool hostWasDown = false;

// All four team buttons are on port D (PD2–PD5), so one read of PIND samples them at the
// same instant — no team is favoured by the order the pins are checked in.
uint8_t teamsDown() { return (~PIND >> 2) & 0x0F; }

void beep(unsigned int ms) {
  digitalWrite(BUZZER_PIN, HIGH);
  buzzOffAt = millis() + ms;
}

void lightTeam(uint8_t team, uint32_t color) {
  strip.setPixelColor(team * 2, color);
  strip.setPixelColor(team * 2 + 1, color);
}

void allLeds(uint32_t color) {
  strip.fill(color);
  strip.show();
}

void openQuestion() {
  state = ARMED;
  answered = 0;
  place = 0;
  lockedOut = teamsDown();
  armedAt = millis();
  lastShown = 0xFFFF;
  allLeds(0x101010);  // dim white: question open
  beep(60);
  Serial.println(F("\nQuestion open — buzzers live"));
  for (uint8_t t = 0; t < 4; t++)
    if (lockedOut & (1 << t)) {
      Serial.print(F("Team "));
      Serial.print(t + 1);
      Serial.println(F(" was already pressing — locked out for this question"));
    }
}

void recordPress(uint8_t team, unsigned long ms) {
  answered |= 1 << team;
  place++;
  Serial.print(place);
  Serial.print(F(". Team "));
  Serial.print(team + 1);
  if (place == 1) {
    firstMs = ms;
    Serial.print(F(" first, "));
    Serial.print((ms - armedAt) / 1000.0, 2);
    Serial.println(F(" s after the question opened"));
    state = LOCKED;
    const uint8_t segs[] = {SEG_P, 0, 0, display.encodeDigit(team + 1)};
    display.setSegments(segs);
    strip.clear();
    lightTeam(team, TEAM_COLOR[team]);
    strip.show();
    beep(400);
  } else {
    Serial.print(F(" — "));
    Serial.print((ms - firstMs) / 1000.0, 3);
    Serial.println(F(" s behind"));
  }
}

void setup() {
  for (uint8_t pin = 2; pin <= 5; pin++) pinMode(pin, INPUT_PULLUP);
  pinMode(HOST_PIN, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  Serial.begin(115200);
  display.setBrightness(7);
  strip.begin();
  strip.setBrightness(80);  // 8 LEDs at this level stay well inside USB power
  // Colour check at power-up: each team's pair lights in turn.
  for (uint8_t t = 0; t < 4; t++) {
    lightTeam(t, TEAM_COLOR[t]);
    strip.show();
    delay(200);
  }
  display.setSegments(READY);
  allLeds(0x101010);
  Serial.println(F("Quiz buzzer ready. Press OPEN (D6) to start a question."));
  Serial.println(F("Before the show: hold any team button now to test it — its LEDs light up."));
}

void loop() {
  unsigned long now = millis();
  if (buzzOffAt && (long)(now - buzzOffAt) >= 0) {
    digitalWrite(BUZZER_PIN, LOW);
    buzzOffAt = 0;
  }

  // Host button: open the next question on each press (falling edge, debounced).
  bool hostDown = digitalRead(HOST_PIN) == LOW;
  if (hostDown && !hostWasDown) {
    openQuestion();
    delay(30);
  }
  hostWasDown = hostDown;

  uint8_t down = teamsDown();
  if (state == IDLE) {
    // Button test between rounds: a held button lights its team's LEDs.
    static uint8_t lastDown = 0xFF;
    if (down != lastDown) {
      for (uint8_t t = 0; t < 4; t++) lightTeam(t, (down & (1 << t)) ? TEAM_COLOR[t] : 0x101010);
      strip.show();
      lastDown = down;
    }
    return;
  }

  // Locked-out teams count again once they let go and press afresh in the next question only.
  uint8_t fresh = down & ~lockedOut & ~answered & 0x0F;
  if (fresh) {
    // A tie inside one PIND read (a few microseconds) goes to the lower team number.
    for (uint8_t t = 0; t < 4; t++)
      if (fresh & (1 << t)) recordPress(t, now);
  }

  if (state == ARMED) {
    // Elapsed seconds since the question opened: "-  7".
    unsigned int s = (now - armedAt) / 1000;
    if (s != lastShown) {
      lastShown = s;
      const uint8_t dash[] = {SEG_G};
      display.setSegments(dash, 1, 0);
      display.showNumberDec(min(s, 999u), false, 3, 1);
    }
  }
}

Compiled for uno on avr 1.8.8 with TM1637 1.2.0 · 3 Nov 2026

How it works

Every school quiz needs a buzzer system, and arguments start the moment two teams press together. This one settles it with numbers: the first press wins, the display and the LEDs show which team it was, and the Serial Monitor lists every press in order with the time behind the winner — to the millisecond.

How it works

The quizmaster presses OPEN to start a question. The display counts the seconds since the question opened (“– 7”) and the LED stick glows dim white.

When a team presses, the Uno records the time with millis(), shows P 2 (player 2) on the TM1637, lights that team’s two LEDs in its colour (red, green, blue or yellow), and beeps for 0.4 s. From then on, other presses don’t change the winner, but each one is printed: 2. Team 4 — 0.183 s behind.

Fairness comes from two details:

  • One read for all buttons. The four team buttons sit on D2–D5, which are bits 2–5 of the ATmega328P’s port D. ~PIND reads all of them at the same instant. A tie inside one read (a window of under a microsecond) goes to the lower team number — it essentially never happens.
  • Lockout. A team that is already holding its button when OPEN is pressed is ignored for that question, so nobody can “camp” on the button.

Between questions, holding a team button lights its LEDs — a quick wiring test before the audience arrives.

What to show at the exhibition

Let visitors play against each other: you be the quizmaster, read a quick question, press OPEN. Keep the laptop’s Serial Monitor visible so people can see how close the presses were — “0.041 s behind” always gets a reaction. Run a round where someone holds the button early to show the lockout.

Questions the examiner may ask

  • Why INPUT_PULLUP? The pin is held HIGH by an internal resistor; pressing the button connects it to GND, so pressed reads LOW. No external resistors are needed.
  • How do you know who pressed first? The loop runs thousands of times a second and samples all four buttons in one port read; the first read that sees a press decides.
  • What is debouncing? A switch contact bounces for a few milliseconds. For the teams only the first edge matters, so bounce can’t change the winner; the OPEN button waits 30 ms after a press.
  • Why a resistor in the WS2812B data line? It damps ringing on the wire and protects the first LED’s input.

Cost in India

About ₹1,100 in total. The arcade buttons are the biggest item (₹40–60 each at local electronics shops or online); plain tactile buttons glued into bottle caps work for a classroom version and bring it under ₹700.

Troubleshooting

One team always wins

Its button is stuck or wired to the wrong side of the switch (4-leg tactile buttons have two pairs that are always connected). Hold nothing and press OPEN — if the Serial Monitor says that team was locked out, its input is reading LOW all the time.

The LEDs show wrong colours or flicker

Check the 330 Ω resistor is in the data line and the stick’s GND goes to the Uno’s GND. If red and green are swapped, your stick is RGB order — change NEO_GRB to NEO_RGB.

The display shows nothing

CLK and DIO are swapped, or VCC isn’t connected. The display should read “rEdy” a second after power-up.

The buzzer clicks but doesn’t beep

You have a passive buzzer. An active buzzer module beeps on its own when its I/O pin is HIGH; a passive one needs tone().

Upgrades

  • Keep scores: a second quizmaster button adds a point to the winning team and shows the scoreboard on the TM1637.
  • Add a 10-second answer timer that starts when a team wins the buzz.
  • Use 8 teams with a second WS2812B stick and pins D11, D12, A0–A3 (read port C as one byte too).
  • Make the team buttons wireless with nRF24L01 modules for a stage-sized quiz.

↑↓ to move · Enter to open