16 Channels of PWM for ESP8266

PCA9685This morning a little board turned up for me – the advert said “Smart Electronics” – but the board says Adafruit 16-channel 12-bit PWM board. At £1.38 each these represent EXCELLENT value – especially as the Adafruit originals are much more expensive. Nicely made with gold connections and all the connectors provided.

Actually when I bought these I was unaware of the Adafruit product – I didn’t see the likeness until I went looking for code to modify for my own use.

http://www.aliexpress.com/item/Smart-Electronics-PCA9685-16-Channel-12-bit-PWM-Servo-Driver-I2C-Interface-for-Arduino-Raspberry-Pi/32464046768.html?spm=2114.13010608.0.73.QEOjVb

https://www.adafruit.com/product/815

(the Adafruit board is blue, the one I have is dark purple) – In essence a very nice little  board which takes in 5v and I2C (in this case software I2C on the ESP8266) and gives out up to 16 channels of 12-bit PWM at up to 1,600hz without external clocks etc.

So right now I’ve added only basic control – a single command that has two uses – one to set up the board, the second to control individual channels. I’ve added this to the ESP8266 home control.

The command works like this – two examples – one for setup, one to control output 0 (ie bit 0 as you can control multiple bits at once) – and assuming device 0x40 (decimal 64)

{pca9685:64,0,1600}

{pca9685:64,1,4000}

where 0 is OFF and 4095 would be full on.

Here’s another example – set all 16 outputs to 100

{pca9685:64,0xffff,100}

PWM ControlI checked out the Adafruit code for Arduino and made a very simplified version – I’m not sure I fully understand the two parameters (ON and OFF – the last two) because setting the first to 0 seems to give the full range using the last parameter only – maybe someone who’s already used this might enlighten us. Anyway, it works, reliably and it’s available. I’ve updated the source and the ROMS.

To test, I wired from the ESP ground, GPIO4 and 5 (already connected to an I2c device with pullups) and Vcc. I also connected +5v (that goes to the 5v rail on the board) and then I connected a LED to the +5v rail and PWM output 0.  really very simple. I guess what I need is some kind of timer-based control to allow slow ramping up and down of brilliance – which would mean you could arrange at least 16 channels of lighting from the ESP8266. Mind you – you can do that with serial RGB LEDS but they’re quite expensive compared to other lighting.

In the photo above I connected 8 outputs to +V and a bar-led – I bought these for testing just like this – as you can see I’ve put values from 2000 to 4060 in there and there’s a nice variation of brilliance on all of them. The speed this is working (1,600hz) in the background (all done by the chip) – waggling the display does not produce any kind of strobing effect). As for levels – at the very dimmest levels with a bright LED you can just tell the steps – but WAY, WAY better than simple 8-bit PWM.

All works – if anyone wants to take the software further who’s already been there, happy to incorporate any working additions.

Follow the Adafruit link to get their Arduino code – here (minus the ESP master library) is what’s left once I get what I wanted…

This is all in the home control software – just reproduced here so you can see what’s involved, maybe point out any improvements etc.

[pcsh lang=”cpp” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

uint8_t PWMAddr=0x40;
#define PCA9685_MODE1 0x0
#define PCA9685_PRESCALE 0xFE

#define LED0_ON_L 0x6
#define LED0_ON_H 0x7
#define LED0_OFF_L 0x8
#define LED0_OFF_H 0x9

#define ALLLED_ON_L 0xFA
#define ALLLED_ON_H 0xFB
#define ALLLED_OFF_L 0xFC
#define ALLLED_OFF_H 0xFD

void IFA pwmWrite(unsigned char addr,unsigned char d)
{
i2c_master_start();
i2c_master_writeByte(PWMAddr << 1);
if (!i2c_master_checkAck())
{
i2c_master_stop();                   // End I2C communication
iprintf(RESPONSE, "Bad PCA9685 I2C\r\n");
}
else
{
i2c_master_writeByte(addr);
i2c_master_checkAck();
i2c_master_writeByte(d);
i2c_master_checkAck();
i2c_master_stop();                   // End I2C communication
}
}

uint8_t IFA pwmRead(uint8_t addr)
{
uint8_t a;
i2c_master_start();
i2c_master_writeByte(PWMAddr << 1);
if (!i2c_master_checkAck())
{
i2c_master_stop();                   // End I2C communication
}
else
{
i2c_master_writeByte(addr);
i2c_master_checkAck();
i2c_master_stop();     i2c_master_start();
i2c_master_writeByte((PWMAddr << 1)|1);
if (!i2c_master_checkAck())
{
i2c_master_stop();
}
else
{
a = i2c_master_readByte();
i2c_master_stop();
}
}
return a;
}

int IFA ifloor(float x) {
int xi = (int)x;
return x < xi ? xi - 1 : xi;
}

void IFA pwmFrequency(uint8_t chipAddr, float freq)
{
PWMAddr=chipAddr;
pwmWrite(PCA9685_MODE1,0); // saves a reset function
freq *= 0.9;  // Correct for overshoot in the frequency setting
float prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
uint8_t prescale = ifloor(prescaleval + 0.5);
uint8_t oldmode = pwmRead(PCA9685_MODE1);
uint8_t newmode = (oldmode&0x7F) | 0x10; // sleep
pwmWrite(PCA9685_MODE1, newmode); // go to sleep
pwmWrite(PCA9685_PRESCALE, prescale); // set the prescaler
pwmWrite(PCA9685_MODE1, oldmode);
os_delay_us(5000);
pwmWrite(PCA9685_MODE1, oldmode | 0xa1);  //  This sets the MODE1 register to turn on auto increment.
}

void IFA pwmSet(uint8_t chipAddr,uint8_t num, uint16_t on, uint16_t off)
{
PWMAddr=chipAddr;
i2c_master_start();
i2c_master_writeByte(PWMAddr << 1);
if (!i2c_master_checkAck())
{
i2c_master_stop();                   // End I2C communication
}
else
{
i2c_master_writeByte(LED0_ON_L+4*num);
i2c_master_checkAck();
i2c_master_writeByte(on);
i2c_master_checkAck();
i2c_master_writeByte(on>>8);
i2c_master_checkAck();
i2c_master_writeByte(off);
i2c_master_checkAck();
i2c_master_writeByte(off>>8);
i2c_master_checkAck();
i2c_master_stop();                   // End I2C communication
}
}

[/pcsh]

And so there it is – working 16-channels of PWM (or 32 or more) added to the home control setup so you can control these lights via MQTT or serial via a simple command.

If anyone wants to tinker – the lights all start up as ON – I’d rather they started up as OFF and I’d also like a master ON/OFF. Ok, I could do it the hard way I guess.

I could see a daughter board coming up with 16 MOSFETS on it… actually you could just use it for on-off control if you wanted  – at the price.

One of the BIG benefits for me is – the Espressif PWM is simply not that good – I use it – but for example, you cannot use that AND I2c at the same time because both the Espressif implementation and another I’ve tried both continue to mess with interrupts in the background even when on 100% or off 100%.  This neatly bypasses the issue.

Facebooktwitterpinterestlinkedin

41 thoughts on “16 Channels of PWM for ESP8266

    1. I don’t get the drive in some quarters to use the ESP32… I agree the PWM lighting is a good idea though 12v lamps are not as readily available as 220v lamps. I would use the 6 analog channels of my universal peripheral alongside an ESP8266 using ESP-GO.

  1. Thanks! I’ve been spending the last day or so trying (without success) to adapt the adafruit pca9685 library to use the espressif i2c implementation 🙂

    1. Hi Stewart. Well, the Espressif I2c with some speeding up has worked well for me until I came across the INA219 (see recent posts on the blog) and not only could I not use that I could not SEE it in a scan – so I modified another version of I2c (my source is out there on GIT but not stunningly well documented) and that worked perfectly for both scanning and reading and as a background job I’m in the process of converting over other boards to the newer code so I can get a little space back by ditching the original – both of course are software-only implementations of I2c.

  2. Can you please give an example code and hardware changes needed to connect multiple pca9685 devices in chain

    1. Training is not something I do, Omkar, however as should be obvious from the article, the default address is 64 and I use that in the command. If you want multiple devices, just change the address for the other devices (and adjust links accordingly on the product). And call them individually by their address.

  3. Hi Pete. Two questions: 1) Is it possible to use this interface to read values from a current sensor and a voltage sensor (both analog) into the ESP? 2) Is there a version with less channels? I only need 2 🙂 Cheers

  4. I grabbed a couple of these boards from the vendor at the end of the link above and although I have not got as far as driving them via i2C yet, I have found a couple of issues on first power-up test:

    I am assuming that the boards were built with reference to the schematics and PCB design published by Adafruit (although the actual layout of these boards is not 100% identical and the reverse polarity protection FET is far less beefy), but the current limiting resistor for the power indicator LED is only 120 ohms (rather than Adafruit’s more sensible 220); this means a theoretical LED current of around 40mA with a 5V supply and that is at least 2x what it should be.

    In any event, that is perhaps academic because the anode pad of the LED, which should be connected to board Vcc, is not going anywhere and so the LED won’t light up anyway!

    I could rework both these problems, but for anyone powering up their board and not seeing the LED light up, keep going because the rest of the board might be OK! I’ll get in touch with the vendor and let you know what they say.

    1. So I checked on my Chinese board – and it is in fact getting it’s power to the LED from 5v – which then goes through the LED, through the resistor to ground – and technically that is a tad excessive, personally I’d have put a 1k resistor on there as I don’t see the point of super-bright power lights – however, it really isn’t a problem. The Chinese board works a treat, I’ve had it on test here for some time and represents very good value for money. My LED lights up perfectly so I think you got a duffer – surprising as the build quality on mine is good. I’d imagine you’ll get a refund though the Chinese seem particularly keen on you sending a video to prove it is not working these days – probably not worth the effort 🙂

    2. Edit: Gah – I wrote that piece after a long day. Factoring in LED Vf, the current would be closer to 28mA. Still too high.

  5. Maybe the question is stupid but… Can this board be used as input insted of outputs?

    1. No but I’ve already discussed in here an 8 channel IO expander – and I believe I have a 16-channel IO expander on the way – that would give inputs. This board is specifically designed to give 16 PWM outputs without any load on your processor.

      1. thanks Peter, indeed I’ve see your other post about the 8 channel module and I will look forward your article on the 16 channel one (I’m very interested in those expanders, not only as they can cope with limiten pin of esp8266 but as well it’s very nice to “remote” such amount of input/output runnign just 4-wires cable!)

        1. Giovanni,

          Probably not very “remote”. I2C doesn’t tend to work very well over long wires. You can reduce the value of the pull-up resistors to lower the impedance of the bus, but you start running into the law of diminishing returns fairly quickly (too much current for too little improvement).

          1. Thanks PuceBaboon, of course I’m not talking of hundreds of meter, but in my case being able to have arduino/esp8266 in a wall box and this expander 2/3 meters away is a great help 🙂

  6. Pete,

    Nice find. Thanks for the write-up. I have ordered a few boards.

    Looking around my crib, I notice I have amassed a fair few plug-in 433MHz power switches over the years and I am currently sniffing their output using an ESP8266 running the Arduino rc-switch code library from https://github.com/sui77/rc-switch with a view to doing something ‘MQTT’ with them. Ever considered adding a control mechanism for these devices to your code?

        1. The Sonoffs are indeed cheap – and it is not impossible to replace the sadly small FLASH on them – I’ve a pile of chips ready to go with the pile of Sonoffs I have here – waiting for a rainy day.

  7. Great info Pete, I’m going to order a couple just for the fun of testing (but I’m sure they will be utilized in some of my setup).
    Small question: as mentioned we have on sale same board with capacitor; what is the difference? Buying the one without shoudl I have to add one or I can use the board “as it is” without any soldered?

    Sorry for the eventually “noob” question 🙂

    1. Cap not needed for one off use. Have fun. With more feedback I may expand features. Need fast clear to off in setup.

  8. Peter, Wouldn’t it be more logical/people friendly to have 0 as Off and then ramp up from there ? Just reverse the logic in your code ?

    Craig

    1. Of course it would… I just followed the Adafruit logic as I was short of time – actually one parameter for pulse width would go rather than two really – with 0 at off and 4095 as on. Well the source is there – fire away with ideas – I’m back tomorrow night – if they look good – I’ll update the code.

    2. Ok, I changed it – 3 parameters and output value is 0-4095 – also you can now set as many of the outputs at once as you like as the pin is a bit pattern.
      So to set outputs 1 and 2 to 50..

      {pca9685:64,3,50}

      That’s a lot faster and easier.

      1. When you have boards like this, if you use multiple i2c, you desolder all the pull-up resistors except 1? Or what?

  9. Typo: 0x40 is 64 decimal, but commands report 65… Interesting board, I’ve 2 of them, 1 as yours, an other as an arduino shield…

    Peter, do you know if the arduino node allows i2c via Firmata? If yes we could use an arduino as extension…

    1. I CAN handle hex on the line parser but I figured decimal might be easier for people…. if you’ve a unit reporting 65 – you have a short….

      I’ve no reason to go down the Arduino route when the ESP can do as I am slowly finding out – just about everthing the Arduino can do – not quite got that Adafruit full graphics library running without Arduino – but give it time.

      1. Understood… I spotted the typo just to have coherent info between description and code

        Right on arduino, was just saying it’s an other option

Comments are closed.