Auteur Sujet: Problème pour utiliser l'horloge externe du PCA9685  (Lu 30619 fois)

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #30 le: janvier 10, 2021, 07:03:20 pm »
Bonjour,
merci pour le partage
pour mieux faire tu peux :
1) utiliser le spi hardware du méga, le but c'est que les hc595 soient renseignés sans monopoliser la cpu
2) utiliser le hardware d'un timer pour générer le latch, en mode ctc, et avec une interruption au front montant pour lancer le nouveau remplissage des hc595 - qui sera pris en comte au prochain front montant ; le but c'est d'avoir un découpage automatique et rigoureusement on time du découpage des crans du pwm, et toujours sans intervention de la cpu
je n'ai pas forcément été clair, fais moi le savoir

c'est quel modèle de teensy que tu attends  ? (on peut multiplier la fréquence du pwm par le nombre de spi hardware du mcu)

simontpellier

  • Full Member
  • ***
  • Messages: 115
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #31 le: janvier 10, 2021, 09:45:59 pm »
Bonsoir,

et merci pour la réponse !

J'avoue que non, tout n'est pas bien clair mais ça tient à moi. Je n'ai par exemple pas idée de ce qu'est un SPI hardware, pas plus que lent ou rapide)

Mais... on est tout de même assez bien synchronisés puisque pour cadencer le SPI des 74HC j'ai utilisé un timer (le 2), avec un réglage de la fréquence PWM par bascule du registre TCNT2
(mais pas en mode CTC... erreur ?

Je joins le code. Que j'ai pu greffer dans mon code principal sans occasionner le moindre souci ni ralentissement. Et avec un cahier des charges respecté :
- 40 PWM possiblement générées
- 125 pas de réglage (avec un talon à 53, valeur empirique personnelle, ce qui donne une plage de réglage effective entre 53 et 178 et par conséquent un pas de réglage plus fin)
Ainsi régulée, la fréquence "shiftPWM" est totalement indépendante du nombre de 74HC pilotés, du moins dans la limite du "cahier des charges" et dans la plage 30Hz-100Hz (et peut-être au delà ?)

Voici le code. Les variables consigne PWM sont fixes et auraient aussi bien pu être déclarées comme constantes... dans la réalité, c'est bien sûr au code de les déterminer en temps réel.
/* ARDUINO MEGA */

const int SS_PIN = 25;     // SS(slaveSelect)/slave - pin 14 (DS / Serial Data Input) on the 74hc595
const int MOSI_PIN = 26;   // MOSI (Master Out Slave Inv- The Master line for sending data to the peripherals) - pin 12 (ST_CP / Storage Register Clock Input (latch)) on the 74hc595
const int MISO_PIN = 29;   // MISO (Master In Slave Out - The Slave line for sending data to the master) - pin 11 (SH_CP / shift register clock input) on the 74hc595

const int8_t number_of_74hc595 = 1;
const int8_t numOfRegisterPins = number_of_74hc595 * 8;
boolean pin74hc[numOfRegisterPins] = { 0 };      // data-table, matching with the state of the 74HC595 ouputs

 // pseudos PWM à simuler - pour chaque PWM, le tableau donne le cran entre 20 et 120
uint8_t PWM[40] = { 54, 64, 105, 94, 70, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0 };   

byte tcnt = 192; // le timer commute tous les (256-tcnt) cycles
/*
  avec PRESCALER à 32 (TCCR2B = 0b00000011)
  tcnt=126 => 30Hz (les valeurs de tcnt sont empiriques faute de linéarité dans les valeurs de fréquence obtenues)
  tcnt=160 => 40Hz
  tcnt=180 => 50Hz
  tcnt=192 => 60Hz 
  tcnt=208 => 80Hz
  tcnt=218 => 100Hz
*/     
byte cran = 0;

void setup() {
  Serial.begin(115200);

  pinMode(SS_PIN, OUTPUT);
  pinMode(MOSI_PIN, OUTPUT);
  pinMode(MISO_PIN, OUTPUT);

  bitClear (TCCR2A, WGM20); // WGM20 = 0
  bitClear (TCCR2A, WGM21); // WGM21 = 0
  TCCR2B = 0b00000011;
  TIMSK2 = 0b00000001; // Interruption locale autorisée par TOIE2
}

void writeRegisters() {
  PORTA &= ~_BV(PA4);       // pin 26
  for (int8_t i((number_of_74hc595)*8-1) ; i>=0 ; i--) {
      PORTA &= ~_BV(PA7);   // pin 29
      if (pin74hc[i]==0) /*=>*/ PORTA &= ~_BV(PA3);   // pin 25
      else /*=>*/ PORTA |= _BV(PA3);
      PORTA |= _BV(PA7);    // pin 29
    }
  PORTA |= _BV(PA4);
}

void setRegisterPins(byte cran) {  //Place les pins du 74HC595 à l'état HAUT ou BAS
    for(uint8_t engine(0) ; engine<numOfRegisterPins ; engine++) {
      pin74hc[engine] = cran<PWM[engine];
    }
  writeRegisters();
}

ISR(TIMER2_OVF_vect) {
  TCNT2 = tcnt ;
  cran++;
  setRegisterPins(cran);
  if (cran==178) cran=53;
}

void loop() {

/* CODE PRINCIPAL ICI */
}

Le SPI dédié aux 74HC est maintenant sur les broches 26-29 du MEGA... parce que j'utilisais déjà les 50-52  pour le bus CAN et parce que je n'y ai vu aucune différence.
Mais voilà bien une zone d'ombre pour moi qui a résistée à mes questions à Google. Si quelqu'un peut m'indiquer le début de la piste, ça sera apprécié !

(Le Teensy à venir : un 3.5)
« Modifié: janvier 10, 2021, 09:51:51 pm par simontpellier »

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #32 le: janvier 11, 2021, 10:05:15 pm »
cadencer en pwm avec réglage de la période c'est tout aussi bon que le ctc, j'ai oublié de le mentionner

c'est pas mal, mais il faut faire le spi par le hardware ; ton arduino arrive à s'en sortir car il lui reste un peu de temps entre la fin de l'émission des trames spi et l'interruption, mais si tu charges encore la bête, tu vas te retrouver avec des "unexpected behaviors"

on peut faire le spi de 2 manières :
soit en soft, comme toi, en faisant bagoter manuellement les pins en digital HIGH ou LOW (tu l'a fais en écrivant direct sur les registres, ça va aussi  (mais il y aurait aussi mieux comme méthode pour faire ça))
soit en utilisant le périphérique spi hardware de l'avr : une fois configuré, il n'y a plus qu'a écrire le byte à transmettre dans le périphérique, et celui-ci s'occupe tout seul de faire bagoter les pins MOSI et SCK, sans intervention de la cpu, qui reste disponible pour le reste du travail : c'est tout l'interrêt d'avoir des périphériques hardware, et en effet, souvent les programmateurs ignorent cela, je t'engage à te mettre au courant, c'est largement documenté et pas sorcier

pareil pour le latch (ou SS ou RCLK) : il vaut mieux configurer et utiliser une des 2 sorties pwm de ton timer2, plutôt que de l'écrire manuellement sur la pin 25

le spi hardware est sans doute déjà pris par le can sur l'avr, donc il faut débrancher le can si tu veux tester
le teensy a un can hardware, ce qui permet de libérer ses 3 spi hardwares pour piloter 3 branches de hc595 distinctes, et par là multiplier la fréquence du pwm par 3 ... tu vas pouvoir t'amuser !

je t'ai représenté le truc sur la pj

la 1ère ligne, c'est le signal pwm généré par l'arduino, et raccordé au(x) latch des hc595 ; la flèche vers le haut implique 2 choses :
1) le latch des hc595, qui prennent en compte, au front montant, les données qui viennent de leur être shiftés ; la durée de tes crans sera nickel
2) l'interruption provoquée par le timer du pwm

la 2ème ligne représente grossièrement les signaux MOSI (ou data ou SER) et SCK (ou SRCLK) envoyés par le spi hardware ; le déclenchement de cet envoi se fait dans la routine de l'interruption évoquée ci-dessus ; si tu fais ces signaux en soft, tu vois qu'il reste peu de temps à l'arduino pour faire autre chose ; si c'est automatique par le hardware, il aura tout son temps

les 3èmes et 4èmes lignes montrent les signaux obtenus (il n'y a que 6 crans dans l'exemple) sur 2 sections différentes
exemples : 1/6 3ème ligne : 3/6 4ème ligne
vbui fgazuityer ?
« Modifié: janvier 12, 2021, 10:07:22 am par trimarco232 »

simontpellier

  • Full Member
  • ***
  • Messages: 115
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #33 le: janvier 12, 2021, 10:14:30 pm »
wouah cette fois j'ai compris... le principe !
D'un côté le CPU dont le boulot est d'exécuter le code
de l'autre le hard qui offre des services gratuits... très improductif en effet de s'embêter à les reproduire dans du code !

le timer qui se charge de faire "bagoter" (chez moi ça "commute"... pas la même promo on dirait) plutôt qu'une instruction ça c'est grandiose ! (pas encore mis en œuvre mais va falloir !)
Et pour me documenter sur le SPI "harware" ?  pas besoin de Google... à priori tout est dans la datasheet.

Un grand merci pour cette explication, pour le temps que ça demande obligatoirement. Avec un schéma de plus ! (*)
Pour l'instant, en l'état, ça fonctionne déjà à merveille... au départ c'était "pour voir" mais l'élimination des PCA9685 semble en prime solutionner des problèmes de plantages du bus I2C, c'est bon à signaler au passage puisqu'il y a maintenant des milliers (presque) d'abonnés à cette chaîne !

Juste ça que je n'ai vraiment pas compris : la conclusion (... pas la même promo...)

(* une remarque sur le schéma : les deux sections sur lesquelles les pulses ne montent pas en même temps ne pourront pour le coup jamais être synchronisées ! Mais l'idée passe bien)

A nouveau : merci !

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #34 le: janvier 13, 2021, 05:24:02 pm »
on peut fabriquer des signaux de toutes formes avec cette méthode
la 2ème ligne montre un décalage d'un cran avant l'application de la puissance : ce principe est utilisé dans des ic spécialisés pour répartir les appels de courant ; bien entendu, cette méthode ne nous convient pas, il faudra déterminer l'alim en conséquence
content de voir que tu suis  8)

hinano70

  • Newbie
  • *
  • Messages: 18
    • Voir le profil
Re : Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #35 le: janvier 14, 2021, 02:05:00 pm »
j'ai réfléchi ... ! ... ?

il y a sans doute une solution beaucoup plus simple, tout dépend de la capacité de vos systèmes à la mettre en oeuvre, mais on est quand-même sur le forum locoduino, ça doit être possible !
l'idée, c'est de s'attaquer à la racine du problème, cad. à la transition entre deux pca9685, qui sont on l'a vu, forcément désynchronisés
on va prendre par exemple, pour fixer les idées, 31 sections, donc 2 pca :
- le 1er pca commande les sections 1 à 16, le 2ème pca commande les sections 16 à31
- donc la section 16 peut être, au choix, commandée par le 1er ou par le 2ème pca
- quand le train passe de la section 15 à la section 16, la section 16 est commandée par le 1er pca (pas par le 2ème) : la transition entre la section 15 et la section 16 se fait nickel
- quand le train passe de la section 16 à la section 17, la section 16 est commandée par le 2ème pca (plus par le 1er) : la transition entre la section 16 et la section 17 se fait chrome
- le choix duquel des 2 pca pour la section 16 se fait par un simple "ou câblé", avec 2 diodes et une résistance de pull down de 470R ; la sortie correspondante du pca qui ne commande pas la section 16 est simplement mise à 0%

que vous en pense-t-il ?

La voila ma solution !: commencer par alimenter une section par 1 PCA et finir de l'alimenter par un autre. Merci trimarco232.
Ca peut être fait logiciellement. Mais le montage avec 2 diodes ... m'intéresse et je n'ai pas bien compris. Peux tu me faire un petit schéma, merci.

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #36 le: janvier 14, 2021, 06:52:25 pm »
voilà
dans l'exemple, je suis parti sur 40 sections, il faut 3 pca9685
il reste 6 sorties de libres
les 3 pca9685 sont spécialisés pour les signaux pwm ; tu peux choisir d'y mettre aussi les signaux dir1 et dir2, pour l'élégance du câblage : cela t'oblidera toutefois à disposer d'avantage d'ensembles diodes + résistance

hinano70

  • Newbie
  • *
  • Messages: 18
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #37 le: janvier 14, 2021, 09:09:31 pm »
Merci, je teste ce week-end (il faut que je réadresse les sections ....)
Je viens de tester une version plus simple à modifier, pour voir (je ne suis pas un pro en électronique) :
sortie16 L298 > diode > section 16
sortie17 L298 > diode > section 17
J'ai rajouté 2 diodes donc.
Cette fois au lieu d'un ralentissement, il y a une très légère accélération au changement de section, mais plus de broutement.
Au risque de faire hurler Simontpellier, j'alimente encore les L298 qu'avec 2 fils sur In1 et In2 !

En fait j'utilise des PCA pour tout, j'en ai déjà installé 6 et je vais en rajouter au moins 2 pour les feux sur lesquels je travaille actuellement!
- commande des moteurs d'aiguillage SG90
- alimentation des sections
- commande des feux de signalisation (ce qui permet de moduler l'éclairement)
ça marche nickel sauf lorsqu'il y a changement de PCA sur 2 sections contigües, mais ça progresse !!!

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #38 le: janvier 15, 2021, 12:22:27 am »
les 2 diodes ne suffisent pas, il faut aussi y ajouter la résistance et la mettre à la masse
il faut commander la vitesse par les entrées enableA et enableB (pwm) du L298 ; les entrées 1, 2 et 3, 4 du L298 ne servent qu'à déterminer le sens de marche, 2 doit être l'inverse de 1 (et réciproquement ; et pareil pour 3 et 4)

mon devoir d'information m'oblige à dire ceci :
on peut se passer des diodes et des résistances, en utilisant les pca9685 en mode open-drain
l'inconvénient, c'est que par défaut les entrées pwm des L298 seraient HIGH, ce qui entraine la puissance maximale, pendant l'initialisation des pca9685 ; c'est pourquoi je préconise les résistances de pull-down

tu peux toutefois faire ceci dans le cadre d'un test (tu ne mets pas ta trix à 700€ sur les rails) :
- configurer les pca9685 en open-drain (trouver sur la toile comment faire)
- relier ensemble, sans autre forme de procès : (sortie 16 du 1er pca) - (sortie 1 du 2ème pca) - (entrée enable (pwm) correspondante du L298 correspondant)
- pour les 2 sorties qui sont sur la même entrée, mettre le pwm à 100% pour celle qui n'opère pas, et mettre le pwm à la valeur de la vitesse voulue, pour celle qui opère

une config de test pourrait avoir l'allure suivante :
« Modifié: janvier 15, 2021, 07:41:21 am par trimarco232 »

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #39 le: janvier 15, 2021, 11:51:21 pm »
(...)
Mais auparavant : cette idée que tu as suggérée (puis retirée ??) de registres 74HC595 pilotés en SPI rapide.
(...)
Bonjour simontpellier,
je l'avais retiré car mon choix perso et désintéressé penchait pour la solution des pca9685 ...
mais bien entendu, on va continuer de parler de ton projet ici, je m'efforcerai à t'aider pour le mener à terme
il est même, je pense, très intéressant de développer parallèlement 2 approches d'une même problématique, ainsi, - car il y a toujours des amateurs pour l'analogique - ceux qui rechercheront une solution pourront opter pour celle qui leur plaît

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #40 le: janvier 17, 2021, 04:41:37 pm »
Bonjour,
tu peux essayer ceci
c'est optimisé pour un avr ; il faut mettre le latch sur la pin 19
tu peux mesurer la fréquence en regardant le latch

#include <SPI.h>

/********** macros ***********/
// nombre de crans
#define crans 128
#define spi_latch_pin 19 // pin19 // PORTB permet l'instruction bitSet

/********** déclarations des variables ***********/
// octet à envoyer sur un 74hc595 // compteur de crans
uint8_t byte_to_spi, cran = 0;
// 40 PWM à simuler - pour chaque PWM, le tableau donne le cran entre 16 et 127
uint8_t PWM1[8] = { 54, 24, 105, 94, 109, 80, 123, 0 };
uint8_t PWM2[8] = { 54, 25, 106, 95, 110, 81, 124, 1 };
uint8_t PWM3[8] = { 54, 26, 107, 96, 111, 82, 125, 2 };
uint8_t PWM4[8] = { 54, 27, 108, 97, 112, 83, 126, 3 };
uint8_t PWM5[8] = { 54, 28, 109, 98, 113, 84, 127, 4 };


/**********  setup  ***********/
// le setup déroule 1 fois après le démarrage de l'arduino
void setup(void) {
  // configure spi
  SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
  // initialise les broches qui doivent être en sortie
  digitalWrite(spi_latch_pin, LOW); // latch est LOW
  pinMode(spi_latch_pin, OUTPUT);

  Serial.begin(115200);

}

/**********  boucle principale ***********/
void loop(void) {

  // transferts vers 74hc595
  cran++; // la rampe qui monte les crans
  bitClear(PORTB, 0); // pin19 // prépare le latch

  byte_to_spi = 0; // met tout à LOW
  for ( uint8_t bit0to7 = 0; bit0to7 < 8; bit0to7++ ) {  // cherche chaque section
    if (PWM5[bit0to7] >= cran) bitSet(byte_to_spi, bit0to7);
  }
  SPDR = byte_to_spi; // lance le transfert sans en attendre la fin
  byte_to_spi = 0; // et au suivant ...
  for ( uint8_t bit0to7 = 0; bit0to7 < 8; bit0to7++ ) {
    if (PWM4[bit0to7] >= cran) bitSet(byte_to_spi, bit0to7); // si la valeur dépasse la rampe ...
  }
  SPDR = byte_to_spi;
  byte_to_spi = 0;
  for ( uint8_t bit0to7 = 0; bit0to7 < 8; bit0to7++ ) {
    if (PWM3[bit0to7] >= cran) bitSet(byte_to_spi, bit0to7); // ... la sortie sera HIGH
  }
  SPDR = byte_to_spi;
  byte_to_spi = 0;
  for ( uint8_t bit0to7 = 0; bit0to7 < 8; bit0to7++ ) {
    if (PWM2[bit0to7] >= cran) bitSet(byte_to_spi, bit0to7); // ... si non elle reste LOW
  }
  SPDR = byte_to_spi;
  byte_to_spi = 0;
  for ( uint8_t bit0to7 = 0; bit0to7 < 8; bit0to7++ ) {
    if (PWM1[bit0to7] >= cran) bitSet(byte_to_spi, bit0to7);
  }
  SPDR = byte_to_spi;

  bitSet(PORTB, 0); // pin19 // latch
  if (cran == 127) cran = 0; // retour en début de période pwm, rampe à 0
  // fin des transferts vers 74hc595



  // what else ?


} // fin loop(void)

simontpellier

  • Full Member
  • ***
  • Messages: 115
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #41 le: janvier 17, 2021, 09:10:52 pm »
Ah oui ! l'essayer c'est l'adopter !
je viens de lancer, ça sort du 10kHz !

J'ai fait des essais de mon côté sur le SPI hardware et je suis très très loin de ça ! (Avec un code qui je dois dire ne ressemble en rien)
Évidemment je vais étudier ça de très près. A première vue, je suis juste surpris de voir un seul SPI.beginTransaction dans le setup (et jamais de endTransaction... qui laisserait la main pour autre chose ?)
Mais en tous cas le résultat brut est là, à moi de l'intégrer (dans tous les sens du terme).

________________   APRES PRISE EN MAIN _______________________

J'ai en effet bien besoin d'aide, merci pour la proposition et c'est fort agréable de savoir qu'il y a une hot-line dédiée!

Après étude de ton code et différents essais, voici ce que je comprends (mais peut-être pas) et ce que je ne comprends pas (et ça c'est certain) :

1 - comprends :
- le code en l'état ne fonctionne pas, c'est un bout de code qui pourrait aider
- sa structure permet de faire du daisy-chainning très simplement (si c'est bien ça, c'est très astucieux)

2 - comprends pas :
- SPI.beginTransaction   dans le setup
- ligne 6 : #define spi_latch_pin 19 // pin19 // PORTB permet l'instruction bitSet
       choisir une autre broche semble tout aussi possible
- ligne 32 : bitClear(PORTB, 0); // pin19 // prépare le latch
             La broche 19 n'est pas sur le port B mais D et il faudrait écrire sur le bit 2 et non 0

- if (PWMx[bit0to7] >= cran) bitSet(byte_to_spi, bit0to7);
   pas plutôt : if (PWMx[bit0to7] < cran) bitSet(byte_to_spi, bit0to7);
   car si le cran dépasse la consigne PWM "bit0to7", ça doit provoquer le falling, non ? C'est en tous cas comme ça que ça fonctionne avec mon code.
   (détail, à propos de "bit0to7" : il me semble que "byte0to7" serait plus exact et facile à comprendre. De plus, pour normaliser et comme en réalité ces datas représentent la PWM
   à appliquer à une section donnée, ça pourrait donc encore plus clairement s'écrire :    if (PWMx[section] < cran) bitSet(byte_to_spi, section);)

3 - comprends pas bien : le choix de bitSet pour construire la trame (voir mes essais)

Pour ma part, voici mes maigres avancées :
- le passage au SPI hardware a un effet très modeste, le gain n'est que d'une 20aine de %
- devant me plier au protocole SPI, j'ai des difficultés à construire la trame. Elle est limitée par le protocole à 32bits ce qui fait que je ne peux pas atteindre les 40PWM "imposées" sans devoir faire du daisy-chainning (ou définir deux esclaves). Mais donc ça se solutionne
Le plus ennuyeux est que la construction de la trame est semble-il ce qui ralentit le code à tel point que le bénéfice du SPI rapide disparaît presque complètement
Voici le nouveau code, avec une syntaxe quasiment normalisé pour que les échanges soient plus faciles.
Dans lequel j'ai essayé deux méthodes pour construire la trame : utiliser des bitWrite est TROIS FOIS moins rapide que l'autre, d'où mon interrogation, plus haut, sur bitSet (que j'ai donc eu l'idée d'essayer, mais il nécessite un "if" et le résultat est encore plus dégradé qu'avec bitWrite)

/* ARDUINO MEGA */
#include <SPI.h>
//#include "SPIDaisyChain.h"

#define crans 128

const int8_t howmany_74HC = 4;
boolean pin74HC[howmany_74HC*8] = { 0 };      // data-table, matching with the state of the 74HC595 ouputs

 // 40 pseudos PWM à émuler - pour chaque PWM, la valeur donne le cran de consigne
uint8_t PWM[40] = { 54, 64, 105, 94, 109, 80, 130, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0 };   
uint32_t count;
uint32_t chain;

void setup() {
  Serial.begin(115200);

  DDRF |= B00001010;           // A1 & A3 = output
  SPI.begin();
}

void writeSlave1() {        // https://www.arduino.cc/en/Hacking/PinMapping2560
  PORTF &= B11111101;          // PIN A1 (latch) = LOW
  SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
  SPI.transfer(chain);
  SPI.endTransaction();
  PORTF |= B00000010;          // => HIGH
}

void setRegistersSlave1() {       // Place les pins des 74HC595 à l'état HAUT ou BAS
  for (uint8_t cran(0) ; cran<crans ; cran++) {
    chain = 0;
    for(uint8_t section(0) ; section<howmany_74HC*8 ; section++) {
      chain = chain<<1;
      pin74HC[section]=cran<PWM[section];
//      bitWrite(chain, section, pin74HC[section]);
      chain = pin74HC[section] + chain;
    }
  writeSlave1();
  }
}

void loop() {
    setRegistersSlave1();

    count++;
    if (millis() > 3000) {
      Serial.print(count/3);Serial.println("Hz");
      Serial.end();
    }   
}
« Modifié: janvier 18, 2021, 02:55:00 pm par simontpellier »

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #42 le: janvier 18, 2021, 08:48:22 pm »
(...)________________   APRES PRISE EN MAIN _______________________

J'ai en effet bien besoin d'aide, merci pour la proposition et c'est fort agréable de savoir qu'il y a une hot-line dédiée!

Après étude de ton code et différents essais, voici ce que je comprends (mais peut-être pas) et ce que je ne comprends pas (et ça c'est certain) :

1 - comprends :
- le code en l'état ne fonctionne pas, c'est un bout de code qui pourrait aider
c'est sensé fonctionner (mais comme toujours après correction des erreurs, je n'ai pas testé)
- sa structure permet de faire du daisy-chainning très simplement (si c'est bien ça, c'est très astucieux)
c'est juste ce que j'ai trouvé de + facile ... mais en effet, ça permet de s'adapter facilement à d'autres configurations, à d'autres mcu ...

2 - comprends pas :
- SPI.beginTransaction   dans le setup
c'est là qu'on initialise le hardware ; pas besoin de SPI.endTransaction(), c'est comme un ventilteur, ça s'arrête tout seul quand on coupe le courant à la fin ; par la suite les transfert sont lancés en écrivant directement dans le registre d'envoi, on ne reconfigure pas tout le barnum ; aussi, on n'attend pas la fin du transfert, on fait autre chose (calcul de la trame suivante) et pendant ce temps le transfert est fini
- ligne 6 : #define spi_latch_pin 19 // pin19 // PORTB permet l'instruction bitSet
       choisir une autre broche semble tout aussi possible
oui, une autre broche convient, éviter les ports à partir de F, qui n'admettent pas bitSet en 1 cycle
- ligne 32 : bitClear(PORTB, 0); // pin19 // prépare le latch
             La broche 19 n'est pas sur le port B mais D et il faudrait écrire sur le bit 2 et non 0
ok, confondu le numéro de la broche sur une mega, avec sa dénomination arduino  :-[

- if (PWMx[bit0to7] >= cran) bitSet(byte_to_spi, bit0to7);
   pas plutôt : if (PWMx[bit0to7] < cran) bitSet(byte_to_spi, bit0to7);
   car si le cran dépasse la consigne PWM "bit0to7", ça doit provoquer le falling, non ? C'est en tous cas comme ça que ça fonctionne avec mon code. c'est exactement ce que j'ai fait : le bit est systématiquement initialisé à LOW et devient HIGH tant que la consigne dépasse la rampe
   (détail, à propos de "bit0to7" : il me semble que "byte0to7" serait plus exact et facile à comprendre. De plus, pour normaliser et comme en réalité ces datas représentent la PWMje fais une évaluation depuis le bit0 jusqu'au bit7, il m'a semblé logique d'appeler le bit en cours bit0to7
   à appliquer à une section donnée, ça pourrait donc encore plus clairement s'écrire :    if (PWMx[section] < cran) bitSet(byte_to_spi, section);)

3 - comprends pas bien : le choix de bitSet pour construire la trame (voir mes essais)
c'est peut-être tout simplement 2 façons différentes de faire la même chose, chacun utilisant celle qu'il comprend
Pour ma part, voici mes maigres avancées :
- le passage au SPI hardware a un effet très modeste, le gain n'est que d'une 20aine de % c'est déjà ça, le but c'est de faire faire le + de travail possible au hardware, car le soft est déjà très pris par la confection des trames avant envoi
- devant me plier au protocole SPI, j'ai des difficultés à construire la trame. Elle est limitée par le protocole à 32bits ce qui fait que je ne peux pas atteindre les 40PWM "imposées" sans devoir faire du daisy-chainning (ou définir deux esclaves). Mais donc ça se solutionne
Le plus ennuyeux est que la construction de la trame est semble-il ce qui ralentit le code à tel point que le bénéfice du SPI rapide disparaît presque complètement
Voici le nouveau code, avec une syntaxe quasiment normalisé pour que les échanges soient plus faciles.
Dans lequel j'ai essayé deux méthodes pour construire la trame : utiliser des bitWrite est TROIS FOIS moins rapide que l'autre, d'où mon interrogation, plus haut, sur bitSet (que j'ai donc eu l'idée d'essayer, mais il nécessite un "if" et le résultat est encore plus dégradé qu'avec bitWrite) bitSet et bitClear prennent 1 cycle, on ne peut pas faire + vite

/* ARDUINO MEGA */
#include <SPI.h>
//#include "SPIDaisyChain.h"

#define crans 128

const int8_t howmany_74HC = 4;
boolean pin74HC[howmany_74HC*8] = { 0 };      // data-table, matching with the state of the 74HC595 ouputs

 // 40 pseudos PWM à émuler - pour chaque PWM, la valeur donne le cran de consigne
uint8_t PWM[40] = { 54, 64, 105, 94, 109, 80, 130, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0, 54, 64, 105, 94, 109, 34, 73, 0 };   
uint32_t count;
uint32_t chain;

void setup() {
  Serial.begin(115200);

  DDRF |= B00001010;           // A1 & A3 = output
  SPI.begin();
}

void writeSlave1() {        // https://www.arduino.cc/en/Hacking/PinMapping2560
  PORTF &= B11111101;          // PIN A1 (latch) = LOW
  SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
  SPI.transfer(chain);
  SPI.endTransaction();
  PORTF |= B00000010;          // => HIGH
}

void setRegistersSlave1() {       // Place les pins des 74HC595 à l'état HAUT ou BAS
  for (uint8_t cran(0) ; cran<crans ; cran++) {
    chain = 0;
    for(uint8_t section(0) ; section<howmany_74HC*8 ; section++) {
      chain = chain<<1;
      pin74HC[section]=cran<PWM[section];
//      bitWrite(chain, section, pin74HC[section]);
      chain = pin74HC[section] + chain;
    }
  writeSlave1();
  }
}

void loop() {
    setRegistersSlave1();

    count++;
    if (millis() > 3000) {
      Serial.print(count/3);Serial.println("Hz");
      Serial.end();
    }   
}
[/quote]
« Modifié: janvier 19, 2021, 02:59:49 pm par trimarco232 »

simontpellier

  • Full Member
  • ***
  • Messages: 115
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #43 le: janvier 19, 2021, 03:00:35 pm »
Bonjour,
moyennant beaucoup-beaucoup d'essais... ayé tout marche !

Je rapporte auparavant quelques grosses surprises notées en cours de route.

- le SPI logiciel... en réalité il est déjà bien rapide !
     en effet, remplacer la boucle :   for (uint8_t cran(0) ; cran<crans ; cran++)
     par   cran=0; //................// if (cran == 127) cran = 0;
     multiplie la vitesse par 10 ! de 130 à 1200 Hz !

- mais bien loin c'est vrai du SPI Harware : 20kHz
     avec la aussi une surprise (c'est confirmé) : chain = chain<<1;   chain = (cran<PWM5[section]) + chain; tourne à 20Mhz
     tandis que  (PWMx[section] >= cran) bitSet(byte_to_spi, section);     n'atteint pas 9Mz  (mais un poil plus haut que bitWrite)

Voici le code, de plus en plus concis, avec ton code exemple à la base. Et avec comme dernière surprise qu'un code plus "Arduino-rustique" et qui utilise SPI.transfer fait légèrement mieux !

/* ARDUINO MEGA */
#include <SPI.h>

#define crans 128

#define SpiControl 0b01110000   //  sur bits: SPIE SPE DORD MSTR CPOL CPHA SPR1 SPR0
#define bDa  2   // MOSI 51
#define bCk  1   // CLK pin 52
#define bLd  0   // SS pin 53


 // pseudos PWM à émuler - pour chaque PWM, le tableau donne le cran entre de 0 à 127
uint8_t PWM1[] = { 0, 0, 0, 0, 0, 100, 0, 00 };
uint8_t PWM2[] = { 0, 0, 0, 0, 0, 0, 10, 0 };
uint8_t PWM3[] = { 54, 26, 107, 96, 111, 82, 125, 2 };
uint8_t PWM4[] = { 54, 27, 108, 97, 112, 83, 126, 3 };
uint8_t PWM5[] = { 54, 28, 109, 98, 113, 84, 127, 4 };

uint8_t cran = 0;
uint32_t count;
uint8_t chain;

void setup() {
  Serial.begin(115200);
  DDRB |= 1<<bLd | 1<<bCk | 1<<bDa ;
  SPCR = SpiControl ;
}

void setSlaveRegisters() {       //Place les pins des 74HC595 à l'état HAUT ou BAS
  cran++;
  bitClear (PORTB,bLd);

  chain = 0 ;
    for (int8_t section(0); section<8; section++) {
      chain = chain<<1;
      chain = (cran<PWM5[section]) + chain;   /* pour mémoire : pin74HC[section]=cran<PWM[section]; */
    }
    SPDR = chain;
    while (!(SPSR & 1<<SPIF)) {}

        chain = 0 ;
          for (int8_t section(0); section<8; section++) {
            chain = chain<<1;
            chain = (cran<PWM4[section]) + chain;
          }
          SPDR = chain;
          while (!(SPSR & 1<<SPIF)) {}

  (...)
 
  chain = 0 ;
    for (int8_t section(0); section<8; section++) {
      chain = chain<<1;
      chain = (cran<PWM1[section]) + chain;
    }
    SPDR = chain;
    while (!(SPSR & 1<<SPIF)) {}

  bitSet (PORTB,bLd);   
  if (cran == crans-1) cran = 0;   
}

void loop() {
    setSlaveRegisters();   
}

20kHz... de quoi faire tourner même les moteurs qui aiment les hautes fréquences ! (avec mes 40Hz optimum constaté,  je reste sur ma faim d'une explication!)

Beau travail non ? Un TRES GRAND MERCI.
Me reste juste à bagoter le latch avec un timer (je reviendrai) et j'aurai plus qu'à dessiner un réseau avec 40 sections !


« Modifié: janvier 19, 2021, 03:02:33 pm par simontpellier »

trimarco232

  • Sr. Member
  • ****
  • Messages: 267
    • Voir le profil
Re : Problème pour utiliser l'horloge externe du PCA9685
« Réponse #44 le: janvier 19, 2021, 05:55:20 pm »
bravo !
le problème à voir maintenant, c'est le temps qu'il reste au cpu de faire autre chose ...
dans ta maquette, tu as fait tourner en boucle le calcul des trames, il faut insérer du temps pour les autres opérations de l'arduino
au pif, je dirais qu'on peut limiter, par timer, la fréquence à 13kHz, ce qui laisse au cpu 1/3 de sa capacité de calcul
par ailleurs :
- générer le latch directement par le hardware du timer n'est peut-être pas une bonne idée, il faut peut-être pouvoir vérifier que les transferts sont terminés avant de latcher : à faire au début de l'interruption provoquée par le timer
- while (!(SPSR & 1<<SPIF)) {} : il faut enlever ça ! car quand le transfert hardware est lancé par SPDR = chain; , on peut directement passer au calcul de la trame du prochain transfert (pour 8 section), ce qui laisse très largement au spi hardware de terminer le job, donc : pas la peine de vérifier qu'il a fini, et surtout ne pas attendre qu'il ait fini avant d'enchaîner la suite des calculs
- on peut peut-être encore gagner un peu de temps en supprimant la boucle "for section<8", cad. réécrire 8 fois le calcul de chain ... (sauf si le compilo reconnaît la magouille et passe outre ... il faut voir si on a des options de compilation)
« Modifié: janvier 19, 2021, 06:00:15 pm par trimarco232 »