This commit is contained in:
2026-10-16 17:00:37 +02:00
commit 38e1e404fc
16 changed files with 15801 additions and 0 deletions

316
td.md Normal file
View File

@@ -0,0 +1,316 @@
## TD 1 : programmation pour Raspberry Pi Pico
### API du Pico SDK
```cpp
// Initialise un GPIO afin de pouvoir l'utiliser comme entrée ou sortie.
// pin : numéro du GPIO (ex. 15 pour GPIO15).
gpio_init(pin);
// Configure la direction d'un GPIO.
// GPIO_IN : le programme lit l'état électrique de la broche.
gpio_set_dir(pin, GPIO_IN);
// GPIO_OUT : le programme impose l'état électrique de la broche.
gpio_set_dir(pin, GPIO_OUT);
// Impose un niveau logique sur un GPIO configuré en sortie.
// value : 0/false = niveau bas ; 1/true = niveau haut.
// Ex. : allumer/éteindre une LED.
gpio_put(pin, value);
// Lit le niveau logique présent sur un GPIO configuré en entrée.
// Retourne false (niveau bas) ou true (niveau haut).
// Ex. : lire l'état d'un bouton ou d'un capteur numérique.
bool value = gpio_get(pin);
// Active une résistance interne reliant faiblement le GPIO au 3.3 V.
// Permet notamment de fixer l'état d'une entrée lorsqu'aucun composant
// externe ne lui impose de niveau.
gpio_pull_up(pin);
// Même principe, mais la résistance interne relie le GPIO à GND.
gpio_pull_down(pin);
// Suspend l'exécution du programme pendant une durée donnée.
// milliseconds : durée en millisecondes.
// Ex. : sleep_ms(1000) attend environ une seconde.
sleep_ms(milliseconds);
// Initialise les entrées/sorties standard utilisées notamment par printf().
// À appeler une fois au démarrage si le programme utilise printf().
stdio_init_all();
// Affiche du texte et des valeurs sur la sortie standard.
// %d est remplacé ici par la valeur entière passée en argument.
// Ex. : observer la valeur renvoyée par un capteur.
printf("value = %d\n", value);
// Fixe le niveau PWM produit sur un GPIO.
// value est compris entre 0 et la valeur passée à pwm_set_wrap().
// 0 = toujours bas ; max_value = presque/toujours haut selon la configuration.
// Les valeurs intermédiaires font varier le rapport cyclique.
// Ex. : contrôler la luminosité d'une LED ou la vitesse d'un moteur.
pwm_set_gpio_level(pin, value);
```
---
### Exercice 1 : Branchements
![](./images/Robot_Schema_unwired_breadboard.png)
### Exercice 2 : Hello GPIO
Une LED est reliée au GPIO 15 via une résistance.
1. Configurer GPIO 15 comme sortie numérique.
2. Allumer la LED pendant 1 seconde.
3. Éteindre la LED pendant 1 seconde.
4. Répéter indéfiniment.
5. Modifier le programme pour obtenir une fréquence de clignotement de 5 Hz avec un rapport cyclique de 50 %.
6. Modifier ensuite le clignotement pour obtenir 100 ms allumé et 900 ms éteint.
7. Écrire une fonction, qui allume puis éteint une LED, chaque phase durant `duration_ms` millisecondes, telle que :
```cpp
void blink(uint pin, uint duration_ms);
```
---
### Exercice 3 : Lire un bouton
Un bouton relie GPIO 14 à GND lorsqu'il est appuyé.
1. Configurer GPIO 14 comme entrée.
2. Activer sa résistance interne de pull-up.
3. Lire son état toutes les 100 ms.
4. Afficher l'état brut sur le terminal série sous la forme :
```text
button = 1
button = 1
button = 0
...
```
5. Ajouter une variable booléenne `pressed` qui vaut `true` lorsque le bouton est physiquement appuyé.
Questions :
- Pourquoi utilise-t-on ici une résistance de pull-up ?
- Quelle valeur logique est lue lorsque le bouton est relâché ?
- Quelle valeur logique est lue lorsqu'il est appuyé ?
- Pourquoi `pressed` peut-il être l'inverse de la valeur retournée par `gpio_get()` ?
---
### Exercice 4 : Utilisation des moteurs
Le robot possède quatre moteurs, regroupés deux par deux : les deux moteurs de gauche sont commandés ensemble, de même que les deux moteurs de droite. Chaque groupe est piloté par un pont en H à l'aide de deux broches du GPIO.
Pour un groupe de moteurs, la vitesse est comprise entre \([-255, 255]\) :
- le signe indique le sens de rotation ;
- la valeur absolue indique la puissance appliquée par PWM ;
- 0 correspond à l'arrêt.
1. Écrire la fonction `void controleMoteurs(int vitesseGauche, int vitesseDroite)` telle que :
```cpp
controleMoteurs(255, 255); // avancer à pleine puissance
controleMoteurs(128, 128); // avancer à environ 50 %
controleMoteurs(-255, -255); // reculer à pleine puissance
controleMoteurs(0, 0); // arrêt
```
On utilisera les constantes suivantes :
```cpp
#define LEFT_MOTOR_PIN_A ...
#define LEFT_MOTOR_PIN_B ...
#define RIGHT_MOTOR_PIN_A ...
#define RIGHT_MOTOR_PIN_B ...
```
Pour chaque groupe de moteurs :
- si la vitesse est positive, appliquer le PWM sur la broche A et mettre B à 0 ;
- si la vitesse est négative, mettre A à 0 et appliquer le PWM sur B ;
- si la vitesse est nulle, mettre les deux broches à 0.
2. Qu'est-ce que produit l'appel suivant ?
```cpp
controleMoteurs(200, -200);
```
---
### Exercice 5 : Missions
À partir de l'API de commandes décrite dans le cours :
1. Écrire un programme qui fait avancer le robot pendant 1 seconde, puis reculer le robot pendant 1 seconde.
2. Écrire un programme qui fait tourner le robot de 90° vers la gauche.
## TD 2 : comportement autonome avec un contrôleur PID
Équation d'un contrôleur PID en temps continu :
\[
u(t) = K_p \cdot e(t) + K_i \cdot \int_0^t e(\tau) \, d\tau + K_d \cdot \frac{de(t)}{dt}
\]
Et en version discrète, pour une implantation numérique :
\[
u[n] = K_p \cdot e[n] + K_i \cdot \sum_{k=0}^{n} e[k] + K_d \cdot (e[n] - e[n-1])
\]
Expliquer le rôle de chacune des composantes de la fonction discrète du PID, sachant que :
- \(u[n]\) : commande envoyée au système au pas de temps \(n\)
- \(e[n]\) : erreur mesurée au pas de temps \(n\), avec \(e[n] = distance_{cible} - distance_{mesurée}\)
Pour simplifier :
- erreur proportionnelle \(e_p\) : distance voulue − distance mesurée
- erreur intégrale \(e_i\) : somme des \(e_p\) calculées depuis le début
- erreur dérivée \(e_d\) : \(e_{p}[n] - e_{p}[n-1]\)
**Remarque.** La période d'échantillonnage \(\Delta t\) est fixe. Elle est intégrée aux gains : \(K_i\) contient le facteur \(\Delta t\) de l'intégrale, \(K_d\) contient le facteur \(1 / \Delta t\) de la dérivée.
---
### Exercice 1 : composante proportionnelle (P)
*Comment le terme proportionnel influence-t-il la réaction du robot ?*
> Un robot doit maintenir une distance de 10 cm avec un mur lorsqu'il le longe.
> Sa distance actuelle au mur est de 6 cm.
1. Que signifie \(e[0]\) ? Calculez sa valeur.
2. Si le gain proportionnel \(K_p = 0.5\), quelle est la correction appliquée ?
3. Que se passe-t-il si \(K_p = 2\) ?
---
### Exercice 2 : composante intégrale (I)
*Comment l'intégrale corrige-t-elle les erreurs persistantes ?*
> Le robot reste trop près du mur pendant plusieurs pas de temps.
> Aux trois premiers pas de temps : \(e[0] = 4, e[1] = 4, e[2] = 4\).
> On garde \(K_p = 0.5\) et on prend un gain intégral \(K_i = 0.1\).
1. Calculer la commande totale \(u[2]\) après 3 mesures (composantes P et I).
2. Quelle est la part due à l'intégrale ?
3. Que se passe-t-il si \(K_i\) est trop grand ?
---
### Exercice 3 : composante dérivée (D)
*Comment la dérivée anticipe-t-elle les changements rapides ?*
> Le robot s'éloigne du mur. On poursuit l'exercice 2 :
> \(e[2] = 4\), puis \(e[3] = 1\).
> On garde \(K_p = 0.5\) et \(K_i = 0.1\), et on prend un gain dérivé \(K_d = 0.2\).
1. Calculer la variation d'erreur \(e[3] - e[2]\).
2. Calculer la correction due à la dérivée.
3. Quelle est la commande totale \(u[3]\) ?
---
### Exercice 4 : pertinence des composantes
*Comment diagnostiquer un mauvais réglage du contrôleur ?*
> - Situation A : le robot oscille autour de la distance cible.
> - Situation B : le robot corrige trop lentement sa trajectoire.
> - Situation C : le robot dépasse systématiquement la distance cible.
1. Que dire de \(K_p\), \(K_i\) et \(K_d\) dans chacune de ces trois situations ?
2. Comment peut-on corriger ces dysfonctionnements ?
## TD 3 : distribution du calcul et étude des compromis
Le robot embarque un Raspberry Pi Pico WH :
- microcontrôleur RP2040 : deux cœurs Cortex-M0+, sans unité de calcul flottant ;
- 264 KB de SRAM, 2 MB de mémoire flash ;
- puce radio CYW43439 : Wi-Fi 802.11n dans la bande 2,4 GHz.
Le robot crée son propre point d'accès Wi-Fi. Le téléphone s'y connecte, lit la télémétrie par `GET /api/lines` et envoie les commandes par `POST /api/command`.
---
### Exercice 1 : temps de calcul et fréquence de lecture
> Le capteur VL53L1X mesure en continu, indépendamment du processeur. Il produit au plus une mesure toutes les 20 ms (**50 Hz**).
>
> - Pico WH cadencé à 125 MHz
> - Lecture d'un résultat sur le bus I2C : \~0,5 ms
> - Calcul d'un PID en `float` : \~20 µs
1. La boucle de contrôle attend chaque nouvelle mesure, la lit, puis calcule le PID. Quelle est la fréquence maximale de cette boucle ? Quel élément la limite ?
2. Quelle part du temps processeur la lecture et le calcul occupent-ils à cette fréquence ? Citer d'autres tâches du robot qui utilisent le reste.
3. Que se passe-t-il si l'on fait tourner la boucle à 100 Hz ?
4. Proposez une fréquence raisonnable pour un robot roulant à 0.5 m/s.
---
### Exercice 2 : occupation mémoire
> On veut stocker un historique de 100 erreurs pour calculer un PID discret.
>
> - Pico WH : 264 KB de SRAM
> - Chaque erreur est un `float` (4 octets)
1. Quelle quantité de mémoire est utilisée pour stocker 100 erreurs ? Quelle part de la SRAM cela représente-t-il ?
2. Quelles valeurs le PID discret du TD 2 doit-il réellement conserver d'un pas de temps au suivant ? Combien d'octets faut-il ?
3. On veut garder sur le robot un historique de la télémétrie pour le tracer sur le téléphone. Un échantillon contient `ms` (`uint32_t`), `front_mm`, `side_mm`, `left` et `right` (`int16_t` chacun). À 50 Hz, combien d'octets par seconde faut-il ? Combien de secondes tiennent dans un tampon de 32 KB ?
4. Proposer une solution pour réduire cette empreinte mémoire sans perdre l'information utile.
---
### Exercice 3 : énergie et efficacité
> Un robot utilise un PID mal réglé qui provoque des oscillations.
>
> - Les moteurs consomment 100 mA en ligne droite
> - En cas d'oscillations : consommation moyenne = 150 mA
> - Batterie des moteurs = 1000 mAh
1. Quelle est l'autonomie des moteurs avec et sans oscillations ?
2. Quel est le gain en autonomie si le PID est bien réglé ?
3. Quelle stratégie PID permet de réduire les oscillations ?
---
### Exercice 4 : précision et bruit de mesure
> Le robot est immobile face à un mur situé à 200 mm. Le VL53L1X renvoie des distances entières en millimètres.
>
> - Mesures successives : 198, 202, 195, 205, 200
> - Distance cible du PID : 200 mm
1. Calculez l'erreur moyenne et l'écart-type des mesures.
2. Calculez les termes \(e[n] - e[n-1]\) du PID pour cette série. Quelle est l'influence du bruit sur le terme dérivé ?
3. Proposez une méthode pour filtrer les mesures avant le calcul du PID. Quel est son coût en latence à 50 Hz ?
---
### Exercice 5 : latence dans une architecture distribuée
> Pour alléger le Pico, on propose de calculer le PID sur le téléphone, dans `robot.js`. Le robot envoie ses mesures dans la télémétrie ; le téléphone calcule la commande et la renvoie par Wi-Fi.
>
> - Capteur VL53L1X : une mesure toutes les 20 ms
> - Télémétrie : une ligne toutes les 100 ms (`{"telemetry":100}`)
> - Le téléphone lit les lignes en attente par `GET /api/lines` toutes les 100 ms
> - Trajet d'une requête ou d'une réponse HTTP sur le Wi-Fi : \~5 ms quand le canal est libre, \~50 ms quand il est encombré
> - Calcul du PID dans le navigateur : \~1 ms
> - Vitesse du robot : 0.5 m/s
1. Quelle est la latence entre la mesure et l'application de la commande, dans le meilleur et dans le pire cas, quand le canal est libre ?
2. Même question quand le canal est encombré.
3. Quelle distance le robot parcourt-il pendant ces latences ?
4. Est-ce acceptable pour un robot devant éviter des obstacles à 20 cm ?
5. Une salle de TP compte une douzaine de robots, chacun avec son point d'accès 2,4 GHz. Combien de canaux de 20 MHz ne se chevauchent pas dans cette bande ? Quel est l'effet sur la latence ?
6. Proposez une stratégie pour réduire l'impact de cette latence (*e.g.* prédiction, calcul local, hybridation).