Achieving Nanosecond Delays for WS2812B (bitbang) LEDs in Arduino

Hey,

I'm trying to bitbang a WS2812B LED in blue using Arduino C++. The WS2812B protocol requires precise delays in nanoseconds, but the delayMicroseconds function can't achieve this—my oscilloscope shows a minimum delay of about 1 µs, which is too long for the first transition.

I found asm volatile("nop");, but it doesn't help me reach the 450 ns required. Is there a way to achieve this in Arduino C++, or do I need to use assembly code?

I tested a sketch to measure four NOP instructions with my oscilloscope, but the results are inconsistent. When I add another NOP or remove one, the timing changes randomly.

Any suggestions on how to achieve more reliable timing?

I tried not to use digitalWrite to reduce overhead, but with no luke.

#include <Arduino.h>

#define NEO_PIN 4 

void setup()
{
  pinMode(NEO_PIN, OUTPUT); 
}

void loop()
{

  GPIO.out_w1ts.val = (1 << NEO_PIN);
  asm volatile("nop; nop; nop; nop;"); 
  GPIO.out_w1tc.val = (1 << NEO_PIN); 

  delay(1000); 
}

You will want to use assembly. Anas Kuzechie shows the timing for WS2812 and how to add an Assembly file to a sketch.

1 Like

What Arduino are you using? A Nano? An Uno? Other? It may matter.

The information in this thread should help:

It's easily done. In my case, I've a .cpp file containing an interrupt routine that does a number of things; one of them is writing to a string of WS2812D pixels. The timing is accurate to the WS2812D spec that I have, and rock-stable, as observed with an oscilloscope.
As Bill notes in his contributions to that thread, there are things that can happen when the compiler optimizes your code; he points out how to avoid that.

DigitalWrite is a non-starter. Too slow by a longshot.
DigitalWriteFast is faster, but requires a constant for the pin number to achieve the best speed; that must be specified at compile time, so you can't make the pin # a variable that you pass in to the routine, for example.

Since I was locked in to a particular Arduino, and wasn't concerned about making the pin number adjustable, I decided to just use direct port access, such as
` PORTB |= 0b00100000; //start high portion of bit

Delays were done using

 _delayNanoseconds(187) ;

The reset time (>60 us low) was done using

    _delayNanoseconds(60000) ;

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.