Le type float

Jusqu'à présent, nous avons utilisé des entiers. Ce ne sont pas les seuls types de variables disponibles, nous allons nous intéresser ici aux nombres réels.

Dans cette page, nous allons faire clignoter la led avec une période de 1s, et avec un temps d'allumage qui va diminuer de 0,5s à 0.

Clignoter avec des entiers

Reprenons le programme "blink with delay" de la page précédente qui va nous servir de base:

void setup()
{
  Serial.begin(115200); // Pour indiquer le temps
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  Serial.println(F("La led est allumée pendant 0,5s"));

  digitalWrite(LED_BUILTIN, HIGH);
  delay(500); // Led allumée pendant 0,5s
  digitalWrite(LED_BUILTIN, LOW);
  delay(500); // Led éteinte pendant 0,5s
}

Pour répondre à notre problème il va falloir diminuer la temporisation après l'allumage et augmenter d'autant celle qui est après l'extinction. Pour faire varier les 500 ms, nous allons utiliser une variable, nommée tempsAllume (pas d'accents pour les noms des variables). Cette variable est non signée, elle dépasse 255, mais elle est plus petite que 65535, un word convient tout à fait. On déclarera avant loop(=:

word tempsAllume = 500;

Si on la déclarait dans loop, elle serait initialisée à 500 à chaque passage.

Il faut aussi changer l'affichage en remplaçant la ligne d'affichage par:

Serial.print(F("La led est allumée pendant "));
Serial.print(tempsAllume);
Serial.println(F("ms"));

Pour le premier delay(=, on remplace simplement 500 par tempsAllume:

delay(tempsAllume); // Led allumée pendant un temps décroissant

Le deuxième delay(=, peut donner (temps des deux delay(=doit faire 1000):

delay(1000 - tempsAllume); // Led éteinte pendant un temps croissant

Bien entendu, en fin de boucle par exemple, il faudra diminuer tempsAllume de 10%. Comme on utilise pour l'instant des entiers, on ne peut pas multiplier le temps par 0,90 mais on peut soustraire 10%:

tempsAllume = tempsAllume - (tempsAllume/10);

Ce qui peut s'écrire:

tempsAllume -= tempsAllume / 10;

Bien entendu, faites l'essai!

Il y a forcément des arrondis car tempsAllume/10 est tronqué à l'entier inférieur (car le résultat est un entier). D'autre part quand tempsAllume ne vaut plus que 9ms, il ne peut plus diminuer car 9/10 vaut zéro (dans l'espace des entiers) et soustraire 0 à tempsAllume ne le change plus.

On commencera par noter que delay() attend un unsigned long et que 1000 est un int (par défaut, quand on ne dit rien un entier est un int). Le compilateur le sait, il va automatiquement faire la conversion. Il va en être pareillement si on mélange des entiers avec des réels.

Les réels

Avec Uno/Nano/Mega, le premier type de réels s'appellent float. C'est un nombre qui est loin d'être un vrai réel au sens mathématique, le nombre de valeurs possible est fini. Mais c'est un nombre codé en binaire, mais si je fais l'analogie avec le décimal, on dira que l'on peut l'exprimer de la façon suivante:

<chiffre non nul>,<mantisse> . 10<exposant>

exemple: e = 1,6 10-23
          = 3,14 100
         cent = 1,0 102

Sur Uno/Nano/Mega il existe deux types de réels float et double qui sont équivalents. Sur d'autres systèmes les doubles utilisent souvent plus de mémoire et permettent un intervalle de représentation plus important et une précision accrue. Pour nous, ce ne sera pas le cas. Nos float et nos double occupent 4 octets en mémoire, peuvent représenter des nombres entre -3,4.1038 et +3,4.1038 avec 6 à 7 chiffres significatifs.

Les calculs faits avec des octets est natif (le microcontrôleur fait les opérations avec une seule instruction). Les calculs sur des entiers 16 bits doivent se faire en plusieurs fois et c'est un peu plus long, sur des entiers 32 bits c'est encore plus long, et c'est encore plus complexe avec les réels. C'est pour cela que si on peut éviter les réels,on le fait. On doit, pour économiser le temps et le code, utiliser de préférence les plus petits tailles d'entiers et si ce n'est pas possible, on utilisera les réels. Je suppose que c'est pour ne pas avoir un code trop important que les double ne sont pas sur 8 octets.

Si on définit tempsAllume par:

float tempsAllume = 500;

Sans spécifications 500représente un entier et pas un réel. Une constante numérique réelle doit avoir le point décimal ou la présence de la puissance de 10 (lettre e ou E comme exposant). On écrira donc plutôt (au choix):

float tempsAllume = 500.0;
         ou
float tempsAllume = 5.0e2;
         ou
float tempsAllume = 5e2;
         ou
float tempsAllume = 5E2;

Serial.print() et Serial.println() affichent le nombre sans exposants et peut traduire correctement l'intervalle ]-109, +109[. En dehors de ces valeurs c'est ovf qui s'affiche (ovf: overflow = débordement).

Par défaut Serial.print() et Serial.println() affichent un réel avec deux chiffres après la virgule. Si on veut afficher X avec N chiffres après la virgule (0≤N), on écrira:

Serial.println(X, N);

Il n'y a pas de limites maximale pour N, mais ce n'est pas utile d'avoir plus de 7 chiffres significatifs. Un nombre comme 0,00000123456789 peut être affiché avec 12 chiffres après la virgule, plus est inutile.

Du fait de la représentation binaire sous-jacente, il se peut qu'il y ait des arrondis. Par exemple l'affichage de 0,02 donnera 0.0199999990

Retour au programme

Reprenez le programme avec les entiers, redéfinissez tempsAllume comme un float. Quand nous allons faire delay(tempsAllume); comme la fonction attend un et qu'on passe un float,le codage n'est pas le même. Mais comme les variables et les fonctions ont été déclarées, le compilateur le sait et peut faire automatiquement la conversion. Nous n'auront rien à faire. Quand nous mélangeons les différents entiers et les différents réels, cela se passe tout seul. Mais il faut être conscient qu'il y a plus de code généré. Il ne faut donc pas choisir n'importe comment les types de variables.

Pour:

delay(1000 - tempsAllume); // Led éteinte pendant un temps croissant

il y a une opération entre un entier et un réel. Il faut que le compilateur choisisse si tout est transformé en entiers ou en réels. Quand on a des types entiers différents, c'est la taille la plus grande qui est retenue et quand il y a des entiers et des réels, c'est le type réel qui est choisi. Dans la ligne ci dessus cela n'a pas vraiment d'importance, 1000 sera transformé en float, puis la soustraction sera faite, puis le résultat sera transformé en unsigned long avant d'être envoyé à la fonction. On devrai plutôt écrire dans le cas présent:

delay(1000.0 - tempsAllume); // Led éteinte pendant un temps croissant

ou

delay(1e3 - tempsAllume); // Led éteinte pendant un temps croissant

Avec les optimisations du compilateur, si on ne met que 1000, il y a quand même de fortes chances que le compilateur remplace de lui même 1000 par 1000.0 .

Pour faire diminuer tempsAllume de 10%, on peut utiliser au choix:

tempsAllume = tempsAllume * 0.9;
tempsAllume *= 0.9;
tempsAllume /= 1.1;
tempsAllume -= tempsAllume/10;
tempsAllume -= tempsAllume/10.0;
...

Les deux dernières formes donneront en fait la même chose car il faut faire d'abord la division avant la soustraction et comme tempsAllume est un float, l'opération se fera entre float.

Quand on connaît le compilateur, on peut savoir qu'une division prends plus de temps qu'une multiplication, et donc que la deuxième forme est sans doute la meilleure.

Vous avez maintenant tous les éléments pour faire flasher la led jusqu'à l'extinction. Notez quand même que le temps qui est affiché est la valeur de tempsAllume et non pas le paramètre en millisecondes qui est transmis à delay. Quand l'affichage indique "4.36 3.93 3.53 3.18 2.86, en réalité le temps qui est fait est de "4ms 3ms 3ms 3ms 2ms... Si on veut un affichage plus représentatif, il faut afficher non pas tempsAllume mais la valeur qui est transformée en unsigned long. Cela se fait demandant la conversion:

Serial.print((unsigned long) tempsAllume);

Comme ce n'est qu pour nous et que la valeur ne dépasse pas 500, on peut aussi bien demander la conversion en word voir même en int:

Serial.print((unsigned long) tempsAllume);

La macro F( )   <<     >>   switch case

Vous avez tous les renseignement pour mener à bien cette étape, mais n'hésitez pas à utiliser les forums pour avoir de l'aide. N'hésitez pas à faire des essais, de tester d'autres possibilités. C'est ainsi que l'on apprend. Bon apprentissage.