TP Mbed OS

Le but de ce TP est de prendre en main le système d'exploitation Mbed OS. Nous utiliserons pour cela les cartes Discovery IoT node déjà utilisées lors d'un TP précédent. Nous connecterons ensuite cette carte en I²C à d'autres systèmes.

Mise en place de l'environnement

Mbed OS dispose d'un client en ligne de commande mbed qu'on peut installer dans un environnement virtuel Python qui permet d'installer des paquets sans nécessiter de privilège au niveau du système d'exploitation.

Tout d'abord, on crééra un environnement virtuel dans $HOME/mbed en faisant :

$ virtualenv -p python2 ~/mbed

On activera ensuite cet environnement (il faudra le faire dans toutes les shells où on souhaitera l'utiliser) avec la commande :

$ source ~/mbed/bin/activate

Il suffit ensuite d'installer mbed avec

$ pip install mbed-cli

Dès lors, on dispose dans cet environnement Python virtuel de la commande mbed.

Création d'un nouveau projet

Nous allons créer un nouveau projet appelé "mbed-intro". Placez vous là où vous souhaitez créer le projet et utilisez les commandes suivantes pour créer le projet  :

$ mbed new se752

Dans le répertoire se752, on va maintenant configurer l'utilisation de la suite GCC ARM globalement (les binaires arm-none-eabi-gcc et similaires doivent être dans la variable $PATH) et l'utilisation de la carte Discovery IoT node localement pour le projet :

$ mbed toolchain --global GCC_ARM
$ mbed target DISCO_L475VG_IOT01A

On peut vérifier la configuration courante avec la commande mbed config -L.

Faire clignoter une LED

Mbed OS se programme en C++. On fournira un fichier main.cpp qui contient le code principal. Nous allons commencer par faire clignoter une led en utilisant la définition LED1 correspondant à la première led de notre carte. Voici le contenu du fichier main.cpp :

#include <mbed.h>

static DigitalOut led(LED1);

int main() {
  for (;;) {
    led = !led;
    wait(0.2);
  }
}</mbed.h>


Nous pouvons maintenant compiler ce programme en tapant

$ mbed compile

Cette commande génère un fichier ./BUILD/DISCO_L475VG_IOT01A/GCC_ARM/se752.bin ainsi qu'un fichier ELF ./BUILD/DISCO_L475VG_IOT01A/GCC_ARM/se752.elf.

Si la carte est bien configurée en stlink (sinon voir avec nous pour la repasser de JLink en stlink) on peut la connecter et flasher le programme automatiquement en utilisant

$ mbed compile --flash

Gestion des boutons

Configurez une deuxième led afin qu'une seconde après l'appui sur le bouton la led s'allume pendant 100ms. Pour cela, utilisez les Event de Mbed OS.

Gestion de l'affichage

Commencez par réimplémenter les fonctions du TP précédent (bare metal) pour gérer l'affichage de la matrice de leds, en utilisant les primitives de Mbed OS pour contrôler les sorties.

Une fois les fonctions de base testées, vous devrez :

  • créer deux Queue nommées matrix_full et matrix_empty qui contiendront respectivement les adresses des buffers prêts à être affichés sur la matrice de leds et les adresses des buffers utilisables pour travailler dessus (hors affichage) ;
  • créer le type rgb_t qui contient trois entiers sur 8 bits r, g, et b ;
  • créer deux tableaux statiques d'éléments de type rgb_t permettant de représenter une image ;
  • placer les adresses de ces deux tableaux dans matrix_empty ;
  • créer un thread chargé de gérer l'affichage ;
  • modifier votre programme main pour qu'il appelle une fonction blink_matrix.

Le contenu du thread d'affichage

Ce thread devra :

  1. initialiser la matrice de leds et éteindre toutes les leds ;
  2. attendre de manière bloquante l'adresse d'un buffer à afficher depuis matrix_full et le stocker dans une variable current_buffer ;
  3. afficher le contenu de l'image située dans current_buffer ;
  4. obtenir de manière non-bloquante un nouveau buffer à afficher depuis matrix_full ;
  5. si un nouveau buffer est disponible, placer current_buffer dans matrix_empty puis copier la valeur du nouveau buffer dans current_buffer ;
  6. retourner à l'étape 3.

Le contenu de la fonction blink_matrix

Cette fonction devra :

  1. obtenir de manière bloquante un buffer libre depuis matrix_empty ;
  2. remplir ce buffer de rouge puis le placer dans matrix_full ;
  3. attendre une demi seconde ;
  4. obtenir de manière bloquante un buffer libre depuis matrix_empty ;
  5. remplir ce buffer de vert puis le placer dans matrix_full ;
  6. attendre une demi seconde.

Une alternance rouge et verte devrait être visible sur la matrice de leds.

Mise en place de l'I²C esclave

Le périphérique i2c1 est disponible sur le connecteur CN1 de la carte (pins 9 et 10 du connecteur). On souhaite pouvoir recevoir des commandes par ce port en mode esclave et y réagir.

Toute communication commence par une écriture du maître vers l'esclave contenant uniquement le code de la commande souhaitée. Ensuite, le maître fait soit une écriture pour passer les paramètres, soit une lecture pour lire la réponse de l'esclave. Dans tous les cas, une commande est constituée de deux échanges.

Un scratchpad de 4 octets constituera une zone mémoire de taille 4 que le maître I²C pourra librement utiliser à travers des écritures et des lectures. La valeur initiale sera composée de zéros.

Liste des commandes I²C
Commande (hexa) Direction Longueur des données échangées Contenu des données échangées
02 Écriture 4 Contenu à écrire dans le scratchpad
06 Écriture 1 Le bit 0 (poids faible) est écrit dans LED1, le bit 1 dans LED2 et le bit 2 dans LED3.
10 Écriture 192 Image à afficher sur la matrice de leds
80 Lecture 1 Nombre d'appui sur le bouton BUTTON1 depuis la dernière lecture
82 Lecture 4 Contenu du scratchpad
83 Lecture 1 Adresse I²C de l'esclave sur 7 bits

 

Voici un exemple de séquence et les résultats attendus :

Exemples de messages (dans l'ordre où ils sont transmis)

Écriture : 83

Lecture de longueur 1

Renvoie l'adresse I²C de l'esclave (ici 0x2A).

Écriture : 02

Écriture : 10 20 30 40

Stocke 0x10 dans le premier octet du scratchpad, 0x20 dans le second, 0x30 dans le troisième et 0x40 dans le quatrième.

Écriture : 82

Lecture de longueur 4

Renvoie les 4 octets du scratchpad (ici 0x10, 0x20, 0x30 et 0x40).

Écriture : 06

Écriture : 04

Éteint les leds LED1 et LED2 et allume la LED3 en puissance maximale.

Écriture : 10

Écriture de 64 ×3 (soit 192) octets

Récupère un buffer depuis la file d'attente des buffers d'affichage libres, stocke les octets reçus dedans, puis le transmet à la file d'attente de l'affichage.

Écriture : 80

Lecture de longueur 1

Récupère le nombre d'appuis sur le bouton poussoir depuis le lancement du programme.

Écriture : 80

Lecture de longueur 1

Récupère le nombre d'appuis sur le bouton poussoir depuis l'exécution de la commande précédente.

Travail à faire (suggestion d'architecture)

  1. Configurer l'esclave I²C sur la carte en regardant les GPIO à utiliser dans la documentation de l'IoT Node dans un fichier i2c.cpp et son header i2c.h, dans une fonction i2c_init. Ne pas oublier de configurer l'adresse de cet esclave.
  2. Écrire une fonction int wait_for_request() qui attend une transaction I²C et renvoie 0 si une requête en lecture arrive ou 1 si une requête en écriture arrive.
  3. Écrire une fonction int answer_read_request(const char *data, size_t len) qui attend une requête en lecture (en utilisant wait_for_request) et envoie le contenu de data (avec longueur len). Si une requête en écriture arrive à la place ou si une erreur d'écriture de data a lieu, la fonction renvoie 1, ou 0 si tout s'est bien passé.
  4. Écrire une fonction int get_write_request_parameters(char *data, size_t len) qui attend une requête en écriture (en utilisant wait_for_request) et remplit le contenu de data (avec longueur len) à partir de ce qu'envoie le maître. Si une requête en lecture arrive à la place ou si une erreur de lecture de data a lieu, la fonction renvoie 1, ou 0 si tout s'est bien passé.
  5. Écrire une fonction loop qui, en boucle infinie, attend une transaction en écriture sur l'I²C (en utilisant wait_for_request) puis traite la commande entrante en appelant une fonction handle_command.
  6. Créer un thread dans i2c_init qui a comme point d'entrée la fonction loop.

On notera que les fonctions wait_for_request, answer_read_request, get_write_request, handle_command et loop ne nécessitent pas d'être préfixées par i2c_ ou quelque chose de similaire car elles seront déclarées static et ne seront donc pas exportées lors de l'édition de liens. Il n'y a donc aucun risque de collision avec une fonction de même nom qui viendrait d'une autre unité de compilation.

Programme de test

Un programme de test mbed-test.bin est attaché à cette page. Vous pouvez le récupérer et le flasher sur une carte Discovery IoT node en le copiant sur le disque virtuel présenté par l'interface stlink.

Le programme de test attend l'appui sur le bouton bleu de la carte de test puis teste :

  • la récupération de l'ID de la carte esclave ;
  • les modifications des leds de la carte esclave ;
  • l'utilisation en lecture et écriture du scratchpad de la carte esclave ;
  • l'affichage d'une animation simple sur la matrice de leds de la carte esclave ;
  • la récupération du nombre de clicks sur le bouton bleu de la carte esclave.
Fichier attachéTaille
Binary Data Binaire de test72.28 Ko