To LED or NOT to LED

ledAt the time I started writing this at 11.33am this morning I had already demonstrated one of my little boards running MQTT and a LED in one case and a mains bulb in the other. Now it’s time to move on. I put a request out this morning if anyone had some code to handle the serial LEDs and indeed as part of a bigger project someone did. The code really is simple – it’s the timing that’s the thing.  The serial LEDS have 4 wires (avoid the older ones). Power (5v), ground, in and out. You daisy chain them – out from one into the input of the other.

I’ll not go into timings here but a wide pulse to the input of the first LED resets them all so they are ready to receive information. A series of pulses comprising R G B information (1 byte each) sets up the first LED. It then passes on everything it sees to the next one – buffered. By this simple but ingenious leap of faith (who at one point would have put a microchip inside of a LED) we end up being able to control endless (almost anyway) LEDs on one wire. If you want them all the same – it’s a doddle – if you want to send a complete sequence so each  LED is a different colour – well you have to have an array to store the values. In this case I’m interested only in making them the same colour – for lighting – but I have to start with what’s available.

So at this point I assume you have an MQTT broker and you can get a board talking to it. I also assume you have the Eclipse environment on your PC and you can compile the MQTT code and FLASH it. No other assumptions are made. So what you are looking at above is one of my development boards with an ESP-01 sitting in it – all the board is for is to allow me to plug in an FTDI for programming – and to run 5v to the board – it has a regulator and a voltage shifter (2 resistors) for the serial INPUT to the board – no need to level shift output to the FTDI.

Below it is a board we had made to house 10 of the serial LEDs – I think ADAFRUIT do a range of boards or you can just buy the strip from Ebay. 3 leads – gnd, 5v and  signal. IN THEORY they run from 3v3 but I’ve no big sources of 3v3 power so once I get this working I’ll try 5v. For now – this hooks to GPIO-0 (when it’s not grounded for programming) and 3v3 and GND.

I am starting with the basic MQTT software and I’m making a topic “firstLedStrip” just because I can – I could have called the topic “pete” – matters not.   I plan to be able to send 255,255,255 for white and MAYBE another parameter for the number of LEDS… I don’t need individual LED colouring… but the principle is the same… Let’s go for getting one light working THEN get ambitious.

This is my version of the code that Markus pointed me to and it’s almost identical – just changed it a little for experimentation. Now I have TWO issues I hope you guys can help me with.. they may or may not be related.. here’s an opportunity for someone to shine..

 

#define WSGPIO 0

static void ICACHE_FLASH_ATTR send_ws_0()
{
  uint8_t i;
  i = 4; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 9; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

static void ICACHE_FLASH_ATTR send_ws_1()
{
  uint8_t i;
  i = 8; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 5; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

static int ICACHE_FLASH_ATTR gpio_ws2812( char *buffer, int length )
{

  GPIO_OUTPUT_SET(GPIO_ID_PIN(WSGPIO), 0);

  os_intr_lock();
  const char *end = buffer + length;
  while( buffer != end ) {
    uint8_t mask = 0x80;
    while (mask) {
      (*buffer & mask) ? send_ws_1() : send_ws_0();
      mask >>= 1;
    }
    ++buffer;
  }
  os_intr_unlock();

  return 0;
}

#define WSGPIO 0

static void ICACHE_FLASH_ATTR send_ws_0()
{
  uint8_t i;
  i = 4; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 9; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

static void ICACHE_FLASH_ATTR send_ws_1()
{
  uint8_t i;
  i = 8; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 5; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

static int ICACHE_FLASH_ATTR gpio_ws2812( char *buffer, int length )
{

  GPIO_OUTPUT_SET(GPIO_ID_PIN(WSGPIO), 0);

  os_intr_lock();
  const char *end = buffer + length;
  while( buffer != end ) {
    uint8_t mask = 0x80;
    while (mask) {
      (*buffer & mask) ? send_ws_1() : send_ws_0();
      mask >>= 1;
    }
    ++buffer;
  }
  os_intr_unlock();

  return 0;
}

 

See the code above – sends out an array to GPIO0. Ok, so I could send something like this.

char bar[3] = { 255, 0, 255 };
gpio_ws2812( bar,3);

And…. the FIRST LED lights up – if I send twice as much..

char bar[6] = { 255, 0, 255 ,4,5,6};
gpio_ws2812( bar,6);

 

TWO LEDS light up – Wheeeeeeeeeeeeeeeeee… except they’re lighting up WHITE and not taking ANY notice of the colours.

 

SO – why did I add ICACHE_FLASH_ATTR to the function calls – because without that – MERELY ADDING TWO of those functions brought back this reponse from the compiler… (this is added to the standard MQTT library).

 

13:30:37 **** Build of configuration Default for project mqtt_pub_ps_ser ****
mingw32-make.exe -f C:/Users/Peter/workspace/mqtt_pub_ps_ser/Makefile all
CC user/user_main.c
AR build/app_app.a
LD build/app.out
c:/espressif/xtensa-lx106-elf/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld.exe: build/app.out section `.text’ will not fit in region `iram1_0_seg’
collect2.exe: error: ld returned 1 exit status
C:/Users/Peter/workspace/mqtt_pub_ps_ser/Makefile:128: recipe for target ‘build/app.out’ failed
mingw32-make.exe: *** [build/app.out] Error 1

13:30:38 Build Finished (took 734ms)

 

WHY? Are we that near the limit – I am assuming the changes I made put the routines into FLASH  -why would I be running out of RAM for heavens sake and how do I get around that.

That’s the first problem – adding the highlighted info it all compiles and KIND OF WORKS.

Next problem – possibly related possibly not – take THE FIRST example..

char bar[3] = { 255, 0, 255 };
gpio_ws2812( bar,3);

I trigger this off with a publication – and it works a treat every time – one light lights up but it’s supposed to be purple – it’s always WHITE. Here’s what actually goes out thanks to my remarkably cheap Saleae logic analyser…

Output GPIO0 normally low – here’s the package.

Image635565800289650314

Looks good to me – you can see the 255, then the 000 then the 255. Here’s a zoom for timings… of the 255…

255

and the 0

0

 

So  some updates for you at 15:11 -  the timings WERE miles off – someone said this might be to do with compiler settings – I have no idea how to alter those. I’m using the default settings… I started messing with the settings and could get colours (We’re talking WS2812B LEDS here with 1 timings of 0.9us high then 0.35us low – and 0 timings of  0.0.35us high then 0.9us low

This is the nearest I can get without changing the whole lot into assembly – which isn’t a bad idea if anyone wants to chip in.

Finally it did not help I was expecting RGB order whereas in fact the serial stream is GRB order…Here is what I finally ended up using – I put the ICACHE_FLASH_ATTR elsewhere and that issue still needs resolving or it’s going to come back over and over.

#define WSGPIO 0

/*

static void  send_ws_0()
{
  uint8_t i;
  i = 4; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 9; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

static void  send_ws_1()
{
  uint8_t i;
  i = 8; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 5; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}
*/

static void  send_ws_0()
{
  uint8_t i;
  WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1); for (i=0;i<1;++i);
  WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0); for (i=0;i<2;++i);
}

static void  send_ws_1()
{
  uint8_t i;
  WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1); for (i=0;i<6;++i);
  WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

static int  gpio_ws2812( char *buffer, int length )
{

  GPIO_OUTPUT_SET(GPIO_ID_PIN(WSGPIO), 0);

  os_intr_lock();
  const char *end = buffer + length;
  while( buffer != end ) {
    uint8_t mask = 0x80;
    while (mask) {
      (*buffer & mask) ? send_ws_1() : send_ws_0();
      mask >>= 1;
    }
    ++buffer;
  }
  os_intr_unlock();

  return 0;
}

And with that and the call…

if (strcmp(topicBuf,"myleds")==0)
    {
        char bar[9] = { 0, 255, 0,255,0,0,0,0,255 };
        gpio_ws2812( bar,9);
    }

we get….

rgb

 

Wheeeeeeeeeeeeeeeeeee…

 

BUT the FIRST light is only correct on the second invocation – so I’m assuming I need that port initialisation in my INIT file and the port sent LOW (hence ensuring reset).. I’ll do that next – and then having already identified the topic I need to parse “xxx,xxx,xxx” into 3 numbers, pop them in – and we’re off!! IDEALLY I’d like a 4th parameter, dynamically allocate the number of LEDS in the way I did in the FASTLED modification – but how to MALLOC in here is anyone’s guess – I need some means to tell me now much RAM I have left.

In case you’re panicking – when I get this going I’ll detail the changes needed to the existing MQTT code to get this far – you WILL need to be able to compile – and that is why I’ve asked the designer of the MQTT package ot consider setup via the serial port for Access point, MQTT Broker and subscriptions as that would make it possible for people to use the package with, say and Arduino without having to compile.  Time will tell, I may learn how to do that myself.

More later – and now to try the whole 10 LEDS – I really like pushing my luck.

 

15:48 – I’ve now put “    GPIO_OUTPUT_SET(GPIO_ID_PIN(WSGPIO), 0); // for LEDS” into the INIT function to set the output on power up – and having put in the compiler optimisation as suggested by GREG – the speed has gone right up so that the original settings were almost right..

Here’s what I’ve ended up with..

static void  send_ws_0()
{
  uint8_t i;
  i = 4; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 9; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

static void  send_ws_1()
{
  uint8_t i;
  i = 10; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 1);
  i = 3; while(i–) WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_ID_PIN(WSGPIO), 0);
}

HOWEVER that first red led is very hit and miss – as I trigger off the update – it takes a few attempts.  At first attempt it’s orange – then maybe after 3 or 4 goes I get RED.. something still not quite right..

Looking at the graph I’m getting a wide pulse at the very start – I have NO idea why… if I send 2 packages it’s still the same…

wide

 

16:32 I’m having a break – WITH the ESP8266 compiler  optimisations the guys have suggested – I cannot get rid of that wide pulse at the start. WITHOUT the optimisations, the ‘1’ state ZERO is too wide…

Time for a coffee and relax before I hit something.

28 thoughts on “To LED or NOT to LED

  1. Pete
    Thanks for your blog. I am following it for the last weeks and just nned to start working on this ESP8266.

    With regards to the above topic, it’s clear your timings are wrong.
    I’ve worked with WS2812 & WS2812B a lot with various AVR and other procs and this timing things is a pain.
    The bit length shall be in the 1µs rans while yours is much above this.
    Check the values in the datasheet (such as the one published by Adafruit at (http://www.adafruit.com/datasheets/WS2812.pdf) as well as excellent Tim’s blog here (https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/).

    Hope this helps

    • I think your playing with mapping the code in flash versus CACHE might have impacted the execution time. If the code is meant to be cacheable and executed from the internal cache memory and you moved it to external flash, then the execution is slower.

      I’ve not played yet with the ESP SDK but it’s likely you have to solve you linking issues.

  2. Without the ICACHE_FLASH_ATTR, the code is placed in ram, I don’t think you can execute code from ram.

    Have you tried creating your purple leds by running code from an arduino, checking the serial trace with the saleae, then comparing against the esp8266 trace?

  3. No but while waiting for someone to come up with a miracle I’m going to assume that all that MQTT code is depleting the RAM and try putting OTHER routines into FLASH instead. I’m sure this is wrong but it’s worth a try..

  4. Another great idea up the swanny.. I took off the ICACHE_FLASH_ATTR attributes for the three functions and of course the code would not compile – so instead I put it on the general MQTT receiver – and the init functions- none of which have anything to do with the LEDS. Made ABSOLUTELY NO DIFFERENCE to the timings – which remain – the on time for a ONE is 1.8us.

    The question is.. WHY? If I reduce those counts- the timing is going to be very inaccurate…

  5. The ESP8266 has 32K of RAM.. which seems like plenty for this, so I wonder two things:
    1. How much RAM is used by the messaging and wifi support? (Pesky buffers..)
    And
    2. This sort of implies that the without the compiler attribute, your CODE is winding up in RAM (vs flash), which seems odd to me. But then, I’m super-new to Xtensa and the ESP8266.

    • Well this is a worry – especially as the default MQTT code has only 2 subscriptions – as one would likely want a few more, are we rapidly going to run out of memory unless putting that prefix on all routines.

  6. The timings from my code assume you compile with -O2 or -Os. Your project maybe uses -O0 (the default when not specifying anything to the compiler), which makes lots of things slower.

    • Markus can you simplify that a little… I am VERY new to using Eclipse and the code here – I don’t actually know how to make that change – this is the first time I’ve heard of this – up to now I’ve just altered, compiled and Flashed. Can you explain what those options are and where to go to change them? And if the wrong settings make things slower, why are they defaults??

      • The -O2 is a compiler option to optimize the code. This is set in the makefile. If you are using the MQTT makefile, check around line 42-50, there are options for debug and release. Ensure the release build has -O2.

        Early versions of the MQTT library had this set to -O0 which means no optimization, and therefore slow code.

    • Greg – sure enough – made a BIG difference to the timings!!! Wonder if that makes a big difference to RAM usage and the necessity for the ICACHE_FLASH_ATTR flag…

  7. This makefile looks stupid
    Release -> “-O0” which means no optimisation (in order to allow code debugging)
    Debug -> “-O2” which means optimisation.

    It should be the opposite and in your tool you should choose the “Release” flavor.
    At last as we don’t have any kind of debugger for teh ESP, we don’t care of the “Debug” flovor.
    So you can simplify and use “-O2” all the time.

  8. Guys – thanks for all your help here – but at the start of every package I’m getting a wide positive pulse. Is there any chance that the optimisation only kicks in after the first run of the loop. ie I’m getting slow code for the first HIGH/LOW then fast afterwards – because that’s what it looks like – there is no other reason – if I send 0 – which is .35us high followed by .9us low.. 8 times in a row – the FIRST high is nearly 3us wide and it’s screwing the first LED colour.. every time…

  9. Sir, you are having too much fun with this. Another great post, thanks, and some great comments.

    I’d be playing along today, except we’re off to the Boat Show, and boats are about the only thing I like better than hacking. 😉

    Cheers.

  10. Hi Ian – oh that’s WAY overkill – simple Arduino micro – £3 from China will handle the leds no problem at all – but I’m trying to avoid using the second processor – the ONLY thing that is stopping me right now is my lack of knowledge of the ESP8266 assembly – for the time sensitive bit… essentially a character pointer, 3 bytes per led and a kind of manchester encoding for each bit…. I need consistent 0.3us and 0.9us pulses… from start through to the end.

    • That’s where I started – did you notice he’s having trouble with tolerances. No way around it – we need a block of assembly code to handle going from the buffer to the LEDs – I know exactly what’s needed – just don’t know assembly – happy to work with anyone to get this sorted

  11. I know a few assembly languages, but Xtensa LX3 is new to me (also: it looks interesting– zero overhead loops!). I’ll look at it, but I’m not sure I’ll have a relevant amount of time to do that before someone else gets it going. I’ll take a look though!

  12. Oh if you know the language – it’s not difficult AT ALL. Here’s what’s needed.

    You have a character pointer 0 in the simplest version I believe would work, a routine passing that character pointer needs to process 3 bytes and return. For each byte start at the top bit and work down to the bottom. So for each bit – 24 in total… if it’s a ONE… toggle GPIO0 0.9us UP, 0.35us DOWN. If it’s a ZERO, the opposite i.e. 0.35us DOWN, 0.9us UP. There will be a compiler directive to turn off any optimisation. That will process the WS2812b chips. The WS2812 chips are slightly different but once we have this working, that’s a doddle (the latter chips are 0.7us up, 0.6us down for 1, 0.35us up, 0.8us down for 0 0 you can see why they upgraded them).

    That’s basically it.

    In the bigger picture we process those 3 bytes – and if we need more leds of a different colour – we loop that looking at the next 3 bytes.. in the case of them all being the same colour, we simply loop the same 3 bytes X amount of times (and that should be an integer as at least in theory you can run a hell of a lot more than 256 LEDS).

    • Oh, believe me, I’m all too familiar with the WS2811/12 timing.
      While I’m interested in working out the Xtensa LX3 asm code for this, I don’t have an ESP8622 myself to hack on.
      Which module would you recommend that I get that’ll let me focus most of my energy on code dev, rather than wrangling the hardware?
      I can set up the toolchain in the meantime, and see how much of our work in FastLED’s signal timing code will be reusable.

      • CHEAPEST solution – ESP-01 – you should pay no more than £3.

        The ESP-01 only has 2 usable pins apart from Serial in and out – ie GPIO0 and GPIO2 – and it’s GPIO0 that I’m looking to code for the serial LEDS.

        If however you want the most amazing bargain on the planet you might go the whole hog and buy this – I’m waiting for mine…

        http://www.ebay.co.uk/itm/301475108343

Leave a reply to Scargill Cancel reply