Adafruit 16-Channel 12-bit PWM/Servo Driver

Bonjour,

Grace à ce sujet :
http://arduino.cc/forum/index.php/topic,147877.0.html

J'ai découvert ce shield :

Je ne suis pas un habitué de l'I2C, pouvez-vous me confirmer que la vitesse de communication de ce bus est suffisante ?
Ma question concerne le délais entre l'envoi d'une demande modification de "paramètre PWM" par l'Arduino et la modification effective en sortie du shield.

D'autre part, la communication I2C doit surement utiliser des interruptions... mais lequelles ? que va-t-il rester de disponible au niveau des timers et des interrutpions de l'Arduino ?

Toutes vitesses est suffisante en soi, tout dépend des exigences !

Pour quelle utilisation veut tu prendres ce shield?

Bonjour,

Tout dépend de ton application, quelle fréquence de rafraichissement souhaite tu atteindre ?

20 ms de délais me paraît un maximum à ne pas atteindre...

Ce shield me paraît utile pour soulager l'Arduino dans ce genre de cas par exemple :
http://arduino.cc/forum/index.php/topic,116024.0.html
Dans ce projet je ne comptais autopiloter que 2 voies parce que j'arrivais à la limite de l'utilisation des timers.

20 ms de délais me paraît un maximum à ne pas atteindre..

Tu pars déjà perdant alors puisque 20ms c'est la récurrence des tops utilisés par les servos. Ceci dit dans la spécification de la carte ils parlent d'un rafraichissement à 60Hz mais pour moi cela n'est pertinent que pour la fonction PWM.

D'autre part, la communication I2C doit surement utiliser des interruptions... mais lequelles ?

Si tu regardes sur le site Adafruit, la carte ne prend en interface que l'alimentation et les 2 fils du bus I2C plus un Output Enable.

fdufnews:

20 ms de délais me paraît un maximum à ne pas atteindre..

Tu pars déjà perdant alors puisque 20ms c'est la récurrence des tops utilisés par les servos. Ceci dit dans la spécification de la carte ils parlent d'un rafraichissement à 60Hz mais pour moi cela n'est pertinent que pour la fonction PWM.

Non je suis un battant :wink:
60Hz - 20 ms - oui c'est la fréquence de rafraîchissement des servos.
Ce que je souhaite, c'est que quand l'Arduino demande au shield de changer la position d'un servo (setPWM), le temps de traitement de la demande par le shield ne prenne pas plus de temps qu'une "boucle" de signal servo (60Hz - 20ms)

fdufnews:

D'autre part, la communication I2C doit surement utiliser des interruptions... mais lequelles ?

Si tu regardes sur le site Adafruit, la carte ne prend en interface que l'alimentation et les 2 fils du bus I2C plus un Output Enable.

Oui je pense juste que pour communiquer en I2C, l'ATmega 328P utilise forcément des interruptions...
J'aimerais être certains que les 3 timers restent complètement disponibles à tous les niveaux.
IDEM pour les interruptions.

UniseV:
Ce que je souhaite, c'est que quand l'Arduino demande au shield de changer la position d'un servo (setPWM), le temps de traitement de la demande par le shield ne prenne pas plus de temps qu'une "boucle" de signal servo (60Hz - 20ms)

Une séquence I2C (en écriture) avec le PCA9685 :
start (1 bit) + adresse (7 bits + 1 bit R/W) + ACK (1 bit) + commande (8 bits) + ACK (1 bit) + valeur (8 bits) + ACK (1 bit) + stop (1 bit) = 29 bits
Clock I2C max : 400KHz (requiére une modification du code de Wire/utility/twi.h) -> 13793 màj de valeurs PWM par seconde
Clock I2C "normale" : 100Khz -> 3448 màj de valeurs PWM par seconde

Je fait pas le calcul sur N servomoteurs mais on doit être largement en dessous des 20ms :grin:

UniseV:
Oui je pense juste que pour communiquer en I2C, l'ATmega 328P utilise forcément des interruptions...
J'aimerais être certains que les 3 timers restent complètement disponibles à tous les niveaux.
IDEM pour les interruptions.

L'I2C ne génère aucune interruption excepté en mode "esclave" :

En mode "maitre" les transferts sont bufferisé sur 32 octets (donc non bloquant tant que le buffer n'est pas plein).
(pour être vraiment précis il y a une interruption en mode maitre, en interne, qui s'active à chaque fin de transfert pour charger l'octet suivant)

Sinon pour les timers ils sont toujours disponible, pas de problème pour ça (excepté le timer0 qui est utilisé par le système pour delay(), millis(), micros(), ...).
Les interruptions doivent être activées pour que l'I2C fonctionne.

Merci pour tes réponses.
L'estimation du temps de dialogue I2C m'est très précieuse et me donne un idée générale dont j'avais besoin.

Après m'être un peu re-plongé dans la datasheet du 328P, je vois plus clairement le problème :
Comme tu le dis, les interruptions doivent être actives pour que l'I2C fonctionne... et elles sont utilisées...

Dans mon code j'utilise '"interrupt capture pin" ou ICP1 pour décoder un signal PPM, il ne faut pas que les interruptions I2C et ICP1 se téléscopent... (sachant que je ne maitrise aucunement les ICP1)

UniseV:
Dans mon code j'utilise '"interrupt capture pin" ou ICP1 pour décoder un signal PPM, il ne faut pas que les interruptions I2C et ICP1 se téléscopent... (sachant que je ne maitrise aucunement les ICP1)

Tu peut rendre le transfert I2C bloquant en utilisant la librairie bas niveau TWI directement sans buffer.
Du coup plus de problème possible de télescopage entre interruptions.

Sinon tu peut réactiver les interruptions dans ton interruption de décodage PPM ("nested interrupts") avec cli();

Voici l'état actuel de mon code :

// Lecture PPM6ch & écriture PWM V003
// C'est le timer1 qui est utilisé pour les 2 fonctions

#define PWM1 9  // Pin Sortie PWM 1
#define PWM2 10 // Pin Sortie PWM 2
#define SWI1 11 // Pin Switch servo 1
#define SWI2 12 // Pin Switch servo 2
#define LEDP 13 // Pin de la LED "locale" de l'Arduino

boolean endTrame=0,failsafe=0,firstEdge=1;

//Structure Channel
struct channel {
  unsigned int val;
  boolean autop;
};

// Données de capture du Rx
volatile unsigned int vchUp[8];
volatile unsigned int vchPos[6];
volatile unsigned int ocr1a=3000,ocr1b=3000;
struct channel cha[6];
byte curEdge;

void setup()
{
  // Initialisation des valeur auto-pilotes à 0
  for (int i=0; i < 6; i++) { 
    cha[i].autop=0;
  }


  // Pin setup, parmetrage des PIN en sortie :
  pinMode(PWM1,OUTPUT); // Timer1 PWM1 (OCR1A)
  pinMode(PWM2,OUTPUT); // Timer1 PWM2 (OCR1B)
  pinMode(SWI1,OUTPUT); // Selecteur d'entrée servo 1
  pinMode(SWI2,OUTPUT); // Selecteur d'entrée servo 2
  pinMode(LEDP,OUTPUT); // Arduino LED
  digitalWrite(LEDP,HIGH); // Allumage de la LED de l'Arduino

  // Sortie DEBUG
  Serial.begin(57600);
  Serial.println("PPM read & PWM write");
  // Timer1 setup
  TCCR1A=B10100000; // OCR2A/OCR2B : Falling mode, Timer: normal mode
  TCCR1B=B00000010; // Falling edge CAPTUREPIN detection, Timer:normal mode, Prescaler=clk/8 
  TIMSK1=B00100001; // Activate CAPTUREPIN interrupt & OVF interrupt
  OCR1A=3000;
  OCR1B=3000;

  delay(1000); // pour etre sur que les interruptions aient tourné avant la main loop

}

ISR(TIMER1_CAPT_vect)
{
  vchUp[curEdge]=ICR1; // Capture des timestamp 1 à 6
  curEdge++;
  if (curEdge>7) {     // A partie du 7ème...   
    TCNT1=0;           // RESET du counter pour éviter le FailSafe
    TCCR1A=B11110000;  // OCR2A/OCR2B : En mode front montant
    TCCR1C=B11000000;  // OCR2A/OCR2B : Déclenchement "manuel" pour monter les 2 signaux servo
    TCCR1A=B10100000;  // OCR2A/OCR2B : En mode front descendant
    OCR1A=ocr1a;       // Mise à jour de la valeur du servo 1
    OCR1B=ocr1b;       // Mise à jour de la valeur du servo 2
    if ((vchUp[7]-vchUp[1]) > 30000) {  //Si la trame totale dépasse 15ms - Trame KO ou mauvaise synchro
      curEdge=0;            //Remise à 0 du compteur de fronts                      
    }
    else {                  //Sinon, une bonne trame est capturée, on calcule donc la valeur des canaux
      curEdge=1;
      for (int i=0;i<6;i++) {
        vchPos[i]=(vchUp[i+2]-vchUp[i+1]); //Mesure des canaux (diviser par 2 pour obtenir des µs)
      }
      endTrame=1;           // Pour dire à la MainLoop que de nouvelles valeurs sont disponibles
    }
  }
}

ISR(TIMER1_OVF_vect)
{
  failsafe=1;  // Le compteur a attends sa limite (32ms), on déclenche donc le FailSafe
  TCCR1A=B11110000; // OCR2A/OCR2B : En mode front montant
  TCCR1C=B11000000; // OCR2A/OCR2B : Déclenchement "manuel" pour monter les 2 signaux servo
  TCCR1A=B10100000; // OCR2A/OCR2B : En mode front descendant
  OCR1A=ocr1a;      // Mise à jour de la valeur du servo 1
  OCR1B=ocr1b;      // Mise à jour de la valeur du servo 2
}

void loop()
{
  if (endTrame==1) {
    cli();
    for (int i=0;i<6;i++) {
      cha[i].val=vchPos[i]; // Recopie les valeurs des canaux
    }
    sei();
    endTrame=0;
    if (failsafe){ 
      failsafe=0;
      Serial.println("End of FailSafe");
    }

    // La 1ere partie "intelligente" du code se trouve ICI
    // Elle n'est jouée que si les canaux ont "bougé"

    if (cha[4].val > 2500) { // Si le canal 5 n'est pas au min...
      cha[1].autop=1; // Mettre le canal 2 en auto-pilote
      digitalWrite(SWI1,HIGH);
      if (cha[4].val > 3500) { // Si le canal 5 est au max...
        cha[2].autop=1; // Mettre le canal 3 en auto-pilote
        digitalWrite(SWI2,HIGH);
      }
    }
    else {
      cha[1].autop=0; // Mettre le canal 2 en pilotage manuel
      digitalWrite(SWI1,LOW);
      cha[2].autop=0; // Mettre le canal 3 en pilotage manuel
      digitalWrite(SWI2,LOW);
    }

    for (int i=0; i < 6; i++){ // Imprimer les valeurs des canaux sur le port série
      Serial.print(cha[i].val);
      Serial.print(";");
    } 
    Serial.print(ocr1a);
    Serial.print(";");
    Serial.print(ocr1b);
    Serial.println(";");
  }

  // La 2eme partie "intelligente" du code se trouve ICI
  // Elle est jouée à chaque tour de boucle

  if (cha[1].autop==0) ocr1a=(cha[5].val); // Si le canal 2 est en pilotage manuel Le PWM1 est celle du canal 5
  else{                                    // Si non, le canal 2 est auto-piloté...
    if (ocr1a>2000) ocr1a--;               // Une simple boucle qui fait bouger lentement le servo d'un bout à l'autre puis revenir
    else ocr1a=4000;
    delay(1);
  }
  if (cha[2].autop==0) ocr1b=(cha[5].val); // Si le canal 3 est en pilotage manuel Le PWM2 est celle du canal 6
  else{                                    // Si non, le canal 3 est auto piloté...
    if (ocr1b>2000) ocr1b--;               // Une simple boucle qui fait bouger lentement le servo d'un bout à l'autre puis revenir
    else ocr1b=4000;
    delay(1);
  }

  if (failsafe){
    Serial.print("FAILSAFE");
    cha[1].autop=1;
    cha[2].autop=1;
    Serial.print(ocr1a);
    Serial.print(";");
    Serial.print(ocr1b);
    Serial.println(";");

  }
}

On peut dire qu'il est en 2 parties :

  • Un partie synchro qui ne se situe QUE dans les interruptions dont le rôle est de lire le PPM et de générer les 2 signaux servo
  • Une partie "multi-tâches" (dans la main loop), qui gère la partie "intelligente" du code et les éventuels calculs

L'avantage du shield en question, c'est qu'il me décharge de la génération des signaux servo.

Alors comme tu le dis, je pourrais réactiver les interruptions dans mes interruptions "ICP1" pour permettre à l'I2C de travailler même pendant mes inter.

Mais je préfère une solution où tout est orchestré par l'Arduino... lire le PPM dure 12 ms et j'ai un "silence" de minimum 10ms après ça... il faut que je cale mon envoi I2C pendant ce silence.... donc qu'il soit lancé par une interruption, cela parait-il faisable ?

Bon j'ai avancé dans mes tests, j'ai ajouté la librairie Wire.h et Adafruit_PWMServoDriver.h dans mon sketch et celui-ci lance des commande en I2C vers le shield que je n'ai pas encore...

Pour simuler le shield, j'ai mis un Arduino Nano avec le code d'exemple fournit avec Wire, appelé "Wire Slave Receiver", les 2 bête sont connectée en GND-A4-A5.

La bonne nouvelle, c'est que je vois bien dans le moniteur du Nano des données arriver :

La mauvaise nouvelles, c'est que mes 2 Arduino plante après ça... la main loop ne tourne plus sur les 2...
Le master attendrait-il un Ack ?

EDIT : J'ai fait un test avec I2C_Anything.h et mes 2 Arduino et c'est un succès, ça valide au moins le hardware.

EDIT 2 : Après avoir regardé la bibliothèque d'Adafruit, le master attend bien un retour du shield... mais je ne pensais pas que ce genre de requête I2C planterais le Slave dans son mutisme.

Fin du monologue, j'ai acheté 2 de ces shield.

Je testerais les délais et la librairie dès leur réception, dans un nouveau sujet moin touffu. :wink:

Salut!

J'ai vu que tu avais fait référence à mon post. J'ai finalement acheté ce shield et fait un tutorial sur mon blog:

@++

Merci pour l'info, j'en ai acheté 2 dont je me sert très régulièrement, je n'ai finalement pas mesuré les délais car je n'ai pas de problème à ce niveau.

Je suis très satisfait de ce shield dont le principaux avantages sont :

  • Décharger l'Arduino de la gestion des servo (ou autres...)
  • Fiabiliser la position de servos (no jitters !)

Pour ma part, je branche souvent des bandes de LED RVB, un seul shield peut déjà en piloter 5 et il reste même une voie de libre !