Built-in project · ESP32-C3-DevKitM-1 and Arduino Uno R3

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.

The circuit runs here once the page has loaded.

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.

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 pinConnects to
Arduino Uno R3 pin GNDESP32-C3-DevKitM-1 pin GND
LED, blue pin CESP32-C3-DevKitM-1 pin GND
LED, green pin CESP32-C3-DevKitM-1 pin GND
Arduino Uno R3 pin 2ESP32-C3-DevKitM-1 pin 4
Resistor, 220 Ω (1) pin 1ESP32-C3-DevKitM-1 pin 5
Resistor, 220 Ω (2) pin 1Arduino Uno R3 pin 9
LED, blue pin AResistor, 220 Ω (1) pin 2
LED, green pin AResistor, 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.

sketch.ino · ESP32-C3-DevKitM-1
// 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);
}
sketch.ino · Arduino Uno R3
// 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
    If PULSE_MS shrinks 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.