Examen pratique SE203/4SE03/4SE07
Merci de lire attentivement l'ensemble de ces consignes avant de commencer.
Modalités
- Durée : 3 heures
- Documents autorisés : tout ce qui se trouve sur le site pédagogique, les pages de manuel (
man
), un dictionnaire bilingue. Tout autre document, papier ou numérique, est interdit - Cet examen est strictement individuel. Toute communication avec une autre personne est rigoureusement interdite. De même l'usage d'outils de génération de code (IA ou non) est interdit.
Organisation
Cet examen est découpé en deux parties indépendantes :
- Une partie (5 points) consiste à déboguer du code
- Une autre partie (15 points) consiste à développer du code pour utiliser l'accéléromètre de la carte de TP
Squelette de code
❎ Un squelette de code vous est fourni, vous devez le récupérer ici (vous pouvez utiliser la commande tar xJf src.tar.xz
pour extraire l'archive).
Ce code implémente une grande partie du TD de l'UE (initialisation de l'UART, de la matrice, des timers, des horloges, des interruptions, etc.).
Les deux parties de l'examen se basent sur ce squelette de code.
Rendu de l'examen
Pour rendre votre production à la fin de l'examen :
- Nettoyez le répertoire où se trouve le code que l'on vous a donné et dans lequel vous avez apporté des modifications (il ne doit plus y avoir de fichiers .o, .d, de fichiers ELF...)
- Créez une archive tar.xz du contenu du répertoire (depuis le répertoire parent) :
$ tar cJf rendu.tar.xz src/
- Envoyez cette archive via l'interface de rendu accessible ici (attention à ne pas soumettre l'archive initiale mais bien celle contenant vos modifications).
La procédure de rendu peut prendre quelques minutes, ne vous y prenez pas trop tard. Vous pouvez faire plusieurs fois le rendu, chaque rendu écrase le rendu précédent.
Notation / Consignes importantes
Un barème indicatif est proposé dans le texte.
La qualité du code, et en particulier le respect des consignes données durant l'UE (cours, revu de code, etc.), sera particulièrement prise en compte dans la notation.
Important : le code, et en particulier vos ajouts et corrections, doit compiler tout seul en tapant simplement make
. Assurez-vous donc de bien modifier le Makefile
de l'archive pour que ce soit le cas.
Login
❎ Connectez vous maintenant à l'interface de rendu, authentifiez-vous et notez quelque part (dans un fichier texte par exemple) les logins renvoyés par l'application (login court et long). Nous vous demanderons au cours de la séance de vérifier que le login présent sur la feuille d'émargement est bien le même que celui renvoyé par l'interface de rendu.
PATH
N'oubliez pas de définir la variable d'environnement PATH
pour pouvoir accéder aux outils :
$ export PATH=/comelec/softs/bin:$PATH
Partie Debug (5 points)
Le code qui vous a été fourni implémente le mini-projet de l'UE.
Il devrait notamment répondre à la partie 9 UART+IRQ+LED (sauf la toute dernière partie sur la gestion des erreurs de transmission, mais l'examen ne porte pas sur cette partie).
On s'attendrait notamment à pouvoir envoyer une image par le port série à la carte et voir cette image s'afficher.
L'image que l'on souhaite afficher est dans le fichier frame.bin
(situé dans l'archive fournie). Affichée correctement, toutes les LED devraient être éteintes, sauf trois, une en rouge, une en vert et une en bleu (identique à la photo affichée sur la page partie 9 UART+IRQ+LED).
Cependant, ça ne marche pas...
❎ Trouvez les trois erreurs présentes dans le code et qui font que ça ne marche pas (et corrigez-les).
Note : pour rappel, pour pouvoir envoyer l'image à la carte, il faut configurer correctement son terminal en exécutant ./stty.sh
(les paramètres de transmission sont les suivants : 38400 bits/s, 8 bits de données, pas de parité, 1 bit de stop), puis envoyer l'image à l'aide de la commande cat frame.bin > /dev/ttyACM0
.
Quelques informations complémentaires :
- L'image affichée en permanence est celle contenue dans le tableau
image
défini dansmain.c
- Les fonctions liées à l'UART, y compris le gestionnaire d'interruption, sont dans
uart.c
Partie Accéléromètre : utilisation d'un nouveau périphérique (15 points au total)
Objectif
La carte IoT Node est doté d'un module intertiel (accéléromètre 3 axes et gyroscope 3 axe) de modèle LSM6DSL. Nous n'utiliserons que la partie accéléromètre.
Grâce à cet accéléromètre, il est possible de détecter l'orientation dans l'espace de la carte (le principe est expliqué plus loin). Grâce à cette information, nous allons réaliser l'application suivante avec la matrice de LED. À un instant donné, une LED et une seule est allumée. Quand on va incliner la carte, le point allumé va se déplacer progressivement vers le point le plus haut (comme une bulle d'air qui serait emprisonné dans un liquide). Dit autrement, si le bord gauche de la matrice est plus haut que le bord droit (par rapport à l'horizontal, la carte penche vers la droite), le point allumé va progressivement se déplacer vers le bord gauche. De même sur l'autre axe.
Documents
Pour implémenter cette fonctionnalité, vous aurez besoin des documents suivants (les 5 premiers sont les mêmes que ceux utilisés en TP, seul le dernier est nouveau) :
- La datasheet du processeur STM32L475VGT6
- Le manuel de référence du processeur STM32L475VGT6
- Le manuel de programmation des STM32 basés sur un Cortex M4
- Le manuel d'utilisateur de la carte IoT Node
- Les schémas électroniques de la carte IoT Node
- La datasheet du module intertiel LSM6DSL
Barème
Cette partie est découpée en quatre sous-parties :
- I2C : 5 points
- Accéléromètre : 4 points
- Interruptions : 3 points
- Affichage : 3 points
I2C (5 points)
L'I2C est un bus de communication très utilisé dans l'embarqué. Il permet généralement de connecter des micro-contrôleurs à des périphériques simples (flash, capteurs...).
Physiquement le bus I2C est composé de deux fils : un fil de données (SDA
) et un fil d'horloge (SCL
). Il s'agit d'un protocole série synchrone, les données étant transmises en série (chaque bit l'un après l'autre) sur le fil de données, au rythme de l'horloge SCL
.
Les entités connectés sur le bus I2C peuvent se comporter en maître ou en esclave. Les transferts sont initiés uniquement par un maître, vers une autre entité qui se comportera en esclave. L'horloge est généré par le périphérique maître.
Chaque périphérique esclave a une adresse sur 7 bits (dans la version d'I2C qui nous intéresse).
Au niveau le plus bas, I2C permet à un maître d'envoyer un ou plusieurs octets vers un esclave (écriture) ou de lire un ou plusieurs octets depuis un esclave (lecture).
Écriture
Maître : START | ADRESSE (7 bits) / 0 (Write) | | DATA (8 bits) | | DATA (8 bits) | | STOP
Esclave : | | ACK | | ACK | | ACK |
Lecture
Maître : START | ADRESSE (7 bits) / 1 (Read) | | ACK | | NACK | STOP
Esclave : | | DATA (8 b) | | DATA (8 b) | |
START
(début d'échange) et STOP
(fin d'échange) sont des conditions particulières du signal SDA
par rapport à l'horloge. Tout le reste (y compris ACK
, acquittement positif et NACK
, acquittement négatif) sont des bits échangés au rythme de l'horloge.
Restart
Il est possible, et cela sera nécessaire pour parler à l'accéléromètre, d'enchaîner deux échanges, en particulier une écriture et une lecture en supprimant le STOP
à la fin du premier échange, le deuxième START
étant appelé dans ce cas dans les documents un RESTART
.
Travail à faire
Dans un fichier approprié, vous allez écrire les fonctions de base pour manipuler le bus I2C et faire des échanges simples qui nous permettrons par la suite de communiquer avec l'accéléromètre.
Dans notre cas, le micro-contrôleur sera maître sur le bus I2C et nous utiliserons des adresses I2C de 7 bits.
Identification de l'I2C et des IO nécessaires
❎ Identifiez, à partir des documents fournis, le bus I2C (il y en a plusieurs) et les pins auxquels l'accéléromètre LSM6DSL est relié. Indiquez vos réponses en commentaires en début du fichier dans lequel vous allez mettre les fonctions ci-dessous.
Indice : partez de la documentation de la carte, puis des schémas électroniques.
Initialisation de l'I2C
❎ Écrivez une fonction i2c_init
qui initialise le bus I2C auquel est relié l'accéléromètre. Cette fonction doit notamment :
- Configurer correctement les pins nécessaires (
SDA
etSCL
) pour que le micro-contrôleur et l'accéléromètre puissent dialoguer en I2C - Configurer l'horloge du contrôleur I2C (plusieurs sources d'horloge sont possibles, cf. registre
RCC_CCIPR
, on choisira l'horlogePCLK
) - Initialiser le contrôleur I2C en suivant la procédure décrite figure 393 (p. 1272 du manuel de référence)
- Les champs
NOSTRETCH
,ANFOFF
etDNF
peuvent rester dans leurs valeurs par défaut après reset - Concernant les timings I2C, on calculera les différentes valeurs demandées du registre
I2C_TIMINGR
à partir des données suivantes :- L'horloge du contrôleur I2C
I2CCLK
estPCLK
qui est à 80 MHz (soit une période de 12,5 ns) - Choisissez une valeur de
PRESC
intelligente pour avoir une périodet_PRESC
facile pour les générer les timings suivants t_SCLL
= 5 µst_SCLH
= 5 µst_SCLDEL
= 1 µst_SDADEL
= 500 ns- Ces valeurs sont conformes à ce qu'attend l'accéléromètre et donnent une fréquence d'horloge I2C (
SCL
) de l'ordre de 100 kHz.
- L'horloge du contrôleur I2C
- Les champs
Transferts de données
❎ Écrivez une fonction void i2c_write(uint8_t saddr, const uint8_t *data, uint8_t num, uint8_t stop)
Cette fonction prend les arguments suivants :
saddr
: adresse du périphérique esclave I2C concerné par le transfert (7 bits)data
: pointeur vers les données à transmettre au périphériquenum
: nombre d'octets à transmettre (num
<= 255)stop
: si 0, ne pas envoyer la conditionSTOP
à la fin du transfert des données
Cette fonction envoie (écriture) num
octets (maximum 255) situés à l'adresse data
vers le périphérique I2C dont l'adresse est saddr
. Si stop
vaut 0, la condition STOP
n'est pas transmise permettant de générer un RESTART
lors d'un transfert suivant immédiatement.
Indice : on pourra s'inspirer du texte page 1288 et de la figure 407 page suivante. La branche de gauche du schéma (NACKF
) ne vous intéresse pas.
Note : on regardera attentivement la description du champ SADD
.
❎ Écrivez une fonction void i2c_read(uint8_t saddr, uint8_t *data, uint8_t num, uint8_t stop)
Cette fonction prend les arguments suivants :
saddr
: adresse du périphérique esclave I2C concerné par le transfert (7 bits)data
: pointeur vers l'adresse où stocker les données reçuesnum
: nombre d'octets à recevoir (num
<= 255)stop
: si 0, ne pas envoyer la conditionSTOP
à la fin du transfert des données
Cette fonction reçoit (lecture) num
octets (maximum 255) et les place à l'adresse data
depuis le périphérique I2C dont l'adresse est saddr
. Si stop
vaut 0, la condition STOP
n'est pas transmise permettant de générer un RESTART
lors d'un transfert suivant immédiatement.
Indice : on pourra s'inspirer du texte de la page 1292 et de la figure 410 page suivante.
Accéléromètre (4 points)
Protocole d'échange
Le protocole I2C ne permet que d'échanger des octets entre un maître et un esclave identifié par une adresse.
Pour permettre d'accéder à ses différentes fonctionnalités, l'accéléromètre dispose d'un protocole de communication construit au dessus du protocole basic I2C. Ce protocole est expliqué dans la section 6.3.1 pages 38 et 39 de la datasheet du composant.
L'accéléromètre est un esclave I2C situé à l'adresse 0b1101010
(la pin SA0
étant connectée à la masse).
Au sein de l'accéléromètre, il y a des registres, identifiés par une adresse sur 8 bits. Le protocole de communication permet au maître de lire ou d'écrire dans un de ces registres (ou éventuellement dans plusieurs en même temps).
La table 14 explique comment écrire dans le registre d'adresse SUB
la données DATA
. On notera qu'il s'agit là d'une transaction d'écriture I2C classique de deux octets (SUB
puis DATA
) terminée par une condition STOP
classique.
La table 16 explique comment lire depuis le registre d'adresse SUB
. La données lue est DATA
. On notera ici qu'il s'agit de deux échanges I2C, le premier d'écriture d'un octet (SUB
), sans STOP
suivi d'une lecture d'un octet.
Travail à faire
Dans un fichier approprié, vous allez écrire les fonctions nécessaires pour manipuler l'accéléromètre.
Protocole
Pour les deux fonctions suivantes, vous utiliserez notamment les fonctions I2C que vous avez déjà écrites précédemment.
❎ Écrivez une fonction void accel_write(uint8_t reg, uint8_t value)
Cette fonction prend les arguments suivants :
reg
: adresse du registre au sein de l'accéléromètrevalue
: valeur à écrire dans le registre
Cette fonction écrit la valeur value
dans le registre reg
de l'accéléromètre.
❎ Écrivez une fonction uint8_t accel_read(uint8_t reg)
Cette fonction prend l'argument suivant :
reg
: adresse du registre au sein de l'accéléromètre
Cette fonction retourne la valeur du registre reg
de l'accéléromètre.
Initialisation de base de l'accéléromètre
❎ Écrivez une fonction void accel_init()
Cette fonction :
- Appelle la fonction
i2c_init
pour initialiser le bus I2C utilisé par l'accéléromètre - Effectue un reset logiciel de l'accéléromètre (en utilisant le registre
CTRL3_C
de l'accéléromètre, cf. page 62 de la datasheet de l'accéléromètre) - Lit la valeur du registre
WHO_AM_I
de l'accéléromètre et compare la valeur lue à la valeur attendue. Si cette valeur n'est pas correcte, rentrez dans une boucle infinie (pour vous permettre de détecter que vous n'arrivez pas à communiquer avec l'accéléromètre)
Activation de l'accéléromètre
❎ Complétez votre fonction accel_init
pour activer l'accéléromètre (i.e. faire en sorte qu'il mesure l'accélération sur les trois axes régulièrement).
Dans notre cas, nous n'avons pas besoin d'effectuer des mesures trop souvent. Vous configurerez l'accéléromètre avec la fréquence d'échantillonnage la plus basse (1,6 Hz), cf. registres CTRL1_XL
et CTRL6_C
.
Lecture d'un échantillon
Un échantillon est composé de la mesure de l'accélération sur 3 axes : X, Y, Z. L'accélération sur chaque axe est codé sur 16 bits en complément à 2 (signé).
❎ Définissez une structure accel_value_s
permettant de stocker l'accélération mesurée sur chacun des trois axes.
❎ Écrivez une fonction void accel_value_read(accel_value_s *val)
qui attend qu'un échantillon soit disponible puis récupère l'accélération sur les 3 axes depuis l'accéléromètre et stocke les valeurs lues dans la structure pointée par val
. Indice : regardez le registre STATUS_REG
ainsi que OUTX_L_XL
et les suivants.
❎ Faites quelques tests et regardez les valeurs que vous obtenez en fonction des différentes orientations de la carte de TP.
Accéléromètre et Interruptions (3 points)
Plutôt que d'interroger régulièrement l'accéléromètre pour voir si un nouvel échantillon est disponible (polling), nous allons configurer les interruptions pour être prévenu automatiquement.
L'accéléromètre dispose de deux pins d'interruption INT1
et INT2
. Seule la première (INT1
) est connectée au micro-contrôleur.
Activation de la source d'interruption
❎ Complétez votre fonction accel_init
pour faire en sorte que la pin INT1
de l'accéléromètre passe à 1 lorsque de nouvelles données sont disponibles en provenance de l'accéléromètre. Indice : registre INT1_CTRL
.
Détection de l'interruption par le micro-contrôleur
❎ Complétez votre fonction accel_init
pour faire en sorte qu'un passage à 1 de la pin INT1
de l'accéléromètre déclenche une interruption au niveau du micro-contrôleur.
On s'aidera de la page Génération d'une interruption par l'appui du bouton B2, la procédure étant ici très similaire (attention néanmoins la pin étant différente).
Réaction à l'interruption
❎ Dans la fonction réagissant à l'interruption, appelez votre fonction accel_value_read
pour récupérer le nouvel échantillon.
Affichage (3 points)
Tout est maintenant en place pour réaliser l'application désirée.
Le comportement voulu est le suivant :
- Au démarrage de l'application, une seule LED, proche du milieu de la matrice est allumée.
- À chaque mesure d'accélération en provenance de l'accéléromètre (fréquence de 1,4 Hz), sur chacun des deux axes du plan de la matrice, si la carte est suffisamment loin de l'horizontal sur cet axe, la LED se déplace d'une position sur cet axe dans la direction la plus haute (comme une bulle d'air).
L'accéléromètre permet de détecter l'orientation de la carte. En effet, plus que la simple accélération (qui devrait être nulle si l'accéléromètre ne bouge pas), il mesure les forces exercées par une masse sur les trois axes. Au repos cette masse exerce une force liée à la gravité. Au repos, on peut donc mesurer l'orientation de la carte en regardant la valeur sur les trois axes.
❎ Implémentez le comportement attendu