Every embedded project starts the same way: configure a pin as an output, drive it HIGH, drive it LOW, repeat. The Arduino Blink program does exactly that with one LED, one resistor, and a few lines of code. It also confirms that your board, USB cable, driver, and IDE all work together. This guide covers the built-in LED method, an external LED circuit, the resistor math, a non-blocking version, and a register-level version for readers who want to see what digitalWrite() hides.
Quick Takeaways
- Blink needs only two functions:
pinMode()to set direction anddigitalWrite()to set the pin level. - Always put a 220Ω–330Ω resistor in series with an external LED on a 5V Arduino pin.
- The Uno’s onboard LED sits on pin 13, which the
LED_BUILTINconstant maps for you. - Replace
delay()withmillis()timing as soon as your sketch needs to do more than one thing.
| Spec | Value (Arduino Uno R3) |
|---|---|
| Microcontroller | ATmega328P |
| Clock speed | 16MHz |
| Logic level | 5V |
| Max per-pin current (recommended) | 20mA (40mA absolute max) |
| Onboard LED pin | D13 (PB5) |
| Programming interface | USB (UART bridge) |
What You Need
| Component | Quantity | Notes |
|---|---|---|
| Arduino Uno (or compatible) | 1 | Nano and Mega work with the same code |
| USB cable (Type-B for Uno) | 1 | Must carry data, not just power |
| 5mm LED (red) | 1 | Forward voltage about 2.0V |
| 220Ω resistor | 1 | Red-red-brown bands |
| Breadboard | 1 | Half-size is enough |
| Jumper wires | 2 | Male-to-male |
Install the free Arduino IDE (2.x) from arduino.cc before you start. Plug the board in, then pick your board and port under Tools.
Step 1: Blink the Built-In LED
The Uno has a small LED soldered to D13, already wired through its own resistor. You need no extra parts to test it.
- Connect the board over USB.
- Open File → Examples → 01.Basics → Blink.
- Select Tools → Board → Arduino Uno and choose the correct Port.
- Click Upload.
The L LED near pin 13 should flash once per second. If it does, your toolchain works.
The Blink Code, Line by Line
// Blink: toggles the onboard LED once per second.
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // Configure pin 13 as an output (writes the DDR register bit)
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // Drive pin to 5V: LED turns on
delay(1000); // Hold for 1000 ms
digitalWrite(LED_BUILTIN, LOW); // Drive pin to 0V: LED turns off
delay(1000); // Hold for 1000 ms
}
setup()runs once after power-up or reset.loop()runs forever aftersetup()finishes.LED_BUILTINis a board-defined constant. It equals 13 on the Uno, so the same sketch ports to other boards.delay(1000)halts the CPU’s sketch logic for 1000 milliseconds.
Step 2: Wire an External LED
The onboard LED proves the board works. An external circuit teaches you the real skill: switching a load from a GPIO pin.
Understand LED Polarity
An LED is a diode. Current flows in one direction only.
- Anode (+): the longer leg. Connect it toward the resistor and the signal.
- Cathode (−): the shorter leg, next to the flat edge of the plastic lens. Connect it to GND.
Reverse the LED and nothing lights, but nothing breaks either at 5V.
Calculate the Resistor
An LED has almost no resistance once it conducts. Without a resistor, the pin sources current until something fails. Use Ohm’s law:
R = (Vsupply − Vf) / I
For a red LED at 5V with a target of 15mA:
R = (5V − 2.0V) / 0.015A = 200Ω
The nearest standard value is 220Ω, which gives about 13.6mA. That is bright and safely under the 20mA pin rating.
| LED Color | Typical Vf | Resistor for ~10mA at 5V | Nearest Standard Value |
|---|---|---|---|
| Red | 2.0V | 300Ω | 330Ω |
| Yellow | 2.1V | 290Ω | 330Ω |
| Green | 2.2V | 280Ω | 270Ω |
| Blue | 3.2V | 180Ω | 180Ω |
| White | 3.2V | 180Ω | 180Ω |
Forward voltage varies by manufacturer. Check the datasheet when you can. A higher resistor value only dims the LED, so rounding up is always safe.
Build the Circuit
Power the board off (unplug USB) before wiring.
- Insert the LED into the breadboard with legs in separate rows.
- Connect a 220Ω resistor from the LED’s anode row to an empty row.
- Run a jumper from that empty row to D8.
- Run a jumper from the LED’s cathode row to a GND pin on the Arduino.
The signal path is: D8 → 220Ω resistor → LED anode → LED cathode → GND.
The resistor can sit on either side of the LED. The current is the same everywhere in a series loop.
Upload the External LED Sketch
// Blink an external LED on digital pin 8.
const uint8_t LED_PIN = 8; // Pin connected to the resistor/LED chain
const uint16_t BLINK_MS = 500; // Half-period in milliseconds
void setup() {
pinMode(LED_PIN, OUTPUT); // D8 becomes a push-pull output
}
void loop() {
digitalWrite(LED_PIN, HIGH); // ~5V on the pin: current flows through the LED
delay(BLINK_MS);
digitalWrite(LED_PIN, LOW); // 0V on the pin: current stops
delay(BLINK_MS);
}
Using named constants instead of bare numbers makes the sketch easier to change later. Change BLINK_MS to 100 for a fast flicker, or 2000 for a slow pulse.
Step 3: Stop Blocking the CPU with millis()
delay() is simple, but it blocks. While it runs, your sketch cannot read a button, poll a sensor, or handle serial data. The fix is to compare timestamps from millis(), which returns milliseconds since power-up.
// Non-blocking blink using millis().
const uint8_t LED_PIN = 8;
const unsigned long INTERVAL_MS = 500;
unsigned long previousMillis = 0; // Timestamp of the last toggle
bool ledState = LOW; // Current LED level
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
unsigned long now = millis(); // Read the timer once per pass
// Subtraction stays correct even when millis() rolls over (~49.7 days)
if (now - previousMillis >= INTERVAL_MS) {
previousMillis = now; // Save the time of this toggle
ledState = !ledState; // Flip the state
digitalWrite(LED_PIN, ledState);
}
// Other tasks can run here without waiting on the LED
}
| Method | CPU Blocked? | Code Complexity | Best For |
|---|---|---|---|
delay() |
Yes | Lowest | First sketches, quick tests |
millis() |
No | Low | Multitasking, button + LED sketches |
| Timer interrupt | No | Medium | Precise timing, low-power designs |
Declare timestamp variables as unsigned long. Using int overflows after about 32 seconds and breaks the timing.
Step 4: See What digitalWrite() Does (Register-Level Blink)
On the ATmega328P, pin 13 is port PB5. Three registers control a port:
- DDRB: data direction. A 1 makes the pin an output.
- PORTB: output latch. A 1 drives the pin high.
- PINB: input state. Writing a 1 here toggles the matching PORTB bit on this chip.
// Direct port manipulation: blinks pin 13 (PB5) without digitalWrite().
void setup() {
DDRB |= (1 << DDB5); // Set bit 5 of DDRB: PB5 (pin 13) becomes an output
}
void loop() {
PORTB |= (1 << PORTB5); // Set bit 5 of PORTB: pin 13 goes HIGH
delay(500);
PORTB &= ~(1 << PORTB5); // Clear bit 5 of PORTB: pin 13 goes LOW
delay(500);
}
Direct register writes finish in a couple of clock cycles at 16MHz. A digitalWrite() call takes several microseconds because it looks up the pin, checks for PWM, and disables interrupts briefly. For a 1Hz blink the difference is invisible. For bit-banged protocols it matters.
Built-In LED Pins Across Common Boards
LED_BUILTIN hides these differences, but the table shows why you should use it.
| Board | Onboard LED Pin | Logic Level | Notes |
|---|---|---|---|
| Arduino Uno R3 | D13 | 5V | Shares SPI SCK |
| Arduino Nano | D13 | 5V | Same as the Uno |
| Arduino Mega 2560 | D13 | 5V | Same as the Uno |
| ESP32 DevKit (typical) | GPIO2 | 3.3V | Varies by board vendor |
| Raspberry Pi Pico | GP25 | 3.3V | Pico W routes its LED through the wireless chip |
On 3.3V boards, recalculate the resistor. A red LED at 3.3V with 10mA needs about 130Ω, so use 150Ω.
Real-World Project: A Heartbeat Status Indicator
A blinking LED is more useful than it looks. Professional firmware often uses one as a heartbeat to show the main loop is alive. A frozen LED means a hung program.
Extend the millis() sketch with a fault pattern:
// Heartbeat LED: slow blink = normal, fast blink = fault.
const uint8_t LED_PIN = 8;
const uint8_t FAULT_INPUT = 2; // Pull to GND to simulate a fault
unsigned long lastToggle = 0;
bool ledState = LOW;
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(FAULT_INPUT, INPUT_PULLUP); // Internal ~20-50k pull-up: HIGH when open
}
void loop() {
// LOW on the input means a fault: blink every 100 ms, else every 1000 ms
unsigned long interval = (digitalRead(FAULT_INPUT) == LOW) ? 100 : 1000;
if (millis() - lastToggle >= interval) {
lastToggle = millis();
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
}
}
Wire a jumper from D2 to GND to trigger the fast blink. The INPUT_PULLUP mode removes the need for an external pull-up resistor.
Troubleshooting: LED Won’t Blink
| Symptom | Likely Cause | Fix |
|---|---|---|
| Upload fails with “port not found” | Charge-only USB cable or missing driver | Swap the cable and reinstall the CH340/FTDI driver on clone boards |
| Upload works, external LED dark | LED reversed | Flip the anode and cathode |
| LED dark, wiring looks correct | Wrong pin number in code | Match LED_PIN to the jumper location |
| LED very dim | Resistor too large (e.g., 10kΩ) | Check the color bands, use 220Ω–330Ω |
| Onboard LED blinks, external does not | Open breadboard connection | Press the parts in and check rail splits |
| Blink timing looks wrong | Wrong board selected in the IDE | Re-select the board under Tools |
Measure with a multimeter if you have one. With the sketch running at HIGH, you should read about 5V at D8 relative to GND, and roughly 2V across a red LED.
Safe Practices
- Never connect an LED directly to a pin or to 5V without a series resistor.
- Do not draw more than 20mA from any one pin or about 200mA total from the chip.
- To drive larger loads such as LED strips or relays, use a MOSFET or BJT switch instead of the pin itself.
- Disconnect USB before changing wiring.
FAQ
Why does my Arduino LED need a resistor?
An LED limits nothing on its own. Once the supply passes its forward voltage (about 2V for red), current rises sharply. A series resistor sets the current, typically 10–15mA, which protects both the LED and the pin’s 20mA rating.
What does pin 13 do on an Arduino Uno?
Pin 13 (port PB5) is a normal digital I/O pin that also carries the SPI SCK signal. The board connects the onboard L LED to it, so any sketch that toggles pin 13 flashes that LED.
How do I make the LED blink faster or slower?
Change the value passed to delay() or the INTERVAL_MS constant. The value is in milliseconds, so 250 gives a 2Hz blink (250 ms on, 250 ms off) and 1000 gives 0.5Hz.
Can I blink an LED without using delay()?
Yes. Compare millis() against a stored timestamp and toggle the pin when the interval has passed. This keeps the CPU free to read sensors and buttons while the LED blinks.



