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é à 0
bool 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.

TypeTaille (AVR / ARM)ContenuUsage typique
bool1 otrue / falseun état, un drapeau
char1 oun caractère ('A')texte, code ASCII
byte / uint8_t1 oentier 0–255une valeur d’octet, un registre
int2 o / 4 oentier signécompteur, mesure brute
unsigned int2 o / 4 oentier ≥ 0index, quantité
long4 ogrand entier signégrands compteurs
unsigned long4 ogrand entier ≥ 0temps (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 double est 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 fixe
unsigned long dernierAffichage = 0;     // unsigned long : du TEMPS (millis)
const unsigned long PERIODE = 1000;     // ms entre deux affichages
 
void 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 ?

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

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.

Voir aussi