PCA9685 Controllo I2C a 16 canali PWM per LED e Servomotori

PCA9685 Controllo I2C a 16 canali PWM per LED e Servomotori

Il chip della NXP Semiconductor PCA9685 è in grado di comandare, in maniera indipendente, fino a 16 canali output PWM, con una risoluzione di 12 bit (4096 livelli).
Possiede una porta di comunicazione I2C che consente un facile utilizzo con qualunque microcontrollore che dispone di tale interfaccia, fra i quali ci sono quelli della famiglia Arduino.
Il PCA9685 nasce per poter pilotare i LED, ma opportunamente configurato sarà in grado di gestire anche i servomotori.

Le principali caratteristiche sono:

Alimentazione da 3 V a 5.5 V
bus I2C (indirizzo di default 0x40 modificabile con appositi jump)
16 canali di uscita a 12 bit (4096 valori)
frequenza del PWM programmabile da 24Hz fino a 1526 Hz
ogni canale d’uscita eroga max 25mA a 5V (se si necessita di più corrente andrà collegato all’uscita un BJT od un MOSFET che possa lavorare alle frequenze impostate)
duty cycle 0% – 100%
Tutti gli output devono essere alla stessa frequenza.

Maggiori informazioni si possono trovare direttamente sul sito del costruttore qui.
Il datasheet è scaricabile da qui.

Il PCA9685 sui principali siti di ecommerce si trova già montato su una scheda simile a questa:

Piedini di alimentazione:
GND – questi piedini sono tutti in comune.
VCC – piedino di alimentazione direttamente connesso al PCA9685 (l’alimentazione può andare da 3 a 5 VCC) .
V+ – questi pin sono tutti in comune. V+ è opzionale e serve solo ad alimentare gli eventuali servomotori collegati (solitamente si utilizza una tensionedi di 5/6 VCC, ma si può arrivare fino a 12 VCC).

Piedini di controllo:
SCL – I2C clock, si può usare con la logica a 3V o 5V ed ha già una resistenza di pullup da 10k collegata a VCC.
SDA – I2C data ed ha le stesse caratteristiche del pin SCL.
OE – Output Enable. Si può utilizzare per disabilitare tutte le uscite. Se messo alto le disabilita, se messo basso le abilita. Normalmente è messo basso con una resistenza. Questo pin è opzionale e si può anche non utilizzare.

Output Ports:
Sono disponibili 16 porte di uscita. Ogni porta ha 3 pin: V +, GND e l’uscita PWM. Ogni PWM funziona in modo completamente indipendente, ma devono avere tutti la stessa frequenza. Quindi ad esempio dato che per i LED solitamente si utilizza una frequenza di 1.0 KHz, ma per i servomotori di solito se ne usa una a 60 Hz non si possono utilizzare metà uscite per i LED a 1,0 KHz e metà a 60 Hz per i servomotori.
La corrente massima per pin di uscita è di 25 mA ed in serie alle uscite sono collegate delle resistenze da 220 ohm. Inoltre la logica di uscita è la stessa di VCC.

Per Arduino esistono anche delle Shield con il PCA9685, di seguito due esempi:

Il loro funzionamento è simile alla scheda descritta sopra, fatta eccezione per:
– il piedino OE solitamente non viene gestito
– l’alimentazione sul morsetto non deve suparare i 5V e alimenta sia il Chip PCA9685 che i Servomotori (o i Diodi LED).

Per utilizzare con semplicità il PCA9685 ci viene incontro la libreria creata da Adafruit, la Adafruit PWM Servo Driver Library, che può essere scaricata dal loro github da qui, oppure una copia dal nostro sito da qui.

Aggiungiamo la libreria appena scaricata all’IDE di Arduino andando su “Sketch” –> “#include libreria” –> “Aggiungi libreria da file .ZIP…” e selezionare la libreria scaricata.

NOTA: dopo l’installazione della libreria potrebbe essere necessario riavviare l’IDE.

UTILIZZO DEL PCA9685 ED ARDUINO PER PILOTARE I SERVOMOTORI

Per l’uso con un Arduino Uno si può utilizzare il seguente schema:
Ora andiamo su “File” –> “Esempi” –> “Adafruit PWM Servo Driver Library” –> “servo” e avremo il seguente codice (di seguito sono stati rimossi alcuni commenti per renderlo più snello):

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); //default address 0x40
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);

#define SERVOMIN  150 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  600 // This is the 'maximum' pulse length count (out of 4096)
#define USMIN  600 // This is the rounded 'minimum' microsecond length based on the minimum pulse of 150
#define USMAX  2400 // This is the rounded 'maximum' microsecond length based on the maximum pulse of 600
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

// our servo # counter
uint8_t servonum = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("8 channel Servo test!");

  pwm.begin();
  pwm.setOscillatorFrequency(27000000);
  pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates

  delay(10);
}

void setServoPulse(uint8_t n, double pulse) {
  double pulselength;

  pulselength = 1000000;   // 1,000,000 us per second
  pulselength /= SERVO_FREQ;   // Analog servos run at ~60 Hz updates
  Serial.print(pulselength); Serial.println(" us per period");
  pulselength /= 4096;  // 12 bits of resolution
  Serial.print(pulselength); Serial.println(" us per bit");
  pulse *= 1000000;  // convert input seconds to us
  pulse /= pulselength;
  Serial.println(pulse);
  pwm.setPWM(n, 0, pulse);
}

void loop() {
  Serial.println(servonum);
  for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);
  for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);

  for (uint16_t microsec = USMIN; microsec < USMAX; microsec++) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);
  for (uint16_t microsec = USMAX; microsec > USMIN; microsec--) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);

  servonum++;
  if (servonum > 7) servonum = 0; // Testing the first 8 servo channels
}

Le voci che più ci interessano sono:

#define SERVOMIN 150
#define SERVOMAX 600
Con questi due valori si indicano i valori minimi e massimi per far funzionare il servo da 0 a 180 gradi.
ATTENZIONE.: si deve far attenzione a quali parametri si utilizzano, altrimenti potremmo anche danneggiare il servo!

pwm.setPWM(servonum, 0, pulselen);
Con questo si inviano i parametri alla scheda (numero del canale, 0, pulselen). Nel nostro caso pulselen andrà da 150 a 600. Invieremo così la lunghezza del segnale PWM, che il servomotore interpreterà come una posizione angolare dello stesso.

Se vogliamo utilizzare i gradi al posto di pulselen, basterà utilizzare il comando MAP di Arduino e creare una mappa di corrispondaza gradi / lunghezza impulso, così:

int angolo = 0; // da 0 a 180
int posizione = map(angolo, 0, 180, SERVOMIN, SERVOMAX);
pwm0.setPWM(servonum, 0, posizione);

Quindi uno sketch di esempio che utilizza i gradi potrebbe essere il seguente:

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm0 = Adafruit_PWMServoDriver();

#define SERVOMIN  120
#define SERVOMAX  470
#define SERVO_FREQ 50

int servonum = 0; // da 0 a 15
int posizione;
int angolo;

void setup() {
  pwm0.begin();
  pwm0.setOscillatorFrequency(27000000);
  pwm0.setPWMFreq(SERVO_FREQ);
  delay(10);
}

void loop() {
  angolo = 0;
  posizione = map(angolo, 0, 180, SERVOMIN, SERVOMAX);
  pwm0.setPWM(servonum, 0, posizione);
  delay(2000);

  angolo = 90;
  posizione = map(angolo, 0, 180, SERVOMIN, SERVOMAX);
  pwm0.setPWM(servonum, 0, posizione);
  delay(2000);

  angolo = 180;
  posizione = map(angolo, 0, 180, SERVOMIN, SERVOMAX);
  pwm0.setPWM(servonum, 0, posizione);
  delay(2000);

  angolo = 90;
  posizione = map(angolo, 0, 180, SERVOMIN, SERVOMAX);
  pwm0.setPWM(servonum, 0, posizione);
  delay(2000);
}

In pratica fa muovere il servo da 0° a 90°, poi a 180° ed infine a 90° per ripartire da 0°.

Più approfonditamente la funzione pwm.setPWM necessita di 3 parametri:

pwm.setPWM(channel, on, off)

Questa funzione imposta l’inizio (on) e la fine (off) del segmento alto dell’impulso PWM su un canale specifico. Specificare il valore tra 0..4095 quando il segnale si accenderà e quando si spegnerà. Il canale indica quale delle 16 uscite PWM deve essere aggiornata con i nuovi valori.
Quindi:

channel: il canale che deve essere aggiornato con i nuovi valori (0..15)
on: il valore (tra 0..4095) quando il segnale deve passare da basso ad alto
off: il valore (tra 0..4095) quando il segnale deve passare da alto a basso

Nota: Per pilotare un servomotore il valore di on sara 0 mentre il valore di off sarà quello di pulselen.

Utilizzare i canali come GPIO
(non utilizzare questa funzione con i canali collegati ai servomotori!)

Ci sono anche alcune impostazioni speciali per accendere o spegnere completamente i pin ed utilizzarli come se fossero un’estensione dei piedino OUTPUT di arduino:

È possibile impostare il pin in modo che sia completamente attivo

pwm.setPWM (channel, 4096, 0);

È possibile impostare il pin in modo che sia completamente spento

pwm.setPWM (channel, 0, 4096);

UTILIZZO DEL PCA9685 ED ARDUINO PER PILOTARE I LED

Un esempio di schema da utilizzare potrebbe essere il seguente:

Prima di collegare i LED alla scheda con il  PCA9685 ci dobbiamo ricordare che:
– in serie ad ognuna delle 16 uscite c’è collegata una resistenza di 200 ohm,
– che il livello di tensione di uscita dei 16 canali è uguale a quello dell’ingresso VCC,
– che la massima corrente erogabile da ogni uscita è 25 mA.

Quindi, per esempio, nel nostro caso avremo un livello di uscita a 5 V e, dato che normalmente per alimentare un led rosso a 5 V occorre una resistenza di 330 ohm, dovremo mettere in serie all’uscita una resistenza di circa 130 ohm (330-200=130).
Il valore non è vincolante e potremo usare un valore che va da 100 a 150 ohm.

Lo sketch seguente farà aumentare e diminuire la luminosità del led del canale 0 dal minimo al massimo con una pausa di 2 secondi tra un picco e l’altro.

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

int channel = 0;
void setup() {
  pwm.begin();
  pwm.setOscillatorFrequency(27000000);
  pwm.setPWMFreq(1600);  // This is the maximum PWM frequency
}

void loop() {
  for (int i = 0; i < 4096; i ++) {
    pwm.setPWM(channel, 0, i );
  }
  delay(2000);
  for (int i = 4096; i > 0; i --) {
    pwm.setPWM(channel, 0, i );
  }
  delay(2000);
}

UTILIZZO DI PIU’ PCA9685 CONTEMPORANEAMENTE

Per utilizzare più di un modulo PCA9685 sullo stesso canale i2C si dovrà assegnare ad ogni scheda il proprio indirizzo i2c (che di default è 0x40).

Per farlo si dovrà pondicellare, con una goggia di stagno, le opportune piazzole visibili nella foto qui sotto.

Per programmare l’offset dell’indirizzo, collegare il ponticello dell’indirizzo corrispondente per ciascun ‘1’ binario nell’indirizzo.
Così ad esempio:
l’indirizzo 0x40 = binario 00000 (nessun ponticello necessario)
l’indirizzo 0x41 =  binario 00001 (ponticello su A0)
l’indirizzo 0x42 =  binario 00010 (ponticello su A1)
l’indirizzo 0x43 = binario 00011 (ponticello su A0 e A1)
l’indirizzo 0x44 = binario 00100 (ponticello su A2)

e così via.

Quindi se volessimo usare più di un modulo uno schema di esempio sarà il seguente:

Per l’inizio del nostro sceth sarà:

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41);

void setup() {
  Serial.begin(9600);
  Serial.println("16 channel PWM test!");

  pwm1.begin();
  pwm1.setPWMFreq(1600);  // This is the maximum PWM frequency

  pwm2.begin();
  pwm2.setPWMFreq(1600);  // This is the maximum PWM frequency
}

Approfondimento su setPWM

L’esempio seguente farà in modo che il canale 12 si avvii basso, aumenti di circa il 25% nell’impulso (tick 1024 su 4096), ritorni al 75% basso nell’impulso (tick 3072) e rimanga basso per l’ultimo 25% di impulso:

pwm.setPWM (12, 1024, 3072)