317 lines
12 KiB
Markdown
317 lines
12 KiB
Markdown
## 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
|
||
|
||

|
||
|
||
### 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).
|