Monthly Archives: March 2015

ESP826 + Pi + MQTT + Node-Red Heaven

NetioIf you’ve been following the blog you’ll note I’ve spent a lot of time getting to grips with the Raspberry Pi, Node-Red, the ESP chips using C and of course my earlier home control attempts with NETIO, the NRF24L01 boards and Arduino clones (all of which are working 24/7 – I’m just getting more ambitious).

Well, it’s all starting to come together now. This morning I just put together all the pieces I’ve discussed earlier. On my workbench I have an ESP-01 based board controlling a mains light, I have the Pi with a couple of LEDs on it and I have an ESP-12 based board with some RGB LEDs and a MOSFET driving a PWM channel.

Now, I have to tell you that I’ve discovered something I’d rather not have… the PWM output is , it would seem, driven by software, NOT by hardware. I say that as I’ve realised that if I turn on the PWM output and then start fading RGB LEDs – there is some temporary interference to the PWM – and it is visible. It goes away when the RGB LEDs stop changing however.  This does suggest that it would be better not to run both at once – or indeed palm off the PWM to a serially-driven Arduino-Micro or similar from the serial output of the ESP..  I turn off the interrupts when handling RGB and this no doubt is the cause. It’s not a big deal, it’s just slightly annoying.

However I’ve now put NETIO in charge of this lot and I have to say it’s working a treat. Here is my current test screen on the Samsung S4. I was going to use the NETIO text input box but the author doesn’t seem to have finished this off as it looks awful (it works, it just looks awful) so I’ve missed that off for now.

You can see the S4 screen over on the right. The RGB wheel works a treat, the slider controls the PWM, the LEFT on-off buttons control the LEDs on the Pi, and one of the rightmost pairs controls the mains light on another board.  Node-Red acts as the arbitrator for this lot. TCP in from the NETIO app on the phone is sent through a function block in Node-Red in which I parse the incoming data and make sure I sent back the appropriate response. It’s trivial actually once you get familiar with Node-Red.  For now I’m storing the Pi Pin states in a database but ultimately I’ll store the lot. By and large I’m just sending a message back to NetIO and then sending off an MQTT message to the relevant device as dictated to by the NETIO message.

Node Red

 

var newMsg = { payload: msg.payload.trim() };
var myMsg=newMsg.payload.split("|");

if (myMsg[0]=="gpio") {    newMsg.payload=myMsg[2];
        switch (myMsg[1]) {
            case "0":

newMsg.topic="Update pins set gpio0=" + myMsg[2];
                context.global.gpio0=myMsg[2];
                return [newMsg,null,null,null,msg,null];
            case "1":
                newMsg.topic="Update pins set gpio1=" + myMsg[2];
                context.global.gpio1=myMsg[2];
                return [null,newMsg,null,null,msg,null];
            case "2":
                newMsg.topic="Update pins set gpio2=" + myMsg[2];
                context.global.gpio2=myMsg[2];
                return [null,null,newMsg,null,msg,null];
            case "3":
                newMsg.topic="Update pins set gpio3=" + myMsg[2];
             context.global.gpio3=myMsg[2];
              return [null,null,null,newMsg,msg,null];
        default: 
                return [null,null,null,null,msg,null];
        }
}   
else if (myMsg[0]=="gpio?")
    {
    switch (myMsg[1]) {   
        case "0":
              msg.payload=context.global.gpio0+"\n";
              return [null,null,null,null,msg,null];
        case "1":
              msg.payload=context.global.gpio1+"\n";
              return [null,null,null,null,msg,null];
        case "2":
              msg.payload=context.global.gpio2+"\n";
              return [null,null,null,null,msg,null];
        case "3":
              msg.payload=context.global.gpio3+"\n";
              return [null,null,null,null,msg,null];
     default: 
              return [null,null,null,null,msg,null];
        }   
    }

else if (myMsg[0]=="mqtt") // message and topic passed
    {
    msg.topic=myMsg[1];
    msg.payload=myMsg[2]+"\n";
     return [null,null,null,null,msg,msg];
    }

return [null,null,null,null,msg,null];

Facebooktwittergoogle_pluspinterestlinkedin

ESP01 versus ESP12

And for today’s intriguing question…  I’ve been working on the ESP-12 for some time now – the program has never failed to work on power up… today I wanted to put the code back into an ESP-01 as I only wanted to use GPIO0… same code – same kit – ESP-01 powers up – gives up the usual rubbish on powerup and… nothing.

Works in the ESP-12 – doesn’t work in ESP-01.  Tell me I’m dreaming but are they not identical apart from the actual pinout?

I feel like I’ve entered a parallel universe… used to work. I’ve tried spare boards – makes no difference.

Facebooktwittergoogle_pluspinterestlinkedin

ESP8266 Start up Crap

I’m pretty pleased with my ESP8266 developments so far. Ditching Lua was a good move as I’m no longer tied for resources… there is however one major annoyance that I’ve not seen a cure for…

When you power up the board, in the ESPRESSIF rom there is some setup info – along the lines of…

rubbish

See that garbage at the top – that is something like 76Kbaud debug material at the start, then everything up to “connected with loft-east”. If anyone has discovered a way to turn that off – please do let me know. I’m assuming it is in the binary files and beyond our reach.

Facebooktwittergoogle_pluspinterestlinkedin

ESP8266 Tidy PWM

pwmIf you check out an earlier post I managed to get PWM working nicely on the ESP-12 on the otherwise unused GPIO15. Well, it was a little messy – so I’ve tidied it up with the use of a struct.

Here it is..  You’ll need to make minor mode to the PWM .C page as per my earlier blog.

In your variable setup…

typedef struct {
    uint8_t channel;
    uint16_t frequency;
    uint8_t actual;
    uint8_t bright;
    uint32_t timeout;
    uint8_t minimum;
} PWM;
PWM pwm;

And a 100 step gamma correction table…

static const uint8_t PWMTable[100] = {0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,

4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9,10, 11, 11, 12, 13, 13, 14, 15, 16,

17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 30, 31, 33, 34, 36, 38, 40, 42, 44,

46,48, 51, 53, 56, 59, 62, 64, 68, 71, 74, 78, 81, 85, 89, 93, 97, 102, 107, 111,

116, 122, 127, 133, 139, 145, 151, 157, 164, 171, 178, 186, 194, 202, 210, 218,

226, 234, 244, 250 ,255 };

In your INIT code

pwm.channel = 0;
pwm.frequency=500;
pwm_init(pwm.frequency, &pwm.channel);
pwm_set_duty(pwm.channel, 0);
pwm_start();

In your timer callback (mine is every 20ms)

if (pwm.timeout) {
    if (--pwm.timeout == 0) pwm.bright = pwm.minimum;
}
if (pwm.actual != pwm.bright) {
    if (pwm.bright > 99) pwm.bright = 99;
    if (pwm.actual > pwm.bright) pwm.actual--;
    else if (pwm.actual < pwm.bright) pwm.actual++;
    pwm.channel = PWMTable[pwm.actual];
    pwm_set_duty(pwm.channel, 0);
    pwm_start();
}

And that’s it – just adjust pwm.bright from 0-99 and if you set pwm.timeout the light will fade out after a while –and if you set pwm.minimum when it times out it will time out to that value.

Handy for general LED strip lighting, SAD lighting etc.

Facebooktwittergoogle_pluspinterestlinkedin

ESP8266 PWM on One Output

According to the ESP8266 API - you have control over ports 12,13 and 15 for PWM.

I'm interested only in using GPIO15 as I have uses for the other port bits - so I took PWM.H and removed reference to the other two ports - leaving only GPIO15. I changed PWM_CHANNEL to 1 and deleted references to the other two pins.

#ifndef __PWM_H__
#define __PWM_H__
#define PWM_CHANNEL 1
struct pwm_single_param {
   uint16 gpio_set;
   uint16 gpio_clear;
   uint16 h_time;
};
struct pwm_param {
   uint16 period;
   uint16 freq;
   uint8 duty[PWM_CHANNEL];
};
#define PWM_DEPTH 255
#define PWM_1S 1000000
#define PWM_0_OUT_IO_MUX PERIPHS_IO_MUX_MTDO_U
#define PWM_0_OUT_IO_NUM 15
#define PWM_0_OUT_IO_FUNC FUNC_GPIO15
void pwm_init(uint16 freq, uint8 *duty);
void pwm_start(void);
void pwm_set_duty(uint8 duty, uint8 channel);
uint8 pwm_get_duty(uint8 channel);
void pwm_set_freq(uint16 freq);
uint16 pwm_get_freq(void);
#endif

I then performed an init... and LO - 0% duty cycle out of GPIO15

Here's my code..

uint8_t PWM_CH[]= {0};
uint16_t freq = 1000;
PWM_CH[0]=0;
pwm_init(freq, PWM_CH);
pwm_set_duty(pwm_CH[0], 0);
pwm_start();

and to change the duty cycle (actual values 0-255)

PWM_CH[0]=X;
pwm_set_duty(pwm_CH[0], 0);
pwm_start();

Voila. The otherwise wasted GPIO15 now usable.

Facebooktwittergoogle_pluspinterestlinkedin

ESP8266 SDK v1.0.0 Out Now

esp8266 v1.0.0The new esp8266 SDK v1.0.0 is out now including various optimisations and a half-decent English manual. How far we have come since only a few short months ago when the code was full of bugs and the English documentation almost non-existent. Today you can get paid to report bugs!!

According to Espressif…  they have fixed various bugs including “interrupt during flash erasing causing wdt reset”, read and write issues with RTC memory, reconnects.. they have added API code to update the cpu frequency, set a mac address filter during “sniffer” and set which interface the UDP broadcast is sent from.

ESP8266Various optimisations included smartconfig, AT updates, PWM and much more.

So does it work? Is if going to give you headaches? I have just downloaded the lot and set my printer off on the 96 page SDK document.. I renamed the original SDK directory in the ESPRESSIF folder structure and added in the new one – I compiled the MQTT code I’ve been working with – and voila – failed – then I realised I’ve added a macro in there (digitalWrite) – which if nothing else proves it was trying to compile the new version) – I fixed the missing macro and the whole thing works – I’ve not had time to extensively test my code but the compilation process works flawlessly and a quick test of my code suggests all is well.

For those still using the AT code, AT+CIPSTATUS now returns the right code!!! the wdt no longer resets on connect failure and much more – it’s all in their blog. Here’s the Espressif BBS link but you’ll need a free account..  You can even now many money for reporting new bugs – perhaps Microsoft should consider this.

Update August 2016: We are now up to SDK v2.0.0 and the SDK goes from strength to strength – well worth downloading the documents from Espressif.

Facebooktwittergoogle_pluspinterestlinkedin

WS2812B LEDS flicker

If you recall my earlier post on the code for handling WS2812B LEDs with an ESP-12 or similar – I’ve been discovering the odd flicker on the first LED – recall I lengthened the reset pulse to get rid of this – well, I was still getting it very occasionally during a long fade of the lights. I’ve lengthened the reset pulse and it has now completely gone (see code in bold). Enjoy.

 

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

   for (i = 0; i < 4; i++)
        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 8, rainb12);
    while (repetition--) {
        for (i = 0; i < length; i++) {
            uint8_t mask = 0x80;
            uint8_t byte = buffer[i];
            while (mask) {
                (byte & mask) ? SEND_WS_12_1() : SEND_WS_12_0();
                mask >>= 1;
            }
        }
    }
    os_intr_unlock();
}

Facebooktwittergoogle_pluspinterestlinkedin

Raspberry Pi Remote Root Access

Now some of you will write in no doubt and say how easy this is – how could I possibly not know this. Well, I didn’t. I found out two other things this week – so here’s the lot in one go.

SD Card Cloning

But before I start, here’s something else I just cleared up – cloning of microSD cards. I was SURE I read there was an issue cloning the Pi SD cards. So having set up my Pi2 the way I wanted it with just about everything but the kitchen sink in it, I decided to start the second Pi2 from scratch – well that didn’t work out because about 2.3rds the way through setting up the many programs, the think ground just about to a halt – I mean like 5 minutes to power up. Was it the microSD?, was it the Pi? was it the setup?  I decided I had nothing to lose by cloning the microSD with Windows 8.1 using Win32DiskManager.

So that I did and sure enough – I now have two fully functioning Pi2 boards, one on 192.168.0.15 and the other on 192.168.0.6 – oh and that includes WIFI – I just plugged an identical WIFI module into the second Pi, powered it up and it just worked.

SD Card Lifetime

Something ELSE I just discovered. So I was getting worried about the lifetime of the microSD given that I’m using mySQL for example which surely must hammer the disk. WELL, it turns out that as well as looking for memory cards with SPEED you need to consider automatic levelling. EH? Don’t worry you don’t have to do anything. So imagine the Pi is writing to the card over and over in the same spot. Well, you only get 10s of thousands of writes and depending what you’re doing that could be wrapped up in weeks never mind years. It turns out that SD cards with automatic levelling (Samsung Ultra comes to mind as against cheap unbranded crap) actually have a controller in there that monitor usage and move stuff around for you!! The downside is that there’s always the possibility you finish what you’re doing, shut off and.. lose some data – so – shut the Pi down properly and use some kind of uninterruptable supply. SOME phone chargers will act like an uninterruptable, some won’t… you’ll have to take pot luck on that, I have 4 different battery packs (JuiceBar etc) and only two of them make reliable uninterruptable power supplies.

Remote Access to Pi Files

and so finally onto remote access…. up to now I’m using WinSCP on the PC talking to TightVNCServer on the Pi2 boards.. but could never get past user pi level access – in other words it I typed in root and a password, no matter what password it would not work. I could have SWORN I had typed in a root password.. however, obviously not.

So off I went to the web to find out how to fix  that, most of the instructions said “just enable a root access password and you can then log in using WinSCP as root”. HAH, all very nice but how the hell do you do THAT?

Turns out it’s easy… open a terminal in the Pi (unless you’re already in command line mode) – and type…

sudo passwd root

at which point you get asked for a password twice and Bob’s your uncle!! So now I can access the Pi files from my PC from either device – as Pi or Root as easy as clicking a couple of buttons.

Simples.

Facebooktwittergoogle_pluspinterestlinkedin

NODE-RED Development

While I’m sitting in a hotel in Birmingham waiting to do my bit in this month’s #FSBCONF I thought I’d drop down a few notes about Node-Red which is definitely my new toy of choice when it comes to a central hub for home control. I say toy, sitting on a Raspberry Pi 2 it is definitely NOT a toy.

nodes

So what you see above is one tiny part of what I’m working on for the home controller. The boards mentioned a couple of articles ago need to know what time it is as they don’t have a clock or battery. So when you plug them in – they need to know the time – and as they are also aware of dusk and dawn which clearly changes from time to time, there’s a whole package goes to them on a regular basis, once a day.

There is a “sunrise” node available to Node-Red, but my requirements are slightly different to that which the standard unit allows – so using the Sunrise node as a base I made my own node.

It turns out that as an absolute mininum you need a .JS file and an HTML file. You put them in a suitable named directory under your pi/node-red/nodes directory and that’s about it – provided you’ve not made any mistakes – it just appears as a block in Node-Red.

So what I wanted was a block which would respond to incoming requests from individual WIFI units with their ID and return time info to the individual nodes, usually after a power up or reset – but also send a message to ALL nodes on a regular basis, say, once a day. The WIFI units are programmed to request info at regular intervals until they get an answer – though I’ve yet to see this miss a beat.

The Boards Login block is an MQTT subscribe block – standard. The Daily Update is a standard inject block – and the Send to Boards is an MQTT publish – against standard.

The bit in the middle is a heavily modified Sunshine block – called duskdawn. I made a directory for it – I put the JS and HTML files in there and started gutting them. So now if you click the block you get…

duskdawn

So here are the two files….

The first is as you can see VERY similar to the suncalc node and uses the same functions – but it does not use it’s own timer – it uses an input.  Also it does not generate simple yes/no outputs but generates a message suitable for MQTT.  If a message comes in that says “fromesp” – it sends out the new message to all. If it comes in from a particular unit, it sends that info back to that unit. The info comprises the time in seconds since 1970, the number of minutes after midnight we call DUSK and the number of minutes past midnight we call DUSK.

module.exports = function(RED) {
    "use strict";
    var SunCalc = require('suncalc');

    function SunNode(n) {
        RED.nodes.createNode(this,n);
        this.lat = n.lat;
        this.lon = n.lon;
        this.start = n.start;
        this.end = n.end;

        var node = this;

        this.on("input", function (msg2) {
            var now = new Date();
            var times = SunCalc.getTimes(now, node.lat, node.lon);
            var nowMillis = Date.UTC(now.getUTCFullYear(),now.getUTCMonth(),now.getUTCDate(),now.getUTCHours(),now.getUTCMinutes(), now.getUTCSeconds());
            var midnightMillis = Date.UTC(now.getUTCFullYear(),now.getUTCMonth(),now.getUTCDate(),0,1);
            var startMillis = Date.UTC(times[node.start].getUTCFullYear(),times[node.start].getUTCMonth(),times[node.start].getUTCDate(),times[node.start].getUTCHours(),times[node.start].getUTCMinutes());
            var endMillis = Date.UTC(times[node.end].getUTCFullYear(),times[node.end].getUTCMonth(),times[node.end].getUTCDate(),times[node.end].getUTCHours(),times[node.end].getUTCMinutes());
            var moon = parseInt(SunCalc.getMoonIllumination(now).fraction*100+0.5)/100;
            var msg = { payload:0, topic:"sun", moon:moon };
            var dawn = (startMillis - midnightMillis);
            var dusk = (endMillis - midnightMillis);
           

           
            if ((nowMillis>dawn)&& (nowMillis<dusk)) { node.status({fill:"yellow",shape:"dot",text:"day"}); }
            else { node.status({fill:"blue",shape:"dot",text:"night"}); }

            msg.topic = msg2.payload + 'toesp';
            msg.payload = '{time:' + nowMillis/1000 + ';dusk:' + dusk/60000 + ';dawn:' + dawn/60000 + '}';
            node.send(msg);

});

    }
    RED.nodes.registerType("duskdawn",SunNode);
}

 

This code is complemented by HTML which identifies what inputs are needed, how many outputs etc. This is even closer to but simpler than the Sunrise files. All of the copyright notices in the originals apply, my modifications are not serious enough to claim any ownership though I should say that when I started playing with this I knew nothing about creating these files and now it seems pretty straightforward.

 

<script type="text/x-red" data-template-name="duskdawn">
  <div class="form-row">
    <label for="node-input-lat"><i class="fa fa-globe"></i> Latitude</label>
    <input type="text" id="node-input-lat" placeholder="51.025">
  </div>
  <div class="form-row">
    <label for="node-input-lon"><i class="fa fa-globe"></i> Longitude</label>
    <input type="text" id="node-input-lon" placeholder="-1.4">
  </div>
 
  <div class="form-row">
    <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
  </div>
</script>

<script type="text/x-red" data-help-name="duskdawn">
    <p>Uses the suncalc module to generate an MQTT output which contains time, dusk and dawn on demand for a specified longitude and latitude.</p>

</script>

<script type="text/javascript">
    RED.nodes.registerType('duskdawn',{
        category: 'advanced-input',
        color:"#ffcc66",
        defaults: {
            name: {value:""},
            lat: {value:"", required:true, validate:RED.validators.number()},
            lon: {value:"", required:true, validate:RED.validators.number()},
            start: {value:"sunrise", required:true},
            end: {value:"sunset", required:true}
        },
        inputs:1,
        outputs:1,
        icon: "sun.png",
        label: function() {
            return this.name||"Sun rise/set";
        },
        labelStyle: function() {
            return this.name?"node_label_italic":"";
        },
        oneditprepare: function() {
            if (($("#node-input-lat").val() === "") && ($("#node-input-lon").val() === "")) {
                if ("geolocation" in navigator) {
                    navigator.geolocation.getCurrentPosition(function(position) {
                        $("#node-input-lat").val(Number(position.coords.latitude.toFixed(5)));
                        $("#node-input-lon").val(Number(position.coords.longitude.toFixed(5)));
                    });
                }
            }
        }
    });
</script>

 

So there you have it – with luck you should find this information useful. I AM told by a very kind reader that I should amend any copyright because of the substantial changes I’ve made – but then without the original I could not have gotten this far (I deal with business forums in another life and people are often very unhelpful – whereas in here I’m constantly amazed at the great feedback and help I get)  so I’ll ask merely this – if you find it useful, please somewhere put a link back to the https://tech.scargill.net blog so that more knowledgeable people will come back to me with potential improvements to everything I’m doing – something that we’ll all benefit from.

And if you happen to spot a Node-Red node that can create a nice web page to set up a central heating system – please DO let me know as that’s my next requirement…

Facebooktwittergoogle_pluspinterestlinkedin

LED Quick Test

Quick test of the new ESP8266 code for driving the ws2812b LEDs… not far off 2 amps at 5v in at one end – GND and ESP8266 signal other end… fully tested with 300 serial LEDS – this is my next WIFI SAD LIGHT once I find something with enough power and some very thick cable. 70 LEDs here.

purple  green  red

Facebooktwittergoogle_pluspinterestlinkedin

ESP8266 WS2812B LEDs on a Plate

LEDSWell, now that I’ve cracked using the WS2812B LEDs – I guess I’d better share the results – especially as it was the work of others that started me off on this road!

I went down a LOT of dead routes with this – firstly I was going to have it programmable how many outputs I would dedicate to the WS2812B LEDs – then ultimately I realised, because we’re talking about a memory buffer feeding the LEDS I might as well just do one long run and split it up as I need!! That also got around some timing issues.  If you look at previous items on this- one fellow wrote a driver for the LEDs and it kind of worked – then if you looked at his ISSUES others said it had problems and another guy wrote an improved version and fretted about needing a capacitor on the output to stop one of the LEDs (the first one) flashing. You don’t need any of that – it’s ALL down to timing.

So – why read further than this line – because I’ve had 300 LEDs on a 5m strip working PERFECTLY and the only reason I’ve not tried 600 LEDs is I don’t have a power supply powerful enough to run 600 of the things!!!  And how quick is the update? Well, to turn all from off to on, you can’t SEE the update.

So…. I#m assuming you’re happy programming in C and know a little about the ESP8266. In my case I’m using the Eclipse environment on a Windows 8.1 PC and having a ball (as against my first steps in a Linux virtual environment which was enough to make a grown man eat his own bairns). Follow me and let’s get started.  I’m using GPIO12 on the ESP-12 – I’ve also used GPIO4, GPIO5 and there’s no reason that I can think of why you can’t use GPIO0 on an ESP-01.

To start the ball rolling you need some place to store info – I find structs handy for that so create this.

typedef struct{
    uint8_t reda;
    uint8_t greena;
    uint8_t bluea;

    uint8_t red;
    uint8_t green;
    uint8_t blue;

    uint32_t rainbow;
    uint16_t rgbnum;
    uint16_t rgbdelay;
    uint8_t  buffer[900];
} LEDS;

LEDS rgb;

 

LEDS[6]That 900 byte buffer.. depending on what you’re doing you may not need more than 3 bytes so by all means change it. A single LED uses 3 bytes – for RED, GREEN and BLUE.  If you want all the LEDS the same colour at any time you can just repeat using the same 3 bytes – but if you want them all different colours then you need 3 bytes per LED. Given that if you’re using C as against Lua (I’m only talking C here, I gave up on Lua) then you probably have bags of RAM, this will hardly make a dent (and we put up with Arduinos for years for WHAT reason?).

So, and you might want to do everything differently to me – but this is what I’ve done… I like to FADE LEDs over time as against turning them on and off so I need the ACTUAL colour of the LED against what I WANT it to be – hence REDA and RED in the above struct.

For GPIO12 I defined this lot..

#define LED_GPIO_12 12
#define LED_GPIO_MUX_12 PERIPHS_IO_MUX_MTDI_U
#define LED_GPIO_FUNC_12 FUNC_GPIO12
#define OUT_ON 1
#define OUT_OFF 0

and in my INIT routine I have this..

PIN_FUNC_SELECT(LED_GPIO_MUX_12, LED_GPIO_FUNC_12);

and that’s it basically – if you want to turn the output on:

GPIO_OUTPUT_SET(LED_GPIO_5, OUT_ON);

or off

GPIO_OUTPUT_SET(LED_GPIO_5, OUT_OFF);

or at least historically that’s how I did it – but you’ll need the initialisation anyway.

You need a callback function to do your timing for you – I have one which debounces keys and handles the RGB delays… so in your INIT you need this.

 

os_timer_disarm(&bounce_timer);
os_timer_setfn(&bounce_timer, (os_timer_func_t *) bounce_cb, (void *) 0);
os_timer_arm(&bounce_timer, 50, 1);

 

Well, you don’t have to call it bounce_timer of course.

So – I have a callback (ie a function that gets called by the system every 50ms (see last line).

Here’s what it looks like – I’ll give you a filled in one later.

LOCAL void ICACHE_FLASH_ATTR bounce_cb(void *arg) {

}

Hey, make loads of these with different times and names – they’re really useful. All that ICACHE stuff – if you don’t put that in, you’ll start eating into RAM – so just include that in your functions.

Ok, here’s the real version..

LOCAL void ICACHE_FLASH_ATTR bounce_cb(void *arg) {

if ((rgb.red != rgb.reda) || (rgb.green != rgb.greena) || (rgb.blue != rgb.bluea)) {
    if (rgb.reda < rgb.red)
        rgb.reda += ((rgb.red - rgb.reda) / (rgb.rgbdelay * 20)) + 1;
    if (rgb.greena < rgb.green)
        rgb.greena += ((rgb.green - rgb.greena) / (rgb.rgbdelay * 20)) + 1;
    if (rgb.bluea < rgb.blue)
        rgb.bluea += ((rgb.blue - rgb.bluea) / (rgb.rgbdelay * 20)) + 1;
    if (rgb.reda > rgb.red)
        rgb.reda -= ((rgb.reda - rgb.red) / (rgb.rgbdelay * 20)) + 1;
    if (rgb.greena > rgb.green)
        rgb.greena -= ((rgb.greena - rgb.green) / (rgb.rgbdelay * 20)) + 1;
    if (rgb.bluea > rgb.blue)
        rgb.bluea -= ((rgb.bluea - rgb.blue) / (rgb.rgbdelay * 20)) + 1;

    if (rgb.rgbnum == 0) { rgb.rgbnum = 1; rgb.reda=rgb.red; rgb.greena=rgb.green; rgb.bluea=rgb.blue; } // instant
    rgb.buffer[0] = ledTable[rgb.reda];
    rgb.buffer[1] = ledTable[rgb.greena];
    rgb.buffer[2] = ledTable[rgb.bluea];

    WS2812OutBuffer(rgb.buffer, 3, rgb.rgbnum); // 3 leds in array, number of repetitions
  }

}

What on EARTH is all that about – well the first line reads – IF the expected colour is in any way from the ACTUAL colour…. DO this lot.

So there’s a little maths depending on rgb.rgbdelay that sets what you want rgb.red, rgb.green and rgb.blue to be. and when that’s done – you fill in the first 3 bytes of the buffer – not with the actual value of the LED but with that put through a lookup table – why? Because the sensitivity of your eyes is not linear. If you don’t use the lookup table, the fade will appear pretty good up to 20% brilliance then will seem to slow down.

Here’s the table – put it anywhere in the same file.

static const 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 };

 

SO – every 50ms, you call a routine pointing to the buffer, 3 bytes (which is how much of the buffer you want to use without starting again and the number of LEDS you want to light up.  So in my case – and you can do things differently once you get this running and do your own thing – I only put the same value (the first 3 locations in the table) into all the LEDS I want to light.   If you use 30 instead of 3 for 10 LEDs, basically whatever you put into those 30 locations (3 per LED) will be what you see. So I’m just repeating the use of the 3 bytes for all the LEDs I’m lighting up – this has been tested from one LED to 300 LEDs.

So all you need now (and this is the one we’ve been waiting for) is the routine to actually drive the LEDs.

I’ll just say that you need some things to be FAST – so don’t say “why did you use that macro to drive the LEDs – another one is more understandable – because then I’d have trouble lighting the LEDs like others.

You need this at the start of your file.. and it MUST be a static.

static uint32_t rainb12;

and in you’re init do this.

rainb12=1<<12;

I know – why do a calculation at compile time – because it makes it easier for you to understand – it’s port 12 !!  Why not a define? Because you might want to use more than one pin! I’m not getting into that one but this is the right starting point. Here’s the routine. Not original – just fixed from the versions that don’t work.

void ICACHE_FLASH_ATTR SEND_WS_12_0() {
    uint8_t time;
    time = 4;
    while (time--)
        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 4, rainb12);
    time = 9;
    while (time--)
        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 8, rainb12);
}

void ICACHE_FLASH_ATTR SEND_WS_12_1() {
    uint8_t time;
    time = 8;
    while (time--)
        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 4, rainb12);
    time = 6;
    while (time--)
        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 8, rainb12);
}

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

        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 8, rainb12);
        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 8, rainb12);
        WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 8, rainb12);
        while (repetition--) {
            for (i = 0; i < length; i++) {
                uint8_t mask = 0x80;
                uint8_t byte = buffer[i];
                while (mask) {
                    (byte & mask) ? SEND_WS_12_1() : SEND_WS_12_0();
                    mask >>= 1;
                }
            }
        }

    os_intr_unlock();
}

Ok, so 2 routines for setting the output – the first one turns the output OFF then on (more off than on) – the second one does the same – but more on than off… and it is that kind of timing that, fired into the WS2812B that makes them work.

The third routine sends a reset pulse to the chips then loops through – if you study the loop you’ll see what I meant about repeating the same buffer section over and over again if you don’t actually want separate colours per LED.

Now to make sense of this – you need to know how these chips work. So let’s say you have a strip of 10 of them. You have on the input power, signal in and ground. You CAN run these on 3v3 – but good luck getting a high power 3v3 supply and they are brighter on 5v anyway. If you buy the LEDs and make your own strip – be REALLY careful as they die even looking at a large soldering iron. Each LED has an input and an output and you string them together in series.

As you can imagine – flashing them when you have to send a signal from one to the next takes time – that’s why they are FAST and why the code is critical.

So basically a wide pulse resets ALL the LEDs so they are listening for info. You send an RGB value to the first input – it sucks it in and lights itself up – from that point on it merely buffers and passes on the message – so when you fire the next 3 bytes worth – the SECOND LED lights up and from there on passes everything on – rather than replicate the wheel…

Notice I’ve written that reset pulse 3 times – in the original code, there was a flickering I could not explain on the first LED (I’m running ground and signal into one end of the LED strip, +5v and ground into the other end. I extended that reset pulse a little and completely eliminated the issue.

 

timing

So as you can see – the timings are simple but critical – 0, 1 and reset… then send out bunches of 3 bytes per LED. That’s it.

In my code I might send an instruction like – I want purple full brilliance, repeated across 10 LEDs – and take 10 seconds to do it…

rgb.red=255;

rgb.green=0;

rgb.blue=255;

rgbnum=10;

rgbdelay=10;

That’s it – it happens in the background;

Enjoy and if this works for you – be sure to let others know the link!

Around the corner – code that does this, responds to both serial and MQTT commands and allows extending the controls to external Arduinos, handles both types of temperature sensor, a debounced input used to count electricity meter pulses OR to manually over-ride an output, central heating control and much more – all working and all at the same time…. more on this later. We’re even getting a board made that handles the lot and includes a mains power supply.

Facebooktwittergoogle_pluspinterestlinkedin

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

//void ICACHE_FLASH_ATTR SEND_WS_0()
//{
//    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 );
//}

//void ICACHE_FLASH_ATTR SEND_WS_1()
//{
//    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..

*(0x60000300)=1

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

https://github.com/esp8266/esp8266-wiki/wiki/gpio-registers

STUDY THIS… IT WILL REMOVE ALL HEADACHES.

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.

WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + 4, 0x1000 );
WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + 8, 0x1000 );

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…

void ICACHE_FLASH_ATTR SEND_WS_0()
{
    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 );
}

void ICACHE_FLASH_ATTR SEND_WS_1()
{
    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;
    os_intr_lock();
    //GPIO_OUTPUT_SET(GPIO_ID_PIN(WSGPIO), 0);
    WRITE_PERI_REG( PERIPHS_GPIO_BASEADDR + 8, theport );

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;
            }
    }
    }
    os_intr_unlock();
}

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;

    myBuffer[0]=ledTable[reda];
    myBuffer[1]=ledTable[greena];
    myBuffer[2]=ledTable[bluea];
    if (rgbnum==0) rgbnum=1;
    WS2812OutBuffer(myBuffer,3,rgbnum);
    }

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.

Facebooktwittergoogle_pluspinterestlinkedin