Site icon AranaCorp

Using a PCA9685 module with Arduino

4.1
(15)

The PCA9685 module is a 16-channel controller that can control 16 PWM outputs via I2C communication. It allows, among other things, to free inputs and outputs of your microcontroller and to drive up to 16 LEDs or servomotors (or any other module taking a PWM signal as input) using two pins (SCL and SDA) while keeping the pins of your microcontroller for other modules such as sensors.

Prerequisite: Driving a servomotor with Arduino

Hardware

Principle of operation

The module is based on the PCA9685 controller, which allows PWM outputs to be controlled using I2C communication and an integrated clock. This module has 6 bridges to select the board address and to place on the same bus up to 62 controllers for a total of 992 actuators (available addresses 0x40 to 0x7F).
It can drive PWM outputs with adjustable frequency and 12-bit resolution. The module is compatible with 5V and 3.3V microcontrollers.

Diagram

The module is equipped with an I2C bus and a power input. The I2C bus is connected as follows:

Code

To use the PCA9685 module, we use the library Adafruit_PWMServoDriver.h. PWM widths are usually given in microseconds over a period of 20ms (50Hz) but these values can change from one actuator to another and between vendors. You will have to modify the values in the code to adapt them to your actuator.
In our case, we use the MG90S actuator whose ranges are 400-2400µs over 20ms.

To set the PWM command, the library provides two functions: setPWM() and writeMicroseconds(). The writeMicroseconds() function allows us to use the constructor values directly. For the setPWM function, we need to find the corresponding pulse width on 4096 (2^12, 12bits).

Example: The frequency is 50Hz so a period of 20ms or 20000µs.

Which gives us the following equivalence:

It’s up to you to use the function that suits you best.

//Libraries
#include <Wire.h>//https://www.arduino.cc/en/reference/wire
#include <Adafruit_PWMServoDriver.h>//https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library

//Constants
#define nbPCAServo 16

//Parameters
int MIN_IMP [nbPCAServo] ={500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500};
int MAX_IMP [nbPCAServo] ={2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500};
int MIN_ANG [nbPCAServo] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int MAX_ANG [nbPCAServo] ={180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180};

//Objects
Adafruit_PWMServoDriver pca= Adafruit_PWMServoDriver(0x40);

void setup(){
//Init Serial USB
Serial.begin(9600);
Serial.println(F("Initialize System"));
pca.begin();
pca.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
}

void loop(){
  pcaScenario();
}

void pcaScenario(){/* function pcaScenario */ 
////Scenario to test servomotors controlled by PCA9685 I2C Module
for (int i=0; i<nbPCAServo; i++) {
  Serial.print("Servo");
  Serial.println(i);
      //int middleVal=((MAX_IMP[i]+MIN_IMP[i])/2)/20000*4096; // conversion µs to pwmval
      //pca.setPWM(i,0,middleVal);
      for(int pos=(MAX_IMP[i]+MIN_IMP[i])/2;pos<MAX_IMP[i];pos+=10){
        pca.writeMicroseconds(i,pos);delay(10);
      }
      for(int pos=MAX_IMP[i];pos>MIN_IMP[i];pos-=10){
        pca.writeMicroseconds(i,pos);delay(10);
      }
      for(int pos=MIN_IMP[i];pos<(MAX_IMP[i]+MIN_IMP[i])/2;pos+=10){
        pca.writeMicroseconds(i,pos);delay(10);
      }
      pca.setPin(i,0,true); // deactivate pin i
    }
}

int jointToImp(double x,int i){/* function jointToImp */ 
////Convert joint angle into pwm command value
     
      int imp=(x - MIN_ANG[i]) * (MAX_IMP[i]-MIN_IMP[i]) / (MAX_ANG[i]-MIN_ANG[i]) + MIN_IMP[i];
      imp=max(imp,MIN_IMP[i]);
      imp=min(imp,MAX_IMP[i]);
      
 return imp;
}

Math behind jointToImp

The function joinToImp() does the same as the map() function in Arduino. It converts a value from a range to another following a linear equation.

  imp=map(x,MIN_ANG,MAX_ANG,MIN_IMP,MAX_IMP);

We know that the relation between the angle and the impulsion width is linear, thus, we can describe this equation by

imp = a * x + b

The idea is to find the coefficients a and b. For this we need two equations. what we know is that :

Which leads to the following equations:

MIN_IMP = a * MIN_ANG + b

MAX_IMP = a * MAX_ANG + b

Working around those two equations, we can solve a and b in two steps:

MAX_IMP - MIN_IMP = a * (MAX_ANG - MIN_ANG)

a = (MAX_IMP - MIN_IMP)/(MAX_ANG - MIN_ANG)

MIN_IMP = (MAX_IMP - MIN_IMP)/(MAX_ANG - MIN_ANG) * MIN_ANG + b

b = MIN_IMP - (MAX_IMP - MIN_IMP)/(MAX_ANG - MIN_ANG) * MIN_ANG

We can then deduce the conversion equation

y = (MAX_IMP - MIN_IMP)/(MAX_ANG - MIN_ANG) * x + MIN_IMP - (MAX_IMP - MIN_IMP)/(MAX_ANG - MIN_ANG) * MIN_ANG

y = (x - MIN_ANG) * (MAX_IMP - MIN_IMP)/(MAX_ANG - MIN_ANG) + MIN_IMP

Applications

Sources

Find other examples and tutorials in our Automatic code generator
Code Architect

How useful was this post?

Click on a star to rate it!

Average rating 4.1 / 5. Vote count: 15

No votes so far! Be the first to rate this post.

Exit mobile version