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.

I2c PWM

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;
__PRESCALE = 0xFE;
__LED0_ON_L = 0x06;
__LED0_ON_H = 0x07;
__LED0_OFF_L = 0x08;
__LED0_OFF_H = 0x09;
__ALLLED_ON_L = 0xFA;
__ALLLED_ON_H = 0xFB;
__ALLLED_OFF_L = 0xFC;
__ALLLED_OFF_H = 0xFD;

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;
    pulseon=4095-pulseoff;
    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);
}


pwmFrequency(1000);
pwmSet(Number(msg.topic),Number(msg.payload));
i2c1.closeSync();

[/pcsh]

6 thoughts on “The PCA9685 on a PI Clone

  1. I did the same as You posted in both posts, this one and i2c post… i didnt change anything in code ( except buss… mine is 1) and node red is getting me:
    Error: Invalid buffer 1
    Any ideas ?

  2. Ok, sort this part
    i2c1.i2cWriteSync(64, 2, pwset);
    Exacly 64 is a decimal address value, and hex would be 0x40 am i right?
    When changing Dec 64 to something elsse i can make couple of function nodes with different addresses?

  3. Hello,
    I’ve read Your post about the usage of PCA9685, i would like to implement it in my node-red flow, but i cannot see where You have address of PCA module?
    At the moment i am using only one PCA with 0x40 address but in later time i would like to use couple of them. Thank You in advance for Your reply.

    1. I may not have implemented address control. trivial to add… I was keeping things simple

      1. So how does it recognizes the PCA? What about other devices connected to the same bus?

Comments are closed.