Two boards, one wire: an ESP32-C3 and an Arduino Uno
An ESP32-C3 pulses a number down one wire and an Arduino Uno counts it back out on the serial monitor. The whole build, an ESP32-C3 and an Arduino Uno and 4 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.
Advanced About 25 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $40. On Mokxi they're free.
What you'll learn
- Sending a number as a count of pulses
- Interrupts that count edges
- Silence as a message separator
- Why two boards need a common ground
- Sharing variables with
volatile
- Difficulty: Advanced
- Time: About 25 minutes
- Board: ESP32-C3-DevKitM-1, Arduino Uno R3
- Parts: 7
How it works
An ESP32-C3 and an Arduino Uno talk over a single wire with no bus and no protocol chip. The sender pulses GPIO 4 that many times: 1 pulse, then 2, up to 6, then back to 1. Each pulse is 40 ms, and a blue LED is lit while it sends.
The Uno counts rising edges on pin 2 using an interrupt. There is no clock or framing, so silence marks the end: after 300 ms with no edge it prints the total. A green LED on pin 9 follows the line. The boards must share a ground.
Parts list
7 parts, plus the jumper wires. They retail for about $40; on Mokxi they're free.
- 1 × ESP32-C3-DevKitM-1In Handheld Console Kit, Desk Buddy Kit.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 2 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, blueIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × LED, greenIn Starter Kit, Halloween Prop 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 |
|---|---|
| Arduino Uno R3 pin GND | ESP32-C3-DevKitM-1 pin GND |
| LED, blue pin C | ESP32-C3-DevKitM-1 pin GND |
| LED, green pin C | ESP32-C3-DevKitM-1 pin GND |
| Arduino Uno R3 pin 2 | ESP32-C3-DevKitM-1 pin 4 |
| Resistor, 220 Ω (1) pin 1 | ESP32-C3-DevKitM-1 pin 5 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 9 |
| LED, blue pin A | Resistor, 220 Ω (1) pin 2 |
| LED, green pin A | Resistor, 220 Ω (2) pin 2 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Talker: one board sending a number to another down a single wire.
//
// There is no bus and no protocol chip here: the number is simply that many
// pulses on GPIO 4, followed by a gap long enough that the far end knows the
// message has ended. GPIO 5 lights while a message is going out, so you can see
// the sender talking as well as the receiver listening.
//
// The line idles low and every pulse is 40 ms, which is glacial for a wire and
// exactly right for watching it happen. The receiver on the Arduino Uno counts
// rising edges on its interrupt pin and prints the total after 300 ms of
// silence.
//
// The two boards must share a ground for any of this to mean anything: 3.3 V
// on this board's pin is only 3.3 V at the Uno's input if both agree where
// zero is.
const int LINK = 4;
const int LAMP = 5;
const int PULSE_MS = 40;
const int GAP_MS = 900;
int value = 1;
void setup() {
pinMode(LINK, OUTPUT);
pinMode(LAMP, OUTPUT);
digitalWrite(LINK, LOW);
digitalWrite(LAMP, LOW);
Serial.begin(115200);
Serial.println("Mokxi ESP32-C3: sending on GPIO 4");
}
void loop() {
Serial.print("sending ");
Serial.println(value);
digitalWrite(LAMP, HIGH);
for (int i = 0; i < value; i++) {
digitalWrite(LINK, HIGH);
delay(PULSE_MS);
digitalWrite(LINK, LOW);
delay(PULSE_MS);
}
digitalWrite(LAMP, LOW);
value = value % 6 + 1;
delay(GAP_MS);
}
// Listener: the other half of the two-board link.
//
// pin 2 the wire from the other board's GPIO, counted on INT0
// pin 9 a lamp that follows the line, so you can see the traffic
//
// The sender puts out a number as that many pulses and then goes quiet. There
// is no framing and no clock: what separates one message from the next is
// silence, so this counts rising edges and prints the total once nothing has
// arrived for GAP_MS.
//
// The count is touched by the interrupt and by loop(), so it is volatile and
// loop() takes its copy with interrupts off, since an unsigned long is four bytes
// and this chip moves one byte at a time, so an interrupt landing halfway
// through the read would hand back a number that never existed.
//
// pin 2 has no pull-up here on purpose. The far board drives the line both
// ways, and a pull-up fighting a push-pull output is current spent for nothing.
const int LINK = 2;
const int LAMP = 9;
const unsigned long GAP_MS = 300;
volatile unsigned long pulses = 0;
volatile unsigned long lastEdge = 0;
static void onEdge() {
pulses++;
lastEdge = millis();
}
void setup() {
pinMode(LINK, INPUT);
pinMode(LAMP, OUTPUT);
Serial.begin(115200);
attachInterrupt(digitalPinToInterrupt(LINK), onEdge, RISING);
Serial.println("Mokxi Arduino Uno: counting pulses on pin 2");
}
void loop() {
digitalWrite(LAMP, digitalRead(LINK) == HIGH ? HIGH : LOW);
noInterrupts();
unsigned long count = pulses;
unsigned long last = lastEdge;
interrupts();
if (count > 0 && millis() - last > GAP_MS) {
Serial.print("received ");
Serial.println(count);
noInterrupts();
pulses -= count;
interrupts();
}
delay(2);
}
Code walkthrough
The key functions of the sketch, and what each one does.
onEdge sketch.ino
The interrupt handler on the Uno. Each rising edge adds one to pulses and notes the time in lastEdge. It does as little as possible, since it interrupts everything else.
static void onEdge() {
pulses++;
lastEdge = millis();
}loop sketch.ino
On the ESP32 it lights the lamp, sends value pulses of 40 ms, waits 900 ms and moves to the next number. On the Uno it mirrors the line to the lamp, copies the count with interrupts off, and prints it after 300 ms of silence.
void loop() {
Serial.print("sending ");
Serial.println(value);
digitalWrite(LAMP, HIGH);
for (int i = 0; i < value; i++) {
digitalWrite(LINK, HIGH);
delay(PULSE_MS);
digitalWrite(LINK, LOW);
delay(PULSE_MS);
}
digitalWrite(LAMP, LOW);
value = value % 6 + 1;
delay(GAP_MS);
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The Uno prints nothing This project
Join the two grounds, then check GPIO 4 goes to the Uno's pin 2. Interrupts need pin 2 or 3. - The count is off This project
IfPULSE_MSshrinks or the wire is noisy, edges can be missed or doubled. Keep the pulses slow. - The blue LED never lights This project
It is on GPIO 5 through a 220 ohm resistor, with its cathode on ground. - 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 LED does not light, or it lights once and then dies. LED
LEDs have polarity. The longer leg (anode) goes toward the positive side. Always add a series resistor, about 220 ohms to 1 k ohm on a 5 V pin. - A blue or white LED stays dark on 3.3 V. LED
Blue and white LEDs drop about 3 V, so 3.3 V leaves almost nothing for the resistor. Use a 5 V supply, or a smaller resistor and a red or green LED. - Upload fails or the board is not found by the computer. ESP32-C3-DevKitM-1
Pick the ESP32-C3 board in your tools and the right port. If it still fails, hold BOOT, tap RESET, then release BOOT to enter download mode and try again.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.