WS2812B without libraries

Hello guys!

I'm trying to do an ARGB controller without libraries, needed a code that i could completely control to keep implementing functions and could sync perfectly with another time sensitive functions too.

The problem is that high-level code have µs lags and i would need ns precision to output data to those WS2812B. That's beyond my current skills right now :pensive:

ok, no more chitchat

This ̶c̶r̶a̶p̶ code is my current point with this, that's just the logic for me to follow and i'm working to otimize it to work faster and precisely.

I tried to implement direct port manipulation and a "delay" with timer1, but i think that's not nearly enough. Still need to implement a better way to check what bit to send, for an if will not work, i know.

//Send data in buffer (takes 225us for 4320b (3ch 60Leds))
    for(byte aRGB_aux=0; aRGB_aux==1079; aRGB_aux++){

      //Send bit 0
      if(aRGB_DataBuffer[aRGB_aux]==0){
        PORTD = PORTD | B00010000;
        TCNT1=0;
        while(TCNT1<=3){}
        //delay(.4us @ 0)
        
        PORTD = PORTD & B11101111;
        TCNT1=0;
        while(TCNT1<=10){}
        //delay(.85us @ 1)
      }

The complete test code for better understanding.

  String SerialInput;
  int Rserial;
  int Gserial;
  int Bserial;
  int LNserial;
  byte astep;

void setup() {
  Serial.begin(9600);
  aRGBinit();
  aRGBwrite(0,0,0,0);
  aRGBwrite(0,0,0,1);
  aRGBwrite(0,0,0,2);
  Serial.print("Red value: ");
}

void loop() {
  aRGBrfsh();
  
  if(((astep==0)&&(Serial.available()))){
    SerialInput=Serial.readString();
    Rserial = SerialInput.toInt();
    Serial.println(Rserial);
    Serial.print("Green value: ");
    astep++;
  }
  if((astep==1)&&(Serial.available())){
    SerialInput=Serial.readString();
    Gserial = SerialInput.toInt();
    Serial.println(Gserial);
    Serial.print("Blue value: ");
    astep++;
  }
  if(((astep==2)&&(Serial.available()))){
    SerialInput=Serial.readString();
    Bserial = SerialInput.toInt();
    Serial.println(Bserial);
    Serial.print("Led address: ");
    astep++;
  }
  if(((astep==3)&&(Serial.available()))){
    SerialInput=Serial.readString();
    LNserial = SerialInput.toInt();
    Serial.println(LNserial);
    Serial.println("");
    aRGBwrite(Rserial,Gserial,Bserial,LNserial);
    Rserial=0;
    Gserial=0;
    Bserial=0;
    LNserial=0;
    astep=0;
    Serial.println("Ready...waiting new command");
    Serial.print("Red value: ");
  }
}
bool aRGB_DataBuffer[1079]={}; //Output data buffer (1080 bits)
unsigned long aRGB_RefreshRate=0; //Refresh rate timer (30.3Hz)
unsigned long aRGB_BitCLK=0; //Bit clock timer

//Initialize aRGB driver
void aRGBinit(){
  Serial.println("aRGB driver loading...");

  //Configure Timer1
  noInterrupts();
  TCCR1A = 0b00000000;
  TCCR1B = 0b00000001;
  interrupts();
  
  //Reset data buffer and update refresh rate timer 
  for(unsigned int aRGB_aux=0 ; aRGB_aux>1079 ; aRGB_aux++){aRGB_DataBuffer[aRGB_aux]=0;}
  aRGB_RefreshRate=millis();;
  
  Serial.println("aRGB driver ready!");
}


//Write RGB value (0-255,0-255,0-255) to a specific LED address (0 - 44)
void aRGBwrite(byte R, byte G, byte B, byte LEDn){
  
  //Offset write point to especified address
  byte aRGB_BufferAddress = 23+(24*LEDn);
  
  //Convert Blue byte value to binary and store on buffer (least significant bit last)
  for(byte aRGB_aux=0 ; aRGB_aux<8 ; aRGB_aux++){
    aRGB_DataBuffer[aRGB_BufferAddress--] = bitRead(B, aRGB_aux);}
    
  //Convert Red byte value to binary and store on buffer (least significant bit last)
  for(byte aRGB_aux=0 ; aRGB_aux<8 ; aRGB_aux++){
    aRGB_DataBuffer[aRGB_BufferAddress--] = bitRead(R, aRGB_aux);}
    
  //Convert Green byte value to binary and store on buffer (least significant bit last)
  for(byte aRGB_aux=0 ; aRGB_aux<8 ; aRGB_aux++){
    aRGB_DataBuffer[aRGB_BufferAddress--] = bitRead(G, aRGB_aux);}
}


//Refresh aRGB channels each 33ms
void aRGBrfsh(){
  
  //Wait for 30.3Hz refresh rate
  if(millis()>=aRGB_RefreshRate+33){
    
    //Time critic zone \\6.7MHz resolution//
    noInterrupts();
    
    //Send data in buffer (takes 225us for 4320b (3ch 60Leds))
    for(byte aRGB_aux=0; aRGB_aux==1079; aRGB_aux++){

      //Send bit 1
      if(aRGB_DataBuffer[aRGB_aux]==1){
        PORTD = PORTD | B00010000; //Enable pin
        //delay(.8us @ 1)

        PORTD = PORTD & B11101111; //Disable pin
        //delay(.45us @ 0)
      }

      //Send bit 0
      if(aRGB_DataBuffer[aRGB_aux]==0){
        PORTD = PORTD | B00010000;
        TCNT1=0;
        while(TCNT1<=3){}
        //delay(.4us @ 0)
        
        PORTD = PORTD & B11101111;
        TCNT1=0;
        while(TCNT1<=10){}
        //delay(.85us @ 1)
      }
    
    //Update refresh rate timer
    aRGB_RefreshRate=millis();

    //End transmision
    interrupts();
}}}

It will be tough to implement the WS2812 in C. You're looking at pulse widths that are only about 2 instruction cycles long; every implementation I've seen resorts to inline assembly language. (you can look at many of the existing drivers - they're open source.)

In general, each bit is 1.25uS long (20 cycles at 16MHz.) For a zero, you send less than 1/3 of that high, and for a one you spend 2/3 of that high, leaving you the final 1/3 of the time to get the next bit ready.

See also Light_WS2812 library V2.0 – Part I: Understanding the WS2812 – Tim's Blog

In fact you do not need 100% exact timing to drive these LED's. The code used in the link is mostly C, but uses inline assembly to send each bit.

Does not the entire data stream for the strip more or less have to be delivered continuously, with small periods, leaving no time or opportunity for doing anything else?

There are smart LEDs that use data and clock lines, they might be easier to use if you need to interleave processor activity.

But this sounds like an XY problem. Would the OP care to explain the circumstnces that give rise to the need to look into this?

a7

It absolutely does, for the WS2812B. Other chips, like the SK6812, have much more relaxed timing, as do many WS2812B clones and variants that are incorrectly sold as WS2812B (since they believe people are too dumb to keep track of other part numbers, apparently). So it's easy to do testing and reach the wrong conclusions unless you have verified parts.

I know this because I did extensive testing with the PIC and oscilloscope. The PIC happens to lack certain "tricky" instruction sequences that are possible on the AVR, that make it possible to index LEDS while their data streams are written out. But I found that the SK6812, while using the same data format, has a more relaxed inter-value interval, which makes it work. With the relaxed values, it's relatively easy to bit bang on almost any processor. But not using the actual WS2812. Also the data sheet for the WS2812B is a tricky one, the numbers are presented in such a way as to fuel arguments about what it says...

Many variants are sold as WS2812B because that's what people search for, but they don't have the same IC embedded.

Right. Look for APA102 (aka Adafruit DotStar).

Another option is to push the WS2812B LED data out via DMA. There's a library that does this for PJRC Teensy. Also, I believe, for ESP devices.

But, DMA is an advanced technique probably not within OP's reach without a library, given:

@gfvalvo THX, I use smart LEDs but endeavour to proceed in all my activities knowing the least it is possible to know about, well, anything.

So I ask as suddenly I realize I have no idea at what point any given pixel starts displaying a new colour that has been delivered to it…

Would the last pixel in a chain of 300 change 9 milliseconds after the first? (Check my maths too).

I can’t say I’ve looked, or noticed, but it is the kind of thing that might make something less than perfect that looks it. Less than perfect.

TIA

a7

The protocol is described in the WS2812B Datasheet. After RESET (at least 50us of data line LOW), each pixel consumes the the first 24 bits (1.25 us/bit) it sees. Any additional bits are ignored and passed down the line. Each pixel displays the new color after some (unspecified) delay once it gets its 24 bits.

Your math is more or less correct. Pixel #299 would change color ~8970 us (1.25 * 24 * 299) after Pixel #0 changes.

Yeah, would be tough or even impossible to get something below µs with C, at least with 16MHz. But i was looking at Pololu library and that can make WS2812 work even on 8MHz CPUs, so "should" be possible to implement something as amateur at assembly.
I'm looking into existing libraries too, already reversed engineered basic thing like BMP085 and DHT libraries, but those WS2812 are way harder to read.

Actually it helped a LOT, thanks!! I was looking for WS2812B on google, maybe i will find more things about NeoPixels.

Absolutely, i'm expecting that for 6~11ms/s the CPU will be sending data and nothing else. But in the remaining time the signal would be in reset state to implement anything else, as the rest of my coding is all timing friendly, that would not be a problem. I would have at least 999.980µs to do everything else i need, this if i don't leave a static color that i wouldn't need to refresh the data

I already have 5M of these WS2812 strips without use, buying another set of strips, even being easier to use defies the purpose.

I want to further upgrade my code to be able to receive ARGB or RGB data from a PC motherboard and send data to another ARGB or RGB controller while controlling a set of WS2812B strips, being able to "easily" read a VGA signal and doing some effect with the ARGB strip...It's all about being able to control how the driver works and how it will sync with other functions. Those timing friendly libraries that i found have some limitations cause it interrupts the program even when i don't need it to, and it's going to be a little harder to implement effects

I already had those WS2812B, that's why i want to use them. I'm doing some research and testing with the oscilloscope to see if i get somewhere close.

The Danois90 reply helped a lot as it says that timing is not THAT strict. The rest is about learning and experimenting.

Example?

I've had great success with PJRC's DMA WS2812B implementation. I can do audio-responsive LED displays with real-time processing on the 16-bit / sample, 2-channel stereo audio running at 44.1 Ksamples / second.

I started a similar thread a while back. Others provided some great info.
Have a look:
WS2812B without fastLED.h - Using Arduino / LEDs and Multiplexing - Arduino Forum

The Adafruit_NeoPixel library has very good comments about how they implemented the protocol, and exactly how many instruction cycles it takes for each bit. The actual code is in-line assembly, because of the necessity to very strictly control the timing, but they comment each line of assembly showing what it does and how many instruction cycles it takes to execute. Hard to understand, but I did go though it a couple of years ago to add an option for a 20MHz clock rate.

@gfvalvo or anyone…

Here


I found this:
  • There is a maximum time the level can stay low (TxL) before a reset is triggered and any loaded data is latched and displayed on the LED.

which isn’t what I was looking for but seems to say the same thing I thought I knew.

The 24*300 data bits are clocked out without any markings of where pixels begin and end. Only the reset pulse between strip.show calls is not a 1 or 0.

Each neopixel accepts its 24 bits of RGB data, reshapes and firms up the data and sends the rest data down the line.

When the reset pulse is recognized, the latched data is transferred and at that time the pixel shows the new color.

So in my example, the last pixel changes 8 milliseconds after the start of transmission, but so do all the pixels in the strip, as it is at that time that they see the reset condition on the data line.

Modulo a slight delay in each pixel’s reshaping circuit.

So I infer they change very nearly simultaneously and that the change ripples quite fast from beginning to end in order.

Since it could work this way and would arguably be better if it did, I hope that it does.

I am not too lazy to do experiment… just not sufficient motivation yet. :expressionless:

a7

Your analysis of turn-on timing down the string would be valid assuming the assertion that the reset period triggers color change rather than an individual pixel consuming its 24 bits. Please post the results if you verify it experimentally. The datasheet is silent on that subject. Not surprising as it supplies minimal details and what it has is written in Chinglish.

That’s an interesting find regarding the timing being more relaxed than commonly assumed. Regardless, I’ll probably stick to driving these strings via DMA from a PJRC Teensy. Especially since it can hook into FastLED and take advantage of all the color manipulation that library supplies. And, as the name implies, it’s very fast and efficient.

It is possible to drive the WS2812B with just PWM from timer1 of Arduino running at 16 MHz. As long as your OVF ISR to load the next byte into the RGB array doesn't exceed 50usec, you should be fine.

The chunk of code that controls timing is ASM, the rest is C++ because it gets compiled with C++ compiler