Ardu? No!Initiation ≫ Transtypage

Transtypage

Reprenons le programme de la page précédente qui permettait un clignotement de la LED à la seconde et un affichage de nombres entre 0 et 99 aléatoires:

const unsigned long DEMI_PERIODE = 500UL;
unsigned long dernierChangement;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // Configuration de la broche de la led
  Serial.begin(115200); // Pour pouvoir afficher dans la console
}

void loop() {
  // On s'occupe de la LED
  if (millis() - dernierChangement >= DEMI_PERIODE) {
    // Changement de l'état de la LED
    if (digitalRead(LED_BUILTIN) == HIGH) // Si la LED est allumée
      digitalWrite(LED_BUILTIN, LOW); // Éteint la led si elle était allumée
    else // Si la LED est éteinte
      digitalWrite(LED_BUILTIN, HIGH); // Allume la led si elle était éteinte
    dernierChangement += DEMI_PERIODE; // Nouveau départ
  }
  
  // On s'occupe des nombres affichés
  Serial.println(random(100)); // Affiche un nombre de 0 à 99
}

Taille des variables

Pour nos variables, nous avons pris des unsigned long car millis() retournait précisément ce type de valeurs. C'est d'ailleurs ce que fait tout le monde... sauf moi! En analysant le programme, on se rend compte que l'on utilise un unsigned long pour DEMI_PERIODE alors que la valeur stockée est de 500 et qu'un simple word suffirait. C'est gâcher 2 octets pour DEMI_PERIODE, et d'ailleurs aussi deux octets pour dernierChangement. Cette page va nous montrer comment économiser 4 octets qui finalement ne servaient à rien (ils contenaient la valeur fixe zéro).

Lorsque l'on fait des temporisations avec millis(), il faut en gros que la temporisation (ici DEMI_PERIODE) soit inférieur à la valeur maximale de l'entier non signé utilisé. Même un peu moins car d'une part toutes les valeurs de millis() n'existent pas car elle s'incrémente parfois de 1, parfois de 2, et d'autre part il faut que l'on passe suffisamment souvent sur le test.

En fonction des types utilisés, on peut avoir les temporisation suivantes:
• avec des byte / uint8_t on peut aller jusqu'à 250ms environ
• avec des word / uint16_t on peut aller jusqu'à 32s environ
• avec des unsigned long / uint32_t on peut aller jusqu'à 49 jours environ
• cela n'a pas d'intérêt d'utiliser uint64_t car millis() retourne qu'un unsigned long.

Avec un word

Notre temporisation n'est que de 500ms, il faut donc une taille capable de contenir 500, un word convient.

Il va quand même y avoir un problème dans le test conditionnel:

if (millis() - dernierChangement >= DEMI_PERIODE)

En définissant la variables dernierChangement et la constante DEMI_PERIODE sur 16 bits, on n'a pas changé millis() qui reste défini sur 32 bits. La soustraction va donc se faire sur 32 bits. Quand millis() dépasse 65535+500, comme dernierChangement ne peut plus avoir la bonne valeur, la condition sera toujours vraie et la led va se mettre à changer d'état à chaque boucle de loop(). Cela ne fonctionne plus.

Avec un word et le transtypage

Le problème vient de millis() qui est sur 32 bits. Il faut donc faire une transformation pour ne garder que les 16 bits de poids faibles. On va alors transtyper la valeur, c'est à dire changer son type pour avoir un word. Cela se fait en mettant le nom du type souhaité et le nombre entre parenthèses. Un peu comme du word() était une fonction qui retourne un word;

if (word(millis()) - dernierChangement >= DEMI_PERIODE)

Tant que millis() est petit, il n'y a pas de problèmes, mais quand millis() dépasse les 65535, la valeur ne tenant plus sur un word, la valeur va être tronquée et on ne va garder que les bits de poids faibles. Cela peut paraître surprenant au début, mais on a déjà vu ce comportement quand on laissait tomber la retenue.

Testez le programme de l'étape précédente en remplaçant les deux unsigned long par des word et en remplaçant la condition comme ci-dessus. Aide?const word DEMI_PERIODE = 500; word dernierChangement; void setup() { pinMode(LED_BUILTIN, OUTPUT); // Configuration de la broche de la led Serial.begin(115200); // Pour pouvoir afficher dans la console } void loop() { // On s'occupe de la LED if (word(millis()) - dernierChangement >= DEMI_PERIODE) { // Changement de l'état de la LED if (digitalRead(LED_BUILTIN) == HIGH) // Si la LED est allumée digitalWrite(LED_BUILTIN, LOW); // Éteint la led si elle était allumée else // Si la LED est éteinte digitalWrite(LED_BUILTIN, HIGH); // Allume la led si elle était éteinte dernierChangement += DEMI_PERIODE; // Nouveau départ } // On s'occupe des nombres affichés Serial.println(random(100)); // Affiche un nombre de 0 à 99 }

Avec un word et un autre transtypage

Ce qui compte surtout, c'est que la comparaison se fasse correctement, c'est à dire sur 16 bits non signés. On peut donc aussi transtyper toute la soustraction:

if (word(millis() - dernierChangement) >= DEMI_PERIODE)

Je vous engage évidemment à essayer aussi cette forme. Il n'y a qu'une parenthèse à déplacer.

Avec un byte et le transtypage

Supposons maintenant que nous voulions une temporisation de 200ms seulement. Cela tient sur un octet, et nous allons donc remplacer les trois word par trois byte ainsi que le 500 qui va passer à 200. Gardez la deuxième forme pour le transtypage:

if (byte(millis() - dernierChangement) >= DEMI_PERIODE)

Cela ca encore fonctionner. Ce n'est donc pas la peine d'utiliser une variable de 4 octets quand un seul suffit. Mais il va y avoir un petit problème avec l'écriture:

if (byte(millis()) - dernierChangement >= DEMI_PERIODE)

Sans que je ne sache exactement pourquoi, le résultat de la soustraction entre deux octets non signés est signé. Cela ne fonctionne donc pas. Ce n'est pas grave, il y a l'autre écriture qui fonctionne.

Bien entendu, je vous engage à tester ces deux modifications.