Dice on an ESP32-C3
A button rolls a die and an ESP32-C3 tumbles the number onto a seven-segment display. The whole build, an ESP32-C3 and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $13.55. On Mokxi they're free.
What you'll learn
- Driving a seven-segment display from plain GPIO pins
- Storing a font as bit masks
- Common cathode wiring with one shared resistor
- Easing a delay to fake a slowing roll
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: ESP32-C3-DevKitM-1
- Parts: 5
How it works
Seven GPIO pins (2, 3, 4, 5, 6, 7 and 10) drive segments a to g of a common cathode seven-segment display. A single 220 ohm resistor joins the common pin to ground. A pushbutton on GPIO 18 goes to ground and uses the internal pull-up.
While nothing is pressed, the loop just waits 10 ms and checks again. On a press it shows 14 random faces from 1 to 6, each held a little longer than the last, so the die seems to slow down. The last face stays on the display, and the number is printed to serial.
Parts list
5 parts, plus the jumper wires. They retail for about $13.55; on Mokxi they're free.
- 1 × ESP32-C3-DevKitM-1In Handheld Console Kit, Desk Buddy Kit.
- 1 × Full-size breadboard
- 1 × Seven segment display
- 1 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Seven segment display pin A | ESP32-C3-DevKitM-1 pin 2 |
| Seven segment display pin B | ESP32-C3-DevKitM-1 pin 3 |
| Resistor, 220 Ω pin 2 | ESP32-C3-DevKitM-1 pin GND |
| Pushbutton pin 2b | ESP32-C3-DevKitM-1 pin GND |
| Seven segment display pin G | ESP32-C3-DevKitM-1 pin 10 |
| Seven segment display pin C | ESP32-C3-DevKitM-1 pin 4 |
| Seven segment display pin D | ESP32-C3-DevKitM-1 pin 5 |
| Seven segment display pin E | ESP32-C3-DevKitM-1 pin 6 |
| Seven segment display pin F | ESP32-C3-DevKitM-1 pin 7 |
| Pushbutton pin 1b | ESP32-C3-DevKitM-1 pin 18 |
| Resistor, 220 Ω pin 1 | Seven segment display pin COM1 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Electronic dice: press the button, watch it tumble, read the number.
//
// segments a b c d e f g -> GPIO 2 3 4 5 6 7 10
// the common cathode -> 220 ohm to GND
// GPIO 18 -> the roll button, to GND, internal pull-up
//
// The same game as the Uno's `dice` example on the other board, and the only
// thing that changes is the pin numbers, which is the point of wiring a
// display to plain GPIOs rather than to a peripheral.
//
// The display is common cathode, so a segment lights when its pin goes HIGH
// and all of the return current leaves through the one resistor on the common
// pin. That is the cheap way to wire one: the more segments are lit the less
// current each gets, so an 8 is dimmer than a 1. Seven resistors, one per
// segment, is the right way and costs six more parts.
//
// DIGITS[] is the usual seven-segment font, one bit per segment, bit 0 = a.
// The tumble is the roll repeated with a delay that grows each step, which
// reads as a die slowing down and settling.
const uint8_t SEG_PINS[] = {2, 3, 4, 5, 6, 7, 10};
const uint8_t BUTTON = 18;
// bit 0 a, 1 b, 2 c, 3 d, 4 e, 5 f, 6 g
const uint8_t DIGITS[] = {
0x3f, // 0
0x06, // 1
0x5b, // 2
0x4f, // 3
0x66, // 4
0x6d, // 5
0x7d, // 6
};
static void show(uint8_t mask) {
for (int i = 0; i < 7; i++) {
digitalWrite(SEG_PINS[i], (mask >> i) & 1u ? HIGH : LOW);
}
}
static bool pressed() { return digitalRead(BUTTON) == LOW; }
void setup() {
for (int i = 0; i < 7; i++) {
pinMode(SEG_PINS[i], OUTPUT);
}
pinMode(BUTTON, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("Mokxi ESP32-C3: press the button to roll");
show(DIGITS[6]);
}
void loop() {
if (!pressed()) {
delay(10);
return;
}
int face = 1;
uint32_t step = 40;
for (int i = 0; i < 14; i++) {
face = (int)random(1, 7);
show(DIGITS[face]);
delay(step);
step += step / 4 + 5;
}
Serial.print("rolled ");
Serial.println(face);
while (pressed()) {
delay(10);
}
}
Code walkthrough
The key functions of the sketch, and what each one does.
show sketch.ino
Takes a bit mask and sets each of the seven segment pins. Bit 0 is segment a, bit 6 is segment g. A 1 bit writes HIGH, which lights the segment on a common cathode display, and a 0 writes LOW.
static void show(uint8_t mask) {
for (int i = 0; i < 7; i++) {
digitalWrite(SEG_PINS[i], (mask >> i) & 1u ? HIGH : LOW);
}
}pressed sketch.ino
Returns true when the button pin reads LOW. Because the pin uses INPUT_PULLUP, it sits HIGH until the button connects it to ground.
static bool pressed() { return digitalRead(BUTTON) == LOW; }loop sketch.ino
Returns early if the button is not pressed. Otherwise it starts with a 40 ms step and runs 14 rounds: pick random(1, 7), show that digit, wait, then lengthen the step by a quarter plus 5. It prints the final face and waits for the button to be released.
void loop() {
if (!pressed()) {
delay(10);
return;
}
int face = 1;
uint32_t step = 40;
for (int i = 0; i < 14; i++) {
face = (int)random(1, 7);
show(DIGITS[face]);
delay(step);
step += step / 4 + 5;
}
Serial.print("rolled ");
Serial.println(face);
while (pressed()) {
delay(10);
}
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The display is dim, and an 8 is dimmer than a 1. This project
That is expected here. One resistor is shared by all lit segments, so more lit segments means less current each. - A segment never lights. This project
Check the pin map: a is GPIO 2, b 3, c 4, d 5, e 6, f 7 and g is GPIO 10. - Segments stay dark, or light when they should be off. Seven segment display
Check whether the display is common cathode or common anode. Common cathode: common pin to GND, drive segments HIGH. Common anode: common pin to VCC, drive segments LOW. - One segment is very bright or the LEDs burn out. Seven segment display
Each segment is an LED, so it needs its own resistor, about 220 ohm to 330 ohm on 5 V. One resistor on the common pin makes brightness change with the number of lit segments. - The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure. - A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground. - The input reads random HIGH and LOW when the button is not pressed. Pushbutton
A floating pin picks up noise. Add a 10 k pull-down or pull-up resistor, or use INPUT_PULLUP and wire the button between the pin and GND. Then pressed reads LOW. - The button seems always pressed, or never changes. Pushbutton
A 4-leg tactile switch has legs joined in pairs. Place it across the breadboard center gap so the pairs sit on different rows. Wired along one side, the legs are already connected.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.