Ce tutoriel est en cours de rédaction. Merci pour votre compréhension !
Les constantes
Une constante est un emplacement dans la mémoire d’exécution qui a une valeur fixe.
Il existe deux façons de “coder” une constante en C/C++ : en utilisant l’attribut const
lors de la déclaration d’une variable ou bien au moyen de la directive de préprocesseur #define
.
Première méthode : constante déclarée avec l’attribut const
Dans ce cas, la constante sera en fait une variable en lecture seule dont on ne peut pas modifier le contenu pendant l’exécution. On y accède (en lecture uniquement, donc) par son nom, exactement comme on le ferait avec n’importe quelle autre variable. Comme toute variable, elle possède un type et occupe un emplacement mémoire (i.e. on peut en prendre l’adresse et stocker cette adresse dans un pointeur 1).
Exemple d’utilisation pour la déclaration d’une approximation de la constante mathématique π (pi) :
const float Pi = 3.14159265359;
Cette approche n’est pas la plus utilisée dans les sketchs Arduino, on lui préfèrera la deuxième méthode, avec la directive de préprocesseur #define
(point immédiatement après).
Elle est néanmoins très utile lorsque les valeurs des constantes doivent être précalculées au début de votre sketch. Un exemple est donné par le sketch de ce tutoriel dont nous donnons ici un court extrait :
...
// Paramètres de l'ADC
#define VREF (3.3) // Tension de référence de l'ADC (en volts)
#define ADC_RES (12) // Résolution de l'ADC (en nombre de bits)
// Calcul du pas, ou quantum, de l'ADC
const float scale = pow(2, (float)ADC_RES);
const float ADC_QUANT = VREF / scale;
...
Les valeurs de scale
et ADC_QUANT
sont calculées à partir des alias fournis par les directives #define
et ne pourront plus être modifiées par la suite.
Précisons que Lors de la compilation, toutes les variables déclarées avec const
sont enregistrées dans la mémoire flash ou EEPROM du microcontrôleur. Elles sont également lues depuis celle-ci au moment de l’exécution du firmware (elles n’occupent pas de place en SRAM).
Deuxième méthode : constante créée en utilisant la directive de préprocesseur #define
La traduction d’un code source en C/C++ en code binaire se fait en trois étapes :
- Action du préprocesseur, qui nous intéresse ici.
- Compilation (voir cet article)
- Edition de liens (voir cet article)
Vous pouvez imaginer l’action du préprocesseur comme une étape de type “rechercher / remplacer” appliquée au sketch, en particulier pour les directives #define
. Après l’action du préprocesseur, toute les lignes contenant des directives #...
on disparu, replacées par des instructions C/C++ 2.
Illustrons cette deuxième méthode par un sketch qui enregistre une série de mesures effectuées par un convertisseur analogique numérique 3 :
01 #define NB_MESURES 15 // Nombre de mesures à enregistrer
02 #define BROCHE A0 // Broche analogique à lire
03 #define DELAI 10 // Intervalle de temps (millisecondes) entre deux conversions de l'ADC
04
05 // Tableau pour enregistrer les mesures, contant NB_MESURES élements
06 uint16_t mes[NB_MESURES];
07
08 // Initialisations
09 void setup() {
10 pinMode(BROCHE, INPUT); // On active l'ADC câblé sur BROCHE
11 }
12
13 // Boucle principale
14 void loop() {
15
16 // On répète NB_MESURES avec une "boucle for"
17 for (uint8_t i = 0; i < NB_MESURES; i++) {
18
19 // Lecture et numérisation (via ADC) de la broche analogique BROCHE
20 // dans le tableau mes
21 mes[i] = analogRead(BROCHE);
22
23 // Temporisation de DELAI millisecondes
24 delay(DELAI);"
25 }
26 }
Les lignes 01 à 03 consistent en des directives de préprocesseur #define
qui attribuent des alias à plusieurs constantes paramétrisant le sketch.
Après action du préprocesseur C/C++, et juste avant compilation, le sketch ressemblera à ceci :
uint16_t mes[15];
void setup() {
pinMode(0xC0, 0x0);
}
void loop() {
for (uint8_t i = 0; i < 15; i++) {
mes[i] = (uint16_t)(analogRead(A0));
delay(10);
}
}
Les commentaires ont été retirés et tous les alias (ceux que nous avions définis mais aussi d’autres définis par le framework Arduino, tels que #A0
, et INPUT
) ont été remplacés par leurs valeurs numériques.
L’intérêt de cette approche est de faciliter les modifications du sketch tout en le rendant plus lisible. Imaginez par exemple que vous souhaitiez augmenter le nombre de mesures de 15 à 30. Il vous suffirait de changer uniquement la ligne 01 comme ceci #define NB_MESURES 30
pour que la modification soit automatiquement appliquée aussi aux lignes 07 et 18. Ceci peut sembler futile pour notre sketch exemple qui ne contient que 26 lignes, mais imaginez qu’il y en ait 3000 ou 6000 avec des dizaines de boucles faisant parfois appel à l’alias NB_MESURES
…
Remarque importante : Les directives de préprocesseur C/C++ ne sont pas des instructions.
De ce fait, les lignes 01 à 04 ne se terminent pas avec un;
et n’utilisent pas l’opérateur d’affectation=
.
Ainsi une ligne de code de cette forme#define NB_MESURES=30;
est fausse et sera rejetée par le préprocesseur avant compilation !
Les types énumérés enum
Il arrive souvent dans un programme qu’une variable ne puisse prendre qu’une seule liste de valeurs préfinies.
Imaginons par exemple que nous ayons construit un robot télécommandé qui peut se déplacer à quatre vitesses différentes à l’aide de moteurs à courant continu, en utilisant la génération de signaux PWM 4 offerte par la fonction analogWrite
du framework Arduino. Plutôt qu’utiliser 4 variables distinctes pour qualifier les rapports cycliques qui seront transmis aux moteurs, il est possible de les regrouper dans une énumération :
enum vitesse = {Varret = 0, Vmini = 64, Vstand = 128, Vmax = 256};
...
enum commande_vitesse CmdV;
CmdV = Varret;
analogWrite(D10, Varret);
...
Constantes de type “caractères”
Une constante caractère est composée d’un ou de plusieurs caractères délimités par des apostrophes, telle que ‘A’, ‘+’ ou ‘\n’ (saut de ligne).
Dans ce dernier cas, on a affaire à un caractère d’échappement. La barre oblique inversée () sert à introduire une séquence d’échappement permettant la représentation visuelle de certains caractères, pour la plupart non graphiques. Autres exemples :
- ’"’ pour un guillemet
- ‘\0’ pour le caractère “null” (terminaison de tableau)
- ‘\r’ pour retour chariot
- ’\’ pour le caractère \
- ’'’ pour le caractère ‘
- Etc.
Constantes de type “chaîne de caractères”
D’après le site Zeste de Savoir … En langage C, une chaîne de caractère n’est rien d’autre qu’un tableau de caractères (de type char
) terminé par un caractère “null” \0
(voir cette page).
1 - On peut l’initialiser avec un texte donné comme ceci, en précisant explicitement sa longueur :
c
char chaine[25] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r' };
Dans ce cas, le compilateur réserve en mémoire 25 variables de type char, les unes à la suite des autres, avec des caractères \0
au-delà du dernier caractère précisé (ici r
, à l’indice 6).
Faites cependant attention à ce qu’il y ait toujours de la place pour un caractère nul.
2 - On peut aussi l’initialiser avec une longueur implicite :
c
char chaine[] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r', '\0' };
Dans ce cas, il vous faudra ajouter le caractère nul à la fin de la liste. De cette façon, on ne “gaspille” pas de la mémoire inutilement en réservant exactement l’espace mémoire requis (exactement 68 éléments).
3 - Dans les deux cas qui précèdent, la chaîne, déclarée comme un tableau, n’est cependant pas une constante, il reste possible de modifier son contenu en accédant aux éléments du tableau chaine
.
La bonne approche consiste à initialisez une chaîne littérale, avntageusement définie sans préciser la taille du tableau qui la contient, comme ceci :
c
char chaine[] = "Bonjour";
Une telle chaîne sera bien une constante, au sens de “variable en lecture seule”, il ne sera plus possible de la modifier en cours d’exécution sans générer une erreur d’exécution.
Liens et ressources
- Le préprocesseur, sur Wikipédia
- Les constantes et variables sur Wikiversity
- Le préprocesseur, sur COOR.fr
- Les variables, sur COOR.fr
- Zeste de Savoir
- Constantes caractères, sur Doc Wiki
- Caractères d’échappement, sur Doc Wiki
Notes au fil du texte
-
Un pointeur est une variable spéciale qui peut contenir l’adresse d’une autre variable. C’est une notion particulièrement délicate du C et du C++ qui permet de manipuler directement le contenu de la mémoire, très puissante mais aussi source potentielle de bugs et de failles de sécurité particulièrement difficiles à analyser, donc controversée. ↩
-
Nous parlons plus en détails du préprocesseur C/C++ dans cette fiche. ↩
-
Un Convertisseur Analogique vers Numérique ou ADC (pour “Analog to Digital Converter”) est un circuit électronique spécialisé dans la conversion de signaux d’entrée analogiques (des tensions éventuellement variables dans le temps, comprises entre 0 et +3,3V en général) en signaux numériques (des nombres entiers proportionnels à la tension d’entrée à chaque instant et compris entre 0 et 4095, soit 212-1, pour un ADC avec une résolution de 12 bits). ↩
-
PWM signifie “Pulse Width Modulation” (en français : “modulation en largeur d’impulsion”). C’est une technique permettant de faire varier le potentiel électrique d’une broche entre 0 et +3,3V (pour le microcontrôleur concerné) selon un signal rectangulaire de fréquence et de rapport cyclique (ratio entre le temps où le signal est à 0V et celui où il est à +3,3V au sein d’une période) dynamiquement ajustables. ↩