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
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.
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.
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.
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!