La programmation du microcontrôleur
Sommaire du chapitre
- La programmation du microcontrôleur
- Le langage
- Le simulateur TinkerCAD
- L’éditeur de code
- Les variables
- Les fonctions
- Le compilateur
- Les instructions spécifiques
- La communication série
- Les opérations courantes
- Les fonctions mathématiques
- Les structures de contrôle
- Guide de référence
- En résumé
- Création du compte
- Création d'un projet
- Edition d'un projet
- Les types de variables
- Généralités
- Prototype d’une fonction
- L’architecture d’un programme
- La configuration des broches
- Le changement d’état des broches
- Les délais
- La lecture des broches
- Intêret de cette communication
- Initialisation de la communication
- Transmission des donneés
- La structure if
- La structure while
- Note importante
La programmation du microcontrôleur
Le langage
Pour programmer notre microcontrôleur, nous allons utiliser le langage C++ qui est un langage de programmation très répandu.
Les variables
Pour manipuler les données dans notre programme, qui n’est plus ni moins une suite d’instructions,
nous allons avoir besoins de créer des variables, c’est à dire de déclarer que nous allons manipuler
une donnée.
Une variable possède donc fondamentalement :
Un nom
Un type
Ce type dépend de la donnée que l’on souhaite manipuler. Cela peut être un nombre entier, un nombre à virgule, une chaîne de caractères... La variable est enregistrée dans la mémoire (Flash ou RAM), la taille occupée (en octet) par la variable dans la mémoire dépendant de son type.Une valeur en cohérence avec le type choisi

L’architecture d’une variable
Un octet est une unité fondamentale de stockage de données en informatique. Il est composé
de 8 bits, et chaque bit peut avoir deux valeurs possibles : 0 ou 1. Cela signifie qu’un octet peut
représenter $2^8$ (soit 256) valeurs différentes, allant de 0 à 255.
Dans les microcontrôleurs et les systèmes embarqués, la mémoire est généralement organisée
en octets, et chaque octet est adressable, ce qui signifie qu’il a une adresse mémoire unique qui
permet d’y accéder.
La section suivante présente les types de variables possible en fonction de leur utilisation.
Les types de variables
byte
Désigne la plus petite unité de mémoire allouable, permettant de stocker un nombre entier compris entre -127 et + 127. Le bit de poids fort est le signe prédictif "+" ou "-", les bits de 1 à 7 correspondent à la valeur.unsigned byte
Idem mais la plage de valeur strictement positiveint
Permet de stocker un nombre entier compris entre -32536 et +32536unsigned int
Idem mais la plage de valeur strictement positivefloat
Permet de manipuler des réelsdouble
Idem, mais ce type est plus précis que le type float et demande plus de ressources au microcontrôleur.char
Ce type est utilisé pour stocker et traiter des caractères de la table ASCIIString
String est un type élaboré qui permet de traiter des chaînes de caractères.bool [1 octet]
Ce type est utilisé pour stocker et traiter des états logiques : true ou falsevoid [1 octet]
Ce type existe uniquement pour le type de retour d’une fonction (section suivante)
Dans un souci de cohérence, nous choisissons le type de variable en fonction des données :
Pour manipuler une température en provenance d’un capteur, nous utiliserons principalement un type float ou double pour avoir une précision supérieure au degré
Pour un compteur de pas, un entier est suffisant
Les fonctions
Généralités
Afin de rendre plus lisible notre programme, il convient de le séparer dans des fonctions, c’est à dire des
portions de code qui effectuent une tâche spécifique ou un ensemble d’opérations bien définies.
Les fonctions sont utilisées pour organiser et structurer un programme en morceaux plus petits et plus gérables,
ce qui facilite la compréhension, la maintenance et la réutilisation du code.
Comme pour les variables, les fonctions reposent sur une architecture. Une fonction est composée de :
Un nom
Un paramètre d’entrée (facultatif)
Une valeur de retour (Par extension, un type de retour)

Prenons un exemple avec une fonction qui va élever un nombre au carré :

Cette function prend un entier en paramètre, l’élève au carré et retourne cette valeur.
Prototype d’une fonction
Une fonction est donc défini par son prototype qui spécifie les détails essentiels de la fonction,
tels que son nom, ses paramètres d’entrée (arguments), et son type de valeur de retour.
Une fonction, en C++, est de la forme suivante :
TypeReturn name(typeOfInput inputName)
//Code
return outputVariable;
La figure suivante implémente notre fonction ”Square”.
int square(int a)
return a*a;
Il est également possible de créer des variables intermédiaires :
int square(int a)
int inputValue = a; //Same type
return inputValue*inputValue;
L’architecture d’un programme
Nos futurs programmes vont se baser sur deux fonctions obligatoires :
fonction setup
fonction loop
Avec une utilisation de Plateform.io, on n'oubliera pas d'inclure systématiquement au début du programme la ligne \#include < Arduino.h >
#include //For Platform.io
void setup()
//Code in setup
void loop()
//Code in loop
void est un type particulier qui veut dire que la fonction ne retourne rien. Dans ce cas, la fonction est simplement vue comme une procédure, c’est à dire un enchaînement d’actions sans attendre de retour particulier.
Le compilateur
Une fois que notre code est rédigé, nous allons invoquer le compilateur qui est un programme qui va transformer
notre code en instructions machine.

Il s'agit de code bas niveau que le microprocesseur de la machine peut comprendre.
Le code suivant affiche le code nécessaire pour modifier l'état d'une broche :
"sbi
"call OneSecondDelay " //delay
"cbi
"call OneSecondDelay " //delay
"rjmp 4f " //exit
"OneSecondDelay: "
"ldi r18, 0 " //delay 1 second
"ldi r20, 0 "
"ldi r21, 0 "
"1: ldi r24, lo8(400) "
"ldi r25, hi8(400) "
"2: sbiw r24, 1 " //10x around this loop = 1ms
"brne 2b "
"inc r18 "
"cpi r18, 10 "
"brne 1b "
"subi r20, 0xff " //1000 x 1ms = 1 second
"sbci r21, 0xff "
"ldi r24, hi8(1000) "
"cpi r20, lo8(1000) "
"cpc r21, r24 "
"breq 3f "
"ldi r18, 0 "
"rjmp 1b "
"3: "
"ret "
Ce code ne sera pas expliqué mais vous pouvez voir que les instructions sont moins lisible pour un être humain.
Ce sont ce genre d’instructions que comprend votre microcontrôleur.
Chaque famille de microcontrôleur possède son jeu de langage, cependant on retrouve de nombreux points communs.
L'un des compilateur les plus connu est pour le language C et pour le langage C++.
Les instructions spécifiques
La configuration des broches
Nous souhaitons utiliser des broches de notre microcontrôleur. Il nous faut donc définir le type de
broche. Pour cela, il existe l’instruction qui prend en argument le numéro de la broche et son type (entrée
ou sortie).
Par exemple, nous souhaitons mettre la broche en sortie :
void setup()
pinMode(D4, OUTPUT);
Pour la carte CREPP.io, cela veut dire que nous allons utiliser la broche en sortie.
Cette broche est reliée à la LED verte. Sur la carte NodeMCU, une LED est déjà câblé cependant, le niveau logique est inversé :
- Un 1 logique sur cette broche allume la LED verte externe et éteint la LED interne.
- Un '0' logique sur cette broche allume la LED bleue interne et éteint la LED externe.
Pour la mettre en entrée, il convient de faire :
void setup()
pinMode(D4, INPUT);
Le changement d’état des broches
Pour changer le niveau logique d’une broche [2], on va utiliser la fonction qui prend en argument le numéro de la broche et son niveau logique
(haut ou bas).
Ne pas oublier d’écrire la fonction avant la fonction loop.
void loop()
digitalWrite(D4, HIGH);
Et pour mettre à l’état Bas, on utilise le mot clé LOW à la place de HIGH :
void loop()
digitalWrite(D4, LOW);
Les délais
Parfois, nous souhaitons des délais dans notre programme, afin de le stabiliser pour certaines actions par
exemple.
Il existe la fonction qui prend en argument le temps pendant lequel le microcontrôleur sera bloqué
à cette instruction pendant la durée passée.
La valeur en argument est exprimée en millisecondes !
void loop()
digitalWrite(D4, HIGH);
delay(1000);
digitalWrite(D4, LOW);
delay(1000);
Physiquement, l'allure de la tension sur la broche est la suivante :
La lecture des broches
Pour lire l’état d’une broche, nous utiliserons la fonction qui prend en argument
le numéro la broche.
Il faudra au préalable avoir mis la broche en entrée :
void setup()
pinMode(D4, INPUT);
void loop()
bool value = digitalRead(D4);
La valeur lue sur la broche sera stockée dans la variable value.
La communication série
Intêret de cette communication
Lors de l’exécution d’un programme, nous souhaitons parfois savoir ou en est le programme,
quelle instruction il est en train de réaliser. Pour cela, le langage Arduino (surcouche C++)
propose une bibliothèque dédiée : la bibliothèque .
Pour les bibliothèques, il faut voir ca comme un regroupement de fonctions.
L’utilisation de la bibliothèque permet de communiquer avec un ordinateur ou d’autres
périphériques à l’aide de la communication série. C’est un outil très utile pour le débogage,
la surveillance et la transmission de données entre votre microcontrôleur et un ordinateur hôte via
un câble USB ou un adaptateur série.
Le protocole série est décrit de manière plus précise dans la section La liaison UART.
Initialisation de la communication
Il faut tout d’abord initialiser la communication série dans la fonction :
void setup()
Serial.begin(9600); //9600 bauds
Transmission des donneés
Vous pouvez envoyer des données depuis le microcontrôleur vers l’ordinateur hôte en utilisant la fonction ou [3]
void loop()
int value = digitalRead(D4);
Serial.print("Value : ");
Serial.println(value);
delay(1000); //wait 1s
Vous pouvez également recevoir des données depuis l’ordinateur hôte vers le microcontrôleur.
Cela peut être utile pour contrôler le microcontrôleur à partir de votre ordinateur.
Cependant, cette section sera abordée plus tard, cela ne rentre pas dans le contexte des bases des
microcontrôleurs.
Les opérations courantes
Il est possible de faire les opérations courantes :
Addition
int a = 2; int b = 3; int c = a + b; Serial.print("Addition : "); Serial.println(c);Soustraction
int a = 2; int b = 3; int c = a - b; Serial.print("Soustraction : "); Serial.println(c);Multiplication
int a = 2; int b = 3; int c = a * b; Serial.print("Multiplication : "); Serial.println(c);Division
int a = 2; int b = 3; double c = (double)a / b; Serial.print("Division : "); Serial.println(c);Modulo
Il s'agit du reste d'une divisionint a = 2; int b = 3; int c = a
Il faut faire attention au type des variables qui entrent en compte dans les
calculs.
Si nous faisons une addition de nombres relatifs [1], il ne faut pas stocker le résultat
dans une variable avec un type non-signé.
Ou alors, une addition de 2 nombres codés sur 8 bits, cela peut donner un nombre plus grand que la représentation maximale d'un des
2 nombres.
Par exemple, $150+150=300$, or pour représenter $300$, il faut plus de 8 bits
Les fonctions mathématiques
En ce qui concerne les fonctions mathématiques, les fonctions trigonométriques sont incluses. Par exemple, pour utiliser la fonction cosinus, on utilisera la fonction qui attend un angle en radian.
Les structures de contrôle
Une fois que nous avons nos variables qui contiennent les éléments que nous souhaitons, il va falloir agir en conséquent, c’est à dire faire des instructions spécifiques en fonction de la valeur.
La structure if
la structure if est une instruction de contrôle de flux qui permet d’exécuter un bloc de code
conditionnellement, en fonction de l’évaluation d’une expression booléenne.
L’expression booléenne est une expression qui peut être soit vraie (true) soit fausse (false).
Voici la syntaxe de base de la structure if :
if (condition)
// if true, do ...
condition est une expression booléenne que vous souhaitez évaluer. Si la condition est vraie, le bloc de
code à l’intérieur des accolades est exécuté ; sinon, il est ignoré.
Voici un exemple simple en utilisant la structure if en C++ :
int value = digitalRead(D4);
if (value == true)
digitalWrite(D3, HIGH);
else
digitalWrite(D3, LOW);
Dans cet exemple, nous lisons l’état logique de la broche puis nous utilisons une structure if pour
vérifier si l’état de cette broche est HAUT. Si c’est le cas, nous allumons une LED sur en utilisant , sinon,
nous l’éteignons.
Il est également possible d’ajouter des clauses else if et else pour gérer plusieurs conditions.
if (condition1)
// Code à exécuter si condition1 est vraie
else if (condition2)
// Code à exécuter si condition2 est vraie (si condition1 est fausse)
else
// Code à exécuter si aucune des conditions précédentes n'est vraie
La structure while
La structure ‘while‘ en C++ est une instruction de boucle qui permet d’exécuter un bloc de code tant qu’une condition spécifiée est vraie (true). La condition est évaluée avant chaque itération de la boucle, et si elle est vraie, le code à l’intérieur du bloc ‘while‘ est exécuté. Voici la syntaxe de base de la structure ‘while‘ en C++ :
while (condition)
//while true, do ...
Voici un exemple simple d’utilisation de la structure ‘while‘ :
int count = 0;
while (count < 5)
Serial.println(count);
count++; //Counter increment
Dans cet exemple, la boucle while s’exécute tant que la valeur de ‘count‘ est inférieure à 5. À chaque
itération de la boucle, la valeur de ‘count‘ est affichée sur le moniteur série à l’aide de
et ensuite incrémentée de 1 avec ‘count++‘.
La boucle continuera à s’exécuter jusqu’à ce que ‘count‘ atteigne la valeur 5, moment où la condition devient
fausse et la boucle s’arrête.
En langage C++, le fait de mettre un ++ derrière une variable permet d’incrémenter de 1 la valeur de la variable.
int myVariable = 0;
//C++ classic
myVariable = myVariable + 1;
Il existe la version avec un retrait de 1 :
int myVariable = 10;
//C++ compact
myVariable–;
//C++ classic
myVariable = myVariable -1;
Il est important de faire attention à ne pas créer de boucles infinies en utilisant la structure ‘while‘. Assurez-vous toujours que la condition finira par devenir fausse pour éviter que le programme ne reste bloqué dans la boucle.
Note importante
La plupart des instructions requièrent un point-virgule à la fin de celle-ci. En cas d'oubli, le compilateur va soulever une erreur.
Guide de référence
La documentation officielle Arduino, pour programmer différents types de microcontrôleurs est disponible à l’adresse https ://www.arduino.cc/reference/fr/
En résumé
Pour programmer notre carte nous avons besoin :
De variables avec un nom, un type et une valeur
De fonctions, avec au minimum :
une fonction
une fonction
Des fonctions spécifiques pour gérer les broches du microcontrôleur :
pour gérer le type de broche
pour mettre une broche à un certain niveau
pour temporiser
pour lire le niveau logique d’une broche (numérique)
Des instructions pour communiquer avec le PC relié au microcontrôleur (
Le simulateur TinkerCAD
L'objectif de ce chapitre est de présenter un outil de simulation Arduino : TinkerCAD.
Le simulateur est disponible à l'adresse suivante : https://www.tinkercad.com
Dans le cadre de l'atelier Electronique Embarquée
2023-2024, les microcontrôleur utilisés sont des ESP12 NodeMCU. Il n'est pas possible de les
simuler sous TinkerCAD, cependant, nous utiliserons une carte Arduino à la place.
Seul le numéro de broche changera car la programmation sera identique.
Création du compte
Il vous faudra tout d'abord créer un compte en cliquant en haut à droite sur [4]} :

Veuillez cliquer sur

Il vous faudra ensuite renseigner vos informations personnelles puis faire .

Après la saisie de votre nouveau mot de passe, cliquer sur .

Il se peut que vous receviez un mail de confirmation à l'adresse mail fournie. Suivez la procédure du mail puis lors de votre première connexion à TinkerCAD, vous tombez sur une interface avec un menu latéral :

Création d'un projet
Sur la page centrale, vous pourrez retrouver vos réalisations :

TinkerCAD permet également de réaliser des projets de CAO 3D.
Edition d'un projet
Dans notre cas, nous souhaitons éditer une simulation Arduino, nous allons donc dans la section Your Designs puis .

Vous arrivez ensuite sur la zone d'édition de circuits.

Nous allons réaliser une simulation avec une carte Arduino et un transistor MOSFET.
Bien que nous n’ayons pas encore évoqué les transistors et les moteurs, ce chapitre a surtout pour objectif de montrer les capacité du simulateur et de se familiariser avec. Il s’agit volontairement d’un circuit plus évolué mais il sera amplement évoqué par la suite.
Le menu de droite permet de faire des recherche de composants.

Il vous suffit de faire un glisser-déposer de la carte Arduino vers la zone centrale pour qu'elle soit affichée dans la zone.

Nous allons ajouter un MOSFET de la même manière :

Pour ajouter des câbles, il suffit de cliquer sur une des bornes des différents composants.

Et pour modifier la couleur des câbles, il faut cliquer sur le bouton coloré dans la barre supérieure :

A vous de réaliser le montage suivant :

Voici donc les mot-clés à saisir pour la recherche des composants :
Arduino Uno r3
resistor
nmos
dc motor
9v battery
Une fois le schéma édité, nous allons pouvoir programmer la carte Arduino.
Pour cela, il faut cliquer sur en haut à droite dans le menu :

Puis mettre le code en mode Texte :



Veuillez le remplacer par le code suivant :
#include //For Platform.io
#define PIN 8 //GATE du transistor
void setup()
pinMode(PIN, OUTPUT); //Mise en sortie de la broche
//Fin setup
void loop()
digitalWrite(PIN, HIGH); //Mise en route du transistor
delay(5000); //Délai de 5s
digitalWrite(PIN, LOW); //Arret du transistor
delay(5000); //Délai de 5s
//Fin loop
Une fois le code copié, nous allons pouvoir lancer la simulation.


Pour preuve, pendant 5 secondes, le moteur affiche une vitesse en tour/min :

Puis pendant 5 autres secondes, la vitesse affiché est nulle. La simulation est donc fonctionnelle.
L’éditeur de code
Pour programmer les cartes NodeMCU basées sur la carte CREPP.io, nous utiliserons l'éditeur de code .
La section détaillant son installation est disponible dans cette section{vscode