Bases de programmation

En vue de l'utilisation de cartes Arduino

Nous abordons ici les bases de la programmation en C/C++. En fait, le langage n'importe pas, ce sont les principes généraux, les mécanismes, qu'il faut retenir.

Le choix du C/C++ est du au fait que nous utilisons des cartes Arduino, avec l'environnement de développement éponyme, qui propose une API en C/C++.

Les puristes ne manqueront pas de faire remarquer que le C et le C++ sont deux langages totalement différents. En effet, c'est le cas lorsque ce dernier est utilisé par des experts. Ici ce n'est pas le cas et la frontière entre les deux est extrêmement mince.

À la fin de cette présentation vous saurez structurer un programme et faire afficher des informations, émises depuis l'Arduino, sur votre ordinateur.

Arduino

Arduino n'est pas qu'une petite carte électronique open source à base de micro-contrôleur ATMega. C'est également un environnement de développement, hérité de Processing, qui se veut simple et qui permet d'écrire, de compiler et de téléverser un programme sur le micro-contrôleur.

Processing se programme en Java, Arduino se programme en C/C++. C'est donc ce dernier langage que nous aborderons ici.

L'environnement Arduino

L'environnement de développement (IDE) permet d'écrire son programme en faisant un peu de coloration syntaxique sur les mots-clés, les noms de fonctions, ... spécifiques à Arduino. Mais plus intéressant, il permet de compiler (c'est à dire, transformer le code écrit par un humain en une version compréhensible par le micro-contrôleur) notre programme en s'adaptant au type de carte Arduino que l'on possède. Pour cela, il faut aller renseigner cette information dans le menu Outils / Carte.

Si des erreurs de programmation sont détectées, elles sont affichées en bas de la fenêtre, dans la zone noire. Il faut toujours commencer par corriger la première erreur avant même de tenir compte des autres. Souvent, cette première erreur en engendre d'autres qui ne sont pas réellement des erreurs. Il faut donc la corriger et recompiler. Bien entendu, une relecture attentive de votre code vous permettra d'éviter ces erreurs à la compilation.

Une fois le programme mis au point et compilant sans erreur, il est temps de le téléverser. Pour cela, il faut commencer par sélectionner le port série qui a été affecté à notre carte Arduino lorsqu'elle a été branchée à un port USB de notre ordinateur. Cette affectation se fait par le système d'exploitation et le nom, l'ordre, ou tout autre considération à propos de ce port, va dépendre du dit système. Sous Windows, vous aurez des ports COM, sous MacOS et Linux, ils auront des noms un peu plus compliqués comme tty.usbxxxxxxx ou /dev/usbxxxx. Le plus délicat est de choisir le bon. Pour ce faire, le mieux est de regarder la liste avant de brancher la carte Arduino sur le port USB et de choisir celui qui est apparu une fois la carte branchée.

À l'aide !

En plus de proposer un environnement pour écrire, compiler et téléverser les programmes, l'environnement Arduino dispose d'une aide en ligne (que vous soyez connecté à l'Internet ou pas) pour chacune des fonctions qu'il propose. Elle est accessible soit via le menu Aide / référence, soit en sélectionnant le nom d'une fonction et en faisant un clic droit dessus.

L'avantage de la documentation trouvée via les moyens décrits ci-dessus est que vous obtenez des explications pour les fonctions installées sur votre système. Aller chercher celles disponible sur le site Arduino est également possible, mais vous n'aurez les informations que pour la toute dernière version des bibliothèques.

Structure d'un programme

À présent que nous avons vu les grandes lignes de l'interface de programmation, attachons nous à la programmation en elle-même.

Un programme est une suite d'instructions, écrites dans un langage de programmation, qui s'exécuteront dans l'ordre dans lequel elles se présentent dans le fichier source. Ces instructions suivent un formalisme particulier dont les principaux sont détaillés dans le reste de ce document.

setup() et loop()

Tout d'abord, les passages obligés. Vous allez écrire dans un langage de programmation spécifique qui, comme tout langage de programmation, a des règles strictes d'écriture.

Dans le cadre d'Arduino, ces règles se limitent à l'utilisation de deux fonctions : setup() et loop(). La première n'est exécutée qu'une seule fois à chaque démarrage du micro-contrôleur et a pour vocation de configurer les différents ports d'entrée/sortie, d'initialiser les différentes variables que l'on va utiliser. Ensuite, la seconde est exécutée en boucle, à l'infini, tant que le micro-contrôleur n'est pas mis hors tension. C'est le cœur de notre programme, là où tout va se faire.

Ci-dessous, vous est présenté un code Arduino minimal.

void setup()
{ }

void loop()
{ }
Programme Arduino minimal.

Vous remarquerez que nos deux fonctions sont précédées d'un mon clé particulier (void), qui signifie que cette fonction ne produit pas de valeur de retour. Nous verrons ça plus en détail dans la section suivante.

Vous remarquerez également que chacune de ces fonctions est suivi d'un couple de parenthèses. C'est ce qui défini que ce sont effectivement des fonctions. Parfois ces parenthèses sont vides, parfois elles contiennent des noms qui sont les arguments de la fonction (les données sur lesquelles notre fonction va travailler).

Enfin, en plus du couple de parenthèses, il y a un couple d'accolades. Elles aussi sont obligatoires. Elles définissent les limites des instructions qui font la fonction. On appelle ça un bloc d'instruction, c'est à dire l'ensemble des instructions qui vont être exécutées à chaque fois que ma fonction sera appelée.

Mais où est main() ?

Les personnes ayant déjà programmées en C/C++ se demanderont pourquoi il n'y a pas la seule fonction obligatoire dans ces langages, c'est à dire la fonction main().

En réalité, cette fonction est présente, mais elle est entièrement prise en charge par l'environnement Arduino. Le réel code qui sera compilé est en fait le suivant :

void setup()	// fonction définie par l'utilisateur
{ }

void loop()	// fonction définie par l'utilisateur
{ }

void main()	// fonction définie par l'IDE Arduino
{
	setup() ;

	for (;;) loop() ;
}
Programme Arduino minimal, tel qu'effectivement compilé.

Commentaires

Il est possible d'ajouter des commentaires dans le code. C'est à dire des phrases, des paragraphes, des bouts de texte, qui ne seront jamais pris en compte par le compilateur, mais qui servent de pense-bête, de moyen de communication entre différents programmeurs.

Ces commentaires, pour être différenciés du code environnant, doivent avoir une syntaxe particulière. Ici, il y a deux façons de faire.

Soit le commentaire concerne une ligne et une seule, alors il suffit de le faire commencer par le couple de caractères //. Tout ce qui est entre ces caractères et la fin de la ligne n'est pas pris en compte.

Soit ils concernent plusieurs lignes et il faudra marquer le début et la fin du bloc de commentaire, respectivement par /* et */. Tout ce qui se trouve entre ces deux couples de caractères sera ignoré.

Il est possible de mettre autant de commentaire que l'on désire, où on le désire, dans la langue que l'on désire. C'est une aide précieuse et une mémoire à ne pas négliger quand on devra revenir sur un programme quelques mois plus tard.

/**
 * Programme ne servant à rien si ce
 * n'est à montrer comment écrire des
 * commentaires.
 */

#define LED 11	// ma LED sera branchée sur le port 11
const int serial_speed = 9600 ;	// ne pas oublier de régler le moniteur série à ma même vitesse

/**
 * Cette fonction n'est exécutée qu'une seule
 * fois au début du programme.
 */
void setup()
{ 
	// Faire toutes les initialisations
}

/**
 * Cette fonction est exécutée en boucle
 * tant que le micro-contrôleur n'est pas mis
 * hors tension.
 */
void loop()
{
	// Ici c'est la boucle principale où tout se passe
}
Exemples d'utilisations de commentaires.

Variables, constantes, types

Un programme tel que celui proposé à la section précédente n'a aucun intérêt et ne produit rien. Ce qui est intéressant c'est de pouvoir traiter des données, faire de calculs, lire des valeurs depuis les ports d'entrée, actionner des effecteurs branchés aux ports de sortie. Le support à tout ces traitement est l'utilisation de variables, décrites ci-dessous.

Variables et types

Une variable, dans un programme informatique, c'est une zones dans la mémoire de l'ordinateur, qui est nommée et dans lesquelles il est possible de lire et d'écrire des valeurs. Ces zones nommées sont crées par le programmeur en définissant de nouvelles variables qui lui permettront de résoudre son problème.

Le langage que nous utilisons est un langage typé, c'est à dire qu'il est obligatoire de donner un type aux variables que nous définissons.

Une variable a donc, pour être correctement formée, un type et un nom.

Les nom d'une variable peut être presque n'importe quoi à partir du moment où :

Le type dépend de l'utilisation que l'on va faire de la variable et il est à choisir dans la liste suivante :

Les types entiers (char,int, long) peuvent être qualifiés par le mot-clé unsigned. Il ne pourront alors plus prendre de valeur négative, mais en compensation, la valeur maximale représentable dans les positifs sera doublée (respectivement 255, 65 535 et 4 295 967 295).

D'autres types existent, hérités du C++ ou crées pour Arduino. Vous les retrouverez sur l'aide en ligne et nous ne les détaillerons pas ici. En effet, les types précédents suffisent pour résoudre les problèmes courants que l'on pourra rencontrer.

Par contre, il existe d'autres types, hérité de l'environnement wiring duquel est tiré le langage utilisé par l'Arduino, qui n'ont rien de nouveau, mais qui sont pratiques en ce sens qu'ils explicitent leur occupation mémoire.

La figure suivante donne un exemple de déclaration de variables. Vous remarquerez également à cette occasion la présence du caractère ; à la fin de cette ligne. Le caractère ; est obligatoire à la fin de toute instruction en C/C++. Déclarer une variable est une instruction.

int compteur ;	// une variable globale
float moyenne ;	// une autre variable globale

void setup()
{ }

void loop()
{ }
Exemple de déclarations de variables dans un programme Arduino.

Durée de vie des variables

Les variables déclarées à l'extérieur de toute fonction sont appelées variables globales. C'est à dire qu'elles sont accessibles depuis n'importe quel endroit du code. Elles sont les pendantes des variables globales qui, a contrario, sont définies à l'intérieur d'un bloc et qui ne seront accessibles que dans ce bloc. Par exemple, une variable définie dans le bloc d'une fonction a une portée, une durée de vie, qui est égale au bloc définissant cette fonction.

En général il est fortement déconseillé d'utiliser des variables globales. Elles peuvent se révéler sources d'erreurs. En effet, rien n'interdit de définir des variables locales qui ont le même nom que des variables globales, et ceci sans aller à l'encontre de la première règle sur la façon de nommer les variables, puisqu'elles ne sont pas dans le même contexte. Mais elles existent pourtant en même temps et le programmeur peut très bien penser modifier la variable locale alors qu'en fait c'est sur la variable globale qu'il travaille. C'est ce qu'on appelle le masquage de variable (shadowing en anglais).

Mais, ceci dit, dans le contexte de la programmation embarquée, il est plus efficace d'utiliser les variables globales. C'est un peu technique, mais cela permet d'économiser de la mémoire au moment des appels de fonctions. Il faut simplement être conscient du problème de masquage évoqué ci-dessus et prendre soin de ne pas créer de problèmes.

Tableaux

Il est souvent nécessaire de manipuler un ensemble de valeurs comme étant une seule entité. Les tableaux pemettent de faire cela simplement. Pour créer un tableau en C/C++, il suffit de créer une variable dont le nom est suivi des caractères [ et ], avec une valeur numérique entre les deux, donnant la taille, en nombre d'éléments du tableau.

Un exemple est donné ci-dessous :

int compteur ;	// une variable globale
int valeurs[10] ; // un tableau de 10 valeurs entières
float moyenne ;	// une autre variable globale

void setup()
{
  valeurs[0] = 0 ;
  valeurs[1] = 76 ;
  valeurs[2] = 121 ;
  valeurs[3] = 153 ;
  valeurs[4] = 178 ;
  valeurs[5] = 198 ;
  valeurs[6] = 215 ;
  valeurs[7] = 230 ;
  valeurs[8] = 243 ;
  valeurs[9] = 255 ;
}

void loop()
{ }
Exemple de déclarations de variables dans un programme Arduino.

Il est important de noter que pour accéder à un élément du tableau, on utilise son indice et que les indices commencent à 0 et se terminent à taille-1.

Constantes

En plus des variables, il est possible de définir des constantes, c'est à dire des entités dont la valeur ne pourra pas être modifiée.

#define

Un moyen simple de définir des constantes est l'utilisation de directives particulières (directive du pré-processeur, pour ceux qui veulent savoir de quoi il s'agit). Ce n'est pas du code C/C++ à proprement parler. Ce n'est pas nom plus une déclaration au sens d'une déclaration de variable. C'est juste un moyen de nommer quelque chose pour pouvoir le réutiliser plus tard facilement.

#define LED 11

void setup()
{ }

void loop()
{ }
définition d'une constante à l'aide d'une directive du pré-processeur.

Vous remarquerez qu'il n'y a pas de ; à la fin de la ligne. Ce n'est pas une instruction du langage, il n'est donc pas nécessaire. Il peut même poser problème s'il est présent. En effet, ce mécanisme de déclaration de constante peut se voir comme du rechercher/remplacer. Le pré-processeur (qui intervient avant le compilateur, pour traiter le programme qu'on a écrit et le transformer en code exécutable par le micro-contrôleur) va recherche le mot qui vient juste après le #define (dans l'exemple, le mot LED) et le remplacer par tout le reste de la ligne (dans l'exemple, le mot 11).

Une constante définie comme ça n'a pas de type. Son type sera déduit par le compilateur au moment de son utilisation, mais de manière grossière.

L'utilisation de la directive #define ne se limite pas à la création de constantes, cependant c'est tout ce que nous verrons dans le cadre de cette introduction.

const

Il existe en C/C++ un moyen pour rendre une variable constante, c'est de lui rajouter le préfixe const.

L'avantage c'est que c'est une grandeur typée, nommées, qui peut-être utilisée comme une variable classique sauf qu'on ne peut pas lui affecter de valeur à un autre moment que celui de sa déclaration. Lors de la compilation, il peut y avoir des messages d'erreurs qui sont émis si l'on ne respecte pas l'utilisation de types. C'est pratique pour le programmeur.

const int speed = 9600 ;

void setup()
{ }

void loop()
{ }
définition d'une variable constante.

Vous remarquerez la présence d'un ; à la fin de la ligne de déclaration de la constante. C'est le même mécanisme que la déclaration d'une variable classique, c'est dont une instruction à part entière.

En conclusion, un petit exemple de code, qui ne fait encore rien, mais qui permet de résumer les différents points que nous avons abordés jusqu'ici.

#define LED 11			// une constante

const int serial_speed = 9600 ;	// une autre constante

int compteur ;	// une variable globale
float moyenne ;	// une autre variable globale

void setup()
{ }

void loop()
{
  char c ;	// une variable locale
}
Exemple de programme Arduino définissant des variables et des constantes.

Afficher des informations depuis l'Arduino

Programmer une carte à micro-contrôleur simple comme l'Arduino est sensiblement différent de la programmation classique. Entre autre, il n'est pas possible d'afficher quoi que ce soit directement sur un écran car ... il n'y a pas d'écran par défaut. Qu'à cela ne tienne, nous allons nous servir de notre ordinateur comme moyen de visualisation. C'est une solution de mise au point de programmes ou une solution de communication entre la carte et un ordinateur.

Cette solution a l'avantage d'être extrêmement simple à mettre en œuvre. Cette simplicité a pourtant un coût : votre programme sera plus volumineux et il prendra plus de temps à s'exécuter. Ces inconvénients peuvent devenir problématiques si la place est limitée (cas de gros programmes) ou si le respect strict du temps est nécessaire (cas d'une application temps réel par exemple). Il faut donc garder ça à l'esprit et être conscient du fait que le déroulement normal de votre programme est modifié lorsque vous utilisez ces instructions.

Pour la faire fonctionner il faut utiliser l'objet Serial qui va utiliser la liaison série du port USB pour faire transiter des données. Ce transfert de données se fait à une certaine vitesse, qui est configurable, et qui s'exprime en bauds. Le baud est une unité qui, en première approximation, peut être assimilé au bit par seconde. La page Wikipedia vous donnera plus de détails si vous êtes curieux. La valeur par défaut est 9600 bauds.

Ensuite, il suffit d'écire les informations les unes à la suite des autres. Il faut s'attendre à des fonctions très basiques ne disposant pas des fonctionnalités plus avancées que l'on peut trouver dans les langages de programmation habituels.

Ci-dessous, un programme illustrant l'utilisation de la ligne série pour tracer l'exécution d'un programme.

#define LED 11

const int serial_speed = 9600 ;

int compteur ;
float moyenne ;

void setup()
{
  Serial.begin(serial_speed) ;	// Initialisation de la ligne série

  Serial.println("Compte des tours de loop()") ;	// Impression d'un message
  
  compteur = 1 ;

  Serial.println("C'est parti !") ;
}

void loop()
{
  /* Impression d'un message composé d'un texte,
   * de la valeur d'une variable et d'un autre texte.
   */
  Serial.print("Je suis dans la boucle pour la ") ;
  Serial.print(compteur) ;
  Serial.println("eme fois") ;

  compteur = compteur + 1 ;
}
Utilisation de la ligne série pour afficher des messages générés par l'Arduino.

Si vous faites exécuter le code ci-dessus par votre Arduino, vous remarquerez sans doute, au bout d'un certain temps, un changement radical de la valeur affichée. C'est un dépassement de capacité. Essayez de changer le type de la variable compteur et voyez ce que ça change.

Faire des tests

Un programme informatique c'est des calculs, un peu d'affichage, mais ce sont également beaucoup de tests et de boucles. Dans cette section nous abordons les tests et dans la suivante, les boucles.

Opérateurs de test

Un test revient à se poser la question « est-ce qu'une valeur est inférieure, égale, ou supérieure à une autre valeur ? ». Il faut pour cela disposer des opérateur adéquats et il se trouve que l'environnement Arduino nous propose les principaux qui sont <, <=, ==, !=, >= et >. Soit respectivement, inférieur, inférieur ou égal, égal, différent, supérieur ou égal et supérieur.

Ici il y a un point important à noter, c'est le double == pour tester l'égalité. C'est pour pouvoir différentier de l'affectation qui se note avec un unique signe =.

Condition de réussite

Ces opérateurs retournent tous une valeur en fonction du résultat du test. Si l'expression est fausse, une valeur nulle (0) est retournée. C'est donc le code pour dire « c'est faux ». Toute autre valeur est considérée comme étant vraie, ou un synonyme de « c'est vrai ».

Grouper les tests

Enfin, il est possible de faire des groupes de tests en reliant chacun des sous-tests par des opérateurs logiques. Il existe principalement trois opérateurs logiques utilisables pour cascader les tests :

Vous pouvez voir un exemple d'utilisation des groupes de tests dans l'exemple complet, plus bas.

Réagir selon le résultat du test

Une fois que l'on a une condition, il faut pouvoir réagir en fonction du résultat. Pour cela, il existe un mot clé réservé, qui est la structure de test if. Un exemple d'utilisation est donné ci-dessous.

#define LED 11

const int serial_speed = 9600 ;

int compteur ;
float moyenne ;

void setup()
{
  Serial.begin(serial_speed) ;	// Initialisation de la ligne série

  Serial.println("Compte des tours de loop()") ;	// Impression d'un message
  
  compteur = 1 ;

  Serial.println("C'est parti !") ;
}

void loop()
{
  /* Impression d'un message composé d'un texte,
   * de la valeur d'une variable et d'un autre texte.
   */
  Serial.print("Je suis dans la boucle pour la ") ;
  Serial.print(compteur) ;
  if ( compteur == 1 ) {
    // On vient ici si la condition est vraie
    Serial.println("ere fois") ;
  } else {
    // On vient ici si la condition est fausse
    Serial.println("eme fois") ;
  }

  compteur = compteur + 1 ;
}
Utilisation d'une structure de test et d'un test.

Ce qu'il faut retenir c'est la syntaxe du if dont le test est obligatoirement entre parenthèses et qui est éventuellement suivi d'un else qui indique la suite d'instructions à exécuter dans le cas où le test serait faut.

Des constructions plus complexes peuvent exister, du type

if ( test )
{
  instructions
}
else if ( test )
{
  instructions
}
else
{
  instructions
}

où il peut y avoir autant de blocs else if que nécessaire et éventuellement (il n'est pas obligatoire) un unique bloc else pour terminer la structure.

Les tests permettent donc d'altérer le comportement séquentiel d'un programme en sautant éventuellement des blocs d'instructions.

Faire des boucles

Faire des tests permet d'orienter la direction que va prendre notre code. C'est nécessaire. Mais parfois il est tout aussi utile de pouvoir faire un ensemble d'actions plusieurs fois d'affilée. C'est le principe des boucles que nous décrivons ici.

Il existe principalement trois types de boucles : for, while et do ... while. Toutes sont basées sur un test qui mettra fin à l'exécution de la boucle et toutes fonctionnent globalement de la même manière. C'est uniquement la syntaxe qui va changer et l'ordre entre le premier test et l'exécution des commandes qui va être modifié pour la structure do ... while.

Ci-dessous, quelques exemples donnant des cas d'utilisation typique de ces structures :

int i ;
				
void setup()
{
  for (i = 0 ; i < 10 ; i = i + 1) {
    Serial.print(i) ;
    Serial.print(" x ") ;
    Serial.print(i) ;
    Serial.print(" = ") ;
    Serial.println(i*i) ;
  }
}
Utilisation de la boucle for. Entre les parenthèses il y a trois éléments, éventuellement vides, séparés par des ; et qui sont, de gauche à droite : des initialisations faites une seule fois au début de la boucle, un test d'arrêt évalué au début de chaque tour boucle et une instruction faite à la fin de chaque tour de boucle.
int i ;
				
void setup()
{
  i = 0 ;
  while (i < 10) {
    Serial.print(i) ;
    Serial.print(" x ") ;
    Serial.print(i) ;
    Serial.print(" = ") ;
    Serial.println(i*i) ;

    i = i + 1 ;
  }
}
Utilisation de la boucle while. Entre les parenthèses il y a un unique élément qui est un test d'arrêt, évalué au début de chaque tour boucle.
int i ;
				
void setup()
{
  i = 0 ;
  do {
    Serial.print(i) ;
    Serial.print(" x ") ;
    Serial.print(i) ;
    Serial.print(" = ") ;
    Serial.println(i*i) ;

    i = i + 1 ;
  } while (i < 10) ;
}
Utilisation de la boucle do ... while. Entre les parenthèses il y a un unique élément qui est un test d'arrêt, évalué à la fin de chaque tour boucle. Contrairement aux boucles for et while, il y aura au moins une exécution des instructions présentes dans le bloc de la boucle avant que le test ne soit évalué.

Le test utilisé sur les trois exemples précédent est une simple inégalité. Il est cependant possible d'utiliser n'importe quel test ou n'importe quelle combinaison de tests entre les parenthèses.

Exemple complet

#define SERIAL_SPEED 9600
#define MAX_VAL 20

byte fatigue ;
unsigned int compteur, i ;
unsigned int valeurs[MAX_VAL] ;
float moyenne ;

void setup()
{
  // Initialisation de la ligne série 
  Serial.begin(SERIAL_SPEED) ;

  Serial.println("Des moyennes assez moyennes...") ;
  
  i = 0 ;
  while ( i < MAX_VAL ) {
    valeurs[i] = 0 ;
    Serial.print(MAX_VAL-i) ;
    Serial.print("...") ;
    i = i + 1 ;
  }
  Serial.println() ;

  compteur = 1 ;
  fatigue = 0 ;

  Serial.println("C'est parti !") ;
}

void loop()
{
  /* Je dis combien de fois j'ai exécuté le fonction
   * loop() en modifiant la phrase pour écrire 1ere,
   * puis 2eme, 3eme, ...
   */
  Serial.print("C'est ma ") ;
  Serial.print(compteur) ;
  if ( compteur == 1 ) {
    Serial.print("ere");
  } else {
    Serial.print("eme") ;
  }
  Serial.println(" fois dans la boucle") ;
  
  if ( !fatigue && compteur > 5000 ) {
    Serial.println("Je fatigue ...") ;
    fatigue = 1 ;
  } else if ( fatigue == 1 && compteur > 20000 ) {
    Serial.println("Je me sens faible ...") ;
    fatigue = 2 ;
  } else if ( compteur == 32767 ) {
    Serial.println("J'abandonne!") ;
    while (1) ;		// boucle infinie!
  }

  /* Je ne calcule la moyenne que quand je dispose de MAX_VAL valeurs.
   * C'est à dire, lorsque mon tableau est rempli.
   * L'opérateur % donne le reste de la division
   * entière (division euclidienne).
   */
  if ( compteur%MAX_VAL == 0 ) {
        Serial.print("J'ai ") ;
	Serial.print(MAX_VAL) ;
	Serial.println(" nouvelles valeurs !") ;
	Serial.println("J'en fais la moyenne ...") ;
	moyenne = 0 ;
	for ( i = 0 ; i < MAX_VAL ; i++ ) {
	  moyenne = moyenne + valeurs[i] ;
        }
        moyenne = moyenne / MAX_VAL ;
        Serial.print("Et je trouve ") ;
	Serial.println(moyenne) ;
  }
  valeurs[compteur%MAX_VAL] = compteur ;

  // Ne pas oublier d'incrémenter mon compteur à chaque tour de boucle
  compteur = compteur + 1 ;
}
Exemple de programme utilisant les principes abordés dans ce document.

Notation des nombres

Les nombres que l'on manipule sont en général représentés en base 10 (nous avons 10 doigts aux mains...) Quand on compte, on passe de 9 à 10, puis à 11, ...

Dans le monde des ordinateurs, d'autres bases sont employées, les plus courantes étant la base 2 et la base 16. Nous expliquons ci-dessous les rudiments de fonctionnement et d'écriture de ces représentations.

Base 2 (binaire)

La base 2 ne comprend que deux valeurs pour représenter ses nombres : 0 et 1. Pourtant, grâce à cela il est possible de représenter tous les nombres entier que l'on désire. Par exemple :

Base 10 Base 2
0 0
1 1
2 10
3 11
4 100
5 101
6 110
7 111

Le nombre en base 2 est représenté comme un polynôme dont les facteurs sont les valeurs binaires et dont les termes sont les puissances de 2 consécutives. Par exemple, pour représenter 6base 2, on a :
110base 2 = 1 × 22 + 1 × 21 + 0 × 20 = 6base 10.

La représentation binaire est celle réellement utilisée dans le cœur des ordinateurs. Ils calculent comme ça et tous les nombres que nous écrivons sont automatiquement transformés en base 2.

Il est bien entendu possible de représenter les nombres négatifs. Pour cela, un mécanisme appelé le complément à deux est utilisé.

Base 16 (hexadécimale)

La base hexadécimale fonctionne sur le même principe que la base 2, mais nous disposons à présent de 16 signes (base 16) pour représenter les nombres : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.

Là encore, il est possible de représenter tous les nombres entier grâce à la base 16. Par exemple :

Base 10 Base 16
0 0
5 5
10 A
15 F
20 14
25 19
30 1E
35 23
40 28

Le nombre en base 16 est représenté comme un polynôme dont les facteurs sont les valeurs de la représentation hexadécimale et dont les termes sont les puissances de 16 consécutives. Par exemple, pour représenter 12345base 10, on a :
3039base 16 = 3 × 163 + 0 × 162 + 3 × 161 + 9 × 160 = 12345base 10.

On peut remarquer que la représentation de 255base 10 est 11111111base 2 mais aussi FFbase 16. C'est la valeur maximale que peut prendre 1 octet (un groupe de 8 bits).

Autant la notation en base 2 est rarement utilisée directement dans les programmes, autant il est fréquent de devoir écrire des nombres en base 16. Pour que notre compilateur s'y retrouve, nous devons lui préciser que la base est différente. Les nombres en base 16 seront pour cela, toujours préfixés des deux caractères 0x. Le bout de code suivant donne des exemples d'initialisation de variables entières.

int a = 1 ;	// valeur 1, quelle que soit la base
int b = 0x10 ;	// valeur 16 exprimée en base 10
int o = 0xFF ;	// valeur 255 exprimée en base 10
Exemples d'initialisation de valriables entières avec des nombres exprimés en base 16.

  1. [↑] ce terme est la traduction de l'anglais upload. En réalité, ce qui est fait au moment du téléversement, c'est la programmation du micro-contrôleur. Pour éviter toute confusion entre le fait d'écrire le programme et le fait de l'envoyer sur le micro-contrôleur, on utilise donc téléverser.