Que contient vraiment une carte Nucléo ?

NIVEAU 0

Objectifs

  • Comprendre ce qu’est un microcontroleur
  • Connaitre les possibilités et les limites des microcontroleurs
  • Créer un code structuré pour un microcontroleur

Pré-requis

Qu’est-ce qu’un microcontroleur ?

Un microcontroleur est un composant électronique actif (c’est à dire nécessitant une alimentation externe) permettant de faire du traitement numérique de l’information de manière embarquée.

Structure simplifiée d’un microcontroleur

Il est constitué des éléments suivants :

Structure simplifiée d’un microcontroleur
  • une unité de calcul, aussi appelée processeur, qui réalise divers calculs (pré-établis et câblés dans la structure du microcontroleur) sur des données à partir d’instructions provenant de la mémoire programme
  • une zone de mémoire programme, communément appelé ROM (Read-Only Memory – ou Flash-ROM), qui stocke la liste des instructions à réaliser par l’application embarquée
  • une zone de mémoire données, communément appelé RAM (Random Access Memory), qui stocke des données temporaires, mais nécessaires à l’exécution des tâches que doivent accomplir l’application embarquée
  • des entrées/sorties, qui servent à récupérer des informations numériques ou analogiques de l’environnement de l’application (pour le cas des entrées) et à piloter ou à communiquer avec d’autres systèmes externes (pour le cas des sorties)

Selon les constructeurs et le type de composant choisi (en fonction de l’application visée), ces microcontrôleurs peuvent intégrer d’autres modules spécifiques : des timers autonomes pour la gestion du temps, des protocoles de communication particuliers et très utilisés dans le monde industriel ou embarqué, des gestionnaires de modulation de largeur d’impulsions pour le pilotage de système à temps de réponse “long”…

Utilisation des microcontroleurs

Les microcontrôleurs sont utilisés dès qu’il est nécessaire d’avoir un traitement numérique d’une information ou d’un signal, associé par la suite : soit à un pilotage d’un système, soit à la transmission de cette information à un autre système (pour un traitement à posteriori de cette donnée).

Une des premières applications des microcontrôleurs était l’intégration d’un élément de calculs dans un système transportable facilement : la calculatrice (dans les années 1970). Associée à de touches numérotées d’un clavier et à un écran, elle devait pouvoir restituer le résultat du calcul demandé par l’utilisateur. Il était donc nécessaire d’avoir un coeur de calculs avec des entrées-sorties spécifiques (contrairement à un processeur généraliste), peu gourmand en énergie et capable d’être autonome.

Ils sont ensuite très utilisés dans le monde de l’industrie : automobile et transports, éclairage, télécommunications, systèmes portatifs… et dans les appareils “grand public” : électroménager, télévision…

Exemple concret d’utilisation d’un microcontroleur / La recherche du maximum de puissance (ou de luminosité dans ce cas simplifié)

Depuis, les microcontrôleurs ont évolués et intègrent toujours plus de fonctionnalités. On les retrouve à présent dans la plupart des appareils “grand public” de faible puissance et portable. Les smartphones fonctionnent, par exemple, actuellement avec des microcontroleurs spécialisées, intégrant un coeur ARM-A (avec des instructions spécialisées dans la gestion des communications et de l’affichage graphique) : Société ARM et l’exemple du Cortex ARM-A72.

Dans tous les cas, il s’agit d’une application qui tourne en permanence et qui doit systématiquement répondre à toutes les sollicitations extérieures du système. C’est pourquoi les programmes embarqués sont souvent basés sur une boucle infinie, afin de scruter en permanence les entrées et mettre à jour les sorties en conséquence.

Comment se programme une carte Nucléo ?

Tout comme les processeurs généralistes (qui équipent la plupart des ordinateurs), les microcontrôleurs ne comprennent que du langage de bas niveau, encore appelé assembleur. Cependant, ces langages étant difficiles à comprendre et à appréhender pour un être humain, il existe des programmes qui permettent de transformer des langages dits de haut niveau (type langage Python, C, C++) vers ces langages machines. Ce sont les compilateurs.

Compilation avec Keil Studio

L’IDE de Keil propose le C/C++ comme langage de haut niveau, pour coder les applications sur les cartes Nucleo. Ce compilateur en ligne est associé à une série de bibliothèques de fonctions permettant de gérer ou/et de communiquer avec des composants externes (écrans LCD, accéléromètres, modules BT…).

Compilation d’un langage de haut niveau vers l’assembleur

Une fois le code source écrit dans le fichier main.c, il est indispensable de compiler le code.

La compilation va réaliser deux étapes différentes :

  1. la traduction du code du langage C vers un langage de bas niveau qu’est l’assembleur
  2. l’attribution des lignes de code et des variables à des espaces mémoires (soit en ROM pour le programme, soit en RAM pour les données ou variables) en fonction de la cible choisie (i.e. la référence de microcontroleur utilisée)

Cette étape de compilation permet également de réunir, dans un seul et même fichier, l’ensemble du code écrit et des fonctions utilisées pour l’application (provenant des diverses bibliothèques utilisées). On obtient alors en sortie un fichier binaire (extenstion .bin ici) pouvant par la suite être téléversé sur la cible.

La compilation sous Keil Studio (via MBED) se fait par le bouton suivant :

A la fin du processus de compilation, le site vous propose de sauvegarder un fichier binaire (extension *.bin) sur votre disque dur. Choisissez l’emplacement où vous souhaitez le stocker et cliquez sur Sauvegarder :

Téléversement

La dernière étape est de transférer le fichier binaire fourni par l’étape précédente sur le microcontroleur.

Téléversement d’un programme binaire vers la cible microcontroleur

Pour cela, on utilise un dispositif qui s’appelle un programmateur. Il permet d’imposer les bons signaux sur le microcontroleur afin de le sortir de son mode de fonctionnement classique, pour l’emmener dans un mode de programmation. L’espace Flash-ROM est alors accessible en écriture pour pouvoir stocker le programme à l’intérieur. Cette phase nécessite souvent d’avoir une tension un peu supérieure à celle d’alimentation standard.

Une fois le téléversement du programme réalisé, le programmateur libère alors le microcontroleur de ce mode de programmation. L’application peut alors tourner normalement.

Dans le cas des cartes Nucléo, le programmateur, appelé ST-Link, est présent sur une partie de la carte. Il permet en plus à la carte Nucléo d’être reconnue comme une “simple” clef USB. Un simple câble USB suffit alors à faire le lien entre l’ordinateur qui a permis de compiler le programme et le microcontroleur présent sur la deuxième partie de la carte Nucléo.

Schéma de principe de la carte Nucléo

Le téléversement se fait alors par simple copier-coller vers la “fausse” clef USB. Les cartes Nucléo sont souvent reconnues par les systèmes d’exploitation par leur nom : NODE xxxx.

Structure d’un programme

Le programme auquel nous allons nous intéresser ici est le template nommé : “Blinky LED…” (programme d’exemple qui fait clignoter la LED présente sur la carte Nucléo).

L’utilisation d’un programme d’exemple permet d’avoir une structure de base du programme. Celui-ci ressemble à cela :

#include "mbed.h"

DigitalOut  myled(LED1);

int main(){
    while(1){
        myled = 1;   // LED is ON
        wait(0.2);   // 200 ms
        myled = 0;   // LED is OFF
        wait(1.0);   // 1 s
    }
}

Très rapidement, afin d’éviter de vous y perdre dans toutes les zones possibles de ce code, vous serez amenés à commenter votre code. Un exemple complet de code commenté, délimitant les diverses zones est donné ici :

/* 
 *   Programme de test
 *      Auteur : J. VILLEMEJANE / Sept 2017 / IOGS
 *      From : MBED Example / Blinky LED
 */

/* Déclaration des ressources externes */
#include "mbed.h"

/* Déclaration des variables globales */

/* Déclaration des entrées/sorties */
DigitalOut  myled(LED1);

/* Fonction principale */
int main(){
    /* Zone d'initialisation */

    /* Boucle infinie */
    while(1){
        myled = 1;   // LED is ON
        wait(0.2);   // 200 ms
        myled = 0;   // LED is OFF
        wait(1.0);   // 1 s
    }
}

Un code source pour de l’embarqué (sur ces cartes Nucléo) reste avant tout un code en langage C. Le compilateur de MBED ajoute une couche supplémentaire de haut niveau permettant d’accèder facilement aux ressources présentes sur ces cartes (entrées, sorties, modules spécifiques…). Nous verrons cela à travers les prochains tutoriaux.

Pour revenir au code précédent, il contient :

  • Une zone de ressources externes : des bibliothèques de fonctions déjà écrites
  • Une zone de déclaration de variables globales : variables qui permettent de gagner de la place en RAM mais qui sont communes à toutes les fonctions
  • Une zone de déclaration des entrées/sorties
  • Une zone de déclaration des prototypes de fonctions : fonctions spécifiques à votre projet
  • Une fonction principale, nommée main, contenant :
    • Une zone d’initialisation : déclaration des variables locales, appel aux fonctions d’initialisations des périphériques…
    • Une boucle infinie (while(1)) : particularité des systèmes embarqués, dont le programme ne doit jamais s’arrêter

Différence entre le C embarqué et le C standard

Variables locales / globales (Pour aller plus loin)

Dans la plupart des applications en langage C, on préfère déclarer les variables à l’intérieur des diverses fonctions, afin de clarifier le code et le rendre le plus universel possible pour pouvoir réutiliser simplement par la suite nos bibliothèques de fonctions. Il permet également de bien séparer les variables et leurs utilisations respectives dans les fonctions.

double moyenneTab(int tab[], int h, int l){
    int i, j;
    for(i = ...)
    ...
}

int main(){
    int i, j, k;
    double resultat;
    ...
}

Cependant, cette méthode est très gourmande en ressources matérielles. En effet, à chaque nouvelle variable déclarée dans une fonction, le compilateur lui associera un nouvel espace mémoire (compatible avec le type de variable associé). Or les microcontrôleurs sont des structures de calcul ne bénéficiant pas d’une grande quantité d’espaces mémoires.

Afin de gagner en quantité de mémoires utilisées, on peut alors utiliser des variables dites globales. Celles-ci ne se déclarent qu’à un seul endroit dans le programme et seront alors communes à l’ensemble des fonctions.

int tab[N], i, j, k, h, l;
double resultat;

double moyenneTab(){
    for(i = ...)
    ...
}

int main(){
    ...
    resultat = moyenneTab();
    ...
}

Le risque est alors de mal les utiliser et d’en réaffecter une à un endroit qui pourrait être critique à l’application embarquée. Il faut donc les manipuler avec beaucoup de précautions.

N.B. Cette dernière remarque sur l’utilisation de variables globales à la place de variables locales tend à disparaître avec l’apparition de microcontrôleurs toujours plus intégrés et donc moins chers, et contenant toujours de plus en plus d’espace mémoire.

MInE Prototyper Prototyper avec Nucleo et MBED

Nucleo – Découvrir les microcontrôleurs des cartes Nucléo