8 years, 10 months ago.

WS2811 test

I bought one of these WS2811 LEDS-with-chip. They require 24 bits, 8 bits per color. By setting bits, you can create all colors.

A logic 0 is represented by: a high of 0.35uS, followed by a low of 0.80uS. A logic 1 is represented by: a high of 0.70uS, followed by a low of 0.60uS.

I just created a simple chunk of code with 24 of these "high and low" pulses... No shining LED. I then checked with my logic analyser: although it is specified in the code that the duration of a high was for example the specified 0.35 micro seconds, what I read doesn't even come close!

I played a bit with values for the wait statement, also tried wait_us instead, but whan it regards milli seconds, the LPC1768 seems pretty precise, but for micro seconds it doesn't even come close.

What I did in my code, for just testing, is:

pin = 1; wait_us(0.35); pin = 0; wait_us(0.80);

I know eacht statement takes time as well, so maybe a pin is high or low much longer and the WS2811 datasheet mentions that timing is very, very critical.

Maybe I'm just adressing this one the wrong way, as I can't imagine that this would not be possible. A micro second is short, but for a microprocessor that thinks in nano seconds, it is long, I'd say...

2 Answers

8 years, 10 months ago.

Short version : The timings are far too tight for you to bit bash it using waits and get reliable operation.

There are two ways you can get good enough timings: Use assembly language and hard code short delay loops to create the timings you need (painful and very micro specific) or you use an SPI output. By sending the correct values over SPI you can use the SPI data to create the correct timings. You set the SPI to use 12 bit transmissions and set the bit rate such that when you send 3 bits per LED bit, 001 or 011 depending if you want to send a 1 or a 0, the timings match the requirements of the LEDs. It takes 6 SPI transfers of 12 bits to set an LED.

I would recommend using the BurstSPI library, it eliminates some of the delays in the standard mbed one. With a single LED you'll probably be OK but with a chain of them any long pause will be considered an end of transmission. This means you want to eliminate the delays as much as possible and calculate all your colours before you start the transmission.

This has already been done so you can either look at the other libraries for an example of how or just use them as is.

library 1, library 2

There is also a library somewhere that uses the assembly code method but I think that was for the wrong CPU for you.

The other thing to look out for is the voltage levels. The WS8211 needs 0.8*Vcc to count as a high. It's supposed to run off 5 V which means it needs 4V to count an input as high. An mbed runs at 3.3V and it's high output is normally around 3.1V.

Most of the LEDs don't actually need 4V on the data in but exactly how much lower the voltage can be varies between LEDs, dropping the LED power supply voltage to around 4V will normally make everything work fine (theoretical threshold = 3.2V) if you have that level of control. Running the LED supply voltage much lower causes issues, the blue doesn't light up very well.

Alternatively you need to add an external buffer or driver of some sort to boost the voltage up to something closer to 5V.

As I said, you may not need to worry about this but if the signals all look ok and it's still not working that that can be the cause.

Accepted Answer
8 years, 10 months ago.

Hi,
The "wait_us" function is declared in Handbook as follows :

/** Waits a number of microseconds.
 *
 *  @param us the whole number of microseconds to wait
 */
void wait_us(int us);


Please notice that the argument is of int type. If you pass it a float then it's automatically converted to int. Since in your case the floating point values are smaller than 1 they will become 0.

There is some discussion about nanosecond timers in the forum. Maybe it can help you.

You're right. I first tried with the normal wait, which seems to be defined as:

/ Waits for a number of seconds, with microsecond resolution (within

  • the accuracy of single precision floating point).
  • @param s number of seconds to wait
  • / void wait(float s);

But when I used "wait (0.00000035), which is 0.35uS, I didn't even come close to that waiting time either, just using a very simple loop like in the example I gave. Any other suggestions why it is not precise (well, sctually, almost a factor 1,000 wrong ;-) )?

posted by George DeB 31 Jan 2016

Frankly, I did the same mistake in the past (keeping float parameter type while moving from wait to wait_us). And it took me some time to figure it out. That's why I responded so quickly :) I was also disappointed with the us resolution as you are. But since I'm not an mbed expert (I'm just trying to share my experience with others) I'm afraid there is nothing more I can do about it. Although I don't think it's a simple issue maybe someone else already solved it and will give you some hints. Good luck.

posted by Zoltan Hudak 31 Jan 2016

For future information:

The core mbed system sets up an interrupt at a fixed rate of 1MHz (1us) and then uses that for all timers, tickers, timeouts, waits etc... This makes it easy to make time based things work across multiple different platforms, once that interrupt is set up the code is the same for everything. The down side is that it limits everything to 1us resolution.

When using wait(float time) the system has to convert the time into cycles, that involves some floating point multiplications. These parts don't have hardware floating point units and so have to do it in software, that is a hit of a few hundred CPU cycles per calculation. In other words it takes more than 1us to calculate how long wait(0.0000001) is telling it to wait. At that point all hope of any accuracy is long gone. If you need a wait that is accurate to within one or two us always use wait_us(), the generic wait is intended for waits of a reasonable portion of a second and will almost always be a few us off.

Generally on something like an mbed you're going to have trouble getting much finer accuracy than 1us while using c, you could run the interrupt faster but on slower parts the overhead would become unacceptable. Using code to generate the delays rather than a timer you need to start worrying about the number of cpu cycles etc... which is hard to predict without knowing exactly what the compiler is going to do and isn't going to be cross platform.

You can manually set up CPU timers and get accuracies down to 1 or two clock cycles but that's not going to be cross platform either.

Hence the method above of using the SPI to generate the signals, getting the hardware to worry about the fine details of the timing without any CPU intervention is by far the best way to do things.

posted by Andy A 01 Feb 2016

I appreciate your helpful information. It's always good to know what's under the hood. Now I see that even 1us wasn't so easy to achieve for such a wide range of hardware and manufacturers. Thank you again for your effort.

posted by Zoltan Hudak 01 Feb 2016