WS2812 driver for 8mhz micro's

Hi All,

I started looking at how to drive ws2812's manually rather than just downloading the neopixel library
I have used the neopixel lib and it works really well

But, I am writing for ATTiny85/13 at the moment and need something that (1) works on ~8mhz (2) very small code to drive ONE tricolour LED

so i read this...

it works well at 16mhz (800khz driving cycle), so I started modifying it
I changed the asm section to speed things up a fair bit, its not quite 800khz but is enough to be within the acceptable limits of the ws2812

  asm volatile(
    // Instruction           Description                
  "startbit:\n\t"         // label
    "ldi  %5, 8\n\t"      // reset bitcount             
  "nextbit:\n\t"          // label                       
    "sbi  %0, %1\n\t"     // SET OUTPUT HIGH
    "sbrc %4, 7\n\t"      // Skip if HiBit in value is clear  
    "rjmp bitset\n\t"     // jump if HiBit is set 
    "cbi %0, %1\n\t"      // clear output bit
    "dec  %5\n\t"         // decrement nBits
    "rol  %4\n\t"         // shift value left to get to next bit
    "brne nextbit\n\t"    // branch if bits not finished
    "rjmp nextbyte\n\t"   // jump to next byte
  "bitset:\n\t"           // label
    "rol  %4\n\t"         // shift value left to get to next bit
    "dec  %5\n\t"         // decrement nBits
    "cbi %0, %1\n\t"      // clear output bit
    "brne nextbit\n\t"    // branch back if bits not finished
  "nextbyte:\n\t"         // label                       -
    "ld   %4, %a8+\n\t"   // val = *p++    a8/a5       
    "cbi   %0, %1\n\t"    // clear output bit
    "dec %9\n\t"          // decrease bytecount 
    "brne startbit\n\t"   // if bytes not finished start again
    ::
    // Input operands         Operand Id (w/ constraint)
    "I" (_SFR_IO_ADDR(PORT)), // %0
    "I" (PORT_PIN),           // %1
    "e" (&PORT),              // %a2
    "r" (high),               // %3
    "r" (val),                // %4
    "r" (nbits),              // %5
    "r" (tmp),                // %6
    "r" (low),                // %7
    "e" (p),                  // %a8
    "w" (nbytes)              // %9
  );

some of the input operands are redundant now (like nBits, high, low, tmp) but I dont yet know how to remove them from the list yet - this is my first proper attempt at AVR ASM !!!

Hope this interests some on here

Regards Bob McNobby

Also, i know it looks repetitive on the branches, but if you look closer you will see that position on the CLI is different in each one..

In the code to drive a '0' it is earlier in the code, and to drive a '1' it appears later in the code

This is as close to the timing spec as I could get

Nevertheless - IT WORKS !!!

No need for assembly language...

I wrote this the day I got mine:

typedef unsigned char byte;

// Port B, pin 0
#define LED_BIT _BV(0)
#define LED_DDR DDRB
#define LED_PORT PORTB
#define LED_PIN PINB
#define NOP __asm__("nop\n\t")


class LED {
  byte r,g,b;
  static void sendByte(byte b) {
    byte mask = 0x80;
    while (mask!=0) {
      if ((b&mask)!=0) {
        LED_PIN = LED_BIT;  // Hi (start)
        NOP;                // Hi
        NOP;                // Hi (250ns)
        NOP;                // Hi
        NOP;                // Hi (500ns)
        NOP;                // Hi (data bit here!)
        NOP;                // Hi (750ns)
        LED_PIN = LED_BIT;  // Lo (875ns)
      }
      else {
        LED_PIN = LED_BIT;  // Hi (start)
        NOP;                // Hi
        LED_PIN = LED_BIT;  // Lo (250ns)
        NOP;                // Lo
        NOP;                // Lo (500ns)
        NOP;                // Lo (data bit here!)
        NOP;                // Lo (750ns)
        NOP;                // Lo (875ns)
      }
      mask >>= 1;           // Lo (1000ns)
    }
  }
public:
  LED& setColor(byte r_, byte g_, byte b_) {
    r = r_;
    g = g_;
    b = b_;
  }
  void send() const {
    cli();
    sendByte(g);
    sendByte(b);
    sendByte(r);
    sei();
  }
};

what speed are you running your micro ?
I think I saw this before and tried it on a ATTiny85 and it didnt work for some reason

8Mhz

Fungus, I tried your code, I couldnt get it to work at all, so I have stuck with mine, thanks

Since I was having problems with using ASM with arrays in a volatile manner (not knowing enough about all this), plus as I didnt want to use memset/malloc to remove dynamic memory addressing, I ended up with this sub which works perfectly...

void WS2812(void) {
    while((micros() - latch50us) < 50L);  // 50us data latch
    cli(); 
    volatile uint8_t green = dmxData[1]; 
    volatile uint8_t red = dmxData[2]; 
    volatile uint8_t blue = dmxData[3]; 
    asm volatile(
      "rcall dobits\n\t"    // do green
      "mov %5, %6\n\t"
      "rcall dobits\n\t"    // do red
      "mov %5, %7\n\t"
      "rcall dobits\n\t"    // do bloo
      "rjmp out\n\t"
    "dobits:\n\t"           // label
      "ldi  %4, 8\n\t"      // reset number of bits
    "nextbit:\n\t"          // label                       
      "sbi  %0, %1\n\t"     // SET OUTPUT HIGH
      "sbrc %5, 7\n\t"      // Skip if HiBit in value is clear  
      "rjmp bitset\n\t"     // jump if HiBit is set 
      "cbi %0, %1\n\t"      // SET OUTPUT LOW
      "rol  %5\n\t"         // shift value left to get to next bit
      "dec  %4\n\t"         // decrement nBits
      "brne nextbit\n\t"    // branch if bits not finished
      "ret\n\t"
    "bitset:\n\t"           // label
      "rol  %5\n\t"         // shift value left to get to next bit
      "cbi %0, %1\n\t"      // SET OUTPUT LOW
      "dec  %4\n\t"         // decrement nBits
      "brne nextbit\n\t"    // branch back if bits not finished
    "ret\n\t"               // return from sub
    "out:\n\t"              // exit
      ::
      // Input operands         Operand Id (w/ constraint)
      "I" (_SFR_IO_ADDR(PORT)), // %0
      "I" (PORT_PIN),           // %1
      "e" (&PORT),              // %a2
      "w" (NULL),              // %3
      "r" (8),                // %4
      "r" (green),              // %5
      "r" (red),                // %6
      "r" (blue)                // %7
    );
  sei();                       
  latch50us = micros();                 
}

mcnobby:
Fungus, I tried your code, I couldnt get it to work at all, so I have stuck with mine, thanks

No worries, use what works.

PS: It was just an example. You might have to do a pinMode() and set output low before you start...