TP 7 — Le bus I²C : température, accéléromètre et gyroscope =========================================================== Objectifs --------- - comprendre le bus **I²C** : deux fils, des adresses, des registres ; - trouver les composants présents sur un bus, et vérifier leur identité ; - lire le **capteur de température** de la carte et convertir sa mesure ; - configurer et lire une **centrale inertielle** (accéléromètre et gyroscope). Durée indicative : deux séances de 3 h. Prérequis : :doc:`tp01_gpio`. Ce qu'il faut savoir -------------------- Le bus I²C ~~~~~~~~~~ L'I²C relie un **maître** (le microcontrôleur) à plusieurs **esclaves** (les capteurs) par deux fils seulement : **SCL**, l'horloge, que le maître impose, et **SDA**, les données, dans les deux sens. Les deux lignes sont tirées au +3,3 V par des résistances, et chaque composant ne sait que les mettre à 0 : c'est ce qui permet à plusieurs composants de partager les mêmes fils. .. tikz:: Le bus I²C2 : deux fils partagés, tirés au +3,3 V. \draw (0,0) node[left]{SDA} -- (10.5,0); \draw (0,-0.8) node[left]{SCL} -- (10.5,-0.8); \draw (1,2.2) node[above]{+3,3 V} -- (2.6,2.2); \draw (1,2.2) to[R, l_=R\textsubscript{p}] (1,0) node[circle, fill, inner sep=1.2pt]{}; \draw (2.6,2.2) to[R, l=R\textsubscript{p}] (2.6,-0.8) node[circle, fill, inner sep=1.2pt]{}; \foreach \x/\t in {4.4/{STM32\\[-2pt]\scriptsize maître}, 7/{TMP102\\[-2pt]\scriptsize 0x48}, 9.6/{VL53L5CX\\[-2pt]\scriptsize 0x29 (robot)}} { \node[draw, rounded corners, fill=black!4, align=center, font=\small, minimum width=20mm] (n) at (\x,-2.3) {\t}; \draw ($(n.north)+(-0.3,0)$) -- (\x-0.3,0) node[circle, fill, inner sep=1.2pt]{}; \draw ($(n.north)+(0.3,0)$) -- (\x+0.3,-0.8) node[circle, fill, inner sep=1.2pt]{}; } \node[font=\scriptsize, align=left, anchor=west] at (4.1,1.3) {les résistances tirent les lignes à 1 ;\\chaque composant ne sait que les mettre à 0}; Chaque esclave a une **adresse sur 7 bits**. Une transaction commence par l'adresse : seul le composant qui la reconnaît répond, en **acquittant** (*ACK*). La plupart des capteurs se lisent comme une petite mémoire : on écrit le numéro d'un **registre**, puis on lit un ou plusieurs octets à partir de lui. La HAL fournit les deux opérations : .. code-block:: c HAL_I2C_Mem_Read (&hi2c2, adresse << 1, registre, I2C_MEMADD_SIZE_8BIT, tampon, nombre_d_octets, delai_ms); HAL_I2C_Mem_Write(&hi2c2, adresse << 1, registre, I2C_MEMADD_SIZE_8BIT, tampon, nombre_d_octets, delai_ms); .. tikz:: Une lecture de registre (HAL_I2C_Mem_Read) : écrire le numéro du registre, puis lire. \tikzset{m/.style={draw, fill=blue!10, minimum height=7mm, font=\scriptsize, inner sep=2pt}, s/.style={draw, fill=orange!18, minimum height=7mm, font=\scriptsize, inner sep=2pt}} \node[m, minimum width=6mm] (a) at (0,0) {S}; \node[m, minimum width=24mm, right=0pt of a] (b) {0x48 + W (0x90)}; \node[s, minimum width=6mm, right=0pt of b] (c) {A}; \node[m, minimum width=18mm, right=0pt of c] (d) {registre 0x00}; \node[s, minimum width=6mm, right=0pt of d] (e) {A}; \node[m, minimum width=7mm, right=0pt of e] (f) {Sr}; \node[m, minimum width=24mm, right=0pt of f] (g) {0x48 + R (0x91)}; \node[s, minimum width=6mm, right=0pt of g] (h) {A}; \node[s, minimum width=16mm, right=0pt of h] (i) {octet fort}; \node[m, minimum width=6mm, right=0pt of i] (j) {A}; \node[s, minimum width=16mm, right=0pt of j] (k) {octet faible}; \node[m, minimum width=7mm, right=0pt of k] (l) {NA}; \node[m, minimum width=6mm, right=0pt of l] (n) {P}; \draw[decorate, decoration={brace, amplitude=4pt}] (a.north west) -- node[above=4pt, font=\scriptsize]{écriture : on choisit le registre} (e.north east); \draw[decorate, decoration={brace, amplitude=4pt}] (f.north west) -- node[above=4pt, font=\scriptsize]{lecture : le TMP102 envoie deux octets} (n.north east); \node[m, minimum width=6mm] at (0,-1.1) {}; \node[font=\scriptsize, anchor=west] at (0.4,-1.1) {émis par le STM32 (maître)}; \node[s, minimum width=6mm] at (5,-1.1) {}; \node[font=\scriptsize, anchor=west] at (5.4,-1.1) {émis par le TMP102 (esclave)}; \node[font=\scriptsize, anchor=west] at (0,-1.7) {S/Sr/P : départ, redépart, arrêt ; A : acquittement ; NA : non-acquittement}; .. important:: La HAL attend l'adresse **décalée d'un bit à gauche** : l'adresse 7 bits 0x48 se passe ``0x48 << 1``, soit 0x90. Oublier le décalage est l'erreur la plus fréquente avec l'I²C. Les capteurs de la carte ~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 22 18 20 40 * - Composant - Bus et broches - Adresse 7 bits - Rôle * - **TMP102** (U3) - I2C2 : SCL PB10, SDA PB11 - 0x48 - capteur de température, soudé sur la carte * - **LSM6DSOX** - I2C3 : SCL PA8, SDA PC9 - 0x6A ou 0x6B - centrale inertielle (accéléromètre et gyroscope), sur un petit module relié au connecteur **J9** Sur un robot complet, d'autres composants partagent le bus I2C2, comme le capteur de distance avant. Seul le TMP102 est toujours présent. Étape 1 — Configurer l'I²C2 et chercher les composants ------------------------------------------------------ #. Créez un projet ``tp07_i2c`` comme au TP 1 (STM32H750VBTx, **SYS → Debug : Serial Wire**, horloge par défaut). #. Dans **Connectivity → I2C2**, choisissez **I2C**. Vérifiez que CubeMX a placé SCL et SDA sur **PB10** et **PB11** (sinon, cliquez sur ces broches et choisissez ``I2C2_SCL`` et ``I2C2_SDA``). Gardez la vitesse **Standard Mode** (100 kHz). #. Générez le code. #. Écrivez un **balayage du bus** : pour chaque adresse de 1 à 127, demandez si un composant répond, et gardez la liste de ceux qui répondent : .. Capture d'écran à insérer : CubeMX, I2C2 en mode I2C sur PB10/PB11, Standard Mode. Déposez _static/captures/sans_os/tp07-i2c2.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp07-i2c2.png .. :align: center .. :width: 90% .. code-block:: c if (HAL_I2C_IsDeviceReady(&hi2c2, adresse << 1, 2, 5) == HAL_OK) { /* un composant a acquitté à cette adresse */ } Affichez le nombre de composants trouvés sur les LED de façade, ou lisez la liste dans le débogueur. .. admonition:: Questions 1. Quelles adresses répondent ? Retrouvez-les dans le tableau ci-dessus. 2. Que se passe-t-il si l'on oublie le décalage ``<< 1`` ? .. dropdown:: Solution de l'étape 1 :icon: code :color: secondary .. code-block:: c #define I2C_TIMEOUT_MS 5u volatile uint8_t g_found[16]; /* adresses qui répondent */ volatile uint32_t g_found_count; static void i2c2_scan(void) { g_found_count = 0u; for (uint16_t a = 1u; a < 128u; a++) { if (HAL_I2C_IsDeviceReady(&hi2c2, (uint16_t)(a << 1), 2u, I2C_TIMEOUT_MS) == HAL_OK) { if (g_found_count < 16u) { g_found[g_found_count] = (uint8_t)a; } g_found_count++; } } } appelée une fois dans ``USER CODE BEGIN 2``. Le tableau se lit dans **Live Expressions** du débogueur. **Ce qui circule sur le bus.** Pour chaque adresse, le contrôleur I²C émet une condition de **départ** (SDA descend pendant que SCL est haut), puis l'octet d'adresse : les 7 bits de l'adresse et un 8\ :sup:`e` bit, 0 pour une écriture. Au 9\ :sup:`e` coup d'horloge, il relâche SDA : si un composant reconnaît son adresse, il tire SDA à 0, c'est l'**acquittement** (*ACK*). Sinon, SDA reste à 1, tirée par les résistances de rappel : c'est un *NACK*. Le contrôleur termine par une condition d'**arrêt**. ``HAL_I2C_IsDeviceReady`` fait exactement cela (deux fois, d'après son troisième argument), et renvoie ``HAL_OK`` en cas d'acquittement. **Le délai de 5 ms** évite de rester bloqué si le bus est en défaut (une ligne collée à 0 par exemple). Le balayage complet prend 127 × 2 tentatives × environ 0,1 ms à 100 kHz : quelques dizaines de millisecondes. .. dropdown:: Réponses aux questions 1 et 2 :icon: light-bulb :color: info **1. Les adresses.** Sur une carte seule : **0x48**, le TMP102 (sa broche d'adresse ADD0 est reliée à la masse ; reliée à V+, il répondrait en 0x49). Sur un robot complet, on voit aussi **0x29**, le capteur de distance avant VL53L5CX, branché sur le même bus I2C2. La centrale inertielle, elle, est sur un autre bus (I2C3) : invisible ici. **2. Sans le décalage.** Sur le bus, l'adresse occupe les **7 bits de poids fort** de l'octet, le bit de poids faible indiquant le sens (lecture ou écriture). La HAL attend l'octet tel qu'il part sur le fil, donc l'adresse **déjà décalée** : 0x48 << 1 = 0x90. Sans le décalage, on demande 0x48 lui-même, que le contrôleur envoie comme l'adresse 0x24 : personne ne répond… ou un autre composant, par hasard, et on lit n'importe quoi. C'est l'erreur la plus fréquente avec l'I²C sur STM32 : les fiches techniques donnent l'adresse sur 7 bits, la HAL la veut sur 8. Étape 2 — Vérifier qu'on parle bien au TMP102 --------------------------------------------- Qu'un composant acquitte à l'adresse 0x48 ne prouve pas que ce soit un TMP102. Pour le vérifier, lisez un registre dont la valeur est connue : le **registre de configuration** (numéro 0x01), qui vaut **0x60A0** au démarrage, d'après la fiche technique du TMP102. #. Lisez 2 octets à partir du registre 0x01, et reconstituez la valeur sur 16 bits : le premier octet reçu est l'octet de poids fort. #. Comparez-la à 0x60A0. .. dropdown:: Solution de l'étape 2 :icon: code :color: secondary .. code-block:: c #define TMP102_ADDR (0x48u << 1) #define TMP102_TEMP 0x00u #define TMP102_CONFIG 0x01u /* Lit un registre 16 bits du TMP102, octet de poids fort en premier. */ static int tmp102_read16(uint8_t reg, uint16_t *out) { uint8_t b[2]; if (HAL_I2C_Mem_Read(&hi2c2, TMP102_ADDR, reg, I2C_MEMADD_SIZE_8BIT, b, 2u, I2C_TIMEOUT_MS) != HAL_OK) { return 0; } *out = (uint16_t)(((uint16_t)b[0] << 8) | b[1]); return 1; } puis, dans ``USER CODE BEGIN 2`` : .. code-block:: c uint16_t c; if (tmp102_read16(TMP102_CONFIG, &c)) { g_tmp_config = c; } /* attendu : 0x60A0 */ **HAL_I2C_Mem_Read** enchaîne deux transactions, comme le demande le TMP102 : une **écriture** du numéro de registre (le « pointeur » du composant), puis, après un nouveau départ (*repeated start*), une **lecture** de deux octets. Le TMP102 envoie l'octet de poids fort en premier (*big-endian*) : ``b[0] << 8 | b[1]`` reconstitue la valeur. **Pourquoi 0x60A0 prouve l'identité.** Ce motif précis (12 bits de résolution, mode de comparaison, 4 Hz de conversion) est la valeur **documentée** au démarrage. Un autre composant à la même adresse n'aurait aucune raison de la donner. On parle de **vérification d'identité** : la plupart des capteurs ont un registre fait pour cela (``WHO_AM_I`` pour la centrale, à l'étape 4). Étape 3 — Lire la température ----------------------------- Le registre de température (numéro 0x00) contient une mesure sur **12 bits, cadrée à gauche** dans deux octets, en **complément à deux** (les températures négatives sont des nombres négatifs). Un pas vaut **0,0625 °C**. #. Lisez 2 octets à partir du registre 0x00. #. Reconstituez la mesure : assemblez les deux octets, décalez de 4 bits à droite, puis étendez le signe si le bit 11 est à 1. #. Convertissez en **millièmes de degré**, avec des entiers : un pas vaut 62,5 m°C. #. Lisez la température toutes les secondes. Posez le doigt sur le TMP102 (repère U3, près du microcontrôleur) et regardez-la monter. .. Capture d'écran à insérer : photo de la carte, le TMP102 (U3) repéré. Déposez _static/captures/sans_os/tp07-tmp102-u3.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp07-tmp102-u3.png .. :align: center .. :width: 90% .. admonition:: Questions 3. Quelle température lisez-vous ? Est-ce la température de la pièce ? Pourquoi ? 4. Le doigt fait-il monter la mesure tout de suite ? De combien, et en combien de temps ? 5. Pourquoi faut-il étendre le signe ? Que lirait-on à −10 °C sans le faire ? .. dropdown:: Solution de l'étape 3 :icon: code :color: secondary .. code-block:: c /* 12 bits cadrés à gauche, complément à deux, 62,5 m°C par pas. */ static int tmp102_temp_mC(int32_t *out) { uint16_t raw16; int32_t raw; if (!tmp102_read16(TMP102_TEMP, &raw16)) { return 0; } raw = (int32_t)(raw16 >> 4); /* les 12 bits utiles */ if (raw & 0x800) { raw -= 4096; } /* extension de signe */ *out = (raw * 625) / 10; /* × 62,5 */ return 1; } et dans la boucle : .. code-block:: c int32_t t; if (tmp102_temp_mC(&t)) { g_temp_mC = t; } HAL_Delay(1000); **Les trois opérations, sur un exemple.** À 25 °C, le TMP102 renvoie ``0x19 0x00`` : .. code-block:: text assemblage : 0x1900 = 0001 1001 0000 0000 décalage >>4 : 0x190 = 400 (les 4 bits de droite sont toujours 0) bit 11 : 0 → positif, rien à faire conversion : 400 × 62,5 = 25 000 m°C = 25,000 °C **Le × 625 / 10.** Multiplier par 62,5 sans virgule : on multiplie par 625, puis on divise par 10. Le résultat est exact, car 400 × 625 = 250 000 est divisible par 10 ; dans le cas général, l'erreur d'arrondi est de 0,1 m°C, bien en dessous de la résolution du capteur. .. dropdown:: Réponses aux questions 3 à 5 :icon: light-bulb :color: info **3. La température lue.** Vers **28 °C** au repos sur la carte de référence (28,6 °C à la mise en service), soit plusieurs degrés au-dessus de la pièce. Le TMP102 mesure **sa propre température**, c'est-à-dire celle du circuit imprimé sur lequel il est soudé : ses pattes sont reliées thermiquement au cuivre de la carte, que le microcontrôleur, les régulateurs et les LED réchauffent. C'est une **température de carte**, très utile pour surveiller l'électronique (et piloter un ventilateur, TP 4), pas une mesure de l'air ambiant. **4. Le doigt.** La mesure monte de quelques dixièmes de degré en quelques secondes (+0,5 °C relevé), pas tout de suite : la chaleur doit traverser le boîtier, et le doigt chauffe aussi le cuivre autour, qui en absorbe une partie. C'est l'**inertie thermique** : un capteur de température mesure toujours avec retard. La mesure redescend ensuite lentement quand on retire le doigt. **5. L'extension de signe.** La mesure est un nombre signé sur **12 bits**, rangé dans un ``int32_t`` de 32 bits. Pour un nombre négatif, le bit 11 (le signe sur 12 bits) vaut 1, mais les bits 12 à 31 du ``int32_t`` valent 0 : le processeur voit un grand nombre **positif**. À −10 °C, la mesure vaut −160, codée 0xF60 sur 12 bits : sans extension, on lit 3 936 pas, soit **+246 °C**. Retrancher 4 096 (2\ :sup:`12`) quand le bit 11 est à 1 recopie le signe sur les bits de gauche : 3 936 − 4 096 = −160. Étape 4 — La centrale inertielle -------------------------------- Cette étape demande le module de centrale inertielle branché sur **J9**. #. Activez **I2C3** dans CubeMX, sur **PA8** (SCL) et **PC9** (SDA), en Standard Mode. Régénérez le code. #. **L'adresse.** Selon le câblage de sa broche SA0, le LSM6DSOX répond en 0x6A ou en 0x6B. Essayez les deux avec ``HAL_I2C_IsDeviceReady``. #. **L'identité.** Lisez le registre **WHO_AM_I** (numéro 0x0F) : il doit valoir **0x6C**. #. **La configuration.** Au démarrage, les capteurs sont éteints. Écrivez : .. list-table:: :header-rows: 1 :widths: 25 15 15 45 * - Registre - Numéro - Valeur - Effet * - CTRL3_C - 0x12 - 0x44 - mise à jour des données par blocs (*BDU*), adresse qui s'incrémente à chaque octet lu * - CTRL1_XL - 0x10 - 0x48 - accéléromètre à 104 Hz, pleine échelle ±4 g * - CTRL2_G - 0x11 - 0x40 - gyroscope à 104 Hz, pleine échelle ±250 °/s #. **La lecture.** Lisez 6 octets à partir de **OUTX_L_A** (0x28) pour l'accéléromètre, et 6 octets à partir de **OUTX_L_G** (0x22) pour le gyroscope : trois axes X, Y, Z de 16 bits chacun, **octet de poids faible en premier**, en complément à deux. #. **Les unités.** D'après la fiche technique : **0,122 mg** par pas pour l'accéléromètre à ±4 g, **8,75 m°/s** par pas pour le gyroscope à ±250 °/s. .. admonition:: Questions 6. Carte posée à plat et immobile, que vaut l'accélération sur chaque axe ? Lequel mesure la gravité ? Avec quel signe ? Qu'en déduire sur l'orientation du module ? 7. Immobile, le gyroscope devrait lire zéro. Que lit-il ? Comment corriger ce décalage, appelé *biais* ? 8. Pourquoi le réglage **BDU** est-il important quand on lit les deux octets d'une mesure en deux fois ? .. dropdown:: Solution de l'étape 4 :icon: code :color: secondary .. code-block:: c #define IMU_WHO_AM_I 0x0Fu #define IMU_CTRL1_XL 0x10u #define IMU_CTRL2_G 0x11u #define IMU_CTRL3_C 0x12u #define IMU_OUTX_L_G 0x22u #define IMU_OUTX_L_A 0x28u static int imu_init(void) { static const uint16_t addrs[2] = { 0x6Au << 1, 0x6Bu << 1 }; uint8_t v; g_imu_addr = 0u; for (uint32_t k = 0u; k < 2u; k++) /* SA0 à 0 ou à 1 */ { if (HAL_I2C_IsDeviceReady(&hi2c3, addrs[k], 2u, I2C_TIMEOUT_MS) == HAL_OK) { g_imu_addr = addrs[k]; break; } } if (g_imu_addr == 0u) { return 0; } if (HAL_I2C_Mem_Read(&hi2c3, g_imu_addr, IMU_WHO_AM_I, I2C_MEMADD_SIZE_8BIT, &v, 1u, I2C_TIMEOUT_MS) != HAL_OK) { return 0; } g_who_am_i = v; if (v != 0x6Cu) { return 0; } /* ce n'est pas un LSM6DSOX */ v = 0x44u; /* CTRL3_C : BDU + IF_INC, en premier */ HAL_I2C_Mem_Write(&hi2c3, g_imu_addr, IMU_CTRL3_C, I2C_MEMADD_SIZE_8BIT, &v, 1u, I2C_TIMEOUT_MS); v = 0x48u; /* CTRL1_XL : 104 Hz, ±4 g */ HAL_I2C_Mem_Write(&hi2c3, g_imu_addr, IMU_CTRL1_XL, I2C_MEMADD_SIZE_8BIT, &v, 1u, I2C_TIMEOUT_MS); v = 0x40u; /* CTRL2_G : 104 Hz, ±250 °/s */ HAL_I2C_Mem_Write(&hi2c3, g_imu_addr, IMU_CTRL2_G, I2C_MEMADD_SIZE_8BIT, &v, 1u, I2C_TIMEOUT_MS); return 1; } /* Trois axes de 16 bits, octet de poids faible en premier. */ static int imu_read3(uint8_t reg, int16_t out[3]) { uint8_t b[6]; if (HAL_I2C_Mem_Read(&hi2c3, g_imu_addr, reg, I2C_MEMADD_SIZE_8BIT, b, 6u, I2C_TIMEOUT_MS) != HAL_OK) { return 0; } for (uint32_t k = 0u; k < 3u; k++) { out[k] = (int16_t)(((uint16_t)b[2u * k + 1u] << 8) | b[2u * k]); } return 1; } et dans la boucle, dix fois par seconde : .. code-block:: c int16_t a[3], g[3]; if ((g_imu_addr != 0u) && imu_read3(IMU_OUTX_L_A, a) && imu_read3(IMU_OUTX_L_G, g)) { for (uint32_t k = 0u; k < 3u; k++) { g_acc_mg[k] = ((int32_t)a[k] * 122) / 1000; /* 0,122 mg / pas */ g_gyro_mdps[k] = ((int32_t)g[k] * 875) / 100; /* 8,75 m°/s / pas */ } } HAL_Delay(100); **Décoder 0x48 (CTRL1_XL).** En binaire, ``0100 1000`` : les 4 bits de gauche (``0100``) choisissent la fréquence, 104 Hz ; les deux suivants (``10``) la pleine échelle, ±4 g. La fiche technique du LSM6DSOX donne ces tableaux de bits, registre par registre. **Lire 6 octets d'un coup** marche grâce au bit ``IF_INC`` de CTRL3_C : après chaque octet lu, le capteur passe tout seul au registre suivant (OUTX_L, OUTX_H, OUTY_L…). Une seule transaction I²C pour trois axes : plus rapide, et les trois valeurs viennent du **même** échantillon. **Ici, l'ordre des octets est inversé** par rapport au TMP102 : poids faible en premier (*little-endian*). Chaque composant a sa convention : on la lit dans sa fiche technique, on ne la devine jamais. **Le transtypage en int16_t** fait de lui-même l'extension de signe : les 16 bits assemblés sont interprétés comme un nombre signé, ce qui est exactement le format du capteur. .. dropdown:: Réponses aux questions 6 à 8 :icon: light-bulb :color: info **6. La gravité.** Un axe lit environ **±1 000 mg**, les deux autres presque 0. Un accéléromètre au repos ne mesure pas zéro : il mesure la **réaction du support**, qui l'empêche de tomber, dirigée vers le haut. L'axe qui pointe vers le haut lit +1 g, celui qui pointe vers le bas −1 g. Sur RatBot, le module est **monté à l'envers** : son axe Z pointe vers le bas, et lit environ **−1 000 mg**. C'est ainsi qu'on vérifie le montage d'une centrale, et le firmware du robot (comme son modèle 3D) en tient compte. **7. Le biais.** Immobile, le gyroscope lit quelques dizaines à quelques centaines de m°/s, différents sur chaque axe : c'est son **biais** (*offset*), propre à chaque puce, et qui varie avec la température. Il paraît faible, mais il s'**intègre** : 0,1 °/s de biais donne 6° d'erreur de cap par minute. On le corrige en faisant la **moyenne** des mesures pendant une ou deux secondes, carte immobile, puis en la soustrayant de chaque mesure. Le firmware du robot le fait à chaque démarrage, et le remesure dès que le robot est à l'arrêt. **8. BDU.** Une mesure de 16 bits est dans **deux registres** de 8 bits, lus l'un après l'autre. Le capteur produit 104 échantillons par seconde : si un nouvel échantillon arrive **entre** la lecture de l'octet bas et celle de l'octet haut, on assemble deux moitiés d'échantillons différents. Près d'un passage d'octet (de 0x00FF à 0x0100, par exemple), l'erreur est énorme : 0x01FF, ou 0x0000 — un faux pic. Avec **BDU** (*Block Data Update*), le capteur ne met à jour les deux registres qu'une fois les deux octets lus. Le défaut est rare, donc difficile à trouver : il vaut mieux ne jamais l'avoir. .. dropdown:: Tout le code de la correction :icon: download :color: secondary :download:`main_user_code.c <../../corrections/sans_os/tp07/main_user_code.c>` — compilé sans avertissement contre la HAL du STM32H7. Ce n'est **pas** un ``main.c`` complet : ce sont les extraits à placer dans les blocs ``USER CODE`` du ``main.c`` généré par CubeMX, chacun sous le commentaire qui indique son bloc. La boucle ``while (1)`` contient une variante par étape, encadrée par ``#if 0`` / ``#if 1`` : n'en activer **qu'une à la fois**. La configuration CubeMX attendue est rappelée en tête du fichier, et les réponses aux questions à la fin. Exercices supplémentaires ------------------------- Ces exercices n'ont pas de correction. - **Alerte de température** : le TMP102 a une sortie ``ALERT`` reliée à PE15. Réglez ses seuils (registres 0x02 et 0x03) pour qu'elle s'active au-dessus de 30 °C, et allumez une LED quand elle passe à 0. - **Niveau à bulle** : les quatre LED indiquent de quel côté la carte penche, d'après l'accéléromètre. - **Détecteur de choc** : une LED s'allume (et le buzzer sonne) quand l'accélération dépasse 2 g sur un axe horizontal. - **Cap** : intégrez la vitesse de rotation du gyroscope autour de l'axe vertical pour obtenir un angle, et observez sa dérive en quelques minutes. Après le TP ----------- Pour rendre la carte au robot, voir :ref:`etat-ratbot`.