NAVIO: RGB LED and servos with PCA9685 [C++, Python]

This document is not updated, please proceed to docs.emlid.com for latest version!

PCA9685 is a PWM generator chip that can be used to control servos and LEDs. It features:

  • 16 channels with separate control
  • 12-bit resolution
  • Configurable frequency
  • I2C operation up to 1MHz
  • Output enable pin

PCA9685 is clocked by the 24.576MHz TCXO oscillator and allows to adjust frequency using the PRE_SCALE register. Rising and falling edges for each channel can be set from 0 to 4095 allowing to control duty cycle as well as phase.

Controlling RGB LED

Channels 0, 1, 2 are connected to Navio’s onboard RGB LED. Since that is a LED with a common anode the logic is inverted – lowest value (0) will result in max brightness for the channel and vice versa.

Controlling servos

Channels 3-15 are available on 2.54mm header pins numbered 1-13 accordingly. Servos can be controlled by setting the correct frequency (50hz is a common frequency) and duty cycle corresponding to the length of a pulse (usually between 1 and 2 milliseconds).

To try to control servo connect the servo to the Navio’s output channel number 1 and run the provided example.

[C++]

LED example

Move to the folder with the source code, compile and run the example

cd Navio/C++/Examples/LED
make
sudo ./LED

In this example channels values for RGB LED are slowly decreased and increased creating the mix of different colors.

#include "Navio/PCA9685.h"

int main()
{
    PCA9685 pwm(RASPBERRY_PI_MODEL_B_I2C, PCA9685_DEFAULT_ADDRESS);
    // PCA9685 pwm(RASPBERRY_PI_MODEL_A_I2C, PCA9685_DEFAULT_ADDRESS);
    
    pwm.initialize();

    uint16_t R = 0, G = 0, B = 4095;

    pwm.setPWM(0, R);
    pwm.setPWM(1, G);
    pwm.setPWM(2, B);

    while (true) {
        for (R = 0; R < 4095; R++)             
        pwm.setPWM(0, R);         
        for (B = 4095; B > 0; B--)
        pwm.setPWM(2, B);

        for (G = 0; G < 4095; G++)             
        pwm.setPWM(1, G);         
        for (R = 4095; R > 0; R--)
        pwm.setPWM(0, R);

        for (B = 0; B < 4095; B++)             
        pwm.setPWM(2, B);         
        for (G = 4095; G > 0; G--)
        pwm.setPWM(1, G);
    }

    return 0;
}

Servo example

Move to the folder with the source code, compile and run the example

cd Navio/Examples/Servo
make
sudo ./Servo

In this example we perform the following:

  1. Initialize the PCA9685
  2. Set the frequency
  3. Set the channel’s value thus controlling the pulse length.

To set the pulse range appropriate for your servo you can change the SERVO_MIN and SERVO_MAX values.

#define NAVIO_RCOUTPUT_1 3
#define SERVO_MIN 1.250 /*mS*/
#define SERVO_MAX 1.750 /*mS*/

#include "Navio/PCA9685.h"

int main()
{
    PCA9685 pwm(RASPBERRY_PI_MODEL_B_I2C, PCA9685_DEFAULT_ADDRESS);
    // PCA9685 pwm(RASPBERRY_PI_MODEL_A_I2C, PCA9685_DEFAULT_ADDRESS);


    pwm.initialize();
    pwm.setFrequency(100);

    while (true) {
        pwm.setPWMmS(NAVIO_RCOUTPUT_1, SERVO_MIN);
        sleep(1);
        pwm.setPWMmS(NAVIO_RCOUTPUT_1, SERVO_MAX);
        sleep(1);
    }

    return 0;
}


[Python]

LED example

Move to the folder with the code and run the example

cd Navio/Python/LED
sudo python LED.py
Servo example

Move to the folder with the code and run the example

cd Navio/Python/Servo
sudo python Servo.py

 

PCA9685 registers

Here’s a short description of the registers:

0x00 – MODE1 – controls sleep, restart, auto-increment, external clocking, addressing

0x01 – MODE2 – controls inversion, output condition, drive type

Settings for additional addresses

0x02 – SUBADDR1

0x03 – SUBADDR2

0x04 – SUBADDR3

0x05 – ALLCALLADDR

Channel control registers: 

0x06 – LED0_ON_L – low byte of 12-bit word for rising edge of channel 0

0x07 – LED0_ON_H – high byte of 12-bit word for rising edge of channel 0

0x08 – LED0_OFF_L – low byte of 12-bit word for falling edge of channel 0

0x09 – LED0_OFF_H – high byte of 12-bit word for falling edge of channel 0

Registers for other channels are similar and their addresses are shifted by 4 per channel, for example, channels for LED1 are: 

0x0A – LED1_ON_L

0x0B – LED1_ON_H

0x0C – LED1_OFF_L

0x0D – LED1_OFF_H

To control all channels at once it is possible to use ALL_LED registers: 

0xFA – ALL_LED_ON_L

0xFB – ALL_LED_ON_H

0xFC – ALL_LED_OFF_L

0xFD – ALL_LED_OFF_H

To set the frequency use the following register:

0xFE 0 PRE_SCALE – minimal value is 3

How does the PCA9685 C++ driver work?

At first, we call for initialize() method which restarts the device by first setting the SLEEP_BIT to ‘0’ in MODE1 register and then setting the RESTART_BIT to ‘1’ in the same register. Also, it sets the AI_BIT to ‘1’ which allows to perform multi byte I2C operations for faster data transfer.

setFrequency() method is used to control the output frequency. It calculates the prescale value based on the required frequency and writes it to the PRE_SCALE register.

setPWM() method allows to set the duty cycle from 0 to 4095 for the output channel. It can be used to control LED’s brightness. It writes four bytes of data representing the rising edge (LED_ON) and falling edge cycles (LED_OFF) to the following registers: LEDx_ON_L, LEDx_ON_H, LEDx_OFF_L, LEDx_OFF_H where x is the output channel number.

setPWMmS() or setPWMuS() methods allow to set the pulse length of the output channel. It can be used to control servos or other PWM-controlled devices. Internally, they use setPWM() method to set the duty cycle which is calculated based on the required length and the clock frequency.

To get more information about PCA9685 please refer to the PCA9685 data sheet.