Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Addressable RGB LED Strips

Status
Not open for further replies.

Hippogriff

Member
I've only ever made use of 12v RGB LED strips where you control all the LEDs at the same time.

When I last looked at individually addressable RGB LED strips, probably around 2 years ago, where you can control each LED separately, they were very expensive and the means of controlling them - from a micro-controller like a PIC - seemed somewhat nebulous... Chinese datasheets only and all that.

Has anyone had any more recent experience with these items?

Initial scans seem to indicate there's a whole bunch of different types out there now... 12V TM1809, 12V WS2801, 5V WS2812, 5V HL1606, 12V LPD6803... has anyone worked with any of these and does anyone have any first-hand experience of how they are to work with?
 
Ive read that the WS2812 can be used via SPI, no need to stick to the strict timing. It seems that chips like the WS2801 are easier to control, they do infact use SPI but you can only get them in SOIC (ie no integrated LED's). However, Theres not a lot of info on the WS2812's if you wanted to use another Micro-People have made them into arduino libaries :/
 
I've been looking at this stuff for hours today and I'm still not entirely sure what kind of strip to jump in with. I've now read about the LPD8806, which is also controlled via SPI. There does not appear to be a datasheet for this. There is LPD8803, LPD8806 and LPD8809 - I'm not even sure what the difference there is.

I want to stay with the PIC, not change to Arduino, but am happy to dig into the depths of things, if there's help available. Trouble with these things appears to be that things haven't got that much clearer since last time I looked (or maybe I'm looking in the wrong place).
 
OK, I just bought 1 metre of non-waterproof WS2812B just as a test. If it works then I can try and go for a longer run that's hopefully IP65 rated... or maybe, if that's not available or if I can't get it to work, something completely different.
 
I bought some of the IC's a while back (WS2801). I just havent gotten around to putting them in a circuit. I might do what your currently doing, just buy some LED's from sparkfun and get those working with code..Then atleast I know my boards work.

I agree about the Arduino thing, its frustrating at times when most of the documentation you come across is written in terms of arduino. Even if you look at examples of code, they make heavy use of libaries to make things simple, which you then have to go through and mentally convert (or atleast get a general idea).

The timing requirements (imho) for the WS2812 are quite ridiculous.

It also seems that Neopixels use these controllers (WS2812), which are documented alot on the web

http://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/

http://learn.adafruit.com/adafruit-neopixel-uberguide/advanced-coding


ADD: I found a libarary for MikroC for WS2812 as well
http://www.libstock.com/projects/view/967/ws-2811-ws2812-led-strip-driver

http://rurandom.org/justintime/index.php?title=Driving_the_WS2811_at_800_kHz_with_an_8_MHz_AVR

SCORE! I found some PIC code! Its in C, which isnt hard to read. Authors Code below.
**broken link removed**


C:
{
  // The data pin must be low for at least 50uS to reset the LED drivers
  PIN_WS2812_LATCH = 0;
  __delay_us(50);

#asm
  MOVLB  HIGH(_tmpData)  // select memory bank 12, so we can access the relevant variables

  MOVLW  LED_ARRAY_SIZE  // ledCount = total number of leds
  MOVWF  BANKMASK(_ledCount),b

  LFSR  0, _ledData  // initialise the ledData pointer to the start of the array (FSR0 = address of _ledData)

byteLoop:  MOVF  POSTINC0,W,c  // get the next byte of ledData and increment the FSR0 pointer
  MOVWF  BANKMASK(_tmpData),b  // save it in tmpData

  MOVLW  0x08;  // bitCount = 8;
  MOVWF  BANKMASK(_bitCount),b

bitLoop:  BSF  LATB, 0,c  // set the output high

  BTFSS  BANKMASK(_tmpData), 7,b // test the m.s. bit of tmpData.
  BRA  writeZero

  // logic ONE  (long HIGH output, followed by short LOW)
writeOne:  RLNCF  BANKMASK(_tmpData),b  // shift tmpData left one place
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  BCF  LATB,0,c  // set the output pin low
  DECFSZ  BANKMASK(_bitCount),b  // decrement the bit counter
  BRA  bitLoop  // if not zero, process the next bit.
  BRA  endByte  // zero - goto the next byte

  // logic ZERO (short HIGH output, followed by long LOW)
writeZero:  BCF  LATB,0,c  // set output low
  RLNCF  BANKMASK(_tmpData),b  // shift tmpData left one place
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  DECFSZ  BANKMASK(_bitCount),b  // decrement the bit counter
  BRA  bitLoop  // if not zero process the next bit

  // Move on to the next byte if any
endByte:  DECFSZ  BANKMASK(_ledCount),b  // decrement the byte counter
  BRA  byteLoop  // if non-zero, process the next byte

  // finished
#endasm

}

This looks like the bulk of the code thats doing the majority of the work. MPlab X marks all the asm wrong, heh. It looks to me that the author is using a series of NOP's for timing.
 
I use JAL with my PIC projects... it's usually... interesting. I bought a couple of the LED rings as well... figured I'd have more things to break. What I don't like about these things are their 'requirements' for additional components like a capacitor and resistor... the 12V RGB LED strips I've used to now seem simpler and more 'robust' overall. Time will tell.
 
I realised the timing is quite intense for this LED so, in preparation, I just wanted to check how quickly I could send a pin high and low... I'm using a PIC16F1823 running at 32MHz. The main loop simply sets pin C0 high, then sets it low. That's it. Nothing more at all.

I connected up logic probes to it from my Saleae and sampled at 16MHz... I was surprised to observe that the low width was said to be 0.5000μs and the high width was said to be 0.1250μs.

Am I nuts to think they would be the same? A high instruction followed by a low instruction, then looping?

I changed the two instructions DIn = high followed by DIn = low to a single instruction... DIn = !DIn and resampled... this time the Saleae has recorded the high width as 1.5000μS and the low width as 1.7500μS.

From:

Code:
forever loop
  DIn = high  -- high for 0.1250μS
  DIn = low   -- low for 0.5000μS
end loop

To:

Code:
forever loop
  DIn = !DIn  -- high for 1.5000μS, low for 1.7500μS
end loop

This is in JAL.

Would anyone else be as surprised by this as I am? Am I doing something wrong? Am I missing something very, very obvious indeed?

Finally, if I have the main loop consisting of 10 x negations of the C0 pin, I get a slightly different output...

With:

Code:
forever loop
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
  DIn = !DIn
end loop

I get the pin low for 1.5000μS and high for 1.2500μS.

I'm a tad confused by this to say the least.
 
Hmmm... looking at the intermediate files, the JAL line "DIn = !DIn" seems to get ASMd to about 13 lines of code (with some labels)... is it unreasonable of me to think that a negation of a pin would be a single line of ASM too? Obviously I'm thinking along the lines of a NOT in 8086 ASM here.
 
I want to give the code a shot (using Swordfish/PIC18F2410) since my curiosity is piqued. Possibly a 12F1840 and C as well.

Seems like your JAL complier may not be optimizing correctly.
 
Yes... if I have, instead, the following in my main loop:

Code:
forever loop
  DIn = on
  DIn = off
  DIn = on
  DIn = off
  DIn = on
  DIn = off
  DIn = on
  DIn = off
  DIn = on
  DIn = off
  DIn = on
  DIn = off
  DIn = on
  DIn = off
  DIn = on
  DIn = off
end loop

Then I get a high width of 0.1250μS and a low width of 0.125μS, which is nice... until the loop comes around when it goes out to 0.5000μS, but I'm not concerned about that as I'll not be doing this in the main forever loop.
 
And I can confirm that the ASM auto-generated by JAL now is a single BCF or BSF line for each of my DIn = on or off lines. That's better.
 
So, poring through the PIC16F1823 datasheet I read that each instruction cycle will take 125ns... which fits with what I've been seeing, where the minimum width of an pin being on or off is 0.1250μs in Saleae. I've read the links provided, but the datasheet for the WS2812B says...
  • a 1 is represented by 0.8μs high, followed by 0.45μs low
  • a 0 is represented by 0.4μs high, followed by 0.85μs low
There's a tiny bit of leeway for each... 150ns!

So... a 1 needs to be done first with 0.1250 + 0.1250 + 0.1250 + 0.1250 + 0.1250 + 0.1250... giving 6 clock cycles for 0.75μs 0r -50ns, then 0.1250 + 0.1250 + 0.1250 + 0.1250 + 0.1250... giving 5 clock cycles for 0.500μs or +55ns.

And... a 0 needs to be done first with 0.1250 + 0.1250 + 0.1250... giving 3 clock cycles for 0.375μs 0r -25ns, then 0.1250 + 0.1250 + 0.1250 + 0.1250 + 0.1250 + 0.1250 + 0.1250... giving 7 clock cycles for 0.875μs or +25ns.

I think!

That's all if the WS2812B datasheet can be trusted (the numbers are different to what's reported elsewhere)... otherwise, it's back to the links and what other people discovered with this LED.
 
That seems to be correct from what Ive read. Here are some numbers. For the hell of it I ran my PIC32MX250F128B at 48Mhz, The fastest it can toggle the port is 4Mhz, 250nS Period (oddly 50/50 duty cycle), but I hope we wont need 32 bit procs for LEDs! PIC18F2410 @32Mhz Positive Pulse width is 500nS, and negative pulse is 260nS on Swordfish SE.

I really need to get my hands on some crystal's. I checked the Clock out on my 18F, and it barely looks like a clock signal at all! Not like I use the I/O's anyway.
 
I seem to have it working... well, the first LED of one of the the rings I bought, but that's exactly what I was aiming for. I've lit it up to 128 on R, G and B. The JAL is really basic and not pretty to look at the moment... just a bunch of high and lows of certain times, then repeated 24 times. But, the key thing is that it lights up the LED to half brightness white... which is what I was aiming for.

Code:
forever loop
  DIn = off
  _usec_delay (55)

  -- Green 1000 0000
  DIn = on  -- 0.1250                       1
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- *0.7500* - 0.8000 indicates T1H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- *0.5000* - 0.4500 indicates T1L; should be a 1

  DIn = on  -- 0.1250                       2
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       3
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       4
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       5
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       6
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       7
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       8
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  -- Red 1000 0000
  DIn = on  -- 0.1250                       1
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- *0.7500* - 0.8000 indicates T1H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- *0.5000* - 0.4500 indicates T1L; should be a 1

  DIn = on  -- 0.1250                       2
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       3
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       4
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       5
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       6
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       7
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       8
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  -- Blue 1000 0000
  DIn = on  -- 0.1250                       1
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- *0.7500* - 0.8000 indicates T1H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- *0.5000* - 0.4500 indicates T1L; should be a 1

  DIn = on  -- 0.1250                       2
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       3
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       4
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       5
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       6
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       7
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0

  DIn = on  -- 0.1250                       8
  asm nop   -- 0.2500
  asm nop   -- *0.3750* - 0.4000 indicates T0H
  DIn = off -- 0.1250
  asm nop   -- 0.2500
  asm nop   -- 0.3750
  asm nop   -- 0.5000
  asm nop   -- 0.6250
  asm nop   -- 0.7500
  asm nop   -- *0.8750* - 0.8500 indicates T0L, should be a 0
end loop

This - for me - confirms the timing requirements in the WS2812B datasheet.
 
Your basically coming to the same conclusion as the code I posted before (The C Code), however I think there might be a easier way. Before I left for work I got SPI running on my 18F2410 (32 Mhz Clock). The SPI Clock was 8Mhz but I sent out "00001001" and The High's were reported to be around 550nS. As I have said, I have read they can be used on SPI, and this might confirm my findings. If this is true, it would be Wayyy easier to do. When I get home I will try sending out "01010101" and see what the timing is. I might slow down the SPI clock as well. Your compiler should have a library to get hardware SPI up and running.

This is getting exciting! Im going to have to buy some of these LED's now. I'll be working on getting my WS2801 working though.
 
I could not get the timings correct inside a loop to allow me to transfer a byte out to DIn. This code appeared to be the most efficient that I could come up with...

Code:
procedure WS2812B_Byte ( byte in pByte
                       ) is
  var bit MSB at pByte : 7
 
  -- 1 = 0.750us high, 0.500us low
  -- 0 = 0.375us high, 0.875us low
  for 8 loop
    if (MSB) then
      DIn = on
      asm nop
      asm nop
      asm nop
      asm nop
      asm nop     -- 0.7500us (T1H)
      DIn = off   -- no nop here, can only achieve 2.000us, but need 0.500us
    else
      DIn = on
      asm nop
      asm nop     -- 0.375us
      DIn = off   -- no nop here, can only achieve 1.625us, but need 0.875us
    end if
    pByte = pByte << 1
  end loop
end procedure

...but I could not get the low period to be short enough... exiting the if, shifting the byte and re-entering the loop meant that I could get nowhere near the 0.500μs required for the T1L and even the 0.875μs required for T0L.

Someone on the JAL scene has helped me come up with another way... which involves a procedure that sends 24 bits to DIn without needing to shift - it's effectively a load of bit assignments from the 3 bytes passed in to the procedure... it works a treat. I was educated that the shifting of bits like I was trying to do is actually rather expensive.

I think that there may be a JAL library for this at some point... obviously I'll have no input to it, myself, otherwise it wouldn't work! :(
 
Can you move the shift into both if statement paths and replace some of the nops? Actually, you may as well do the whole send byte in asm.

Mike.
 
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top