How to Simulate an Arduino Circuit Online Without Hardware
You can test an Arduino circuit before you own the parts. The firmware compiles, the microcontroller runs it instruction by instruction, and the components on the schematic respond — all inside a browser tab.
This tutorial builds the circuit every Arduino course starts with: a push button that lights an LED. It is short, but it contains the one thing that confuses almost everyone the first time, and a simulator is a much better place to meet that confusion than a breadboard.
What "simulating an Arduino" actually means
There are two different things happening, and it helps to keep them apart.
SPICE simulates analog behaviour. It solves the circuit equations to find voltages and currents. It knows nothing about code.
An AVR emulator runs your firmware. It executes the compiled machine code of the ATmega328P — the chip on an Arduino Uno — one instruction at a time, including its timers and I/O registers.
An Arduino circuit needs both. Your sketch decides when pin 13 goes high; the surrounding components decide what that does. CircuPilot compiles the sketch with arduino-cli, runs the resulting machine code in an AVR emulator, and drives the schematic from the emulated pins. When the firmware writes to a pin, the LED wired to it responds.
This is why a simulator can catch a genuine firmware bug. It is running your actual compiled binary, not an approximation of it.
The circuit: a button and an LED
Three components and an Uno:
- A push button from digital pin D2 to ground
- An LED on digital pin D13
- A 220 Ω resistor in series with the LED
You do not need a pull-up resistor for the button, and this is the first useful thing the simulation teaches. The ATmega has one built into every I/O pin; INPUT_PULLUP switches it on.
The sketch that drives it
const int buttonPin = 2;
const int ledPin = 13;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
}
void loop() {
// LOW means pressed. The internal pull-up holds the pin at 5 V
// until the button connects it to ground.
if (digitalRead(buttonPin) == LOW) {
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}
}
The part that confuses everyone
Read that if again. The LED turns on when digitalRead returns LOW — when the button is pressed. That feels backwards, and it is the single most common source of "my button does nothing" on a first Arduino project.
Here is why. INPUT_PULLUP connects an internal resistor between the pin and 5 V. With the button open, nothing else is attached, so the pin sits at 5 V and reads HIGH. Pressing the button connects the pin directly to ground, which wins against the weak internal resistor, and the pin reads LOW.
So the button does not supply a signal. It removes one. The pin is HIGH at rest and LOW when pressed — the opposite of what the wiring diagram suggests to a beginner.
On a breadboard this costs you an afternoon, because a dark LED tells you nothing about which half is wrong. In a simulator you can watch the pin state directly while you hold the button, and the answer is immediate.
Running it in the browser
In CircuPilot, describe the circuit in the prompt box — "an Arduino Uno with a push button on D2 and an LED on D13 through a 220 ohm resistor" — and the schematic and a matching sketch are generated together. You can also place the parts from the sidebar and write the firmware yourself.
Then press Run. Three things happen: the sketch compiles, the emulator starts, and a live pin-state panel appears. Click and hold the button on the schematic. D2 flips to LOW, D13 goes HIGH, and the LED lights while you hold it.
If the sketch does not compile, you get the compiler's own error message with the line number, exactly as arduino-cli reports it.
Experiments worth running
The value of a simulator is that being wrong is free. A few experiments worth running on this circuit:
- Remove
INPUT_PULLUP. Change it topinMode(buttonPin, INPUT)and watch the pin float. The reading becomes unpredictable — this is what an unconnected input actually does, and it is worth seeing once. - Drop the series resistor. The LED still lights in simulation, which is a good reminder that the resistor protects real hardware from a fault the simulator will happily tolerate.
- Add
delay(200)after the state change and hold the button. You have just built the beginning of a debounce routine. - Move the LED to another pin and change
ledPinto match. If you change one and not the other, you now know exactly what that failure looks like.
What a simulator will not tell you
A simulator is honest about logic and timing, and quiet about the physical world. It will not tell you that your button bounces for a few milliseconds, that a long wire picks up noise, or that you are pulling more than the 20 mA a pin should source (40 mA is the absolute maximum, not a target). Treat it as a way to get the design and the firmware right before you spend money — not as proof that the physical build will work.
For the analog side of things, the same circuit can be simulated with ngspice for DC and transient analysis. If you are debugging that half, see why SPICE simulations fail and how to read the errors.
Try it
CircuPilot runs in the browser with nothing to install and no licence. Describe a circuit in any language, generate the firmware, and press Run.