Arduino MEGA 2560 vitesse traitement code / Optimisation

Bonjour,

Ce sujet est la continuité du sujet " Arduino MEGA 2560 récupérer valeur depuis Ethernet " et je souhaite votre avis concernant la réalisation du code de récupération et traitement de messages I2C.
En effet je souhaite utiliser un arduino pour générer un signal carré, la valeur de la fréquence arrivera par I2C. Le hic c'est que le traitement de cette valeur est "lent" et ne me permet pas d'aller aussi vite que souhaité.
Le signal carré sature..

voici le code :


//COM 6
#include <Wire.h>
#define broche 11                              //  broche

int ledState = LOW;                                     // ledState used to set the LED
unsigned long frequenceInt;                             // frequenceInt 
unsigned long previousMicros = 0;                       // previousMicros take account the last state of the output (broche 12)

void setup() {
  Wire.begin();                             // join i2c bus (address optional for master)
  Serial.begin(2000000);                    // start serial for output
  pinMode(broche, OUTPUT);                       // output PIN setup in OUTPUT
}

void loop() {

  Wire.requestFrom(8, 4);         // request 6 bytes from peripheral device #8
  unsigned long value = 0;        //  value
  int i = 0;
  while (Wire.available()) {            // peripheral may send less than requested
    unsigned long c = Wire.read();      // receive a byte and store it in unsigned long so it can be shifted later
    value |= (c << (i*8));              // shift the received byte value and store it with the previous value
    i++;
  }

  frequenceInt = value;

  unsigned long currentMicros = micros();                     
  if (currentMicros - previousMicros >= frequenceInt) {      //   here val must be the frequency value ( 1/2 period value)
      previousMicros = currentMicros;
      ledState = !ledState;
      digitalWrite(broche, ledState);
  }
}

Est-ce que le temps de traitement du message est contraint par le protocole I2C ? Si oui, puis-je utilisé un protocol plus rapide ?

J'ai estimé à 0,54ms le temps de traitement du message.
Lorsque je retire ces quelques lignes de code, j'arrive à obtenir un signal carré beaucoup plus rapide.

Merci d'avance pour vos conseils.

Question basique (en complément du principe d'optimisation du code ou protocole de communication) :
Si je prends un arduino Due qui a une "speed clock" de 84MHz, comparé a l'arduino MEGA a 16MHz, je devrais être capable de réduire le temps de traitement ?

Bonjour,

Entre les protocoles de communication : I2C et Serial (SPI) lequel est le plus rapide ( aussi bien en terme de transfert de data que de traitement après réception ) ?

J'ai vu que SPI est limité à 115200 bds, mais je n'ai pas trouvé d'infos concernant I2C sur l'arduino.

En vous remerciant.

Qu'est-ce que cela veut dire un signal qui sature ?
Décris ce qui se passe.

Il y a deux points différents dans ce que tu expose :

  • L'I2C
  • La génération du signal.

Faux, la fréquence max d'horloge du SPI est la moitié de la fréquence de l'horloge système qui pilote le module SPI. Soit avec une Mega : 8 MHz.
Le seul document à lire est celui de la datasheet du microcontrôleur. Trouver la datasheet sur le site Microchip, pas sur le site Arduino.

Peut-être !
Mais probablement pas dans le rapport 84/16.
Le microcontrôleur de la DUE n'est pas un Atmel d'architecture avr, c'est un Atmel architecture ARM Cortex M3.
Dans les microcontrôleurs basés sur une architecture ARM, tout ne tourne pas à la fréquence horloge, mais souvent à la fréquence moitié, ou moins.
Je t'invite à lire la datasheet de ce microcontrôleur.

Si le SPI est piloté par une horloge système à 42 MHz, l'horloge du SPI ne pourra pas tourner à plus de 21 MHz au lieu de 8 MHz pour le micro de la mega.

Avec une Mega et son microcontrôleur avr, il y a deux voies d'amélioration.

  1. I2C
    Si tu n'as qu'un seul esclave sur le bus et que la distance entre le maitre et l'esclave ne fait pas plusieurs mètres [1], tu peux faire passer la vitesse du bus de 100 kHz à 400 kHz.
    Le changement de fréquence est prévu, mais non documenté. Il faut consulter les fichiers Wire.h et Wire.cpp.
    [1] L'I2C a été pensée pour relier les CI sur une même carte, soit distance max 20 ou 30 cm, elle peut faire mieux, mais ce n'est pas garanti.

  2. Utiliser le SPI si c'est possible, n'étant pas bidirectionnel, il supporte des distances plus longues.

  3. Génération du signal.
    Tu crées un signal de façon logicielle, c'est bien dommage ne pas mieux utiliser le microcontrôleur avec ses timers en mode CTC.

En génération logicielle, il faudrait au strict minimum remplacer la fonction digitalWrite par digitalWriteFast (fichier digitalwritefast.h à trouver et à inclure)

Dans un microcontrôleur tout ne se fait pas par programmation, il y a des modules d'électronique pure (Liaison série, I2C, SPI, PWM,...) qui sont en électronique pure parce que l'électronique est plus rapide que la programmation.

En programmation, il faut exécuter les instructions les unes après les autres et chacune prend plusieurs cycles horloge.
En électronique pure, un front d'horloge peut faire commuter 10 ou 20 circuits simultanément sans que cela pose un problème.

En mode CTC avec une mega tu peux obtenir une fréquence jusqu'à 8 MHz (même raison que pour le SPI => fréquence max = 16 MHz / 2).
Attention, la valeur exacte de la fréquence doit être un entier qui tient dans un registre du microcontrôleur, avec des registres 8 bits tout n'est pas possible.
La formule de calcul est clairement exposée dans la datasheet.

Je n'ai pas regardé le cas de la DUE, étant donné qu'elle est 32 bits au lieu de 8 bits, elle possède plus de possibilités.
Ici la DUE n'a jamais rencontré un franc succès, ses débuts ayant été catastrophiques.
Chez Arduino, il y a aussi les Zero/MKR avec le samd21 Atmel ARM cortex M0, il ne tourne qu'à 48 MHz.

AMHA une mega avec un timer en mode CTC et du SPI devraient faire l'affaire.

1 Like

Un problème mal posé est un problème mal résolu.
Si déjà tu disais exactement ce que tu veux fire.
On est parti d'une liaison Ethernet pour maintenant arriver à de l'I²C qui est 1000 fois plus lent et au final on a aucune idée du vrai problème.
Tu veux généré un signal de quel type?
Sur quelle gamme de fréquence?
Qu'elles sont tes contraintes d'interface?

Bonjour wawann

Le générateur de signal carré est autonome, seul sa vitesse est envoyée via i2C. En quoi la vitesse du bus empêche de "monter" la fréquence, à moins que tu veuilles transporter, par i2C, un signal pour faire de la MF côté générateur.

Cordialement.
jpbbricole

Cette ligne demande 4 octets au périphérique (ta MEGA). Cette opération est réalisée en boucle, donc ralentit fortement la loop().
Si tu inversais esclave et maître ?
La MEGA enverrait donc les données uniquement si besoin, si la valeur change par exemple.

Le mode timer CTC n'est pas perturbé par la programmation.
Une fois lancé, il est autonome.

Je rejoins fdufnews : où est le cahier des charges ?
Il s'écrit avant de commencer à réaliser et non pas pendant la réalisation.

Note : réaliser pris dans tous les sens du terme, pas seulement la programmation.

Bonjour, merci pour vos commentaires et conseils.
Mon projet comportant plusieurs éléments, je l'ai découpé afin de rendre plus facile la réalisation, mais au final j'aimerai :

  • une arduino qui puisse lire une information transmise en continu par le réseau ethernet
  • une arduino capable de générer un signal carré de 1 à 3250 Hz

Compte tenu des difficultés et délais de traitement j'ai fait évolué le projet en utilisant 2 arduino.

  • une arduino Maitre qui traite les messages provenant du réseau ethernet
  • une arduino Maitre qui renvoi l'information " valeur de frequence " en I2C à l'arduino Esclave
  • une arduino Esclave qui ne fait que lire l'information ' valeur de frequence " et générer le signal carré en fonction bien sur de cette valeur.

Sur le papier cela semble assez simple mais je constate que les temps de traitement perturbent fortement la génération du signal carré. En effet si je fais une simple boucle j'arrive facilement à obtenir un signal carré propre et stable à la valeur de 3250Hz, par contre si j'ajoute le code de traitement de la valeur I2C alors je sature à 500Hz avec l'arduino DUE...

J'ai aperçu dans vos commentaire le mode timer CTC, si cela peut aider à générer un signal carré à la fréquence maximale voulue, je vais regarder ce que c'est exactement (car pour l'instant cela ne me parle pas).

En tout cas merci pour tout car cela m'ouvre de nouvelles perspectives !

Bon, du coup si j'ai bien pigé le fonctionnement du CTC, il permet le compter les cycles d'horloge et de créer une interruption a un nombre de cycle donné. Par exemple, en partant de l'arduino MEGA qui tourne à 16MHz, si je veux une interruption toutes les 154µs il faudra compter le nombre de cycles et executer une action tous les 2566 cycles. En jouant sur TCNT1 = 0 et OCR1A = 2566.

J'ai pas encore bien pigé le truc, merci de me confirmer que je vais bien dans la bonne direction !

Après quelques tests, sur l'arduino MEGA, je confirme bien le fonctionnement du CTC avec :
TCNT1 = 0
OCRIA = 1
je fais clignoter une LED à 15,6KHz.

Donc si le fonctionnement du CTC est indépendant de l'exécution du code, alors je devrais être en mesure de faire varier la fréquence de clignotement de cette LED sans être perturbé par le traitement du code contenu dans le void loop () ... test à réaliser...

Cependant j'ai constaté les résultats suivants :
OCRIA = 9 donne fréquence = 3125Hz
OCRIA = 10 donne fréquence = 2840Hz
OCRIA = 11 donne fréquence = 2604Hz
OCRIA = 12 donne fréquence = 2403Hz
OCRIA = 13 donne fréquence = 2232Hz
OCRIA = 14 donne fréquence = 2083Hz

Ces résultats ne sont pas satisfaisants car cela ne me donne pas une précision suffisante. Y a t'il possibilité d'obtenir une finesse plus grande ? hertz par hertz ?

Bon du coup en modifiant les bits CS10, CS11 et CS12 ( prédiviseur du timer ) j'arrive à une résolution suffisante ! ça se présente plutot bien !

Bonjour, j'ai une question :

comment faire pour passer une variable dans la valeur OCRA1 ?

en gros j'arrive a facilement changer la valeur OCRA1 en modifiant manuellement la valeur dans le code, mais maintenant j'aimerai que ces valeurs soient portées par une variable :
Créer une variable globale ?
Voir le code réalisé mais qui ne prend pas en compte la valeur transmise par l'arduino master.

#include <Wire.h>
#define LED_BUILTIN 12

int ledState = LOW;                                     // etat initial de la LED
int val_ocra;					              // valeur OCR1A que l'on reçoit de l'arduino master


void setup () {
  Wire.begin();                             			// i2c bus (address optional for master)
  pinMode(broche, OUTPUT);                       		// output PIN parametré en OUTPUT
  Serial.begin(2000000);              					// vitesse du terminal
  pinMode (LED_BUILTIN, OUTPUT);

// ======================================================================================================================================================
// Paramétrage du timer1, pour qu'il déclenche une interruption, à chaque fois que sa valeur sera égale à celle qu'on aura indiqué dans le registre OCR1A  
// ======================================================================================================================================================
  noInterrupts();                 // On désactive les interruptions, pour commencer
  TCCR1A = 0b00000000;      // pour WGM11=0 et WGM10=0  On règle les bits WGM10, WGM11, WGM12, WGM13 pour fonctionner en mode "CTC"
  TCCR1B = 0b00001001;      // pour WGM12=1 et WGM13=0, puis CS12=0/CS11=0/CS10=1 pour prédiviseur réglé sur division par 1
  TIMSK1 = 0b00000010;      // pour OCIE1A=1, afin d'activer l'interruption par comparaison "A" (test d'égalité entre timer et valeur de registre OCIE1A)

  bitSet(TIMSK1, OCIE1A);         // On met le bit OCIE1A à 1 (contenu dans le registre TIMSK1)

  // Enfin, on met le compteur à zéro, on entre la valeur déclenchant l'interruption (nos "31250" coups d'horloge), et on réactive les interruptions
  TCNT1 = 0;            // Mise du timer1 à zéro
  OCR1A = val_ocra;        // Valeur de comptage de cycles d'horloge qui correspond à la fréquence voulue
  interrupts();         // Et, on ré-active les interruptions
}

// ======================
// Routine d'interruption
// ======================
ISR(TIMER1_COMPA_vect) {
  digitalWrite (LED_BUILTIN, !digitalRead(LED_BUILTIN));		// À chaque appel d'interruption, on inverse l'état de la LED
}
 

void loop () {
  Wire.requestFrom(8, 4);         // request 6 bytes from peripheral device #8
  unsigned long value = 0;        // v = value
  int i = 0;
  while (Wire.available()) {            // peripheral may send less than requested
    unsigned long c = Wire.read();      // receive a byte and store it in unsigned long so it can be shifted later
    value |= (c << (i*8));              // shift the received byte value and store it with the previous value
    i++;
  }

  val_ocra = value;                      // ici on recupere la valeur OCRA1 provenant de arduino master

}

En vous remerciant.

Qu'est-ce tu crois que cela va faire?

Il suffit simplement d'affecter value à OCR1A

Bonjour,

bah oui c'est vrai, encore aurait-il fallu que je mette "OCR1A = val_ocra" (ici OCR1A = frequenceInt) au bon endroit ....

Bon c'est cool, ça fonctionne !
Merci pour vos conseils !

#include <Wire.h>
#define LED_BUILTIN 12

int ledState = LOW;                                     // ledState used to set the LED
unsigned long frequenceInt;                             // frequenceInt 

void setup () {
  Wire.begin();                             // join i2c bus (address optional for master)
  Serial.begin(2000000);              // terminal speed
  pinMode (LED_BUILTIN, OUTPUT);

  // ======================================================================================================================================================
  // Paramétrage du timer1, pour qu'il déclenche une interruption, à chaque fois que sa valeur sera égale à celle qu'on aura indiqué dans le registre OCR1A  
  // ======================================================================================================================================================
  noInterrupts();                 // On désactive les interruptions, pour commencer
  TCCR1A = 0b00000000;
  TCCR1B = 0b00001001;
  TIMSK1 = 0b00000010;

  bitSet(TIMSK1, OCIE1A);         // On met le bit OCIE1A à 1 (contenu dans le registre TIMSK1)

  // Enfin, on met le compteur à zéro, on entre la valeur déclenchant l'interruption (nos "31250" coups d'horloge), et on réactive les interruptions
  TCNT1 = 0;            // Mise du timer1 à zéro
  //OCR1A = frequenceInt;        // Valeur correspondant à 500ms (car 31250 fois 16 µS donne bien 500ms ; pour rappel, ces 16 µS proviennent du calcul 1/(16MHz/256),
  interrupts();         // Et, on ré-active les interruptions
}


// ======================
// Routine d'interruption
// ======================
ISR(TIMER1_COMPA_vect) {
  digitalWrite (LED_BUILTIN, !digitalRead(LED_BUILTIN));
  OCR1A = frequenceInt;			// mise à jour de la valeur OCR1A en fonction de la valeur envoyée par arduino master
}
 
// =================
// Boucle principale
// =================
void loop () {
  Wire.requestFrom(8, 4);         // request 6 bytes from peripheral device #8
  unsigned long value = 0;        // v = value
  int i = 0;
  while (Wire.available()) {            // peripheral may send less than requested
    unsigned long c = Wire.read();      // receive a byte and store it in unsigned long so it can be shifted later
    value |= (c << (i*8));              // shift the received byte value and store it with the previous value
    i++;
  }

  frequenceInt = value;                   // value
  Serial.println(frequenceInt);
}

Pourquoi unsigned long?
OCR1A est sur 16 bits. Donc plutôt utiliser uint16_t.
Pour éviter les mauvaises surprise faire

noInterrupts();
frequenceInt = value;
interrupts();

Car "frequenceInt" était utilisé précédemment pour passer des valeurs plus grandes... mais oui, merci pour la remarque.

Bonjour,
maintenant que j'arrive a générer un signal carré, en modifiant la valeur contenue dans OCR1A j'aimerai aussi pouvoir jouer sur le prédiviseur du compteur, à savoir TCCR1B..

Après plusieurs essais, je n'ai pas l'impression d'arriver à modifier ce paramètre.

Dans le principe, je fais un test dans le Loop qui consiste à lire OCR1A et à définir le paramètrage de TCCR1B en fonction des valeurs OCR1A, mais dans la réalité il ne se passe rien.
Je suppose que cela est fortement lié au type de valeur que je mets dans ma variable prescaler, mais je n'abouti pas. J'ai mis des "marqueurs' afin de vérifier que le test "if" fonctionne bien, ce qui est le cas.

Par ailleurs lorsque je paramètre en manuel TCCR1B, le paramètre fonctionne :
char prescaler = 0b00001101; par exemple.

Voici le code, si vous pouvez me dire ce qui cloche cela m'aiderai beaucoup. Je fortement que c'est fortement lié au code dans le Loop... Merci.

#include <Wire.h>
#define LED_BUILTIN 12

//int pinState = LOW;                   // etat de la PIN qui sert a générer le signal carré
char prescaler;// = 0b00001101;                       // variable d'ajustement du prescaler en fonction de la valeur de frequence
unsigned short frequenceInt;           // frequenceInt 

void setup () {
  Wire.begin();                             // join i2c bus (address optional for master)
  Serial.begin(2000000);              // terminal speed
  pinMode (LED_BUILTIN, OUTPUT);

  // ======================================================================================================================================================
  // Paramétrage du timer1, pour qu'il déclenche une interruption, à chaque fois que sa valeur sera égale à celle qu'on aura indiqué dans le registre OCR1A  
  // ======================================================================================================================================================
  noInterrupts();                 // On désactive les interruptions, pour commencer
  TCCR1A = 0b00000000;            
  //TCCR1B = prescaler;            // TCCR1B = 0b00001001 pour desactiver prescaler  et  TCCR1B = 0b00001101 pour prescaler = 1024
  TIMSK1 = 0b00000010;

  bitSet(TIMSK1, OCIE1A);         // On met le bit OCIE1A à 1 (contenu dans le registre TIMSK1)

  // Enfin, on met le compteur à zéro, on entre la valeur déclenchant l'interruption (nos "31250" coups d'horloge), et on réactive les interruptions
  TCNT1 = 0;            // Mise du timer1 à zéro
  interrupts();         // Et, on ré-active les interruptions
}


// ======================
// Routine d'interruption
// ======================
ISR(TIMER1_COMPA_vect) {
  digitalWrite (LED_BUILTIN, !digitalRead(LED_BUILTIN));
  TCCR1B = prescaler;
  OCR1A = frequenceInt;			// mise à jour de la valeur OCR1A en fonction de la valeur envoyée par arduino master (valeur OCR1A calculée en fonction de frequence horloge, prescaler)
      
}
 
// =================
// Boucle principale
// =================
void loop () {
  Wire.requestFrom(8, 4);         // request 6 bytes from peripheral device #8
  unsigned long value = 0;        // v = value
  int i = 0;
  while (Wire.available()) {            // peripheral may send less than requested
    unsigned long c = Wire.read();      // receive a byte and store it in unsigned long so it can be shifted later
    value |= (c << (i*8));              // shift the received byte value and store it with the previous value
    i++;
  }

  frequenceInt = value;                   // value
  Serial.println(frequenceInt);
  if (value<2430) {
    char prescaler = 0b00001101;
    Serial.println("valeur inferieure à 2430");
    Serial.println(prescaler);
  }
  else {
    char prescaler = 0b00001001;
    Serial.println("valeur supérieure à 2430");
    Serial.println(prescaler);
  }

}

Bon, j'ai commencé à comprendre d'ou l'erreur provient, sans encore avoir à réussi à la corriger.. et effectivement ça provient bien du type de charactere de la variable prescale... j'ai donc fait quelquechose d'un peu plus intelligent... voir le code :

#include <Wire.h>
#define LED_BUILTIN 12

//int pinState = LOW;                   // etat de la PIN qui sert a générer le signal carré
char prescaler[12];            // variable d'ajustement du prescaler en fonction de la valeur de frequence
unsigned short frequenceInt;           // frequenceInt 

void setup () {
  Wire.begin();                             // join i2c bus (address optional for master)
  Serial.begin(2000000);              // terminal speed
  pinMode (LED_BUILTIN, OUTPUT);

  // ======================================================================================================================================================
  // Paramétrage du timer1, pour qu'il déclenche une interruption, à chaque fois que sa valeur sera égale à celle qu'on aura indiqué dans le registre OCR1A  
  // ======================================================================================================================================================
  noInterrupts();                 // On désactive les interruptions, pour commencer
  TCCR1A = 0b00000000;            
  //TCCR1B = 0b00001001;            // TCCR1B = 0b00001001 pour desactiver prescaler  et  TCCR1B = 0b00001101 pour prescaler = 1024
  TIMSK1 = 0b00000010;

  bitSet(TIMSK1, OCIE1A);         // On met le bit OCIE1A à 1 (contenu dans le registre TIMSK1)

  // Enfin, on met le compteur à zéro, on entre la valeur déclenchant l'interruption (nos "31250" coups d'horloge), et on réactive les interruptions
  TCNT1 = 0;            // Mise du timer1 à zéro
  interrupts();         // Et, on ré-active les interruptions
}


// ======================
// Routine d'interruption
// ======================
ISR(TIMER1_COMPA_vect) {
  digitalWrite (LED_BUILTIN, !digitalRead(LED_BUILTIN));
  TCCR1B = prescaler;
  OCR1A = frequenceInt;			// mise à jour de la valeur OCR1A en fonction de la valeur envoyée par arduino master (valeur OCR1A calculée en fonction de frequence horloge, prescaler)
      
}
 
// =================
// Boucle principale
// =================
void loop () {
  Wire.requestFrom(8, 4);         // request 6 bytes from peripheral device #8
  unsigned long value = 0;        // v = value
  int i = 0;
  while (Wire.available()) {            // peripheral may send less than requested
    unsigned long c = Wire.read();      // receive a byte and store it in unsigned long so it can be shifted later
    value |= (c << (i*8));              // shift the received byte value and store it with the previous value
    i++;
  }

  frequenceInt = value;                   // value
  Serial.println(frequenceInt);
  if (value<2430) {
    char prescaler[12] = "0b00001101 ";
    Serial.println("valeur inferieure à 2430");
    Serial.println(prescaler);
  }
  else {
    char prescaler[12] = "0b00001001 ";
    Serial.println("valeur supérieure à 2430");
    Serial.println(prescaler);
  }

}

Bonjour,

Tu as un problème ici:
char prescaler[12] = "0b00001101 ";
Tu redéfinis une variable locale et tu ne modifies pas la variable globale
Ce qu'il faut faire:
strcpy(prescaler, "0b00001101 ");

Idem pour
char prescaler[12] = "0b00001001 ";

Mais je ne vois pas bien ce que tu veux faire. prescaler devrait être défini comme un uint8_t et non comme une string.
Et tu fais simplement:
prescaler = 0b00001101;

Bonjour, bon oui je ne sais pas pourquoi je suis passé par du char... certainement parce que j'étais perturbé par la valeur renvoyée de prescaler dans le moniteur... mais oui ça fonctionne !!!
Merci pour la remarque.