WS2812 LEDs — sold under the trade name NeoPixels — pack a full RGB pixel and a tiny controller into each package. They chain trivially: one wire goes from the Arduino to LED #1, then daisy-chains LED #1's output to LED #2's input, and so on. A hundred LEDs need one Arduino pin. The catch: the timing protocol is brutal.

Why 800 kHz, one wire, no clock

An I²C bus has SDA and SCL — data and a clock line. SPI has MOSI and SCK. Both need a separate wire just for the clock. WS2812 doesn't have one. The data and clock are baked into the same pulse: the width of each HIGH pulse encodes whether the bit is 0 or 1.

  • "0" bit: ~350 ns HIGH, ~800 ns LOW. Total ~1150 ns.
  • "1" bit: ~700 ns HIGH, ~600 ns LOW. Total ~1300 ns.
  • "Reset" (latch the data into all LEDs in the chain): ≥50 µs LOW.

The receiver chip in each LED measures the HIGH pulse width with an internal RC oscillator. Above some threshold (~500 ns) it's a 1; below, it's a 0. Get within ±150 ns of the spec and you're fine. Get sloppy and the LEDs randomly latch the wrong bit.

Why the Arduino library writes assembly

The ATmega328P at 16 MHz takes 62.5 nanoseconds per clock cycle. A 350 ns HIGH pulse is exactly 5.6 cycles long. There's no instruction that "wait 5.6 cycles" cleanly — most AVR ops take 1 or 2 cycles. So Adafruit_NeoPixel.cpp's show() function is hand-written assembly, with NOPs inserted to pad each pulse to the right width. Different code paths exist for 8 MHz vs 16 MHz vs 20 MHz Arduinos because the cycle counts change.

During the show() call, interrupts are DISABLED (cli()). If a USB-RX interrupt fired mid-transmission and stole a few cycles, an LED would see a malformed pulse. So while updating a strip of 60 LEDs (1.7 ms), the Arduino is effectively deaf to everything else.

The tradeoff in plain English

WS2812 saves wires (just one) and silicon (no clock generator, no I²C controller). It pays in CPU: any code that wants to update the strip stops everything else for the duration. For 8 LEDs that's 250 µs — invisible. For 144 LEDs (a 1-meter strip) that's 4 ms — noticeable in motor-control loops. For 1000 LEDs that's 28 ms — your sketch is half-dead during refreshes.

Modern projects with thousands of LEDs use parallel-output techniques (FastLED's "parallel output", or DMA-based drivers on ESP32). But for hobby Arduino with a strip of 60? The bit-banged assembly is fine, and that's why your code's strip.show() just works.