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.