The PCA9685 on a PI Clone

PCA9685If you’ve been keeping up with the blog you’ll know that I’ve added to the script, code to allow NodeJS access to I2c.  And you may also know that for some time my ESP8266 code has supported the little (cheap) purple 16-channel 12-bit PWM/Servo boards called PCA-9685.

The price of these varies from £20 if you like being ripped off by someone buying a product in and sticking a horrendous mark-up on it – to £1.29 from AliExpress – mine came from the latter… in fact I have loads of them lying around.

Given that recent successes in “the-script” with GPIO and I2c displays, about the only thing missing from many of the Raspberry-Pi-like boards is PWM. They of course nearly all have a PWM channel – but in most cases there’s no software support.

So – I dedicated what should have been an hour but took all afternoon, to getting this useful little board running on these machines in Node-Red.  I picked the FriendlyArm NEO PLUS2 at random.

I tried a couple of libraries first of course – and managed to get a Python one working but the NodeJS libraries all seemed to rely on the actual Raspberry Pi for some reason – erm – no.

I already new that I2c was running on there because I have a nice OLED display giving me status info.

So before we start – a couple of things – you should be running my script or at least have i2c-tools installed and know that you board is actually there, in my case I2c channel 0 and location 0x40. I also installed python-smbus (sudo apt-get install python-smbus.  Finally make sure that user Pi (assuming you are using Node-Red as user Pi) is part of the i2c group.


To make life as easy as possible – the function handles everything – all you have to do is put the output number (0-15) in msg.topic and the value (0-4095) in the payload – I’m assuming 1Khz frequency but you can change that.

There are probably a ton of better ways to do this – but hey – there’s no library – so if you want to adapt this for another chip – it’s all there in plain view.

I’ve put the code below – mostly cobbled from other code – but hey – it works.

If you study my earlier post on I2c and the above you will have i2c all setup – not just for ROOT users – but for PI as well.

So – top and bottom of the code opens up I2c channel 0 – it might be that i2cdetect on your system tells you channel 1.

There are then 2 functions – set the frequency – which I do for you – and set the channel and PWM value – which fills in from msg.topic and msg.payload.

Easy when you know how – just dump this in a function and pass it the right info  -I’ve put a trap for values over 4095 (full brilliance) and a special for 0 (off).  Now you can, for the addition of under a couple of quid – go from having one I2c output you can’t find on your board – to 16 sparkly new 12-bit channels.  If you’re planning to stick MOSFETS on this to drive a bunch of white LEDs – and if the wires are long – you might want to consider playing with that frequency (i.e. dropping it) – a value of 1 is clearly visible but once you get down a long lead – the pulse is very narrow and can get lost – but there again – you might want that 🙂

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

__MODE1 = 0x00;
__LED0_ON_L = 0x06;
__LED0_ON_H = 0x07;
__LED0_OFF_L = 0x08;
__LED0_OFF_H = 0x09;

var i2c=global.get("i2c");
i2c1 = i2c.openSync(0);

function pwmFrequency(freq)
    var oldmode, newmode, prescale, prescaleval;
    var pwset=new Buffer([0,0,0,0,0,0]);  
    prescaleval = 25000000;
    prescaleval /= 4096.0;
    prescaleval /= freq;
    prescaleval -= 1.0;
    prescale = Math.floor(prescaleval + 0.5);
    oldmode = i2c1.i2cReadSync(64, __MODE1, 1);
    newmode = (oldmode & 0x7F) | 0x10;
    pwset[0]=__MODE1; pwset[1]=newmode;
    i2c1.i2cWriteSync(64, 2, pwset);
    pwset[0]=__PRESCALE; pwset[1]=Math.floor(prescale);
    i2c1.i2cWriteSync(64, 2, pwset);
    pwset[0]=__MODE1; pwset[1]=oldmode;
    i2c1.i2cWriteSync(64, 2, pwset);
    pwset[0]=__MODE1; pwset[1]=oldmode |0x80;
    i2c1.i2cWriteSync(64, 2, pwset);

function pwmSet(channel, pulseoff)
    var pulseon;
    if (pulseoff>4095) pulseoff=4095;
    if (pulseoff===0) pulseon=0;
    var pwset=new Buffer([0,0,0,0,0,0]);   
    pwset[0]=__LED0_ON_L + 4 * channel; pwset[1]=pulseon & 0xFF;
    i2c1.i2cWriteSync(64, 2, pwset);
    pwset[0]=__LED0_ON_H + 4 * channel; pwset[1]=pulseon >> 8;
    i2c1.i2cWriteSync(64, 2, pwset);
    pwset[0]=__LED0_OFF_L + 4 * channel; pwset[1]=pulseoff & 0xFF;
    i2c1.i2cWriteSync(64, 2, pwset);
    pwset[0]= __LED0_OFF_H + 4 * channel; pwset[1]=pulseoff >> 8;
    i2c1.i2cWriteSync(64, 2, pwset);




Leave a Reply

Your email address will not be published. Required fields are marked *