1er programme : moteurs synchro.
2ème programme moteurs pas synchro.
Voici le code où les moteurs sont parfaitement synchro en phase quand la consigne moteur est à 50 tour/min.
Par contre, les moteurs se désynchronisent peu à peu quand la consigne moteur est à  25 tours/ min (soit 0,4 tours par seconde) ce qui j'imagine doit être normal, mais un petit peu dommage. 
#include <DueTimer.h> // Pour gérer les interruptions sur Timer
#include <PID_v1.h>  // Pour le PID
/* 
 * Constantes Globales à modifier
 */
#define NBMOTEURS 5
// Pins des codeurs
//const uint8_t PINENCODEURA[NBMOTEURS] =  {32,36,44,48};
//const uint8_t PINENCODEURB[NBMOTEURS] =  {33,37,45,49};
const uint8_t PINENCODEURA[NBMOTEURS] =  {32,52,44,48};
const uint8_t PINENCODEURB[NBMOTEURS] =  {33,53,45,49};
// Timers
const uint16_t PERIODE = 35000; // période du Timer3 en µs qui exécute le calcul des vitesses
const uint32_t NBPERIODEPARMINUTE = 60000000 / PERIODE;
// Pins des moteurs 
const uint8_t PINVITESSEMOTEUR[NBMOTEURS] = {3,4,5,6,7};
const uint8_t PINSENSAMOTEUR[NBMOTEURS] = {22,24,26,28};
const uint8_t PINSENSBMOTEUR[NBMOTEURS] = {23,25,27,29};
// PID => Possibilité de les mettre sous forme de tableaux si il y a plusieurs moteurs différents
const double KP = 1, KI = 1, KD = 0; // 0.9; 0,4
const uint16_t MAXPWM = 4095; // 4095 is the max for 12bits résolution
const uint8_t PWMRESOLUTION = 12; // With arduino due you can go to 12bits max résolution
// Constantes de conversions des unités => Possibilité de les mettre sous forme de tableaux si il y a plusieurs moteurs différents
const uint8_t NBTICKSPARTOURSCODEURS = 7;
const uint8_t GEARRATIO = 100;  
const uint16_t NBTICKSPARTOURSSORTIE =  NBTICKSPARTOURSCODEURS * GEARRATIO * 4;  // 4 car on prend sur A et sur B en front montant et décendant
//******************************************************************************************
//******************************************************************************************
// double vitesseSortieRPM = vitesse * NBPERIODEPARMINUTE / NBTICKSPARTOURSSORTIE;
// double consigneSortieTickParPeriode = consigneSortieRPM * NBTICKSPARTOURSSORTIE / NBPERIODEPARMINUTE;
/*
 * Variables globales 
 */
// Variables modifiées dans les interruptions => "volatile"
volatile int16_t compteur[NBMOTEURS] = {0,0,0,0,0};  // nombre de ticks compté
volatile int16_t vitesse[NBMOTEURS] = {0,0,0,0,0};    // nombre de ticks par unité de temps PERIODE3 
volatile bool dernierEtatEncodeurA[NBMOTEURS] = {0,0,0,0,0};
volatile bool dernierEtatEncodeurB[NBMOTEURS] = {0,0,0,0,0};
volatile bool flagTimer3 = 0;  // indique que l'interruption sur timer s'est effectuée
// Variables de PID ("double" car c'est la librairie utilisée qui veut ça ... )
double setpoint[NBMOTEURS] = {0,0,0,0,0};
double input[NBMOTEURS] = {0,0,0,0,0};
double output[NBMOTEURS] = {0,0,0,0,0};
// Tableau de pointeurs sur objets PID
PID PIDMOTEUR[NBMOTEURS] = {
  PID (&input[0], &output[0], &setpoint[0], KP, KI, KD, DIRECT),
  PID (&input[1], &output[1], &setpoint[1], KP, KI, KD, DIRECT),
  PID (&input[2], &output[2], &setpoint[2], KP, KI, KD, DIRECT),
  PID (&input[3], &output[3], &setpoint[3], KP, KI, KD, DIRECT),
  PID (&input[4], &output[4], &setpoint[4], KP, KI, KD, DIRECT)
};
// Autre méthode d'écriture
/*PID PID0(&input[0], &output[0], &setpoint[0], KP, KI, KD, DIRECT);
PID PID1(&input[1], &output[1], &setpoint[1], KP, KI, KD, DIRECT);
PID PID2(&input[2], &output[2], &setpoint[2], KP, KI, KD, DIRECT);
PID PID3(&input[3], &output[3], &setpoint[3], KP, KI, KD, DIRECT);
PID PID4(&input[4], &output[4], &setpoint[4], KP, KI, KD, DIRECT);
PID PIDMOTEURS[NBMOTEURS]= {PID0,PID1, PID2, PID3, PID4};*/
/* 
 * Fonctions  
 */
//Fonction de comptage des interruptions 
// Fonction de comptage générique 
void compteA( uint8_t i) {
  bool etatA = digitalRead(PINENCODEURA[i]);
  (etatA ^ dernierEtatEncodeurB[i]) ? compteur[i]++
:compteur[i]--;
  dernierEtatEncodeurA[i] = etatA;
}
void compteB( uint8_t i) {
  bool etatB = digitalRead(PINENCODEURB[i]);
  (etatB ^ dernierEtatEncodeurA[i]) ? compteur[i]-- : compteur[i]++;
  dernierEtatEncodeurB[i] = etatB;
}
// Fonctions d'interruption spécifique pour chaque pin codeur A et B
void compteA0() {
  compteA(0);
}
void compteA1() {
  compteA(1);
}
void compteA2() {
  compteA(2);
}
void compteA3() {
  compteA(3);
}
void compteA4() {
  compteA(4);
}
void compteB0() {
  compteB(0);
}
void compteB1() {
  compteB(1);
}
void compteB2() {
  compteB(2);
}
void compteB3() {
  compteB(3);
}
void compteB4() {
  compteB(4);
}
// Tableau de pointeurs sur fonction pour faciliter l'usage des interruptions
void (*compteEncodeurA[])(void)= {compteA0,compteA1,compteA2,compteA3,compteA4};
void (*compteEncodeurB[])(void)= {compteB0,compteB1,compteB2,compteB3,compteB4};
// Fonction timer
void timer3Interrupt() {
  for(uint8_t i = 0; i < NBMOTEURS; i++) {
   
vitesse[i] = compteur[i];
    compteur[i] -= (int)setpoint[i]; // conserve l'erreur au lieu de remettre à 0 ... => Transforme en asservissement en position 
  }
  flagTimer3 = 1;
}
// fonction de pilotage des moteurs (à modifier en fonction du driver de moteur utilisé)
void setMoteur(uint8_t moteur, int16_t vitesse) {
  // Sécurités optionnelles pour s'assurer que les entrées sont bien dans les plages attendues...
  if(moteur >= NBMOTEURS) 
   return; 
  vitesse = constrain(vitesse,-MAXPWM, MAXPWM);  // Marges de PWM. 
  // Fin des sécurités optionnelles. 
  if(vitesse == 0 ) {
    // Frein d'urgence ou roue libre ... 
    // à configurer en fonction de ce que la carte moteur permet de faire..
    digitalWrite(PINSENSAMOTEUR[moteur], HIGH);
    digitalWrite(PINSENSBMOTEUR[moteur], HIGH);
  }
  if(vitesse > 0) {
    //Sens de contrôle indiqué comme positif par les codeurs
   
digitalWrite(PINSENSAMOTEUR[moteur], HIGH);
    digitalWrite(PINSENSBMOTEUR[moteur], LOW);
  } else {
    //Sens de contrôle indiqué comme négatif par les codeurs
    digitalWrite(PINSENSAMOTEUR[moteur], LOW);
    digitalWrite(PINSENSBMOTEUR[moteur], HIGH);
  }
  analogWrite(PINVITESSEMOTEUR[moteur], abs(vitesse)); // écriture du PWM 
}
// Fonction de test qui fait tourner tous les moteur dans le même sens avec le même PWM
void testMoteurs( int16_t vitesse) {
  for(uint8_t i = 0; i < NBMOTEURS; i++) {
    setMoteur(i, vitesse);
  }
}
// Fonctions asservissement PID 
void asservissementMoteur(uint8_t moteur) {
  input[moteur] = vitesse[moteur];
  PIDMOTEUR[moteur].Compute();
  setMoteur(moteur, (int)output[moteur]);
}
void asservissementMoteurs() {
  for(uint8_t i = 0; i < NBMOTEURS; i++) {
    asservissementMoteur(i);
  }
}
// Fonction de conversion des unités 
double vitesseSortieRPM(uint8_t moteur) {
  return vitesse[moteur] * (double)NBPERIODEPARMINUTE / (double)NBTICKSPARTOURSSORTIE;
}
void setConsigneMoteur(uint8_t moteur, double rpm) {
  setpoint[moteur] = rpm * (double)NBTICKSPARTOURSSORTIE / (double)NBPERIODEPARMINUTE;
}
void setup() {
  
  Serial.begin(115200); 
  
  for(uint8_t i = 0; i < NBMOTEURS; i++) {
    
   // Initialisation des pins moteurs
   pinMode(PINVITESSEMOTEUR[i], OUTPUT);
   digitalWrite(PINVITESSEMOTEUR[i], LOW);
   pinMode(PINSENSAMOTEUR[i], OUTPUT); 
   digitalWrite(PINSENSAMOTEUR[i], LOW);
   pinMode(PINSENSBMOTEUR[i], OUTPUT);
   digitalWrite(PINSENSBMOTEUR[i], LOW);
   // Initialisation des codeurs
   pinMode(PINENCODEURA[i], INPUT_PULLUP);
   pinMode(PINENCODEURB[i], INPUT_PULLUP);
   attachInterrupt(digitalPinToInterrupt(PINENCODEURA[i]), compteEncodeurA[i], CHANGE);
   attachInterrupt(digitalPinToInterrupt(PINENCODEURB[i]), compteEncodeurB[i], CHANGE);
   // Initialisation des PID 
   PIDMOTEUR[i].SetMode(AUTOMATIC);
   analogWriteResolution(PWMRESOLUTION);
 PIDMOTEUR[i].SetOutputLimits(-MAXPWM, MAXPWM);
  }
  // Initialisation du timer
  Timer3.attachInterrupt(timer3Interrupt);
  Timer3.start(PERIODE); 
  
  //testMoteurs(127);
  // Initialisation des setpoints pour test d'asservissement
  for(uint8_t i = 0; i < NBMOTEURS; i++) {
      setConsigneMoteur(i, 25.26);
      setConsigneMoteur (4,0);
  }
}
void loop() {
    
  if(flagTimer3) {
    asservissementMoteurs();     
    flagTimer3 = 0;
   // Affichage utile uniquement pour debugage
    for(uint8_t i = 0; i < NBMOTEURS; i++) {
      Serial.print(vitesseSortieRPM(i));
      Serial.print(",");    
    }
    
    Serial.println("");
  }
 }
2 eme programme: pas de synchro
#include <DueTimer.h> // Pour gérer les interruptions sur Timer
#include <PID_v1.h>  // Pour le PID
/* 
 * Constantes Globales à modifier
 */
#define NBMOTEURS 5
// Pins des codeurs
const uint8_t PINENCODEURA[NBMOTEURS] =  {32,52,44,48};
const uint8_t PINENCODEURB[NBMOTEURS] =  {33,53,45,49};
// Timers
const uint16_t PERIODE = 35000; // période du Timer3 en µs qui exécute le calcul des vitesses
const uint32_t NBPERIODEPARMINUTE = 60000000 / PERIODE;
// Pins des moteurs 
const uint8_t PINVITESSEMOTEUR[NBMOTEURS] = {3,4,5,6,7};
const uint8_t PINSENSAMOTEUR[NBMOTEURS] = {22,24,26,28};
const uint8_t PINSENSBMOTEUR[NBMOTEURS] = {23,25,27,29};
// PID => Possibilité de les mettre sous forme de tableaux si il y a plusieurs moteurs différents
const double KP = 1, KI = 1, KD = 0; 
const uint16_t MAXPWM = 4095; // 4095 is the max for 12bits résolution
const uint8_t PWMRESOLUTION = 12; // With arduino due you can go to 12bits max résolution
// Constantes de conversions des unités => Possibilité de les mettre sous forme de tableaux si il y a plusieurs moteurs différents
const uint8_t NBTICKSPARTOURSCODEURS = 7;
const uint8_t GEARRATIO = 100;  
const uint16_t NBTICKSPARTOURSSORTIE =  NBTICKSPARTOURSCODEURS * GEARRATIO * 4;  // 4 car on prend sur A et sur B en front montant et décendant
// vitesseSortieRPM = vitesse * NBPERIODEPARMINUTE / NBTICKSPARTOURSSORTIE;
// consigneSortieTickParPeriode = consigneSortieRPM * NBTICKSPARTOURSSORTIE / NBPERIODEPARMINUTE;
/*
 * Variables globales 
 */
// Variables modifiées dans les interruptions => "volatile"
volatile int16_t compteur[NBMOTEURS] = {0,0,0,0,0};  // nombre de ticks compté
volatile int32_t erreur[NBMOTEURS] = {0,0,0,0,0}; // conserve l'erreur
volatile int16_t position[NBMOTEURS] = {0,0,0,0,0}; //  Position absolue comprise entre 0 et NBTICKSPARTOURSSORTIE
volatile int32_t vitesse[NBMOTEURS] = {0,0,0,0,0};    // nombre de ticks par unité de temps PERIODE3 
volatile bool dernierEtatEncodeurA[NBMOTEURS] = {0,0,0,0,0};
volatile bool dernierEtatEncodeurB[NBMOTEURS] = {0,0,0,0,0};
volatile bool flagTimer3 = 0;  // indique que l'interruption sur timer s'est effectuée
// Variables de PID ("double" car c'est la librairie utilisée qui veut ça ... )
double setpoint[NBMOTEURS] = {0,0,0,0,0};
double input[NBMOTEURS] = {0,0,0,0,0};
double output[NBMOTEURS] = {0,0,0,0,0};
// Tableau de pointeurs sur objets PID
PID PIDMOTEUR[NBMOTEURS] = {
  PID (&input[0], &output[0], &setpoint[0], KP, KI, KD, DIRECT),
  PID (&input[1], &output[1], &setpoint[1], KP, KI, KD, DIRECT),
  PID (&input[2], &output[2], &setpoint[2], KP, KI, KD, DIRECT),
  PID (&input[3], &output[3], &setpoint[3], KP, KI, KD, DIRECT),
  PID (&input[4], &output[4], &setpoint[4], KP, KI, KD, DIRECT)
};
// Autre méthode d'écriture
/*PID PID0(&input[0], &output[0], &setpoint[0], KP, KI, KD, DIRECT);
PID PID1(&input[1], &output[1], &setpoint[1], KP, KI, KD, DIRECT);
PID PID2(&input[2], &output[2], &setpoint[2], KP, KI, KD, DIRECT);
PID PID3(&input[3], &output[3], &setpoint[3], KP, KI, KD, DIRECT);
PID PID4(&input[4], &output[4], &setpoint[4], KP, KI, KD, DIRECT);
PID PIDMOTEURS[NBMOTEURS]= {PID0,PID1, PID2, PID3, PID4};*/
/* 
 * Fonctions  
 */
//Fonction de comptage des interruptions 
// Fonction de comptage générique 
void compteA( uint8_t i) {
  bool etatA = digitalRead(PINENCODEURA[i]);
  (etatA ^ dernierEtatEncodeurB[i]) ? compteur[i]++ :compteur[i]--;
  dernierEtatEncodeurA[i] = etatA;
}
void compteB( uint8_t i) {
  bool etatB = digitalRead(PINENCODEURB[i]);
  (etatB ^ dernierEtatEncodeurA[i]) ? compteur[i]-- : compteur[i]++;
  dernierEtatEncodeurB[i] = etatB;
}
// Fonctions d'interruption spécifique pour chaque pin codeur A et B
void compteA0() {
  compteA(0);
}
void compteA1() {
  compteA(1);
}
void compteA2() {
  compteA(2);
}
void compteA3() {
  compteA(3);
}
void compteA4() {
  compteA(4);
}
void compteB0() {
  compteB(0);
}
void compteB1() {
  compteB(1);
}
void compteB2() {
  compteB(2);
}
void compteB3() {
  compteB(3);
}
void compteB4() {
  compteB(4);
}
// Tableau de pointeurs sur fonction pour faciliter l'usage des interruptions
void (*compteEncodeurA[])(void)= {compteA0,compteA1,compteA2,compteA3,compteA4};
void (*compteEncodeurB[])(void)= {compteB0,compteB1,compteB2,compteB3,compteB4};
// Fonction timer
void timer3Interrupt() {
  for(uint8_t i = 0; i < NBMOTEURS; i++) {
    vitesse[i] = compteur[i];
    compteur[i] = 0;
    position[i] += vitesse[i];
    
    while(position[i] >= NBTICKSPARTOURSSORTIE) {
      position[i] -= NBTICKSPARTOURSSORTIE; 
    }
    while(position[i] < 0) { // pourquoi ZERO, pourquoi pas NBTICKSPARTOURSSORTIE?
      position[i] += NBTICKSPARTOURSSORTIE; 
    }
    erreur[i] += vitesse[i] - (int)setpoint[i]; // conserve l'erreur au lieu de remettre à 0 ... => Transforme en asservissement en position
  } 
  flagTimer3 = 1; 
} 
// fonction de pilotage des moteurs (à modifier en fonction du driver de moteur utilisé) 
void setMoteur(uint8_t moteur, int16_t vitesse) { 
  
  // Sécurités optionnelles pour s'assurer que les entrées sont bien dans les plages attendues... 
  if(moteur >= NBMOTEURS) return; 
  vitesse = constrain(vitesse,-MAXPWM, MAXPWM); // Marges de PWM. 
  // Fin des sécurités optionnelles. 
  
  if(vitesse == 0 ) { 
    // Frein d'urgence ou roue libre ... 
    // à configurer en fonction de ce que la carte moteur permet de faire... 
    digitalWrite(PINSENSAMOTEUR[moteur], HIGH); 
    digitalWrite(PINSENSBMOTEUR[moteur], HIGH); 
  } 
  else if(vitesse > 0) { 
    //Sens de contrôle indiqué comme positif par les codeurs 
    digitalWrite(PINSENSAMOTEUR[moteur], HIGH); 
    digitalWrite(PINSENSBMOTEUR[moteur], LOW); 
  } else { 
    //Sens de contrôle indiqué comme négatif par les codeurs 
    digitalWrite(PINSENSAMOTEUR[moteur], LOW); 
    digitalWrite(PINSENSBMOTEUR[moteur], HIGH); 
  } 
  analogWrite(PINVITESSEMOTEUR[moteur], abs(vitesse)); // écriture du PWM 
} 
// Fonction de test qui fait tourner tous les moteur dans le même sens avec le même PWM 
void testMoteurs( int16_t vitesse) { 
  for(uint8_t i = 0; i < NBMOTEURS; i++) { 
    setMoteur(i, vitesse); 
  } 
}
// Fonctions asservissement PID 
void asservissementMoteur(uint8_t moteur) { 
  input[moteur] = vitesse[moteur] + erreur[moteur]; 
  PIDMOTEUR[moteur].Compute(); 
  setMoteur(moteur, (int)output[moteur]); 
} 
void asservissementMoteurs() { 
  for(uint8_t i = 0; i < NBMOTEURS; i++) { 
    asservissementMoteur(i); 
  } 
} 
// Fonction de conversion des unités 
double vitesseSortieRPM(uint8_t moteur) { 
  return vitesse[moteur] * (double)NBPERIODEPARMINUTE / (double)NBTICKSPARTOURSSORTIE; 
} 
void setConsigneMoteur(uint8_t moteur, double rpm) { 
  setpoint[moteur] = rpm * (double)NBTICKSPARTOURSSORTIE / (double)NBPERIODEPARMINUTE; 
} 
void setup() { 
  Serial.begin(115200); 
  for(uint8_t i = 0; i < NBMOTEURS; i++) { 
    // Initialisation des pins moteurs 
    pinMode(PINVITESSEMOTEUR[i], OUTPUT); 
    digitalWrite(PINVITESSEMOTEUR[i], LOW); 
    pinMode(PINSENSAMOTEUR[i], OUTPUT); 
    digitalWrite(PINSENSAMOTEUR[i], LOW); 
    pinMode(PINSENSBMOTEUR[i], OUTPUT); 
    digitalWrite(PINSENSBMOTEUR[i], LOW); 
    
    // Initialisation des codeurs 
    pinMode(PINENCODEURA[i], INPUT_PULLUP); 
    pinMode(PINENCODEURB[i], INPUT_PULLUP); 
    attachInterrupt(digitalPinToInterrupt(PINENCODEURA[i]), compteEncodeurA[i], CHANGE); 
    attachInterrupt(digitalPinToInterrupt(PINENCODEURB[i]), compteEncodeurB[i], CHANGE); 
    
    // Initialisation des PID 
    PIDMOTEUR[i].SetMode(AUTOMATIC); 
    analogWriteResolution(PWMRESOLUTION); 
    PIDMOTEUR[i].SetOutputLimits(-MAXPWM, MAXPWM); 
  } 
  
  // Initialisation du timer 
  Timer3.attachInterrupt(timer3Interrupt); 
  Timer3.start(PERIODE); 
  //testMoteurs(127); 
  
  // Initialisation des setpoints pour test d'asservissement 
  for(uint8_t i = 0; i < NBMOTEURS; i++) { 
    setConsigneMoteur(i, 50.10); 
  }   
} 
void loop() { 
  if(flagTimer3) { 
    asservissementMoteurs(); // Commenter cette ligne pour déplacer à la main le moteur 
    flagTimer3 = 0; 
    // Affichage utile uniquement pour debugage Serial.println("vitesseSortieRPM:"); 
    for(uint8_t i = 0; i < NBMOTEURS; i++) { 
      Serial.print(vitesseSortieRPM(i)); 
      Serial.print(", "); 
    } Serial.println(""); 
    Serial.println("position absolue en tick"); 
    for(uint8_t i = 0; i < NBMOTEURS; i++) { 
      Serial.print(position[i]); Serial.print(", "); 
    } 
    Serial.println(""); 
  } 
}