WS2812b Success on the ESP-12

This one has been long in the making.  I’ve had a fair bit of success with the little ESP-12 and it is now my favourite despite the 2mm centres.

WS2812BWhy, because it HAS everything – lots of port bits, A/D convertor etc. why on EARTH we all went off and used the little ESP-01 I can no longer understand especially as the ESP-12 is the same price!

Ok, so getting ports on and off was no big deal and even reading them was simple – but getting the timings right for the WE2812b LEDs has been a pain.

I eventually found a blog (all of this is referred to in the comments section of a blog a couple of days ago in here) which detailed how to use the WS2812b. Then I found a guy saying it was no good – and offering his slight modification – and I tested it and.. lo and behold – on GPIO0 it works.           

But could I HELL get it to work on other ports – as I was stumbling around in the dark as to how to use the PORTS – the problem is timings – the WS2812b need VERY short pulses and they are not very forgiving if you don’t get the timings right. There are LOADS of ways to turn ports on and off on the ESP8266 but they all take differing times and if the port operation is part of your (very tight) timing – then any change will stop this from working.

Here is the original code that worked – on GPIO2 -  this is just the bit for individual pulses and is commented out

//    uint8_t time;
//    time = 4; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1 );
//    time = 9; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0 );

//    uint8_t time;
//    time = 8; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1 );
//    time = 6; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0 );

So you see, that statement WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1 ); is an integral part of the loop timing. Can’t go messing with it… but here’s the thing – this UTTERLY MEANINGLESS gook is actually incredibly simple…

Taking the first one it is this..

(*((volatile uint32_t *)(0x60000300 + (0+(0))))) = (uint32_t)(1)

and narrowing that down it is simply..


Ok, so this made NO sense to me AT ALL…. until I hit THIS link thanks to one of you kind people.


It turns out that there are two magical locations on the chip – which I’m now using to turn things on and off. They are 0x6000304 to turn things on and 0x6000308 to turn things off – go ahead and define pretty names for them. This is weird. If you fire a 32 bit number at these locations – the first one will turn on any port who’ bit is HIGH while ignoring the rest. The second address does the same to turn them back off.

So if you consider binary 1 to be GPIO0, 2 to be GPIO1 etc etc this makes sense.

These two lines will turn the port 12 (GPIO12) on then off VERY, VERY quickly.


EH? 0x1000 in binary is 1000000000000

Port 0 would be 000000000001 or just 0.

Weird, yes, FAST, certainly. Now you might say well that’s all good for one port but what happens if I want to use another port – well as long as you don’t want to handle them at the same time, it turns out you can use a STATIC variable and it appears to work just as well!!!

So – enough bull – here’s some code. I do not claim ownership – little bits are mine, little bits are from other people etc… well done to all I would say.


Somewhere up at the top – some global vars

uint16_t reda;
uint16_t greena;
uint16_t bluea;

uint16_t red;
uint16_t green;
uint16_t blue;

uint16_t rgbnum;
uint16_t rgbdelay;


static uint32_t theport;

Now the bits that do the work…

    uint8_t time;
    time = 4; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + 4, theport );
    time = 9; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + 8, theport );

    uint8_t time;
    time = 8; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + 4, theport );
    time = 6; while(time--) WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + 8, theport );

void ICACHE_FLASH_ATTR WS2812OutBuffer( uint8_t * buffer, uint16_t length, uint16_t repetition )
    uint16_t i;

while (repetition--)
    for( i = 0; i < length; i++ )
        uint8_t mask = 0x80;
        uint8_t byte = buffer[i];
        while (mask)
            ( byte & mask ) ? SEND_WS_1() : SEND_WS_0();
            mask >>= 1;

Now why do I have REDA and RED -  well I don’t want these lights instantly changing colour – that’s boring – I want it to happen in the background – so – make yourself a 50ms timer callback and dump this inside.

/// rgb to fade from any colour to any other colour for any number of LEDS for any given period (rgbdelay)

if ((red!=reda)||(green!=greena)||(blue!=bluea))
    uint8_t myBuffer[3];
    if (reda<red) reda+=((red-reda)/(rgbdelay*20))+1;
    if (greena<green)  greena+=((green-greena)/(rgbdelay*20))+1;
    if (bluea<blue)  bluea+=((blue-bluea)/(rgbdelay*20))+1;
    if (reda>red) reda-=((reda-red)/(rgbdelay*20))+1;
    if (greena>green)  greena-=((greena-green)/(rgbdelay*20))+1;
    if (bluea>blue)  bluea-=((bluea-blue)/(rgbdelay*20))+1;

    if (rgbnum==0) rgbnum=1;

I’ve only allocated 3 bytes for the RDB buffer as I’m not interested in individual colours – just changing them at once – and I repeat the whole thing by rgbnum – the number of LEDS and rgbdelay the time you want this to take to change from one colour to the next. It will do a nice job and smoothly without any horror colour changes – basically for each colour if the desired colour does not match the actual colour, adjust it by 10%.

oh and you’ll need this table… I found that on the web too. It’s simple enough – note that increasing values have little effect at first, getting more and more relevant as you get higher up. That’s because the difference in brilliance between 10 and 20 is large, the difference between 240 and 250 is almost zilch – to our eyes anyway.

So this is non-lossy –as the colours fade down, colour accuracy goes down but as you fade them back up it returns.

uint8_t ledTable[256] = {
  0,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
  1,   2,   2,   2,   2,   2,   2,   2,   2,   3,   3,   3,   3,   3,   4,   4,
  4,   4,   4,   5,   5,   5,   5,   6,   6,   6,   6,   7,   7,   7,   7,   8,
  8,   8,   9,   9,   9,  10,  10,  10,  11,  11,  12,  12,  12,  13,  13,  14,
14,  15,  15,  15,  16,  16,  17,  17,  18,  18,  19,  19,  20,  20,  21,  22,
22,  23,  23,  24,  25,  25,  26,  26,  27,  28,  28,  29,  30,  30,  31,  32,
33,  33,  34,  35,  36,  36,  37,  38,  39,  40,  40,  41,  42,  43,  44,  45,
46,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,
61,  62,  63,  64,  65,  67,  68,  69,  70,  71,  72,  73,  75,  76,  77,  78,
80,  81,  82,  83,  85,  86,  87,  89,  90,  91,  93,  94,  95,  97,  98,  99,
101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 119, 121, 122, 124,
125, 127, 129, 130, 132, 134, 135, 137, 139, 141, 142, 144, 146, 148, 150, 151,
153, 155, 157, 159, 161, 163, 165, 166, 168, 170, 172, 174, 176, 178, 180, 182,
184, 186, 189, 191, 193, 195, 197, 199, 201, 204, 206, 208, 210, 212, 215, 217,
219, 221, 224, 226, 228, 231, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255 };

Oh, you also need to ensure that the output you use is actually an output!!!! So why are you waiting – moodlighting – SAD LIGHTS ---  I’ve not tested more than 30 LEDS so do some tests before going wild.

Another thing to note – avoid the callback fading starting while the WIFI is setting up… I found using this immediately at startup caused issues – once you have connections it’s fine.


15 thoughts on “WS2812b Success on the ESP-12

  1. Great work Pete!
    Do we also need to make changes to the compiler flags for optimisations for this to work? If so can you give some details there?

  2. When I first played with this a month ago the compiler flags got in the way, no optimisation it was too slow, optimisation on it was irregular. I can only say that I have made NO changes to the basic setup - and it is working. I'm not yet an expert on the compiler optimisation so you'll have to play it by ear.

  3. sounds good. i'll have a play around as soon as I can

    Thanks again for all the effort of getting this info together in one place!

  4. I'm really struggling with this... i cant get consistent timings no matter what i do. any chance you could zip up your entire project folder in Eclipse and post it?

  5. Same here with the timings ..... but who knows, a bit more digging around might find the reasons why some have luck, others don't. Have worn out about two shovels so far! Got a couple more though:)

  6. i did have some luck with this code!

    #define WSGPIO 2

    uint8_t time;

    time = 4; while(time--) GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << WSGPIO);
    time = 9; while(time--) GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << WSGPIO);

    uint8_t time;

    time = 8; while(time--) GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << WSGPIO);
    time = 6; while(time--) GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << WSGPIO);


    pretty much does the same thing, just uses a different way of writing to the register. this works well but some colours are a little off. I havent looked at that too much yet though. should just be a matter of tweaking some timings.
    I also have an issue with the first LED being white regardless of what I send

  7. I think Peter has cracked it and will let us know the solution when he gets some time. ( always on holiday by the sound of it! 🙂 NOT.
    In the meantime the solution I have is that of getting rid of the first 32 bit stream, and adding an led count to the number sent out.
    In the same way as controlling the output level, the port setting ( Input / Output) can be set.
    For the first 32 bit out I make the Pin used an input, so the same routines are followed, but whatever it is causing the issue gets sorted, then from that point out it's set as an output.

    In the while repetition loop I do an 'if else' on the repetition value so that the first output block gets the call WRITE_PERI_REG (PERIPHS_GPIO_BASEADDR + 20, 0x4000); [that's the PIN_DIR_INPUT reg and in my case pin14 0x4000). This sets it as input. (Pulldown required).
    Then all other blocks get ....WRITE_PERI_REG (PERIPHS_GPIO_BASEADDR + 16, 0x4000); that's the PIN_DIR_OUTPUT reg.
    Again only "1" bits written have any effect. Might help in order to play with the strips, whilst waiting for the real solution.
    I do have an issue with 14 as an output due to there being quite a heavy pullup somewhere which I can't get rid of as yet. Always more to find out!

    1. I certainly have - with a lot of help! Even getting to grips and have heavily modified a NODE-RED node to do what I want - next thing will be writing one from scratch.

  8. -Tip Line-

    Not to do with the WS2812s, but I think most of the people who hang out here will be interested to know that Martin Harizanov has published his latest ESP work on GitHub. His build has httpd, mqtt, ntp, ds18b20 and dht22 all rolled in, with web-based configuration for just about everything. So if you boot the ESP with his code and then connect on port 80 with a browser you can, among other things, change the IP address and reboot the ESP to have the change take effect. There's also a very nice scheduler page for setting up daily times during a whole week for heating and cooling on/off points. This is really worth a look-see.

    1. I'll take another look when those who do such things make it work in the Windows Eclipse environment. I could not imagine going back to working in a Linux emulator. I do like his scheduler page.

  9. First, thank you very much for helping a devout Assembly programmer, and VB 6.0 programmer (don't laugh it still works great for my stuff) actually understand what you have done with C. Your coding methods and comments are very clear.

    Regarding the WS2812b propensity to have the first set of RGB pixels rather flaky I have some in depth experience with that issue even with PERFECT timing.

    I, like many, use these devices for large Christmas displays synchronized to music and send images to them in rapid succession.

    The manufacturers of the various Christmas Display controllers for these lighting displays give the ability to null out the first set of RGB pixels as they fight this problem but not because of the controller timing but because of the waveform presented to the WS2812b strips, which YES does alter that once perfect timing.

    Even with a controller producing perfect timing, usually the cables from the controller to the WS2812b add inductance/capacitance to the data line causing the rise and fall times to be rather soft and not nice and crisp.

    However, the output circuitry of every node in the RGB string of WS1212b will recondition the data signal such that even with soft edges or some ringing from the controller --> 1st Pixel the next RGB pixels will receive nice clean rise/fall edges. This signal conditioning continues with every subsequent RGB set of pixels regardless of the number of RGB pixels.

    In one of my MegaTrees with 16 strings and over 6,500 pixels, I had to null the first set of pixels in every string simply because of my cable lengths.

    Great stuff you have created and I will be using the ESP8266's and WS2812bs for lighting similar to what you have created as mood lighting for my Pipe Organ console lights.

    1. I would never have a go at a VB programmer - used it for many years myself. As for cables I did wonder about that and I have kept the cable from the ESP to the LEDs short, maybe 0.5metres. But then as the unit itself is WIFI there is nothing to stop you doing that.

      That is a good point about the first LED and I will do some tests with longer cables - it should be trivial to do a check for the first pixel (a flag) and zero out the RGB component.... but up to now with the current software I'd not seen any issues.

  10. Hi all,
    I just found this blog, while searching and lookng for experiences, to take control over my first try of a 10x ws2812 LED stripe with an esp8266 nodemcu.
    I found lots of ressources. But frankly speaking it very seldom to find something to just download and let go. Its very hard to search and tinker a running project together. Or to let an as "runnable" advertised code really run. Also in this blog, the c-code is distributed in serveral posts, with important pieces missing-. No chance for me to get a conistent pice.
    I also found CNLohrs examples but they did not compile under my just set up eclipse-mars environment. Even that i followed all instructions very carefully.
    The only trivial thing, which worked from first try for me was
    Even that I cant read spanish, the offered arduino skech ran in seconds.
    Can someone provide a complete code of this blog?
    Is there a library using DMA for ESP8266 for ws2812?
    Or better a load-and-go example for the ESP8266 Arduino IDE? Or even better for Eclipse-Mars?
    Sorry for my unhappyness from my first tries.

Comments are closed.