Une variable se déclare en donnant son type, son nom, et souvent une valeur de départ : int compteur = 0;. Le type fixe deux choses, ce que la variable peut contenir (un entier, un caractère, un nombre à virgule) et la place qu’elle occupe en mémoire. En embarqué, où chaque octet compte et où certains calculs sont lents, ce choix n’est jamais neutre : un type mal choisi, et le programme déborde, tronque ou ralentit sans prévenir.
À quoi ça sert ?
Choisir le bon type, c’est éviter une famille entière de bugs parmi les plus déroutants, parce qu’ils ne produisent aucune erreur de compilation. Le code part sur la carte et se comporte mal. Trois conséquences matérielles, invisibles sur un ordinateur, deviennent concrètes ici :
un type trop petit déborde : passé sa valeur maximale, il « repart » brutalement (souvent dans le négatif) et fausse tout ;
un float coûte cher sur un microcontrôleur sans calcul flottant matériel — chaque opération est lente ;
une valeur de temps mal typée déborde en quelques dizaines de secondes.
Le bon réflexe n’est pas « je mets int partout », mais « quelle est l’amplitude de cette valeur, et de quelle nature est-elle ? ».
Déclarer une variable
La forme générale : type nom = valeur;. On peut déclarer sans initialiser (int x;, valeur indéterminée — à éviter) ou avec (int x = 0;, recommandé).
int compteur = 0; // un entier, initialisé à 0bool enMarche = false; // un booléen (vrai/faux)char lettre = 'A'; // un caractère (guillemets simples)
Une fois déclarée avec un type, la variable garde ce type : on ne peut pas y ranger n’importe quoi sans conséquence (ranger 3.7 dans un int le tronque à 3).
Les types entiers
Ce sont les plus courants. Ils diffèrent par leur taille (donc leur valeur maximale) et par leur signe.
Type
Taille (AVR / ARM)
Contenu
Usage typique
bool
1 o
true / false
un état, un drapeau
char
1 o
un caractère ('A')
texte, code ASCII
byte / uint8_t
1 o
entier 0–255
une valeur d’octet, un registre
int
2 o / 4 o
entier signé
compteur, mesure brute
unsigned int
2 o / 4 o
entier ≥ 0
index, quantité
long
4 o
grand entier signé
grands compteurs
unsigned long
4 o
grand entier ≥ 0
temps (millis())
Deux points sensibles, source de la majorité des bugs de type :
Le préfixe unsigned retire le signe. La variable ne stocke alors que des valeurs positives, mais en échange sa valeur maximale double. Un int sur 2 octets va de −32 768 à +32 767. Un unsigned int va de 0 à 65 535.
La taille de int change selon la carte. Elle vaut 2 octets sur AVR (Uno R3, Mega, Nano) et 4 octets sur ARM (Uno R4, ESP32, Teensy). Un même code peut donc déborder sur une carte et pas sur l’autre — la cause de bugs « impossibles à reproduire » la plus classique. En cas de doute sur l’amplitude, prendre long (toujours 4 octets).
Les nombres à virgule
Pour une grandeur physique (tension, température, distance), on utilise un float :
float tension = 3.3; // 4 octets, nombre à virgule
Deux mises en garde propres à l’embarqué. D’abord, sur AVR le doubleest un float (même précision) : il n’apporte rien. Ensuite, sur ces mêmes cartes sans unité de calcul flottant, chaque opération sur un float est nettement plus lente qu’un calcul entier. On réserve donc les flottants aux calculs qui les exigent vraiment (conversions physiques), et on reste sur des entiers partout ailleurs.
Le texte
Un seul caractère tient dans un char ('A', guillemets simples). Une suite de caractères — une chaîne — peut se ranger de deux façons :
un tableau char[] : la chaîne « à la C », légère, sans surprise mémoire ;
la classe String (majuscule) : plus confortable (concaténation avec +, méthodes toutes prêtes), mais ce n’est pas du C++ standard — c’est un ajout d’Arduino, et elle consomme de la mémoire dynamique.
Sur un microcontrôleur à faible RAM, l’usage intensif de String peut fragmenter la mémoire et provoquer des plantages difficiles à diagnostiquer. Le sujet est traité sous l’angle ressource dans gestion mémoire.
Fixer une valeur constante : const
Une valeur qui ne change pas (une broche, un seuil) se déclare const. Le compilateur empêche alors de la modifier par erreur, et le code se lit mieux :
const int BROCHE_LED = 13;const float SEUIL = 25.0;
C’est la forme préférée au #define vu dans la structure d’un programme : const est typée et respecte la portée, là où #define n’est qu’un remplacement de texte.
Code à lire
Ce sketch lit un capteur et affiche sa tension une fois par seconde. Il fait jouer quatre types à bon escient. Observez pourquoi chacun est choisi.
const int BROCHE = A0; // const int : une broche fixeunsigned long dernierAffichage = 0; // unsigned long : du TEMPS (millis)const unsigned long PERIODE = 1000; // ms entre deux affichagesvoid setup() { Serial.begin(115200);}void loop() { if (millis() - dernierAffichage >= PERIODE) { // une fois par seconde dernierAffichage = millis(); int brut = analogRead(BROCHE); // int : valeur ADC 0..1023 float tension = brut * (5.0 / 1023.0); // float : conversion en volts Serial.print("brut = "); Serial.print(brut); Serial.print(" tension = "); Serial.print(tension, 2); // 2 décimales Serial.println(" V"); }}
Chaque type est choisi pour ce qu’il porte : int pour la valeur brute entière de l’ADC, float pour la tension (une grandeur à virgule), unsigned long pour le temps (qui dépasserait vite la capacité d’un int). Écrire 5.0 plutôt que 5 dans la conversion force un calcul flottant et évite la division entière (voir Pièges).
Pièges
int qui déborde selon la carte. Sur AVR, int plafonne à 32 767. Au-delà il bascule dans le négatif. Le même code sur Uno R4 ou ESP32 (int sur 4 octets) ne déborde pas, d’où des bugs qui n’apparaissent que sur certaines cartes. En cas de doute, long.
unsigned long obligatoire pour le temps. Une durée issue de millis() rangée dans un int déborde en une trentaine de secondes. Le temps se stocke toujours en unsigned long (voir temporisation).
Division entière inattendue.5 / 2 vaut 2, pas 2.5 : tant que les deux nombres sont entiers, le résultat est entier. Pour obtenir 2.5, au moins un opérande doit être flottant : 5.0 / 2. Erreur classique dans les conversions de mesures.
Comparer deux float avec ==. Les flottants sont approchés : a == b est rarement fiable. Comparer plutôt à une tolérance : abs(a - b) < 0.01.
Croire le float « gratuit ». Sur AVR, chaque opération flottante est lente, et double n’apporte aucune précision de plus. Réserver les flottants aux calculs qui les exigent.
Exercices
Exercice 1 — Choisir le bon type
Un débitmètre renvoie un nombre de litres pouvant monter jusqu’à 50 000. Sur une Uno R3, dans quel type ranger cette valeur sans risque de débordement ? Et si la valeur pouvait être négative ?
Corrigé de l'exercice 1
Sur Uno R3 (AVR), int plafonne à 32 767 : 50 000 le ferait déborder. Comme la valeur est positive, le plus juste est unsigned long (0 à ~4 milliards), ou au minimum unsigned int (0 à 65 535), qui suffit tout juste mais laisse peu de marge. Si la valeur pouvait être négative, on prend long (signé, 4 octets), car unsigned interdit le négatif.
Exercice 2 — Le calcul faux
Ce calcul de moyenne donne parfois un résultat aberrant. Trouvez les deux problèmes et corrigez-les.
int a = 20000;int b = 18000;int moyenne = (a + b) / 2; // sur Uno R3
Corrigé de l'exercice 2
Deux pièges cumulés sur une Uno R3 :
Débordement : a + b = 38 000, au-delà des 32 767 d’un int AVR → le résultat intermédiaire bascule dans le négatif avant même la division.
Division entière : même sans débordement, / 2 tronque la partie décimale.
Correction : typer assez large pour la somme, et garder un entier si la moyenne doit l’être.
long a = 20000;long b = 18000;long moyenne = (a + b) / 2; // 19000, sans débordement
Si l’on voulait une moyenne décimale, on passerait par un float : float moyenne = (a + b) / 2.0;.
Raccrochage projet
Étape 4 de la phase de preuve de concept — convertir une mesure de capteur (valeur brute → grandeur physique) impose de choisir les types à chaque étape. C’est là que se logent débordements et divisions entières.
Critère « Programmer ou paramétrer un contrôleur numérique » — un code bien typé produit des mesures justes et des calculs fiables, indépendamment de la carte retenue.