As you may have read in Part 1, the thing works. Well, parts of it anyway.
What doesn’t work yet is the PWM of the LEDs.
First off, what’s PWM?
Pulse Width Modulation describes a technique we can use to regulate the power of digital devices like they were analog or to regulate the power of electric motors. While it’s primarily used for these motors there are other applications as well including LED control. With LEDs it works by changing the time the they stay on.
So, how does this help us?
Remember that digital signals have only 2 possible states: on or off, 1 or 0. There is NO in between, never. Otherwise we would be talking about analog signals.
This means that our LED has only 2 possible states as well. It’s either on or off. That’s cool for making simple patterns like blinking or flashing. But what if we want to dim it? Have one LED on, the second off and the third glowing half as bright?
That’s where PWM comes into play.
Basically we start pulsing our LED on and off really fast. Fast enough so our eyes can’t perceive its true state. But there’s more to it! The whole thing doesn’t help us if our LED is 1/1000 of a millisecond OFF and 999/1000 ON. It would still be pretty bright. (99,9% bright to be exact). We have to modulate the pulse width as well.
In our case the PCA9685 (The LED Controller I’m using) counts constantly from 0 to 4095. It never stops till you tell it to. Now, every output on this controller has two 12-Bit registers assigned to it:
– One for on step
– One for off step
E.g. If we set the on step to 1000 and off to 2000 the controller would count from 0 to 1000 then switch the LED on, count to 2000 and switch it off again resulting in 1000/4096 or roughly one quarter of light.
Here’s a diagram from the PCA9685’s datasheet for reference:
Don’t forget that one round from 0 to 4095 happens incredible fast so our eyes will never see the actual pulses like in the diagram. We can only perceive the uniform light.
Now that we understand how PWM works, we can use the PWM function of our PCA9685 to control LED brightness.
If it would work that is…
While I understood how PWM works, talking with the PCA9685 is a whole different matter.
To control the PCA9685 we use I2C. This protocol is standardized and always consists of the following: The bus itself, one or more master devices and a number of slaves. The bus consists of 2 signal lanes; SDA for data and SCL for clock. The master always provides the clock. Which is really just alternating 1s and 0s. Every 1 in the clock cycle fits 1 bit. So after „0101“ you processed 2 bits. Slaves are allowed to stretch(hold the clock down) to indicate they need more processing time till the next byte.
As you might have guessed, the SDA lane is used to send the actual bits.
If we put a probe to each lane and measure it with an oscilloscope the whole thing looks like this (pardon my reflection):
Each command segments consists of one byte and one last ACK bit indicating if the transmission has been acknowledged or not. In this particular example we see one complete transmission and a second one just starting. (constant 1 on the clock line indicates the start/end of the transmission).
We’ll ignore the second transmission and take a closer look at the first which sends 5 bytes over the bus:
ADDRESS REGISTER VALUE REGISTER VALUE S 1000'0110 A 0000'1111 A 0000'0010 A 0000'1110 A 0101'1000 A S LED2_ON_H LED2_ON_L 0x86 0x0F(15) 0x02 0x0E(14) 0x58
If you look at the yellow signal, you’ll hopefully recognize the bits written in the above transmission. You’ll also see the 9th bit(ACK) in each block which is sent back by the slave. 0 means acknowledged while 1 is NOT acknowledged.
This is a very simple transmission (one that doesn’t work by the way, more on that later) which requests that the slave with the address 0x86h writes the two given values to two registers.
Every I2C transmission starts with the 7-bit address of the requested device and the R/W bit(which declares if we want to write(0) or read(1) from the device). If the device with the specified address is on the bus it will respond by pulling the ACK bit down. What follows depends on the device we’re talking to.
In case of the PCA9685; after requesting a write operation it expects the register we want to write to followed by the value. And so on. (There’s also an ‚Auto Increment‘ setting. If enabled we only have to supply the starting register followed by consecutive values going into each register coming after the first).
Coming back to our transmission we see that I’m writing 0x02h and 0x58h to the registers LED2_ON_H and LED2_ON_L. Remember that the PCA works with 12 bits but since one register can only hold 8 we write them separately. The above results in 0x0258h or 600 decimal. It sets the on step of the PWM cycle to 600.
That concludes the first transmission.
The second one does the same thing to LED2_OFF_H and LED2_OFF_L writing 2000 into it, I checked every single bit, twice, it’s correct.
So, the LED should be pretty dim, right? Since it’s only glowing 1400/4096 of the time?
Nah, the PCA9685 acknowledged every single byte just to disregard the whole thing and put full power into it. No PWM, no dimming. Just continues light (Which is pretty bright for those LEDs I’m using).
Turns out the PCA9685 is a bit of a bitch to talk with. Out of fairness I have to mention that I’m not a native English speaker and might’ve understood something wrong in the datasheet.
Either way, it didn’t work. Even if the above transmission is logically sound.
In the end, enabling the previously mentioned ‚Auto Increment‘ and just writing all 4 values directly after another did the trick. Instead of the above I sent:
ADDRESS START-REG VALUE VALUE VALUE VALUE S 1000'0110 A 0000'1110 A 0101'1000 A 0000'0001 A 1111'0110 A 0000'0001 S LED2_ON_L LED2_ON_H LED2_OFF_L LED2_OFF_H 0x0E(14) 0x58 0x02 0xD0 0x07
This time the PCA9685 obeyed my orders and set the correct PWM.
It’s actually possible to take a photograph of it now without just a smear of light in it!
After PWM started working two other things came up which I want to write down in case someone else gets the same problems:
– Do not set the steps backwards! LED_ON has to be smaller than LED_OFF. If it isn’t you’ll get artifacts when changing values. (Flashing lights etc.)
– Check the INVRT bit in the control register if your lights are bright when you programmed them to be dim. INVRT inverts the logic.
Disclaimer: A lot of this is simplified in an attempt to keep it understandable. There are a lot of additional things to consider when you want to use the PCA9685 or I2C in general. „Read The Fucking Manual“, it’s not a joke, do it! Googling ‚PCA9685 datasheet‘ gets you the sheet.
I’ll put a little video in this post later.