Category Archives: PWM

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.

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
}
}

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.

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