Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379cd03bc5 | ||
|
|
f4a80f4d5b | ||
|
|
372c5a3e1e | ||
|
|
499d11402b | ||
|
|
30d0e356a6 | ||
|
|
d933e348c0 | ||
|
|
85852fcc2f | ||
|
|
ff1628efeb | ||
|
|
8012ce98b3 | ||
|
|
343a557332 |
@@ -18,3 +18,15 @@ build/
|
||||
.env
|
||||
secrets.h
|
||||
*.key
|
||||
|
||||
# Environnement Python
|
||||
.venv/
|
||||
__pycache__/
|
||||
|
||||
/.quarto/
|
||||
**/*.quarto_ipynb
|
||||
|
||||
# Sorties Quarto (PDF générés)
|
||||
_output/
|
||||
.quarto/
|
||||
modules/*.typ
|
||||
|
||||
@@ -1,20 +1,63 @@
|
||||
# Formation Arduino (ESP32)
|
||||
# Formation Arduino (ESP32-S3)
|
||||
|
||||
Support de formation en 13 modules (00 à 12) plus une synthèse du matériel, au format OpenDocument (.odt).
|
||||
Support de formation en 13 modules (00 à 12) plus une synthèse du matériel.
|
||||
Les sources sont en Markdown [Quarto](https://quarto.org) (`modules/*.qmd`) et produisent des PDF.
|
||||
|
||||
| Fichier | Sujet |
|
||||
|---|---|
|
||||
| Module_00_Installation | Installation de l'environnement |
|
||||
| Module_01_Architecture | Architecture |
|
||||
| Module_02_GPIO_NeoPixel | GPIO et NeoPixel |
|
||||
| Module_03_PWM_ADC | PWM et ADC |
|
||||
| Module_04_Serie_Debug | Liaison série et débogage |
|
||||
| Module_05_I2C_SPI | I2C et SPI |
|
||||
| Module_06_Interruptions_Timers | Interruptions et timers |
|
||||
| Module_07_WiFi | WiFi |
|
||||
| Module_08_ServeurWeb | Serveur web |
|
||||
| Module_09_BLE | Bluetooth Low Energy |
|
||||
| Module_10_Energie | Gestion de l'énergie |
|
||||
| Module_11_MQTT | MQTT |
|
||||
| Module_12_ProjetFinal | Projet final |
|
||||
| Synthese_Formation_Materiel | Synthèse du matériel |
|
||||
| `00-installation` | Installation de l'environnement |
|
||||
| `01-architecture` | Découverte du microcontrôleur ESP32-S3 |
|
||||
| `02-gpio-neopixel` | Entrées/sorties numériques et LED NeoPixel |
|
||||
| `03-pwm-adc` | Signaux analogiques : PWM et ADC |
|
||||
| `04-serie-debug` | Communication série et débogage |
|
||||
| `05-i2c-spi` | Bus I2C et SPI |
|
||||
| `06-interruptions-timers` | Interruptions matérielles et timers |
|
||||
| `07-wifi` | Wi-Fi : station, point d'accès, client HTTP |
|
||||
| `08-serveurweb` | Serveur web embarqué |
|
||||
| `09-ble` | Bluetooth Low Energy |
|
||||
| `10-energie` | Gestion de l'énergie |
|
||||
| `11-mqtt` | IoT et MQTT |
|
||||
| `12-projetfinal` | Projet final |
|
||||
| `synthese-materiel` | Synthèse et liste de matériel |
|
||||
|
||||
## Organisation
|
||||
|
||||
```
|
||||
modules/ sources des supports (.qmd)
|
||||
modele/ gabarit PDF (Typst) et thème de coloration du code
|
||||
schemas/ schémas décrits en Python (schemdraw + matplotlib), rendus en svg/ et png/
|
||||
_quarto.yml configuration du projet
|
||||
_output/ PDF générés (non versionnés)
|
||||
```
|
||||
|
||||
## Produire les PDF
|
||||
|
||||
Prérequis : [Quarto](https://quarto.org/docs/get-started/) (inclut Pandoc et Typst).
|
||||
|
||||
```
|
||||
quarto render # tous les supports
|
||||
quarto render modules/03-pwm-adc.qmd # un seul support
|
||||
```
|
||||
|
||||
Les PDF sont créés dans `_output/modules/`.
|
||||
Pour obtenir aussi du Word ou de l'ODT, ajouter `docx:` ou `odt:` sous `format:` dans `_quarto.yml`.
|
||||
|
||||
## Modifier ou ajouter un schéma
|
||||
|
||||
Les images de `schemas/svg/` sont versionnées : il n'est nécessaire de les régénérer que si l'on modifie un schéma.
|
||||
|
||||
```
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r schemas/requirements.txt
|
||||
.venv\Scripts\python schemas\generer.py # ou : generer.py m03
|
||||
```
|
||||
|
||||
Un schéma est une fonction Python déclarée dans `schemas/mXX_*.py` (liste `FIGURES`), puis appelée dans le `.qmd` :
|
||||
|
||||
```markdown
|
||||
{#fig-m03-servo-50hz}
|
||||
```
|
||||
|
||||
## Historique
|
||||
|
||||
Les supports étaient à l'origine au format `.odt` ; ils restent récupérables via le tag `avant-quarto`.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
project:
|
||||
type: default
|
||||
output-dir: _output
|
||||
render:
|
||||
- modules/*.qmd
|
||||
|
||||
lang: fr
|
||||
subtitle: "Formation ESP32-S3 avec Arduino Framework"
|
||||
|
||||
crossref:
|
||||
fig-title: Figure
|
||||
fig-prefix: figure
|
||||
|
||||
format:
|
||||
typst:
|
||||
papersize: a4
|
||||
margin:
|
||||
x: 2cm
|
||||
y: 2.2cm
|
||||
mainfont: "Segoe UI"
|
||||
fontsize: 10pt
|
||||
codefont: "Consolas"
|
||||
toc: false
|
||||
highlight-style: modele/code.theme
|
||||
include-in-header: modele/entete.typ
|
||||
@@ -0,0 +1,217 @@
|
||||
{
|
||||
"custom-styles": {
|
||||
"INI Files": {
|
||||
"Section": {
|
||||
"selected-text-color": "#6f42c1",
|
||||
"text-color": "#6f42c1"
|
||||
}
|
||||
},
|
||||
"Python": {
|
||||
"Builtin Function": {
|
||||
"selected-text-color": "#6f42c1",
|
||||
"text-color": "#6f42c1"
|
||||
},
|
||||
"Import": {
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"Special Variable": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
}
|
||||
},
|
||||
"Rust": {
|
||||
"Attribute": {
|
||||
"selected-text-color": "#24292e",
|
||||
"text-color": "#24292e"
|
||||
},
|
||||
"Macro": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
},
|
||||
"Self": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
},
|
||||
"Trait": {
|
||||
"selected-text-color": "#24292e",
|
||||
"text-color": "#24292e"
|
||||
}
|
||||
},
|
||||
"XML": {
|
||||
"Element": {
|
||||
"selected-text-color": "#22863a",
|
||||
"text-color": "#22863a"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editor-colors": {
|
||||
"BackgroundColor": "#ffffff",
|
||||
"BracketMatching": "#bef5cb",
|
||||
"CodeFolding": "#f6f8fa",
|
||||
"CurrentLine": "#f6f8fa",
|
||||
"CurrentLineNumber": "#24292e",
|
||||
"IconBorder": "#ffffff",
|
||||
"IndentationLine": "#d7dbe0",
|
||||
"LineNumbers": "#c7c2bc",
|
||||
"MarkBookmark": "#8be9fd",
|
||||
"MarkBreakpointActive": "#ff5555",
|
||||
"MarkBreakpointDisabled": "#bd93f9",
|
||||
"MarkBreakpointReached": "#f1fa8c",
|
||||
"MarkError": "#b31d28",
|
||||
"MarkExecution": "#44475a",
|
||||
"MarkWarning": "#e36209",
|
||||
"ModifiedLines": "#d73a49",
|
||||
"ReplaceHighlight": "#50fa7b",
|
||||
"SavedLines": "#28a745",
|
||||
"SearchHighlight": "#ffea7f",
|
||||
"Separator": "#e1e4e8",
|
||||
"SpellChecking": "#ff5555",
|
||||
"TabMarker": "#d1d5da",
|
||||
"TemplateBackground": "#23241e",
|
||||
"TemplateFocusedPlaceholder": "#22231d",
|
||||
"TemplatePlaceholder": "#22231d",
|
||||
"TemplateReadOnlyPlaceholder": "#262721",
|
||||
"TextSelection": "#dee6fc",
|
||||
"WordWrapMarker": "#e1e4e8"
|
||||
},
|
||||
"metadata": {
|
||||
"copyright": [
|
||||
"SPDX-FileCopyrightText: 2020 GitHub Inc.",
|
||||
"SPDX-FileCopyrightText: 2020 Waqar Ahmed <waqar.17a@gmail.com>"
|
||||
],
|
||||
"license": "SPDX-License-Identifier: MIT",
|
||||
"name": "GitHub Light",
|
||||
"revision": 2
|
||||
},
|
||||
"text-styles": {
|
||||
"Alert": {
|
||||
"bold": true,
|
||||
"selected-text-color": "#ff5555",
|
||||
"text-color": "#ff5555"
|
||||
},
|
||||
"Annotation": {
|
||||
"selected-text-color": "#6a737d",
|
||||
"text-color": "#6a737d"
|
||||
},
|
||||
"Attribute": {
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"BaseN": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
},
|
||||
"BuiltIn": {
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"Char": {
|
||||
"selected-text-color": "#032f62",
|
||||
"text-color": "#032f62"
|
||||
},
|
||||
"Comment": {
|
||||
"selected-text-color": "#6a737d",
|
||||
"text-color": "#6a737d"
|
||||
},
|
||||
"CommentVar": {
|
||||
"selected-text-color": "#6a737d",
|
||||
"text-color": "#6a737d"
|
||||
},
|
||||
"Constant": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
},
|
||||
"ControlFlow": {
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"DataType": {
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"DecVal": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
},
|
||||
"Documentation": {
|
||||
"selected-text-color": "#6a737d",
|
||||
"text-color": "#6a737d"
|
||||
},
|
||||
"Error": {
|
||||
"selected-text-color": "#ff5555",
|
||||
"text-color": "#ff5555",
|
||||
"underline": true
|
||||
},
|
||||
"Extension": {
|
||||
"bold": true,
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"Float": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
},
|
||||
"Function": {
|
||||
"selected-text-color": "#6f42c1",
|
||||
"text-color": "#6f42c1"
|
||||
},
|
||||
"Import": {
|
||||
"selected-text-color": "#032f62",
|
||||
"text-color": "#032f62"
|
||||
},
|
||||
"Information": {
|
||||
"selected-text-color": "#6a737d",
|
||||
"text-color": "#6a737d"
|
||||
},
|
||||
"Keyword": {
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"Normal": {
|
||||
"selected-text-color": "#24292e",
|
||||
"text-color": "#24292e"
|
||||
},
|
||||
"Operator": {
|
||||
"selected-text-color": "#24292e",
|
||||
"text-color": "#24292e"
|
||||
},
|
||||
"Others": {
|
||||
"selected-text-color": "#6f42c1",
|
||||
"text-color": "#6f42c1"
|
||||
},
|
||||
"Preprocessor": {
|
||||
"selected-text-color": "#d73a49",
|
||||
"text-color": "#d73a49"
|
||||
},
|
||||
"RegionMarker": {
|
||||
"selected-text-color": "#6a737d",
|
||||
"text-color": "#6a737d"
|
||||
},
|
||||
"SpecialChar": {
|
||||
"selected-text-color": "#005cc5",
|
||||
"text-color": "#005cc5"
|
||||
},
|
||||
"SpecialString": {
|
||||
"selected-text-color": "#032f62",
|
||||
"text-color": "#032f62"
|
||||
},
|
||||
"String": {
|
||||
"selected-text-color": "#032f62",
|
||||
"text-color": "#032f62"
|
||||
},
|
||||
"Variable": {
|
||||
"selected-text-color": "#e36209",
|
||||
"text-color": "#e36209"
|
||||
},
|
||||
"VerbatimString": {
|
||||
"selected-text-color": "#032f62",
|
||||
"text-color": "#032f62"
|
||||
},
|
||||
"Warning": {
|
||||
"selected-text-color": "#ff5555",
|
||||
"text-color": "#ff5555"
|
||||
}
|
||||
},
|
||||
"background-color": "#f6f8fa"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Note : Quarto décale les titres d'un niveau (## -> niveau 1, ### -> niveau 2).
|
||||
// Gabarit commun des supports : couleurs, titres, en-tête et pied de page.
|
||||
#let bleu-formation = rgb("#1a5fb4")
|
||||
#let gris-doux = rgb("#6b7280")
|
||||
|
||||
#set page(
|
||||
header: context {
|
||||
if counter(page).get().first() > 1 [
|
||||
#set text(size: 8pt, fill: gris-doux)
|
||||
Formation ESP32-S3 avec Arduino Framework
|
||||
#h(1fr)
|
||||
#document.title
|
||||
#v(-4pt)
|
||||
#line(length: 100%, stroke: 0.4pt + gris-doux)
|
||||
]
|
||||
},
|
||||
footer: context [
|
||||
#set text(size: 8pt, fill: gris-doux)
|
||||
#h(1fr)
|
||||
#counter(page).display("1 / 1", both: true)
|
||||
],
|
||||
)
|
||||
|
||||
#show heading.where(level: 1): it => block(above: 1.6em, below: 0.8em, width: 100%)[
|
||||
#set text(size: 14pt, weight: "semibold", fill: bleu-formation)
|
||||
#it.body
|
||||
#v(-6pt)
|
||||
#line(length: 100%, stroke: 0.6pt + bleu-formation.lighten(60%))
|
||||
]
|
||||
#show heading.where(level: 2): set text(size: 11.5pt, weight: "semibold", fill: bleu-formation)
|
||||
#show heading.where(level: 2): set block(above: 1.3em, below: 0.6em)
|
||||
|
||||
// Code : le fond et les couleurs viennent du thème modele/code.theme
|
||||
#show raw: set text(size: 8.5pt)
|
||||
|
||||
// Tableaux : en-tête bleu
|
||||
#set table(
|
||||
stroke: 0.5pt + rgb("#d0d7de"),
|
||||
fill: (_, y) => if y == 0 { bleu-formation } else if calc.even(y) { rgb("#f6f8fa") },
|
||||
)
|
||||
#show table.cell.where(y: 0): set text(fill: white, weight: "bold")
|
||||
|
||||
#show figure.caption: set text(size: 8.5pt, style: "italic", fill: gris-doux)
|
||||
|
||||
// Texte des tableaux aligné à gauche (la justification crée des blancs dans les colonnes étroites)
|
||||
#show table: set par(justify: false)
|
||||
|
||||
// Figures : pleine largeur par défaut (une largeur explicite dans le .qmd reste prioritaire)
|
||||
#set image(width: 100%)
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "Module 0 — Prérequis & Installation"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre le rôle et les avantages de l'Arduino IDE pour programmer un ESP32-S3.
|
||||
- Installer et configurer l'environnement de développement.
|
||||
- Identifier et installer les pilotes nécessaires à la reconnaissance de la carte.
|
||||
- Flasher un premier programme (Blink) et valider la chaîne complète (code → compilation → upload → exécution).
|
||||
|
||||
## 1. Matériel utilisé dans cette formation
|
||||
|
||||
Toute la formation s'appuie sur une carte de développement ESP32-S3-WROOM-1 (DevKitC-1 ou équivalent). Points clés à connaître dès maintenant :
|
||||
|
||||
- Microcontrôleur Xtensa LX7 double cœur, cadencé jusqu'à 240 MHz.
|
||||
- Connexion USB native (USB OTG) : pas besoin de convertisseur USB-série externe sur la plupart des DevKits S3.
|
||||
- Une LED RGB adressable (NeoPixel / WS2812) intégrée sur la carte — nous l'utiliserons au Module 2.
|
||||
- Wi-Fi 2,4 GHz + Bluetooth Low Energy (BLE) — attention : pas de Bluetooth Classic sur l'ESP32-S3.
|
||||
- Alimentation 3,3 V logique (contrairement à l'Arduino Uno qui est en 5 V) : ne jamais appliquer plus de 3,3 V sur une broche GPIO.
|
||||
|
||||
::: {.callout-note title="À vérifier avant de commencer"}
|
||||
Le modèle exact de votre carte peut varier (nombre de broches, position de la LED NeoPixel, présence de PSRAM). Repérez sur votre carte la sérigraphie indiquant le numéro de chaque GPIO : elle servira de référence tout au long de la formation.
|
||||
:::
|
||||
|
||||
## 2. Installation de l'Arduino IDE
|
||||
|
||||
### 2.1 Téléchargement
|
||||
|
||||
Rendez-vous sur le site officiel arduino.cc/en/software et téléchargez la version 2.x de l'Arduino IDE (interface plus moderne, gestion de projets multiples, terminal série amélioré) pour votre système d'exploitation (Windows, macOS, Linux).
|
||||
|
||||
### 2.2 Installation des pilotes USB
|
||||
|
||||
Selon la puce USB-série présente sur votre carte (souvent inutile si la carte utilise l'USB natif du S3), il peut être nécessaire d'installer un pilote :
|
||||
|
||||
- CP210x (Silicon Labs) — pour certains DevKits.
|
||||
- CH340/CH343 — pour d'autres clones.
|
||||
|
||||
Sur Windows, le Gestionnaire de périphériques doit afficher un port COM une fois la carte branchée. Sur macOS/Linux, la carte apparaît généralement automatiquement (/dev/tty.usbserial-\* ou /dev/ttyUSB\*/ttyACM\*).
|
||||
|
||||
## 3. Ajout du support ESP32 dans l'Arduino IDE
|
||||
|
||||
Par défaut, l'IDE ne connaît que les cartes Arduino officielles. Il faut ajouter le paquet de définitions Espressif :
|
||||
|
||||
### Étapes
|
||||
|
||||
- Ouvrir Fichier \> Préférences (ou Arduino IDE \> Réglages sur macOS).
|
||||
- Dans « URL de gestionnaire de cartes supplémentaires », ajouter :
|
||||
|
||||
```text
|
||||
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
|
||||
```
|
||||
|
||||
- Ouvrir Outils \> Type de carte \> Gestionnaire de cartes.
|
||||
- Rechercher « esp32 » et installer le paquet « esp32 by Espressif Systems » (dernière version stable).
|
||||
- Redémarrer l'IDE.
|
||||
|
||||
## 4. Sélection et configuration de la carte
|
||||
|
||||
Une fois le paquet installé :
|
||||
|
||||
- Outils \> Type de carte \> ESP32 Arduino \> choisir « ESP32S3 Dev Module ».
|
||||
- Outils \> Port : sélectionner le port série apparu à la connexion de la carte.
|
||||
- Outils \> USB CDC On Boot : Enabled (recommandé pour voir les messages Serial dès le démarrage sur l'ESP32-S3).
|
||||
- Outils \> Upload Speed : 921600 (par défaut, réduire en cas d'échec d'upload).
|
||||
|
||||
::: {.callout-tip title="Astuce mode téléchargement"}
|
||||
Si l'upload échoue avec un message « Failed to connect », maintenez le bouton BOOT appuyé, appuyez brièvement sur RESET, relâchez BOOT, puis relancez le téléversement. Ce geste force la carte en mode bootloader.
|
||||
:::
|
||||
|
||||
{#fig-m00-boot-reset width=90%}
|
||||
|
||||
## 5. Premier programme : Blink
|
||||
|
||||
Nous allons vérifier que toute la chaîne (code → compilation → upload → exécution) fonctionne, en faisant clignoter la LED intégrée.
|
||||
|
||||
{#fig-m00-chaine-developpement}
|
||||
|
||||
```cpp
|
||||
// Sur la plupart des DevKitC ESP32-S3, la LED simple (non RGB) est sur la broche 2.
|
||||
// Vérifiez la sérigraphie de votre carte : elle peut varier.
|
||||
#define LED_PIN 2
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
Serial.println("Demarrage du programme Blink");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(500);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
delay(500);
|
||||
Serial.println("Clignotement...");
|
||||
}
|
||||
```
|
||||
|
||||
Compilez avec le bouton ✓ (Vérifier), puis téléversez avec la flèche → (Téléverser). Ouvrez le Moniteur Série (Outils \> Moniteur Série, vitesse 115200 bauds) pour voir les messages s'afficher.
|
||||
|
||||
## 6. Checklist de fin de module
|
||||
|
||||
- [ ] Arduino IDE installé et à jour.
|
||||
- [ ] Pilotes USB installés si nécessaire, port COM/tty visible.
|
||||
- [ ] Paquet ESP32 (Espressif) installé via le gestionnaire de cartes.
|
||||
- [ ] Carte « ESP32S3 Dev Module » sélectionnée avec le bon port.
|
||||
- [ ] Programme Blink compilé, téléversé et fonctionnel.
|
||||
- [ ] Moniteur série fonctionnel (messages visibles à 115200 bauds).
|
||||
|
||||
## Exercice pratique
|
||||
|
||||
Modifiez le programme Blink pour :
|
||||
|
||||
- Faire clignoter la LED avec un rythme irrégulier (ex : court, court, long — façon morse).
|
||||
- Afficher dans le moniteur série un compteur du nombre de clignotements depuis le démarrage.
|
||||
|
||||
## Pour aller plus loin (facultatif)
|
||||
|
||||
Consultez la documentation officielle Espressif sur le cœur Arduino pour ESP32 : elle liste l'ensemble des cartes supportées et des options spécifiques (partitions, PSRAM, etc.) que nous détaillerons au Module 1.
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: "Module 1 — Découverte du microcontrôleur ESP32-S3"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre ce qu'est un microcontrôleur et en quoi il diffère d'un ordinateur classique.
|
||||
- Connaître l'architecture interne de l'ESP32-S3 (cœurs, mémoire, périphériques).
|
||||
- Identifier les différences clés avec un Arduino Uno/Nano classique.
|
||||
- Repérer les broches spéciales à éviter (strapping pins) et lire un brochage (pinout).
|
||||
|
||||
## 1. Qu'est-ce qu'un microcontrôleur ?
|
||||
|
||||
Un microcontrôleur est un circuit intégré unique regroupant un processeur, de la mémoire vive (RAM), de la mémoire de programme (Flash) et des périphériques d'entrée/sortie (GPIO, ADC, timers, bus de communication). Contrairement à un ordinateur, il n'exécute généralement qu'un seul programme en boucle, sans système d'exploitation complexe, ce qui garantit un comportement déterministe et une consommation réduite.
|
||||
|
||||
L'ESP32-S3 va plus loin qu'un microcontrôleur classique (type ATmega328 de l'Arduino Uno) en intégrant nativement du Wi-Fi et du Bluetooth Low Energy, ce qui en fait un choix privilégié pour les projets connectés (IoT).
|
||||
|
||||
## 2. Architecture interne de l'ESP32-S3
|
||||
|
||||
{#fig-m01-architecture}
|
||||
|
||||
### 2.1 Cœurs de calcul
|
||||
|
||||
- Double cœur Xtensa LX7 cadencé jusqu'à 240 MHz.
|
||||
- Un cœur peut être dédié à des tâches critiques (ex : communication réseau) pendant que l'autre exécute la logique applicative — via FreeRTOS, présent nativement sous le framework Arduino.
|
||||
|
||||
### 2.2 Mémoire
|
||||
|
||||
- SRAM interne : environ 512 Ko, partagée entre données et instructions.
|
||||
- Mémoire Flash externe (souvent 8 ou 16 Mo selon la carte) : stocke le programme (sketch) et éventuellement un système de fichiers (SPIFFS/LittleFS).
|
||||
- PSRAM (RAM externe, optionnelle selon le modèle de carte) : utile pour le traitement d'images ou de gros buffers.
|
||||
|
||||
### 2.3 Connectivité intégrée
|
||||
|
||||
- Wi-Fi 802.11 b/g/n (2,4 GHz).
|
||||
- Bluetooth 5 LE (Low Energy uniquement — pas de Bluetooth Classic/SPP sur le S3, contrairement à l'ESP32 original).
|
||||
|
||||
### 2.4 Interface USB native
|
||||
|
||||
L'ESP32-S3 dispose d'un contrôleur USB OTG natif, permettant la programmation et la communication série directement via USB, sans puce USB-série intermédiaire (selon la carte). Il peut aussi émuler un périphérique USB (clavier, souris, disque) — nous y reviendrons dans un module avancé.
|
||||
|
||||
## 3. ESP32-S3 vs Arduino Uno : les différences essentielles
|
||||
|
||||
| Caractéristique | Arduino Uno (ATmega328) | ESP32-S3 |
|
||||
|-------------------|---------------------------|-------------------------------------|
|
||||
| Cœurs / fréquence | 1 cœur, 16 MHz | 2 cœurs, jusqu'à 240 MHz |
|
||||
| Tension logique | 5 V | 3,3 V |
|
||||
| RAM | 2 Ko | \~512 Ko |
|
||||
| Flash programme | 32 Ko | 8-16 Mo (typique) |
|
||||
| Wi-Fi / Bluetooth | Non (externe requis) | Intégrés (Wi-Fi + BLE) |
|
||||
| Broches ADC | 10 bits, 5 entrées | 12 bits, jusqu'à 20 entrées |
|
||||
| PWM | Limité (Timer matériel) | LEDC — jusqu'à 8 canaux configurables |
|
||||
|
||||
{#fig-m01-uno-vs-s3}
|
||||
|
||||
::: {.callout-warning title="Point de vigilance : tension 3,3 V"}
|
||||
Toutes les broches GPIO de l'ESP32-S3 fonctionnent en logique 3,3 V et ne tolèrent pas 5 V en entrée. Un signal 5 V (capteur, module tiers) doit être abaissé via un diviseur de tension ou un convertisseur de niveau logique avant d'être connecté à une broche.
|
||||
:::
|
||||
|
||||
{#fig-m01-diviseur-tension width=65%}
|
||||
|
||||
## 4. Lire le brochage (pinout) de la carte
|
||||
|
||||
Avant de câbler quoi que ce soit, il est indispensable de repérer sur votre carte :
|
||||
|
||||
- Les broches d'alimentation : 5V (VIN/USB), 3V3, GND.
|
||||
- Les broches GPIO numérotées (GPIO0 à GPIO21 selon les cartes, tous ne sont pas exposés).
|
||||
- Les broches ADC (généralement GPIO1 à GPIO10 sur l'unité ADC1).
|
||||
- La broche pilotant la LED NeoPixel intégrée (souvent GPIO48, à vérifier sur la sérigraphie ou le schéma de votre carte spécifique).
|
||||
|
||||
### Broches à éviter (strapping pins)
|
||||
|
||||
Certaines broches ont un rôle spécial au démarrage (sélection du mode de boot) et peuvent perturber le fonctionnement de la carte si elles sont utilisées comme sorties classiques ou reliées à des composants qui tirent le niveau logique au démarrage :
|
||||
|
||||
- GPIO0 : bouton BOOT — à éviter comme sortie générale.
|
||||
- GPIO3, GPIO45, GPIO46 : broches de strapping — à utiliser avec prudence, éviter en sortie forte au démarrage.
|
||||
- GPIO19/GPIO20 : utilisées par l'USB natif sur de nombreuses cartes S3 — à éviter si vous utilisez la connexion USB.
|
||||
|
||||
::: {.callout-tip title="Bonne pratique"}
|
||||
Gardez toujours sous la main le schéma (pinout diagram) exact de votre référence de carte. Deux cartes vendues comme « ESP32-S3 DevKit » par des fabricants différents peuvent avoir un brochage légèrement différent.
|
||||
:::
|
||||
|
||||
## 5. Exercice pratique : inventaire de votre carte
|
||||
|
||||
Sans câblage, à l'aide de la documentation de votre carte :
|
||||
|
||||
- Dessinez ou annotez un schéma de votre carte en identifiant : alimentation, GND, GPIO utilisables, broches à éviter, broche de la LED NeoPixel.
|
||||
- Écrivez un programme qui affiche dans le moniteur série, au démarrage, la fréquence CPU (`getCpuFrequencyMhz()`), la taille de la mémoire Flash (`ESP.getFlashChipSize()`) et la mémoire heap libre (`ESP.getFreeHeap()`).
|
||||
|
||||
```cpp
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
Serial.printf("Frequence CPU : %d MHz\n", getCpuFrequencyMhz());
|
||||
Serial.printf("Taille Flash : %d octets\n", ESP.getFlashChipSize());
|
||||
Serial.printf("Heap libre : %d octets\n", ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je sais expliquer la différence entre RAM et Flash sur ce microcontrôleur.
|
||||
- [ ] Je connais les broches à éviter (strapping pins) sur ma carte.
|
||||
- [ ] J'ai identifié la broche pilotant la LED NeoPixel intégrée.
|
||||
- [ ] J'ai exécuté le programme d'inventaire et compris les valeurs affichées.
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
title: "Module 2 — Entrées/Sorties numériques & LED RGB NeoPixel"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Maîtriser `digitalWrite()` et `digitalRead()` sur l'ESP32-S3.
|
||||
- Comprendre les résistances de tirage (pull-up / pull-down) et l'anti-rebond logiciel.
|
||||
- Réaliser un chenillard de LED et une lecture de bouton-poussoir.
|
||||
- Piloter la LED RGB NeoPixel intégrée à la carte (protocole WS2812, bibliothèque Adafruit_NeoPixel).
|
||||
|
||||
## 1. Rappels : sorties numériques
|
||||
|
||||
Une broche GPIO configurée en sortie (OUTPUT) ne peut prendre que deux états électriques : HIGH (3,3 V) ou LOW (0 V). C'est la base de tout pilotage de LED, relais, transistor de puissance, etc.
|
||||
|
||||
```cpp
|
||||
#define LED_PIN 4
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(300);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
delay(300);
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Rappels : entrées numériques et résistances de tirage
|
||||
|
||||
Une entrée numérique non connectée (« flottante ») peut lire un état aléatoire à cause du bruit électrique. Il faut donc fixer son état par défaut avec une résistance de tirage :
|
||||
|
||||
- `INPUT_PULLUP` : la broche est tirée à 3,3 V par défaut (état HIGH) ; le bouton doit relier la broche à GND pour donner un état LOW à l'appui.
|
||||
- `INPUT_PULLDOWN` : disponible sur l'ESP32-S3 (contrairement à l'AVR classique) — la broche est tirée à 0 V par défaut.
|
||||
|
||||
{#fig-m02-pull-up width=70%}
|
||||
|
||||
```cpp
|
||||
#define BUTTON_PIN 5
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int etat = digitalRead(BUTTON_PIN);
|
||||
if (etat == LOW) {
|
||||
Serial.println("Bouton presse");
|
||||
}
|
||||
delay(50);
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="Anti-rebond (debounce)"}
|
||||
Un bouton mécanique génère plusieurs transitions HIGH/LOW parasites en quelques millisecondes lors de l'appui. Un simple `delay(50)` après lecture suffit pour un usage pédagogique ; pour un projet robuste, on mesure le temps écoulé avec `millis()` plutôt que d'utiliser `delay()` (voir Module 6 sur les interruptions et timers).
|
||||
:::
|
||||
|
||||
{#fig-m02-rebonds}
|
||||
|
||||
## 3. Exercice guidé : chenillard de LED
|
||||
|
||||
Câblez 4 LED (avec résistances de 220 Ω) sur 4 GPIO libres, puis reproduisez un effet de chenillard :
|
||||
|
||||
```cpp
|
||||
const int leds[] = {4, 5, 6, 7};
|
||||
const int nbLeds = 4;
|
||||
|
||||
void setup() {
|
||||
for (int i = 0; i < nbLeds; i++) {
|
||||
pinMode(leds[i], OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
for (int i = 0; i < nbLeds; i++) {
|
||||
digitalWrite(leds[i], HIGH);
|
||||
delay(150);
|
||||
digitalWrite(leds[i], LOW);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{#fig-m02-chenillard width=75%}
|
||||
|
||||
## 4. La LED RGB NeoPixel intégrée
|
||||
|
||||
Contrairement à une LED classique qui ne prend que deux états (allumé/éteint) ou une intensité en PWM, la LED intégrée sur la plupart des cartes ESP32-S3 DevKit est une LED adressable de type WS2812 (aussi appelée NeoPixel). Elle intègre son propre contrôleur et se pilote avec un protocole série à un seul fil, où chaque LED reçoit une trame de 24 bits (8 bits rouge, 8 bits vert, 8 bits bleu).
|
||||
|
||||
### 4.1 Pourquoi ce n'est pas un simple digitalWrite
|
||||
|
||||
Le protocole WS2812 impose un timing très précis (de l'ordre de la centaine de nanosecondes) pour distinguer un bit 0 d'un bit 1. Le générer « à la main » avec digitalWrite serait peu fiable ; on utilise donc une bibliothèque dédiée qui exploite les ressources matérielles du microcontrôleur pour respecter ce timing.
|
||||
|
||||
{#fig-m02-ws2812}
|
||||
|
||||
### 4.2 Installation de la bibliothèque
|
||||
|
||||
- Dans l'IDE : Croquis \> Inclure une bibliothèque \> Gérer les bibliothèques.
|
||||
- Rechercher « Adafruit NeoPixel » et installer la dernière version (ainsi que sa dépendance Adafruit BusIO si demandée).
|
||||
|
||||
::: {.callout-note title="Identifier la broche de la LED"}
|
||||
Sur la plupart des cartes ESP32-S3-DevKitC-1, la LED NeoPixel est reliée au GPIO48 et nécessite d'alimenter une broche annexe (souvent GPIO47 sur certaines révisions) pour l'activer. Vérifiez impérativement le schéma de votre carte : certains modèles utilisent GPIO38 ou un autre GPIO. Le code ci-dessous utilise une constante `NEOPIXEL_PIN` à adapter.
|
||||
:::
|
||||
|
||||
### 4.3 Premier programme : allumer la LED en rouge
|
||||
|
||||
```cpp
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#define NEOPIXEL_PIN 48 // A adapter selon votre carte
|
||||
#define NB_PIXELS 1
|
||||
|
||||
Adafruit_NeoPixel pixels(NB_PIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
void setup() {
|
||||
pixels.begin();
|
||||
pixels.setBrightness(50); // Luminosite 0-255, a limiter pour ne pas eblouir
|
||||
pixels.setPixelColor(0, pixels.Color(255, 0, 0)); // Rouge
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 Exercice : cycle de couleurs et respiration
|
||||
|
||||
Objectif : faire défiler plusieurs couleurs, puis réaliser un effet de « respiration » (variation progressive de la luminosité) sur une couleur fixe.
|
||||
|
||||
```cpp
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#define NEOPIXEL_PIN 48
|
||||
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
void setup() {
|
||||
pixel.begin();
|
||||
pixel.setBrightness(80);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Cycle de couleurs
|
||||
pixel.setPixelColor(0, pixel.Color(255, 0, 0)); pixel.show(); delay(400);
|
||||
pixel.setPixelColor(0, pixel.Color(0, 255, 0)); pixel.show(); delay(400);
|
||||
pixel.setPixelColor(0, pixel.Color(0, 0, 255)); pixel.show(); delay(400);
|
||||
|
||||
// Effet respiration en bleu
|
||||
for (int b = 0; b <= 255; b += 5) {
|
||||
pixel.setBrightness(b);
|
||||
pixel.setPixelColor(0, pixel.Color(0, 0, 255));
|
||||
pixel.show();
|
||||
delay(15);
|
||||
}
|
||||
for (int b = 255; b >= 0; b -= 5) {
|
||||
pixel.setBrightness(b);
|
||||
pixel.setPixelColor(0, pixel.Color(0, 0, 255));
|
||||
pixel.show();
|
||||
delay(15);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Exercice final : bouton + NeoPixel
|
||||
|
||||
Combinez les notions du module : à chaque appui sur un bouton-poussoir (avec `INPUT_PULLUP` et anti-rebond), la LED NeoPixel doit changer de couleur en suivant un cycle défini (rouge → vert → bleu → blanc → éteint → rouge...).
|
||||
|
||||
| Étape | Comportement attendu |
|
||||
|---------|------------------------------------|
|
||||
| Démarrage | LED éteinte |
|
||||
| 1er appui | LED rouge |
|
||||
| 2e appui | LED verte |
|
||||
| 3e appui | LED bleue |
|
||||
| 4e appui | LED blanche |
|
||||
| 5e appui | Retour à éteint, le cycle recommence |
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je sais configurer et lire un bouton avec `INPUT_PULLUP`.
|
||||
- [ ] Je comprends pourquoi un debounce est nécessaire.
|
||||
- [ ] J'ai identifié la broche exacte de la LED NeoPixel sur ma carte.
|
||||
- [ ] J'ai réalisé l'exercice combinant bouton et changement de couleur.
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: "Module 3 — Signaux analogiques : PWM & ADC"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre le principe du PWM (modulation de largeur d'impulsion) et son usage sur ESP32-S3 via l'API LEDC.
|
||||
- Réaliser un variateur de luminosité et un contrôle de servomoteur.
|
||||
- Comprendre le fonctionnement du convertisseur analogique-numérique (ADC) et ses limites.
|
||||
- Lire un potentiomètre et convertir la valeur brute en unité physique.
|
||||
|
||||
## 1. Qu'est-ce que le PWM ?
|
||||
|
||||
Le PWM (Pulse Width Modulation) simule une sortie analogique en faisant varier rapidement le rapport cyclique (duty cycle) d'un signal carré : plus la portion « HIGH » est longue par rapport à la période, plus la valeur moyenne perçue (tension, luminosité, vitesse) est élevée. C'est la technique utilisée pour faire varier la luminosité d'une LED ou piloter un servomoteur.
|
||||
|
||||
{#fig-m03-pwm-rapport-cyclique}
|
||||
|
||||
## 2. Le PWM sur ESP32-S3 : l'API LEDC
|
||||
|
||||
Contrairement à un Arduino Uno où `analogWrite()` suffit, l'ESP32 utilise un contrôleur matériel dédié appelé LEDC (LED Control), plus flexible (fréquence et résolution configurables) mais nécessitant une configuration explicite. Depuis les versions récentes du core Arduino-ESP32, une API simplifiée `analogWrite()` est également disponible et gère LEDC en interne — nous verrons les deux approches.
|
||||
|
||||
### 2.1 Approche simplifiée : `analogWrite()`
|
||||
|
||||
```cpp
|
||||
#define LED_PIN 4
|
||||
|
||||
void setup() {
|
||||
// Rien de particulier a configurer avec l'API simplifiee
|
||||
}
|
||||
|
||||
void loop() {
|
||||
for (int duty = 0; duty <= 255; duty++) {
|
||||
analogWrite(LED_PIN, duty); // 0 = eteint, 255 = pleine puissance
|
||||
delay(10);
|
||||
}
|
||||
for (int duty = 255; duty >= 0; duty--) {
|
||||
analogWrite(LED_PIN, duty);
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Approche explicite : API LEDC (contrôle fin)
|
||||
|
||||
Utile lorsqu'on a besoin d'une fréquence ou d'une résolution précise (par exemple pour piloter un servomoteur, qui attend une fréquence de 50 Hz).
|
||||
|
||||
{#fig-m03-servo-50hz}
|
||||
|
||||
```cpp
|
||||
#define LED_PIN 4
|
||||
#define PWM_FREQ 5000 // 5 kHz
|
||||
#define PWM_RES 8 // resolution 8 bits (0-255)
|
||||
|
||||
void setup() {
|
||||
ledcAttach(LED_PIN, PWM_FREQ, PWM_RES);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
for (int duty = 0; duty <= 255; duty++) {
|
||||
ledcWrite(LED_PIN, duty);
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="Version du core Arduino-ESP32"}
|
||||
L'API LEDC a changé entre les versions 2.x et 3.x du core Arduino-ESP32 (l'ancienne API utilisait des numéros de canal explicites via ledcSetup/ledcAttachPin). Vérifiez la version installée dans le gestionnaire de cartes si le code ci-dessus ne compile pas, et adaptez-vous à la documentation correspondante.
|
||||
:::
|
||||
|
||||
## 3. Exercice : variateur de luminosité avec potentiomètre
|
||||
|
||||
Nous allons combiner PWM et lecture analogique : la position d'un potentiomètre définit la luminosité d'une LED.
|
||||
|
||||
{#fig-m03-montage-variateur}
|
||||
|
||||
## 4. Le convertisseur analogique-numérique (ADC)
|
||||
|
||||
L'ADC convertit une tension continue (0 à 3,3 V) en une valeur numérique. L'ESP32-S3 dispose d'un ADC 12 bits, soit une résolution de 4096 valeurs possibles (0 à 4095).
|
||||
|
||||
{#fig-m03-adc-quantification}
|
||||
|
||||
| Caractéristique | Valeur |
|
||||
|-----------------------------------|----------------------------------------|
|
||||
| Résolution native | 12 bits (0 à 4095) |
|
||||
| Plage de tension | 0 à 3,3 V (selon atténuation configurée) |
|
||||
| Unité ADC utilisable en Wi-Fi actif | ADC1 uniquement (ADC2 partagé avec le Wi-Fi) |
|
||||
| Fonction de lecture | `analogRead(pin)` |
|
||||
|
||||
::: {.callout-warning title="Limitation importante : ADC2 et Wi-Fi"}
|
||||
L'ADC2 de l'ESP32 partage des ressources internes avec le module Wi-Fi. Si le Wi-Fi est actif (voir Module 7), les lectures sur les broches ADC2 deviennent peu fiables voire impossibles. Pour tout projet connecté utilisant l'analogique, privilégiez les broches de l'ADC1 (généralement GPIO1 à GPIO10 sur l'ESP32-S3).
|
||||
:::
|
||||
|
||||
### 4.1 Lire un potentiomètre
|
||||
|
||||
```cpp
|
||||
#define POT_PIN 1 // Broche ADC1
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int valeurBrute = analogRead(POT_PIN); // 0 a 4095
|
||||
float tension = valeurBrute * (3.3 / 4095.0); // conversion en volts
|
||||
Serial.printf("Brut: %d Tension: %.2f V\n", valeurBrute, tension);
|
||||
delay(200);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Exercice combiné : variateur de LED par potentiomètre
|
||||
|
||||
Utilisez la valeur lue sur le potentiomètre (0-4095) pour piloter la luminosité d'une LED en PWM (0-255) en convertissant l'échelle avec la fonction `map()`.
|
||||
|
||||
```cpp
|
||||
#define POT_PIN 1
|
||||
#define LED_PIN 4
|
||||
|
||||
void setup() {
|
||||
ledcAttach(LED_PIN, 5000, 8);
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int brut = analogRead(POT_PIN);
|
||||
int duty = map(brut, 0, 4095, 0, 255);
|
||||
ledcWrite(LED_PIN, duty);
|
||||
Serial.printf("Potentiometre: %d -> PWM: %d\n", brut, duty);
|
||||
delay(50);
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Aller plus loin : appliquer le PWM à la LED NeoPixel
|
||||
|
||||
Pour rappel (Module 2), la LED NeoPixel ne se pilote pas avec analogWrite ou ledcWrite : elle utilise un protocole numérique dédié (WS2812) géré par la bibliothèque Adafruit_NeoPixel, dont la fonction `setBrightness()` joue un rôle équivalent à celui du PWM pour l'intensité globale. Reliez les deux notions : adaptez l'exercice du variateur pour que ce soit cette fois la luminosité de la LED NeoPixel qui varie selon le potentiomètre.
|
||||
|
||||
```cpp
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#define POT_PIN 1
|
||||
#define NEOPIXEL_PIN 48
|
||||
|
||||
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
void setup() {
|
||||
pixel.begin();
|
||||
pixel.setPixelColor(0, pixel.Color(255, 255, 255));
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int brut = analogRead(POT_PIN);
|
||||
int luminosite = map(brut, 0, 4095, 0, 255);
|
||||
pixel.setBrightness(luminosite);
|
||||
pixel.show();
|
||||
delay(50);
|
||||
}
|
||||
```
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je sais expliquer le principe du duty cycle en PWM.
|
||||
- [ ] Je sais configurer une sortie PWM avec analogWrite ou l'API LEDC.
|
||||
- [ ] Je connais la résolution et les limites de l'ADC de l'ESP32-S3 (notamment ADC2 + Wi-Fi).
|
||||
- [ ] J'ai réalisé le variateur de luminosité piloté par potentiomètre, sur LED classique et sur NeoPixel.
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
title: "Module 4 — Communication série & débogage"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre le fonctionnement du bus UART et son rôle central dans le débogage.
|
||||
- Distinguer les différents ports série disponibles sur l'ESP32-S3.
|
||||
- Utiliser efficacement le moniteur série et les fonctions d'affichage formaté.
|
||||
- Construire une petite console de commandes interactive.
|
||||
|
||||
## 1. Rappel : qu'est-ce que l'UART ?
|
||||
|
||||
L'UART (Universal Asynchronous Receiver-Transmitter) est un protocole de communication série point à point utilisant deux fils : TX (transmission) et RX (réception), sans horloge partagée — les deux extrémités doivent donc être configurées à la même vitesse (baud rate) pour interpréter correctement les bits reçus.
|
||||
|
||||
{#fig-m04-trame-uart}
|
||||
|
||||
Sur l'ESP32-S3, la liaison série vers l'ordinateur (moniteur série de l'IDE) transite le plus souvent directement par le port USB natif du microcontrôleur (USB CDC), et non par un UART matériel classique relié à une puce USB-série externe — d'où l'importance de l'option « USB CDC On Boot » vue au Module 0.
|
||||
|
||||
## 2. Les ports série disponibles
|
||||
|
||||
L'ESP32-S3 dispose de plusieurs contrôleurs UART matériels, utilisables simultanément pour des usages différents (ex : un port pour le debug, un autre pour dialoguer avec un module GPS ou un lecteur RFID).
|
||||
|
||||
| Objet | Usage typique |
|
||||
|---------|----------------------------------------|
|
||||
| Serial | Console USB (moniteur série / debug), via USB CDC sur l'ESP32-S3 |
|
||||
| Serial1 | UART matériel n°1, broches à réassigner explicitement |
|
||||
| Serial2 | UART matériel n°2, broches à réassigner explicitement |
|
||||
|
||||
::: {.callout-note title="Réassignation des broches UART"}
|
||||
Sur l'ESP32-S3, les broches des UART matériels ne sont pas fixes : on les définit avec Serial1.begin(baud, config, rxPin, txPin). Vérifiez que les broches choisies ne font pas partie des broches à éviter identifiées au Module 1.
|
||||
:::
|
||||
|
||||
```cpp
|
||||
// Exemple : Serial2 sur les broches GPIO17 (RX) et GPIO18 (TX)
|
||||
#define RX2 17
|
||||
#define TX2 18
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200); // console USB
|
||||
Serial2.begin(9600, SERIAL_8N1, RX2, TX2); // module externe
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (Serial2.available()) {
|
||||
String recu = Serial2.readStringUntil('\n');
|
||||
Serial.print("Recu du module: ");
|
||||
Serial.println(recu);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{#fig-m04-cablage-serie}
|
||||
|
||||
## 3. Bien utiliser le moniteur série
|
||||
|
||||
### 3.1 Fonctions d'affichage
|
||||
|
||||
| Fonction | Rôle |
|
||||
|-----------------------|----------------------------------------|
|
||||
| `Serial.print(x)` | Affiche x sans retour à la ligne |
|
||||
| `Serial.println(x)` | Affiche x avec un retour à la ligne |
|
||||
| Serial.printf(fmt, ...) | Affichage formaté façon C (%d, %s, %f, %.2f...) |
|
||||
|
||||
```cpp
|
||||
int temperature = 24;
|
||||
float tension = 3.298;
|
||||
|
||||
Serial.printf("Temperature: %d C, Tension: %.2f V\n", temperature, tension);
|
||||
```
|
||||
|
||||
::: {.callout-tip title="Bonne pratique de debug"}
|
||||
Préférez `Serial.printf()` à la concaténation de multiples `Serial.print()` : le code est plus lisible et plus facile à maintenir, surtout lorsqu'on affiche plusieurs variables à la fois.
|
||||
:::
|
||||
|
||||
## 4. Lire des données envoyées depuis le moniteur série
|
||||
|
||||
Le moniteur série permet aussi d'envoyer du texte vers la carte, ce qui est très utile pour créer des interfaces de test simples (changer un paramètre sans reflasher le programme).
|
||||
|
||||
```cpp
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Tapez une commande puis Entree");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (Serial.available()) {
|
||||
String commande = Serial.readStringUntil('\n');
|
||||
commande.trim(); // supprime espaces/retours de fin
|
||||
Serial.print("Commande recue: ");
|
||||
Serial.println(commande);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Exercice pratique : mini console de commandes
|
||||
|
||||
En vous appuyant sur la LED NeoPixel du Module 2, développez une petite console pilotée par le moniteur série :
|
||||
|
||||
- La commande « rouge » allume la LED en rouge.
|
||||
- La commande « vert » l'allume en vert.
|
||||
- La commande « bleu » l'allume en bleu.
|
||||
- La commande « off » l'éteint.
|
||||
- Toute commande inconnue affiche un message d'erreur explicite.
|
||||
|
||||
```cpp
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#define NEOPIXEL_PIN 48
|
||||
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pixel.begin();
|
||||
Serial.println("Commandes disponibles: rouge, vert, bleu, off");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (Serial.available()) {
|
||||
String cmd = Serial.readStringUntil('\n');
|
||||
cmd.trim();
|
||||
cmd.toLowerCase();
|
||||
|
||||
if (cmd == "rouge") {
|
||||
pixel.setPixelColor(0, pixel.Color(255, 0, 0));
|
||||
} else if (cmd == "vert") {
|
||||
pixel.setPixelColor(0, pixel.Color(0, 255, 0));
|
||||
} else if (cmd == "bleu") {
|
||||
pixel.setPixelColor(0, pixel.Color(0, 0, 255));
|
||||
} else if (cmd == "off") {
|
||||
pixel.setPixelColor(0, pixel.Color(0, 0, 0));
|
||||
} else {
|
||||
Serial.println("Commande inconnue.");
|
||||
return;
|
||||
}
|
||||
pixel.show();
|
||||
Serial.println("OK");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Pour aller plus loin (facultatif)
|
||||
|
||||
Ajoutez une commande « couleur R G B » qui accepte trois nombres séparés par des espaces (ex : couleur 128 0 255) en utilisant les fonctions `indexOf()` et `substring()` ou la fonction `sscanf()` pour extraire les valeurs de la chaîne reçue.
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je comprends la différence entre Serial, Serial1 et Serial2.
|
||||
- [ ] Je sais réassigner les broches RX/TX d'un UART matériel.
|
||||
- [ ] Je maîtrise `Serial.printf()` pour un affichage de debug formaté.
|
||||
- [ ] J'ai réalisé la mini console de commandes pilotant la LED NeoPixel.
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
title: "Module 5 — Bus I2C et SPI"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre les principes des bus I2C et SPI et leurs différences.
|
||||
- Câbler et adresser un périphérique I2C (scanner de bus, résolution de conflits d'adresse).
|
||||
- Afficher du texte et des formes sur un écran OLED SSD1306 (I2C).
|
||||
- Lire les données d'un capteur de température/humidité BME280 (I2C).
|
||||
|
||||
## 1. Pourquoi un bus de communication ?
|
||||
|
||||
Les broches GPIO génériques permettent de dialoguer avec un seul composant à la fois, en définissant vous-même le protocole. Pour standardiser les échanges avec des composants variés (écrans, capteurs, mémoires), l'industrie utilise des bus normalisés : I2C et SPI sont les deux plus répandus dans l'écosystème Arduino/ESP32.
|
||||
|
||||
## 2. Le bus I2C
|
||||
|
||||
### 2.1 Principe
|
||||
|
||||
I2C (Inter-Integrated Circuit) utilise seulement deux fils partagés par tous les périphériques du bus :
|
||||
|
||||
- SDA (Serial Data) : ligne de données.
|
||||
- SCL (Serial Clock) : ligne d'horloge, générée par le maître (l'ESP32-S3 dans notre cas).
|
||||
|
||||
Chaque périphérique possède une adresse unique (7 bits, soit 128 adresses possibles) qui permet au maître de s'adresser spécifiquement à lui sur le bus partagé.
|
||||
|
||||
{#fig-m05-bus-i2c}
|
||||
|
||||
### 2.2 Broches I2C par défaut sur ESP32-S3
|
||||
|
||||
Le framework Arduino-ESP32 définit par défaut SDA sur GPIO8 et SCL sur GPIO9 (à vérifier selon votre carte), mais ces broches peuvent être réassignées via Wire.begin(sda, scl).
|
||||
|
||||
```cpp
|
||||
#include <Wire.h>
|
||||
|
||||
void setup() {
|
||||
Wire.begin(8, 9); // SDA, SCL — a adapter selon votre carte
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Scanner de bus I2C
|
||||
|
||||
{#fig-m05-trame-i2c}
|
||||
|
||||
Avant de câbler un module, il est très utile de vérifier qu'il est bien détecté et de connaître son adresse exacte :
|
||||
|
||||
```cpp
|
||||
#include <Wire.h>
|
||||
|
||||
void setup() {
|
||||
Wire.begin(8, 9);
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
Serial.println("Scan du bus I2C...");
|
||||
|
||||
for (byte addr = 1; addr < 127; addr++) {
|
||||
Wire.beginTransmission(addr);
|
||||
byte erreur = Wire.endTransmission();
|
||||
if (erreur == 0) {
|
||||
Serial.printf("Peripherique trouve a l'adresse 0x%02X\n", addr);
|
||||
}
|
||||
}
|
||||
Serial.println("Scan termine.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="Conflit d'adresse"}
|
||||
Si deux modules identiques (deux écrans OLED par exemple) doivent cohabiter sur le même bus, ils doivent avoir des adresses différentes. Certains modules disposent de pastilles de soudure (solder jumpers) permettant de changer leur adresse d'usine.
|
||||
:::
|
||||
|
||||
## 4. Exercice : afficheur OLED SSD1306
|
||||
|
||||
### 4.1 Installation des bibliothèques
|
||||
|
||||
- Adafruit GFX Library (moteur graphique générique).
|
||||
- Adafruit SSD1306 (pilote spécifique à l'écran).
|
||||
|
||||
### 4.2 Câblage
|
||||
|
||||
| Broche écran | Broche ESP32-S3 |
|
||||
|----------------|-----------------------------|
|
||||
| VCC | 3V3 |
|
||||
| GND | GND |
|
||||
| SDA | GPIO8 (ou broche SDA choisie) |
|
||||
| SCL | GPIO9 (ou broche SCL choisie) |
|
||||
|
||||
### 4.3 Premier affichage
|
||||
|
||||
```cpp
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define OLED_ADDR 0x3C // Adresse trouvee avec le scanner I2C
|
||||
|
||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
|
||||
|
||||
void setup() {
|
||||
Wire.begin(8, 9);
|
||||
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
|
||||
Serial.println("Ecran non detecte !");
|
||||
while (true);
|
||||
}
|
||||
display.clearDisplay();
|
||||
display.setTextSize(2);
|
||||
display.setTextColor(SSD1306_WHITE);
|
||||
display.setCursor(0, 0);
|
||||
display.println("Bonjour !");
|
||||
display.display();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Exercice : capteur de température/humidité BME280
|
||||
|
||||
Le BME280 est un capteur environnemental (température, humidité, pression) communiquant en I2C, très utilisé dans les projets de station météo.
|
||||
|
||||
### 5.1 Installation de la bibliothèque
|
||||
|
||||
Installer « Adafruit BME280 Library » depuis le gestionnaire de bibliothèques.
|
||||
|
||||
### 5.2 Lecture des mesures
|
||||
|
||||
```cpp
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_BME280.h>
|
||||
|
||||
Adafruit_BME280 bme;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Wire.begin(8, 9);
|
||||
if (!bme.begin(0x76)) { // ou 0x77 selon le module
|
||||
Serial.println("BME280 non detecte !");
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
float temperature = bme.readTemperature();
|
||||
float humidite = bme.readHumidity();
|
||||
float pression = bme.readPressure() / 100.0F; // hPa
|
||||
|
||||
Serial.printf("T: %.1f C H: %.1f %% P: %.1f hPa\n", temperature, humidite, pression);
|
||||
delay(2000);
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Exercice combiné : mini station météo
|
||||
|
||||
Combinez écran OLED et capteur BME280 pour afficher en temps réel la température et l'humidité, avec rafraîchissement toutes les 2 secondes. Ajoutez un indicateur visuel sur la LED NeoPixel : bleu si température \< 18°C, vert entre 18 et 25°C, rouge au-delà.
|
||||
|
||||
## 7. Le bus SPI : présentation
|
||||
|
||||
SPI (Serial Peripheral Interface) est plus rapide que l'I2C mais utilise davantage de fils : MOSI (données maître→esclave), MISO (esclave→maître), SCK (horloge) et une ligne CS (Chip Select) dédiée par périphérique. Il est privilégié pour les composants nécessitant un débit élevé (écrans TFT couleur, cartes SD, certains modules radio).
|
||||
|
||||
{#fig-m05-bus-spi}
|
||||
|
||||
| Critère | I2C | SPI |
|
||||
|---------------|-----------------------|--------------------------------|
|
||||
| Nombre de fils | 2 (partagés) | 4 (+1 CS par périphérique) |
|
||||
| Vitesse typique | 100 kHz - 1 MHz | Plusieurs MHz à dizaines de MHz |
|
||||
| Adressage | Adresse logicielle | Ligne CS physique dédiée |
|
||||
| Usage typique | Capteurs, petits écrans | Écrans rapides, cartes SD, radio |
|
||||
|
||||
::: {.callout-note title="Approfondissement SPI"}
|
||||
Le câblage et l'utilisation détaillée du SPI (ex : écran TFT, carte SD) seront pratiqués dans un module dédié aux projets avancés, en fonction du matériel disponible pour la formation.
|
||||
:::
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] J'ai exécuté le scanner I2C et identifié les adresses de mes modules.
|
||||
- [ ] J'ai affiché du texte sur l'écran OLED.
|
||||
- [ ] J'ai lu température et humidité depuis le BME280.
|
||||
- [ ] J'ai réalisé la mini station météo combinant écran, capteur et LED NeoPixel.
|
||||
- [ ] Je sais expliquer la différence entre I2C et SPI.
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
title: "Module 6 — Interruptions matérielles & Timers"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre les limites de `delay()` et de la boucle `loop()` classique.
|
||||
- Utiliser `millis()` pour gérer plusieurs tâches périodiques sans bloquer le programme.
|
||||
- Mettre en œuvre une interruption matérielle avec `attachInterrupt()`.
|
||||
- Comprendre le rôle de `IRAM_ATTR` et les bonnes pratiques dans une routine d'interruption (ISR).
|
||||
|
||||
## 1. Le problème de `delay()`
|
||||
|
||||
La fonction `delay()` bloque totalement l'exécution du programme pendant la durée spécifiée : aucune autre instruction (lecture de bouton, mise à jour d'écran, communication réseau) ne peut s'exécuter pendant ce temps. Pour un programme qui ne fait qu'une seule chose, ce n'est pas gênant ; mais dès qu'on veut gérer plusieurs comportements simultanés (ex : clignoter une LED tout en surveillant un bouton), `delay()` devient un frein majeur.
|
||||
|
||||
## 2. La technique du « blink sans delay » avec `millis()`
|
||||
|
||||
`millis()` renvoie le nombre de millisecondes écoulées depuis le démarrage du programme. En comparant cette valeur à un dernier instant mémorisé, on peut exécuter une action périodique sans jamais bloquer la boucle `loop()`.
|
||||
|
||||
```cpp
|
||||
#define LED_PIN 4
|
||||
unsigned long dernierChangement = 0;
|
||||
const unsigned long intervalle = 500; // ms
|
||||
bool etatLed = false;
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
unsigned long maintenant = millis();
|
||||
|
||||
if (maintenant - dernierChangement >= intervalle) {
|
||||
dernierChangement = maintenant;
|
||||
etatLed = !etatLed;
|
||||
digitalWrite(LED_PIN, etatLed);
|
||||
}
|
||||
|
||||
// Ici, on pourrait ajouter d'autres taches (lecture bouton, etc.)
|
||||
// sans que le clignotement ne soit bloque.
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="Pourquoi unsigned long ?"}
|
||||
`millis()` renvoie une valeur qui finit par déborder (« overflow ») après environ 49 jours de fonctionnement continu. Utiliser la soustraction (maintenant - dernierChangement) plutôt qu'une comparaison directe des deux valeurs absolues gère correctement ce débordement grâce à l'arithmétique non signée.
|
||||
:::
|
||||
|
||||
## 3. Exercice : plusieurs tâches périodiques indépendantes
|
||||
|
||||
Reprenez la logique de « blink sans delay » pour gérer simultanément trois comportements à des fréquences différentes : clignotement d'une LED classique toutes les 300 ms, cycle de couleur de la LED NeoPixel toutes les 1000 ms, et affichage d'un message de debug toutes les 5000 ms.
|
||||
|
||||
{#fig-m06-taches-millis}
|
||||
|
||||
## 4. Les interruptions matérielles
|
||||
|
||||
### 4.1 Principe
|
||||
|
||||
Une interruption permet au microcontrôleur de réagir instantanément à un évènement matériel (front montant ou descendant sur une broche), en suspendant momentanément l'exécution de la boucle principale pour exécuter une fonction dédiée, appelée ISR (Interrupt Service Routine). C'est indispensable pour ne manquer aucun évènement rapide, comme une impulsion d'encodeur rotatif ou un appui bref sur un bouton.
|
||||
|
||||
{#fig-m06-deroule-isr}
|
||||
|
||||
### 4.2 `attachInterrupt()`
|
||||
|
||||
```cpp
|
||||
#define BUTTON_PIN 5
|
||||
|
||||
volatile bool interruptionDetectee = false;
|
||||
|
||||
void IRAM_ATTR gererInterruption() {
|
||||
interruptionDetectee = true;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), gererInterruption, FALLING);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (interruptionDetectee) {
|
||||
interruptionDetectee = false;
|
||||
Serial.println("Interruption traitee dans loop()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="Règles d'or d'une ISR"}
|
||||
Une routine d'interruption doit être aussi courte et rapide que possible : pas de `Serial.print()`, pas de `delay()`, pas d'allocation mémoire. La bonne pratique consiste à se contenter de modifier une variable volatile (comme ci-dessus) et à traiter l'évènement dans `loop()`. Le mot-clé `IRAM_ATTR` indique que le code de la fonction doit être placé en RAM interne pour un temps d'exécution garanti, même si le cache Flash est momentanément indisponible.
|
||||
:::
|
||||
|
||||
### 4.3 Les modes de déclenchement
|
||||
|
||||
| Mode | Déclenchement |
|
||||
|----------|----------------------------------------|
|
||||
| RISING | Sur front montant (passage de LOW à HIGH) |
|
||||
| FALLING | Sur front descendant (passage de HIGH à LOW) |
|
||||
| CHANGE | Sur tout changement d'état |
|
||||
| LOW / HIGH | Tant que la broche reste dans cet état (peu utilisé) |
|
||||
|
||||
{#fig-m06-modes-declenchement}
|
||||
|
||||
## 5. Exercice : compteur d'impulsions (encodeur rotatif)
|
||||
|
||||
Un encodeur rotatif génère des impulsions sur deux broches (souvent nommées CLK et DT) permettant de détecter le sens de rotation. Câblez un encodeur rotatif et comptez le nombre de crans parcourus à l'aide d'une interruption sur la broche CLK.
|
||||
|
||||
{#fig-m06-encodeur}
|
||||
|
||||
```cpp
|
||||
#define CLK_PIN 6
|
||||
#define DT_PIN 7
|
||||
|
||||
volatile int compteur = 0;
|
||||
|
||||
void IRAM_ATTR gererRotation() {
|
||||
if (digitalRead(DT_PIN) == HIGH) {
|
||||
compteur++;
|
||||
} else {
|
||||
compteur--;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(CLK_PIN, INPUT_PULLUP);
|
||||
pinMode(DT_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(CLK_PIN), gererRotation, FALLING);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
static int dernierAffiche = 0;
|
||||
if (compteur != dernierAffiche) {
|
||||
dernierAffiche = compteur;
|
||||
Serial.printf("Position: %d\n", compteur);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Les timers matériels
|
||||
|
||||
En complément des interruptions déclenchées par un évènement externe, l'ESP32-S3 dispose de timers matériels internes capables de générer une interruption à intervalle régulier, indépendamment de la boucle `loop()`. Utile pour des tâches critiques en temps (échantillonnage régulier, watchdog applicatif).
|
||||
|
||||
```cpp
|
||||
hw_timer_t *minuteur = NULL;
|
||||
volatile bool declenche = false;
|
||||
|
||||
void IRAM_ATTR onTimer() {
|
||||
declenche = true;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
minuteur = timerBegin(1000000); // 1 MHz -> tick = 1 microseconde
|
||||
timerAttachInterrupt(minuteur, &onTimer);
|
||||
timerAlarm(minuteur, 1000000, true, 0); // declenchement toutes les 1 000 000 us = 1 s
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (declenche) {
|
||||
declenche = false;
|
||||
Serial.println("Tick timer materiel (1s)");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="Version de l'API timer"}
|
||||
Comme pour LEDC, l'API des timers matériels a évolué entre les versions du core Arduino-ESP32 (l'ancienne API utilisait timerBegin(numero, diviseur, compteMontant)). Vérifiez la signature correspondant à votre version installée si le code ne compile pas.
|
||||
:::
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je sais remplacer un `delay()` par une gestion à base de `millis()`.
|
||||
- [ ] Je sais faire cohabiter plusieurs tâches périodiques indépendantes dans `loop()`.
|
||||
- [ ] Je sais déclencher une interruption avec `attachInterrupt()` et écrire une ISR minimale et sûre.
|
||||
- [ ] J'ai réalisé le compteur d'impulsions avec un encodeur rotatif.
|
||||
- [ ] Je comprends le rôle d'un timer matériel par rapport à une interruption externe.
|
||||
@@ -0,0 +1,161 @@
|
||||
---
|
||||
title: "Module 7 — Wi-Fi : station, point d'accès et client HTTP"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre les deux modes de fonctionnement Wi-Fi de l'ESP32-S3 : station (STA) et point d'accès (AP).
|
||||
- Connecter la carte à un réseau Wi-Fi existant.
|
||||
- Créer son propre point d'accès Wi-Fi depuis la carte.
|
||||
- Réaliser une requête HTTP GET vers une API externe et exploiter la réponse JSON.
|
||||
|
||||
## 1. Les modes Wi-Fi de l'ESP32-S3
|
||||
|
||||
| Mode | Rôle | Usage typique |
|
||||
|-------------------------|----------------------------------------|----------------------------------------|
|
||||
| `WIFI_STA` (Station) | La carte se connecte à un routeur/box existant | Envoyer des données sur Internet, rejoindre le réseau local |
|
||||
| `WIFI_AP` (Point d'accès) | La carte crée son propre réseau Wi-Fi | Configuration sans routeur, projet autonome |
|
||||
| `WIFI_AP_STA` | Les deux simultanément | Portail de configuration puis bascule en client |
|
||||
|
||||
{#fig-m07-modes-wifi}
|
||||
|
||||
## 2. Mode station : se connecter à un réseau existant
|
||||
|
||||
```cpp
|
||||
#include <WiFi.h>
|
||||
|
||||
const char* ssid = "NomDuReseau";
|
||||
const char* motDePasse = "MotDePasse";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, motDePasse);
|
||||
|
||||
Serial.print("Connexion en cours");
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
Serial.print("Connecte ! Adresse IP: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-important title="Sécurité : ne jamais versionner ses identifiants"}
|
||||
Évitez d'écrire en clair vos identifiants Wi-Fi dans un fichier destiné à être partagé ou publié (dépôt Git). Une bonne pratique consiste à les isoler dans un fichier séparé non partagé (ex : secrets.h), ou à les saisir via le moniteur série au premier démarrage.
|
||||
:::
|
||||
|
||||
## 3. Mode point d'accès : créer son propre réseau
|
||||
|
||||
```cpp
|
||||
#include <WiFi.h>
|
||||
|
||||
const char* ssidAP = "ESP32-Formation";
|
||||
const char* motDePasseAP = "formation123"; // 8 caracteres minimum
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(ssidAP, motDePasseAP);
|
||||
|
||||
Serial.print("Point d'acces cree. IP: ");
|
||||
Serial.println(WiFi.softAPIP()); // generalement 192.168.4.1
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Client HTTP : interroger une API externe
|
||||
|
||||
Une fois connectée en mode station, la carte peut effectuer des requêtes HTTP vers des services web, par exemple pour récupérer la météo actuelle.
|
||||
|
||||
{#fig-m07-requete-http}
|
||||
|
||||
### 4.1 Bibliothèque nécessaire
|
||||
|
||||
HTTPClient est incluse nativement dans le core Arduino-ESP32. Pour analyser une réponse JSON, installez la bibliothèque « ArduinoJson » (Benoit Blanchon) depuis le gestionnaire de bibliothèques.
|
||||
|
||||
### 4.2 Requête GET simple
|
||||
|
||||
```cpp
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.begin("NomDuReseau", "MotDePasse");
|
||||
while (WiFi.status() != WL_CONNECTED) { delay(500); }
|
||||
|
||||
HTTPClient http;
|
||||
http.begin("https://api.exemple.com/donnees");
|
||||
int codeReponse = http.GET();
|
||||
|
||||
if (codeReponse == 200) {
|
||||
String reponse = http.getString();
|
||||
Serial.println(reponse);
|
||||
} else {
|
||||
Serial.printf("Erreur HTTP: %d\n", codeReponse);
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Exercice : afficher la météo en temps réel
|
||||
|
||||
Combinez cette section avec le Module 5 (écran OLED) : interrogez une API météo publique (par exemple Open-Meteo, qui ne nécessite pas de clé d'API) et affichez la température actuelle sur l'écran OLED, avec rafraîchissement toutes les 10 minutes.
|
||||
|
||||
```cpp
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.begin("NomDuReseau", "MotDePasse");
|
||||
while (WiFi.status() != WL_CONNECTED) { delay(500); }
|
||||
|
||||
HTTPClient http;
|
||||
// Exemple : latitude/longitude de Paris
|
||||
http.begin("https://api.open-meteo.com/v1/forecast?latitude=48.85&longitude=2.35¤t_weather=true");
|
||||
int code = http.GET();
|
||||
|
||||
if (code == 200) {
|
||||
String payload = http.getString();
|
||||
JsonDocument doc;
|
||||
deserializeJson(doc, payload);
|
||||
float temp = doc["current_weather"]["temperature"];
|
||||
Serial.printf("Temperature actuelle: %.1f C\n", temp);
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-important title="Rappel important : ADC2 et Wi-Fi"}
|
||||
Comme vu au Module 3, dès que le Wi-Fi est actif, évitez toute lecture analogique sur les broches de l'ADC2 : privilégiez systématiquement l'ADC1 dans vos projets connectés.
|
||||
:::
|
||||
|
||||
## 6. Indicateur visuel de connexion avec la LED NeoPixel
|
||||
|
||||
Ajoutez un indicateur d'état de connexion sur la LED NeoPixel intégrée : orange clignotant pendant la tentative de connexion, vert fixe une fois connecté, rouge fixe en cas d'échec après un délai (par exemple 10 secondes de tentative).
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je sais connecter la carte à un réseau Wi-Fi existant (mode STA).
|
||||
- [ ] Je sais créer un point d'accès Wi-Fi autonome (mode AP).
|
||||
- [ ] Je sais effectuer une requête HTTP GET et lire le code de réponse.
|
||||
- [ ] Je sais extraire une valeur d'une réponse JSON avec ArduinoJson.
|
||||
- [ ] J'ai réalisé l'affichage météo en temps réel sur l'écran OLED.
|
||||
- [ ] J'ai ajouté l'indicateur de connexion sur la LED NeoPixel.
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: "Module 8 — Serveur Web embarqué"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre le rôle d'un serveur web embarqué et son intérêt pour piloter une carte sans application dédiée.
|
||||
- Créer un serveur HTTP avec WebServer.h et définir des routes.
|
||||
- Construire une page HTML de contrôle simple.
|
||||
- Découvrir le WebSocket pour une communication en temps réel bidirectionnelle.
|
||||
|
||||
## 1. Pourquoi un serveur web embarqué ?
|
||||
|
||||
Plutôt que de développer une application mobile dédiée, l'ESP32-S3 peut lui-même héberger une page web accessible depuis n'importe quel navigateur (smartphone, ordinateur) connecté au même réseau. C'est une solution rapide et universelle pour créer une interface de contrôle ou de supervision d'un projet.
|
||||
|
||||
## 2. Serveur web basique avec WebServer.h
|
||||
|
||||
La bibliothèque WebServer (incluse dans le core Arduino-ESP32) permet de définir des routes (URL) et la réponse associée à chacune.
|
||||
|
||||
```cpp
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char* ssid = "NomDuReseau";
|
||||
const char* motDePasse = "MotDePasse";
|
||||
|
||||
WebServer serveur(80);
|
||||
|
||||
void gererRacine() {
|
||||
serveur.send(200, "text/plain", "Serveur ESP32-S3 operationnel !");
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.begin(ssid, motDePasse);
|
||||
while (WiFi.status() != WL_CONNECTED) { delay(500); }
|
||||
|
||||
Serial.print("Serveur accessible sur : http://");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
serveur.on("/", gererRacine);
|
||||
serveur.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
serveur.handleClient(); // A appeler en permanence, sans delay() bloquant
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="handleClient() et delay()"}
|
||||
`serveur.handleClient()` doit être appelée le plus souvent possible dans `loop()`. Évitez tout `delay()` long dans votre boucle principale, sous peine de rendre le serveur peu réactif — reprenez la technique `millis()` vue au Module 6 si vous devez gérer d'autres tâches périodiques en parallèle.
|
||||
:::
|
||||
|
||||
## 3. Page HTML de contrôle de la LED NeoPixel
|
||||
|
||||
Construisons une page avec des boutons permettant de changer la couleur de la LED NeoPixel intégrée, directement depuis un navigateur.
|
||||
|
||||
{#fig-m08-echanges-serveur}
|
||||
|
||||
```cpp
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#define NEOPIXEL_PIN 48
|
||||
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
WebServer serveur(80);
|
||||
|
||||
const char pageHTML[] PROGMEM = R"rawliteral(
|
||||
<!DOCTYPE html><html><head><meta charset='UTF-8'>
|
||||
<title>Controle LED ESP32-S3</title></head>
|
||||
<body style='font-family:sans-serif;text-align:center;margin-top:50px;'>
|
||||
<h1>Controle de la LED NeoPixel</h1>
|
||||
<a href='/rouge'><button style='padding:15px 30px;margin:5px;'>Rouge</button></a>
|
||||
<a href='/vert'><button style='padding:15px 30px;margin:5px;'>Vert</button></a>
|
||||
<a href='/bleu'><button style='padding:15px 30px;margin:5px;'>Bleu</button></a>
|
||||
<a href='/off'><button style='padding:15px 30px;margin:5px;'>Eteindre</button></a>
|
||||
</body></html>
|
||||
)rawliteral";
|
||||
|
||||
void changerCouleur(int r, int g, int b) {
|
||||
pixel.setPixelColor(0, pixel.Color(r, g, b));
|
||||
pixel.show();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pixel.begin();
|
||||
WiFi.begin("NomDuReseau", "MotDePasse");
|
||||
while (WiFi.status() != WL_CONNECTED) { delay(500); }
|
||||
|
||||
serveur.on("/", []() { serveur.send(200, "text/html", pageHTML); });
|
||||
serveur.on("/rouge", []() { changerCouleur(255,0,0); serveur.send(200,"text/html",pageHTML); });
|
||||
serveur.on("/vert", []() { changerCouleur(0,255,0); serveur.send(200,"text/html",pageHTML); });
|
||||
serveur.on("/bleu", []() { changerCouleur(0,0,255); serveur.send(200,"text/html",pageHTML); });
|
||||
serveur.on("/off", []() { changerCouleur(0,0,0); serveur.send(200,"text/html",pageHTML); });
|
||||
serveur.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
serveur.handleClient();
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="PROGMEM"}
|
||||
Le mot-clé PROGMEM place la chaîne de caractères HTML directement en mémoire Flash plutôt qu'en RAM, économisant une ressource précieuse lorsque la page grandit en taille.
|
||||
:::
|
||||
|
||||
## 4. Introduction au WebSocket
|
||||
|
||||
Le modèle utilisé ci-dessus (rechargement de page à chaque clic) fonctionne mais reste rudimentaire. Le WebSocket établit une connexion persistante entre le navigateur et la carte, permettant un échange de données en temps réel sans recharger la page — idéal pour afficher en continu une mesure de capteur ou réagir instantanément à une action utilisateur.
|
||||
|
||||
{#fig-m08-http-vs-websocket}
|
||||
|
||||
Cette technique nécessite la bibliothèque tierce « ESPAsyncWebServer » couplée à « AsyncTCP », plus avancée que WebServer.h. Elle sera abordée en détail dans le cadre du projet final (Module 12) si le projet choisi le nécessite (ex : dashboard de supervision en direct).
|
||||
|
||||
## 5. Exercice : dashboard de supervision
|
||||
|
||||
En combinant les modules précédents, créez une page web affichant :
|
||||
|
||||
- La température et l'humidité actuelles (capteur BME280 du Module 5).
|
||||
- Un bouton pour changer la couleur de la LED NeoPixel.
|
||||
- L'adresse IP et le temps de fonctionnement (uptime, via `millis()`) de la carte.
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je sais créer un serveur web basique avec des routes définies.
|
||||
- [ ] Je sais renvoyer une page HTML complète avec des boutons d'action.
|
||||
- [ ] Je comprends la différence entre le modèle HTTP classique et le WebSocket.
|
||||
- [ ] J'ai réalisé le dashboard de supervision combinant capteur, LED et informations système.
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
title: "Module 9 — Bluetooth Low Energy (BLE)"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre les concepts fondamentaux du BLE : serveur, service, caractéristique.
|
||||
- Créer un serveur BLE sur l'ESP32-S3 exposant une caractéristique lisible et modifiable.
|
||||
- Piloter la LED NeoPixel depuis une application smartphone générique (ex : nRF Connect).
|
||||
- Comprendre les notifications BLE pour l'envoi de données en temps réel.
|
||||
|
||||
## 1. BLE sur l'ESP32-S3 : ce qu'il faut savoir
|
||||
|
||||
::: {.callout-warning title="Pas de Bluetooth Classic sur l'ESP32-S3"}
|
||||
Contrairement à l'ESP32 « original », l'ESP32-S3 ne supporte pas le Bluetooth Classic (profil SPP notamment). Seul le Bluetooth Low Energy (BLE) est disponible. Cela signifie qu'on ne peut pas l'apparier comme un simple périphérique série Bluetooth classique : toute communication passe par le modèle GATT (services et caractéristiques) décrit dans ce module.
|
||||
:::
|
||||
|
||||
## 2. Vocabulaire du BLE (modèle GATT)
|
||||
|
||||
| Terme | Définition |
|
||||
|---------------|----------------------------------------|
|
||||
| Serveur BLE | L'appareil qui expose des données (ici, l'ESP32-S3) |
|
||||
| Client BLE | L'appareil qui se connecte et consulte les données (ex : smartphone) |
|
||||
| Service | Un regroupement logique de fonctionnalités, identifié par un UUID unique |
|
||||
| Caractéristique | Une donnée précise à l'intérieur d'un service (ex : une couleur, une température), également identifiée par un UUID |
|
||||
| Notification | Mécanisme permettant au serveur d'envoyer une mise à jour au client sans que celui-ci ait à demander (poll) |
|
||||
|
||||
{#fig-m09-hierarchie-gatt}
|
||||
|
||||
::: {.callout-note title="Qu'est-ce qu'un UUID ?"}
|
||||
Un UUID (Universally Unique Identifier) est un identifiant sur 128 bits garantissant qu'aucun autre service ou caractéristique dans le monde n'utilisera accidentellement le même identifiant. Pour vos projets personnels, générez vos propres UUID aléatoires via un générateur en ligne plutôt que de réutiliser des exemples trouvés sur Internet.
|
||||
:::
|
||||
|
||||
## 3. Créer un serveur BLE minimal
|
||||
|
||||
La bibliothèque BLE est incluse nativement dans le core Arduino-ESP32 (aucune installation nécessaire).
|
||||
|
||||
```cpp
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <BLE2902.h>
|
||||
|
||||
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
|
||||
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
|
||||
|
||||
BLECharacteristic *caracteristique;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
BLEDevice::init("ESP32-S3-Formation");
|
||||
BLEServer *serveur = BLEDevice::createServer();
|
||||
BLEService *service = serveur->createService(SERVICE_UUID);
|
||||
|
||||
caracteristique = service->createCharacteristic(
|
||||
CHARACTERISTIC_UUID,
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
|
||||
);
|
||||
caracteristique->setValue("En attente...");
|
||||
|
||||
service->start();
|
||||
BLEDevice::getAdvertising()->start();
|
||||
Serial.println("Serveur BLE demarre, en attente de connexion...");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Réagir à l'écriture d'une valeur (callback)
|
||||
|
||||
Pour piloter la LED NeoPixel depuis un smartphone, il faut détecter quand le client BLE écrit une nouvelle valeur dans la caractéristique.
|
||||
|
||||
```cpp
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#define NEOPIXEL_PIN 48
|
||||
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
|
||||
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
|
||||
|
||||
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
class MonCallback : public BLECharacteristicCallbacks {
|
||||
void onWrite(BLECharacteristic *car) override {
|
||||
String valeur = car->getValue();
|
||||
Serial.print("Valeur recue: ");
|
||||
Serial.println(valeur);
|
||||
|
||||
if (valeur == "rouge") pixel.setPixelColor(0, pixel.Color(255,0,0));
|
||||
else if (valeur == "vert") pixel.setPixelColor(0, pixel.Color(0,255,0));
|
||||
else if (valeur == "bleu") pixel.setPixelColor(0, pixel.Color(0,0,255));
|
||||
else if (valeur == "off") pixel.setPixelColor(0, pixel.Color(0,0,0));
|
||||
pixel.show();
|
||||
}
|
||||
};
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pixel.begin();
|
||||
|
||||
BLEDevice::init("ESP32-S3-Formation");
|
||||
BLEServer *serveur = BLEDevice::createServer();
|
||||
BLEService *service = serveur->createService(SERVICE_UUID);
|
||||
|
||||
BLECharacteristic *car = service->createCharacteristic(
|
||||
CHARACTERISTIC_UUID,
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
|
||||
);
|
||||
car->setCallbacks(new MonCallback());
|
||||
car->setValue("off");
|
||||
|
||||
service->start();
|
||||
BLEDevice::getAdvertising()->start();
|
||||
Serial.println("Pret. Connectez-vous avec nRF Connect.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Tester avec une application smartphone
|
||||
|
||||
Sans développer d'application dédiée, on peut tester un serveur BLE avec une application générique :
|
||||
|
||||
- nRF Connect for Mobile (Nordic Semiconductor) — disponible sur Android et iOS.
|
||||
- LightBlue — alternative populaire sur iOS.
|
||||
|
||||
### Étapes de test
|
||||
|
||||
- Téléverser le programme, ouvrir le moniteur série pour confirmer le démarrage.
|
||||
- Ouvrir l'application, scanner les appareils BLE à proximité, repérer « ESP32-S3-Formation ».
|
||||
- Se connecter, ouvrir le service correspondant à l'UUID défini.
|
||||
- Sélectionner la caractéristique, utiliser la fonction d'écriture pour envoyer le texte « rouge », « vert », « bleu » ou « off ».
|
||||
|
||||
## 6. Les notifications : envoyer des données du serveur vers le client
|
||||
|
||||
À l'inverse de l'écriture (client → serveur), les notifications permettent au serveur d'envoyer spontanément une donnée au client sans attendre une requête (par exemple, la valeur d'un capteur mise à jour toutes les secondes).
|
||||
|
||||
{#fig-m09-sequence-ble width=85%}
|
||||
|
||||
```cpp
|
||||
// A ajouter a la caracteristique lors de sa creation :
|
||||
// BLECharacteristic::PROPERTY_NOTIFY en plus de READ/WRITE
|
||||
// et service->createCharacteristic(..., ...)->addDescriptor(new BLE2902());
|
||||
|
||||
// Puis, dans loop(), pour notifier une nouvelle temperature :
|
||||
String valeur = String(bme.readTemperature());
|
||||
caracteristique->setValue(valeur.c_str());
|
||||
caracteristique->notify();
|
||||
```
|
||||
|
||||
## 7. Exercice combiné : télécommande BLE
|
||||
|
||||
Réalisez une carte pilotable entièrement en BLE : écriture pour changer la couleur de la LED NeoPixel, et notification envoyant la température lue par le BME280 (Module 5) toutes les 5 secondes vers l'application smartphone.
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je comprends la différence entre service et caractéristique dans le modèle GATT.
|
||||
- [ ] Je sais créer un serveur BLE exposant une caractéristique en lecture/écriture.
|
||||
- [ ] J'ai testé la communication avec une application smartphone générique.
|
||||
- [ ] Je comprends le rôle des notifications BLE.
|
||||
- [ ] J'ai réalisé la télécommande BLE combinant écriture et notification.
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: "Module 10 — Gestion de l'énergie"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre les enjeux de consommation d'un projet ESP32-S3 alimenté sur batterie.
|
||||
- Distinguer les différents modes de veille (modem-sleep, light sleep, deep sleep).
|
||||
- Mettre en œuvre un réveil programmé (timer) et un réveil sur évènement externe (GPIO).
|
||||
- Concevoir un capteur autonome optimisé pour l'autonomie sur batterie.
|
||||
|
||||
## 1. Pourquoi se soucier de la consommation ?
|
||||
|
||||
Un ESP32-S3 alimenté en continu (Wi-Fi actif, calcul permanent) consomme couramment 100 à 250 mA. Sur batterie, cela vide une pile de 2000 mAh en moins d'une journée. Pour un capteur autonome destiné à fonctionner plusieurs mois sur pile, il faut exploiter les modes de veille pour ne consommer significativement que pendant les brefs instants où la carte est réellement active.
|
||||
|
||||
## 2. Les différents modes de veille
|
||||
|
||||
| Mode | Consommation typique | État conservé | Réveil possible via |
|
||||
|--------------|------------------------|----------------------------------------|--------------------------------|
|
||||
| Actif (normal) | 80-250 mA | Tout | — |
|
||||
| Modem-sleep | \~20-30 mA | RAM, CPU actif par intermittence | Automatique (Wi-Fi/BT en veille) |
|
||||
| Light sleep | \~0,8 mA | RAM et état conservés | Timer, GPIO, UART... |
|
||||
| Deep sleep | \~10-150 µA | RTC RAM uniquement, redémarrage du programme | Timer, GPIO (EXT0/EXT1) |
|
||||
|
||||
{#fig-m10-consommation-modes}
|
||||
|
||||
::: {.callout-note title="Le deep sleep redémarre le programme"}
|
||||
Contrairement au light sleep, sortir du deep sleep provoque un redémarrage complet du programme (comme après un reset) : `setup()` est ré-exécuté depuis le début. Seule la zone mémoire RTC (déclarée avec `RTC_DATA_ATTR`) est préservée entre deux cycles de deep sleep, ce qui permet de conserver un compteur ou un état minimal.
|
||||
:::
|
||||
|
||||
## 3. Deep sleep avec réveil par timer
|
||||
|
||||
Cas d'usage typique : un capteur qui se réveille toutes les X minutes, effectue une mesure, l'envoie, puis se rendort.
|
||||
|
||||
{#fig-m10-cycle-deep-sleep}
|
||||
|
||||
```cpp
|
||||
#define TEMPS_REVEIL_SECONDES 60
|
||||
|
||||
RTC_DATA_ATTR int compteurReveils = 0; // conserve sa valeur entre deux deep sleep
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(200);
|
||||
|
||||
compteurReveils++;
|
||||
Serial.printf("Reveil numero %d\n", compteurReveils);
|
||||
|
||||
// ... effectuer ici la mesure, l'envoi de donnees, etc. ...
|
||||
|
||||
Serial.println("Retour en deep sleep.");
|
||||
esp_sleep_enable_timer_wakeup(TEMPS_REVEIL_SECONDES * 1000000ULL); // en microsecondes
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Jamais atteint : le deep sleep redemarre au setup()
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Deep sleep avec réveil sur GPIO (bouton)
|
||||
|
||||
Autre cas d'usage : une carte qui reste endormie jusqu'à ce qu'un utilisateur appuie sur un bouton.
|
||||
|
||||
```cpp
|
||||
#define BOUTON_REVEIL GPIO_NUM_4
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(200);
|
||||
|
||||
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
|
||||
if (cause == ESP_SLEEP_WAKEUP_EXT0) {
|
||||
Serial.println("Reveil provoque par le bouton.");
|
||||
} else {
|
||||
Serial.println("Premier demarrage (pas un reveil).");
|
||||
}
|
||||
|
||||
// ... traitement ...
|
||||
|
||||
esp_sleep_enable_ext0_wakeup(BOUTON_REVEIL, 0); // reveil sur niveau LOW
|
||||
Serial.println("Deep sleep, en attente d'un appui bouton...");
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
```
|
||||
|
||||
::: {.callout-note title="Broche compatible RTC obligatoire"}
|
||||
Le réveil par GPIO (EXT0/EXT1) en deep sleep nécessite d'utiliser une broche appartenant au domaine RTC du microcontrôleur. Toutes les broches ne sont pas éligibles : vérifiez la documentation de votre référence de carte pour identifier les GPIO compatibles RTC.
|
||||
:::
|
||||
|
||||
## 5. Bonnes pratiques pour un projet sur batterie
|
||||
|
||||
- Couper le Wi-Fi/BLE dès que la transmission de données est terminée (`WiFi.disconnect(true)`; `WiFi.mode(WIFI_OFF)`;).
|
||||
- Réduire la fréquence CPU si le calcul n'est pas critique en performance (`setCpuFrequencyMhz()`).
|
||||
- Limiter le temps d'éveil au strict nécessaire : mesurer, envoyer, dormir.
|
||||
- Éviter les composants annexes qui consomment en continu (LED témoin, capteur non coupé) — prévoir une alimentation commutée si besoin (MOSFET de coupure).
|
||||
- Pour la LED NeoPixel : elle consomme significativement plus qu'une LED simple (jusqu'à 60 mA à pleine luminosité blanche) — la couper explicitement (couleur noire) avant le deep sleep, et limiter sa luminosité (setBrightness) pour un usage sur batterie.
|
||||
|
||||
## 6. Exercice : capteur météo autonome
|
||||
|
||||
En combinant les modules précédents, réalisez un capteur qui :
|
||||
|
||||
- Se réveille toutes les 5 minutes via un timer deep sleep.
|
||||
- Lit la température et l'humidité (BME280, Module 5).
|
||||
- Affiche un court flash de couleur sur la LED NeoPixel selon la température (bleu/vert/rouge, comme au Module 5), puis l'éteint.
|
||||
- Retourne en deep sleep.
|
||||
- Affiche dans le moniteur série, à chaque réveil, le nombre total de réveils depuis la mise sous tension (compteur en `RTC_DATA_ATTR`).
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je sais différencier modem-sleep, light sleep et deep sleep.
|
||||
- [ ] Je sais programmer un réveil par timer en deep sleep.
|
||||
- [ ] Je sais programmer un réveil par GPIO en deep sleep, sur une broche compatible RTC.
|
||||
- [ ] Je connais au moins trois bonnes pratiques pour réduire la consommation d'un projet sur batterie.
|
||||
- [ ] J'ai réalisé le capteur météo autonome en deep sleep.
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
title: "Module 11 — IoT & Cloud : le protocole MQTT"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Comprendre le principe du protocole MQTT (publish/subscribe) et son intérêt en IoT.
|
||||
- Connecter l'ESP32-S3 à un broker MQTT.
|
||||
- Publier des données de capteurs et s'abonner à des commandes.
|
||||
- Visualiser les données dans un tableau de bord (Node-RED ou Home Assistant).
|
||||
|
||||
## 1. Pourquoi MQTT plutôt qu'HTTP pour l'IoT ?
|
||||
|
||||
Le modèle HTTP vu au Module 7 fonctionne bien pour des requêtes ponctuelles, mais devient lourd pour des objets connectés qui doivent échanger fréquemment de petits messages (température toutes les secondes, changement d'état instantané). MQTT (Message Queuing Telemetry Transport) est un protocole léger conçu spécifiquement pour l'IoT, basé sur un modèle publish/subscribe (publication/abonnement) plutôt que sur des requêtes/réponses.
|
||||
|
||||
## 2. Le modèle publish/subscribe
|
||||
|
||||
| Élément | Rôle |
|
||||
|-------------|----------------------------------------|
|
||||
| Broker | Serveur central qui reçoit les messages publiés et les redistribue aux abonnés (ex : Mosquitto, HiveMQ) |
|
||||
| Topic (sujet) | Chaîne hiérarchique identifiant un flux de données (ex : maison/salon/temperature) |
|
||||
| Publish | Envoyer un message sur un topic donné |
|
||||
| Subscribe | S'abonner à un topic pour recevoir tous les messages publiés dessus |
|
||||
|
||||
{#fig-m11-arborescence-topics width=90%}
|
||||
|
||||
Avantage clé : les appareils ne communiquent jamais directement entre eux — tout transite par le broker, ce qui simplifie grandement l'architecture (un capteur ne connaît pas l'adresse du tableau de bord qui affichera ses données, il publie simplement sur un topic).
|
||||
|
||||
{#fig-m11-publish-subscribe}
|
||||
|
||||
## 3. Choisir un broker MQTT
|
||||
|
||||
- Mosquitto : broker open-source, installable localement (Raspberry Pi, PC, serveur domestique) ou utilisable en test public (test.mosquitto.org).
|
||||
- HiveMQ Cloud : offre gratuite hébergée, pratique pour démarrer sans installation.
|
||||
- Le broker intégré à Home Assistant : pratique si vous utilisez déjà cette plateforme domotique.
|
||||
|
||||
::: {.callout-important title="Sécurité en production"}
|
||||
Pour un usage de test/formation, un broker public non sécurisé (comme test.mosquitto.org) est acceptable. Pour un déploiement réel, utilisez toujours une connexion chiffrée (MQTTS, port 8883) avec authentification par identifiants ou certificat.
|
||||
:::
|
||||
|
||||
## 4. Connexion MQTT avec PubSubClient
|
||||
|
||||
Installez la bibliothèque « PubSubClient » (Nick O'Leary) depuis le gestionnaire de bibliothèques.
|
||||
|
||||
```cpp
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
const char* ssid = "NomDuReseau";
|
||||
const char* motDePasse = "MotDePasse";
|
||||
const char* brokerMQTT = "test.mosquitto.org";
|
||||
const int portMQTT = 1883;
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
|
||||
void connecterMQTT() {
|
||||
while (!client.connected()) {
|
||||
Serial.print("Connexion au broker MQTT...");
|
||||
String idClient = "ESP32S3-" + String(random(0xffff), HEX);
|
||||
if (client.connect(idClient.c_str())) {
|
||||
Serial.println(" connecte !");
|
||||
} else {
|
||||
Serial.printf(" echec, code erreur: %d. Nouvelle tentative dans 2s.\n", client.state());
|
||||
delay(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.begin(ssid, motDePasse);
|
||||
while (WiFi.status() != WL_CONNECTED) { delay(500); }
|
||||
|
||||
client.setServer(brokerMQTT, portMQTT);
|
||||
connecterMQTT();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!client.connected()) connecterMQTT();
|
||||
client.loop(); // A appeler en permanence, comme handleClient() pour le serveur web
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Publier des données de capteur
|
||||
|
||||
```cpp
|
||||
unsigned long dernierEnvoi = 0;
|
||||
const unsigned long intervalle = 5000;
|
||||
|
||||
void loop() {
|
||||
if (!client.connected()) connecterMQTT();
|
||||
client.loop();
|
||||
|
||||
unsigned long maintenant = millis();
|
||||
if (maintenant - dernierEnvoi >= intervalle) {
|
||||
dernierEnvoi = maintenant;
|
||||
|
||||
float temperature = bme.readTemperature();
|
||||
char message[10];
|
||||
dtostrf(temperature, 4, 1, message); // conversion float -> texte
|
||||
|
||||
client.publish("formation/esp32s3/temperature", message);
|
||||
Serial.printf("Publie: %s\n", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. S'abonner à un topic (recevoir des commandes)
|
||||
|
||||
Réutilisons la LED NeoPixel : elle pourra être pilotée à distance en s'abonnant à un topic de commande.
|
||||
|
||||
```cpp
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#define NEOPIXEL_PIN 48
|
||||
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
void callbackMQTT(char* topic, byte* payload, unsigned int longueur) {
|
||||
String message;
|
||||
for (unsigned int i = 0; i < longueur; i++) message += (char)payload[i];
|
||||
|
||||
Serial.printf("Message recu sur %s: %s\n", topic, message.c_str());
|
||||
|
||||
if (message == "rouge") pixel.setPixelColor(0, pixel.Color(255,0,0));
|
||||
else if (message == "vert") pixel.setPixelColor(0, pixel.Color(0,255,0));
|
||||
else if (message == "bleu") pixel.setPixelColor(0, pixel.Color(0,0,255));
|
||||
else if (message == "off") pixel.setPixelColor(0, pixel.Color(0,0,0));
|
||||
pixel.show();
|
||||
}
|
||||
|
||||
void connecterMQTT() {
|
||||
while (!client.connected()) {
|
||||
String idClient = "ESP32S3-" + String(random(0xffff), HEX);
|
||||
if (client.connect(idClient.c_str())) {
|
||||
client.subscribe("formation/esp32s3/commande");
|
||||
} else {
|
||||
delay(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pixel.begin();
|
||||
// ... connexion WiFi ...
|
||||
client.setServer(brokerMQTT, portMQTT);
|
||||
client.setCallback(callbackMQTT);
|
||||
connecterMQTT();
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Visualiser les données : Node-RED ou Home Assistant
|
||||
|
||||
Une fois les données publiées sur le broker, il est possible de les visualiser sans écrire de code supplémentaire :
|
||||
|
||||
- Node-RED : outil de programmation visuelle par flux, avec un nœud MQTT natif et un tableau de bord (dashboard) configurable en quelques clics.
|
||||
- Home Assistant : plateforme domotique qui peut découvrir automatiquement les topics MQTT et générer des cartes de suivi (courbes de température, boutons de commande).
|
||||
|
||||
::: {.callout-note title="Portée pédagogique"}
|
||||
La mise en place détaillée de Node-RED ou Home Assistant dépasse le cadre strict de la programmation Arduino/ESP32 ; ce module se concentre sur la partie « objet connecté » (ESP32-S3). L'installation et la configuration de l'outil de visualisation pourront faire l'objet d'un support complémentaire selon les besoins du groupe.
|
||||
:::
|
||||
|
||||
## 8. Exercice final du module : station météo connectée
|
||||
|
||||
En combinant tous les modules précédents, réalisez une station météo IoT complète :
|
||||
|
||||
- Lecture température/humidité via le BME280 (Module 5).
|
||||
- Publication MQTT toutes les 30 secondes sur un topic dédié.
|
||||
- Abonnement à un topic de commande pilotant la couleur de la LED NeoPixel.
|
||||
- Affichage local sur l'écran OLED de la dernière valeur publiée et de l'état de connexion MQTT.
|
||||
|
||||
## Checklist de fin de module
|
||||
|
||||
- [ ] Je comprends le modèle publish/subscribe et le rôle du broker.
|
||||
- [ ] Je sais connecter l'ESP32-S3 à un broker MQTT.
|
||||
- [ ] Je sais publier une donnée de capteur sur un topic.
|
||||
- [ ] Je sais m'abonner à un topic et réagir à un message reçu.
|
||||
- [ ] J'ai réalisé la station météo connectée combinant capteur, MQTT, OLED et LED NeoPixel.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: "Module 12 — Projet final"
|
||||
---
|
||||
|
||||
## Objectifs du module
|
||||
|
||||
- Mobiliser l'ensemble des compétences acquises (GPIO, PWM, ADC, I2C, interruptions, Wi-Fi, BLE, énergie, MQTT) dans un projet personnel cohérent.
|
||||
- Rédiger un cahier des charges simple avant de coder.
|
||||
- Structurer un programme Arduino de taille conséquente (plusieurs fichiers, fonctions bien séparées).
|
||||
- Présenter et démontrer son projet.
|
||||
|
||||
## 1. Méthodologie : avant de coder
|
||||
|
||||
Un projet réussi commence par une phase de cadrage, même brève. Avant d'ouvrir l'IDE, répondez par écrit aux questions suivantes :
|
||||
|
||||
- Quel problème mon projet résout-il, ou quel besoin satisfait-il ?
|
||||
- Quelles sont les fonctionnalités indispensables (MVP - produit minimum viable) et lesquelles sont « bonus » si le temps le permet ?
|
||||
- Quel matériel est nécessaire (capteurs, actionneurs, alimentation) et est-il disponible ?
|
||||
- Le projet doit-il fonctionner de façon autonome (batterie) ou reste-t-il branché en continu ?
|
||||
- Quels modules de la formation seront mobilisés ?
|
||||
|
||||
{#fig-m12-demarche-projet}
|
||||
|
||||
## 2. Exemples de projets (par ordre de complexité croissante)
|
||||
|
||||
| Projet | Modules mobilisés | Description |
|
||||
|------------------------------|---------------------|----------------------------------------|
|
||||
| Station météo de bureau | 1, 2, 3, 5 | Écran OLED affichant température/humidité locales, LED NeoPixel comme indicateur de confort |
|
||||
| Station météo connectée | 5, 7 ou 11 | La précédente, mais publiant les données sur Internet (API ou MQTT) et consultable à distance |
|
||||
| Capteur autonome sur batterie | 5, 10, 11 | Mesure périodique avec deep sleep, transmission MQTT, autonomie de plusieurs semaines |
|
||||
| Domotique locale | 2, 3, 8 | Serveur web pilotant plusieurs relais/LED, avec retour d'état en temps réel |
|
||||
| Télécommande BLE personnalisée | 6, 9 | Boîtier avec boutons/encodeur pilotant un appareil via BLE (ex : diaporama, musique) |
|
||||
| Jeu de réflexes lumineux | 2, 4, 6 | Jeu type « Simon » utilisant plusieurs LED (ou la NeoPixel en RGB) et un buzzer, avec score affiché en série |
|
||||
|
||||
::: {.callout-note title="Un projet libre est aussi possible"}
|
||||
Cette liste est indicative : n'importe quel projet personnel combinant au moins trois modules de la formation et incluant une utilisation réfléchie de la LED NeoPixel est recevable.
|
||||
:::
|
||||
|
||||
## 3. Cahier des charges type (à compléter)
|
||||
|
||||
| Rubrique | À compléter |
|
||||
|----------------------------------------|---------------|
|
||||
| Nom du projet | |
|
||||
| Objectif en une phrase | |
|
||||
| Fonctionnalités indispensables | |
|
||||
| Fonctionnalités bonus (si le temps le permet) | |
|
||||
| Matériel nécessaire | |
|
||||
| Modules de la formation mobilisés | |
|
||||
| Contraintes (autonomie, taille, coût...) | |
|
||||
|
||||
## 4. Structurer un projet de taille conséquente
|
||||
|
||||
Au-delà d'une centaine de lignes, un seul fichier .ino devient difficile à maintenir. L'IDE Arduino permet de scinder un croquis en plusieurs onglets/fichiers, automatiquement inclus à la compilation :
|
||||
|
||||
- MonProjet.ino : contient uniquement `setup()` et `loop()`, orchestrant les appels aux autres fonctions.
|
||||
- capteurs.h / capteurs.cpp (ou simplement un onglet capteurs.ino) : fonctions de lecture et traitement des capteurs.
|
||||
- affichage.ino : fonctions liées à l'écran OLED et à la LED NeoPixel.
|
||||
- reseau.ino : fonctions Wi-Fi/MQTT/BLE.
|
||||
|
||||
{#fig-m12-structure-projet}
|
||||
|
||||
::: {.callout-tip title="Bonne pratique de structuration"}
|
||||
Une fonction doit avoir une seule responsabilité claire (ex : `lireTemperature()`, `afficherEcran()`, `publierMQTT()`). Cette organisation facilite grandement le débogage et la relecture, surtout en formation collective où le code peut être partagé et commenté entre apprenants.
|
||||
:::
|
||||
|
||||
## 5. Grille d'auto-évaluation avant présentation
|
||||
|
||||
| Critère | Validé ? |
|
||||
|----------------------------------------|------------|
|
||||
| Le projet répond au besoin défini dans le cahier des charges | ☐ |
|
||||
| Le code compile sans erreur ni avertissement bloquant | ☐ |
|
||||
| Les fonctions sont nommées clairement et bien séparées | ☐ |
|
||||
| Le programme gère les cas d'erreur (capteur non détecté, Wi-Fi indisponible...) | ☐ |
|
||||
| La LED NeoPixel est utilisée de façon pertinente (état, alerte, feedback) | ☐ |
|
||||
| Le projet a été testé sur une durée représentative de son usage réel | ☐ |
|
||||
| Une courte documentation (schéma de câblage, liste du matériel) accompagne le code | ☐ |
|
||||
|
||||
## 6. Présentation du projet
|
||||
|
||||
Chaque participant présente son projet en quelques minutes :
|
||||
|
||||
- Démonstration du fonctionnement réel sur la carte.
|
||||
- Explication des choix techniques (pourquoi ce protocole, cette architecture).
|
||||
- Difficultés rencontrées et solutions apportées.
|
||||
- Pistes d'amélioration si le projet devait être poursuivi.
|
||||
|
||||
## Conclusion de la formation
|
||||
|
||||
Ce parcours a couvert la prise en main du matériel, les entrées/sorties, les signaux analogiques, les bus de communication, la gestion du temps et des évènements, la connectivité Wi-Fi et BLE, la gestion de l'énergie et l'IoT via MQTT — un socle de compétences complet pour aborder de façon autonome tout projet basé sur l'ESP32-S3 avec le framework Arduino. La suite naturelle consiste à approfondir un domaine particulier selon vos projets futurs : traitement du signal, IA embarquée (TinyML), protocoles radio longue portée (LoRa), ou intégration à des écosystèmes domotiques existants.
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: "Synthèse de la formation & liste de matériel"
|
||||
---
|
||||
|
||||
## 1. Vue d'ensemble des modules
|
||||
|
||||
{#fig-m13-parcours-formation}
|
||||
|
||||
| Module | Titre | Compétences clés |
|
||||
|----------|-----------------------------|----------------------------------------|
|
||||
| 0 | Prérequis & Installation | Arduino IDE, pilotes USB, ajout du support ESP32, premier Blink |
|
||||
| 1 | Découverte du microcontrôleur | Architecture ESP32-S3, différences vs Arduino Uno, lecture du pinout |
|
||||
| 2 | GPIO & LED NeoPixel | Entrées/sorties numériques, anti-rebond, pilotage de la LED RGB intégrée |
|
||||
| 3 | PWM & ADC | Variateur de luminosité, lecture analogique, potentiomètre |
|
||||
| 4 | Communication série & debug | UART, ports série multiples, console de commandes |
|
||||
| 5 | Bus I2C et SPI | Scanner I2C, écran OLED, capteur BME280, comparatif I2C/SPI |
|
||||
| 6 | Interruptions & Timers | `millis()`, `attachInterrupt()`, encodeur rotatif, timers matériels |
|
||||
| 7 | Wi-Fi | Mode station/point d'accès, requêtes HTTP, API météo |
|
||||
| 8 | Serveur Web embarqué | WebServer.h, page HTML de contrôle, notion de WebSocket |
|
||||
| 9 | Bluetooth Low Energy | Serveur BLE, services/caractéristiques, notifications |
|
||||
| 10 | Gestion de l'énergie | Modes de veille, deep sleep, réveil timer/GPIO, capteur autonome |
|
||||
| 11 | IoT & MQTT | Broker MQTT, publish/subscribe, station météo connectée |
|
||||
| 12 | Projet final | Cahier des charges, structuration de projet, présentation |
|
||||
|
||||
## 2. Liste de matériel consolidée (à acheter)
|
||||
|
||||
Cette liste regroupe l'ensemble des composants nécessaires pour réaliser tous les exercices de la formation. Les quantités indiquées correspondent à un poste de travail (un apprenant ou un binôme).
|
||||
|
||||
### 2.1 Carte et informatique
|
||||
|
||||
| Article | Quantité | Modules concernés | Remarques |
|
||||
|----------------------------------------|------------|---------------------|----------------------------------------|
|
||||
| Carte ESP32-S3-DevKitC-1 (WROOM-1) | 1 | Tous | Vérifier la présence d'une LED NeoPixel intégrée et la référence exacte du pinout |
|
||||
| Câble USB-C (données, pas uniquement charge) | 1 | Tous | Indispensable pour la programmation et l'alimentation |
|
||||
| Ordinateur avec Arduino IDE installé | 1 | Tous | Voir Module 0 pour l'installation |
|
||||
|
||||
### 2.2 Composants électroniques de base
|
||||
|
||||
| Article | Quantité | Modules concernés | Remarques |
|
||||
|----------------------------------------|------------|------------------------------|-------------------------------------|
|
||||
| Breadboard (plaque d'essai) 830 points | 1 | 2, 3, 4, 5, 6 | Support de câblage sans soudure |
|
||||
| Kit de fils de câblage (jumpers M-M, M-F) | 1 kit | Tous les TP câblés | Prévoir mâle-mâle et mâle-femelle |
|
||||
| LED 5 mm (couleurs variées) | 4 à 6 | Module 2 (chenillard) | Rouge, vert, bleu, jaune conseillées |
|
||||
| Résistances 220 Ω | 6 | Module 2 | Protection des LED |
|
||||
| Résistances 10 kΩ | 2 | Complément pull-down si besoin | Facultatif selon montage |
|
||||
| Bouton-poussoir (push-button) 4 pattes | 2 | Modules 2, 4, 6 | Un de rechange conseillé |
|
||||
| Potentiomètre rotatif 10 kΩ | 1 | Module 3 | Avec bouton de préhension si possible |
|
||||
|
||||
### 2.3 Modules et capteurs
|
||||
|
||||
| Article | Quantité | Modules concernés | Remarques |
|
||||
|----------------------------------------|------------|---------------------|----------------------------------------|
|
||||
| Écran OLED I2C SSD1306 0,96" (128x64) | 1 | 5, 7, 8, 11 | Vérifier alimentation 3,3V compatible |
|
||||
| Capteur BME280 (température/humidité/pression), I2C | 1 | 5, 10, 11 | Attention aux clones BMP280 (pas d'humidité) : bien vérifier la référence BME280 |
|
||||
| Encodeur rotatif avec bouton intégré (type KY-040) | 1 | 6 | Génère 2 signaux CLK/DT + bouton poussoir |
|
||||
|
||||
### 2.4 Alimentation et énergie (Module 10 et projets autonomes)
|
||||
|
||||
| Article | Quantité | Modules concernés | Remarques |
|
||||
|----------------------------------------|------------|-----------------------|----------------------------------------|
|
||||
| Pile ou batterie externe (powerbank, ou support piles AA/LiPo) | 1 | Module 10, projet final | Selon le projet choisi ; un support 2xAA avec câble JST suffit pour les tests |
|
||||
| Câble/connecteur adapté à l'alimentation choisie | 1 | Module 10 | Vérifier compatibilité avec l'entrée d'alimentation de la carte |
|
||||
|
||||
### 2.5 Facultatif / selon projet final
|
||||
|
||||
Selon le projet choisi par chaque apprenant au Module 12, du matériel additionnel peut être nécessaire : relais, buzzer, servomoteur, capteur supplémentaire (PIR, ultrason...), boîtier d'impression 3D, etc. Ce matériel n'est pas listé ici car dépendant du projet individuel — à définir une fois le cahier des charges rédigé (voir Module 12, section 3).
|
||||
|
||||
::: {.callout-note title="Application smartphone (gratuite, pas d'achat)"}
|
||||
Pour le Module 9 (BLE), chaque apprenant doit disposer d'un smartphone avec l'application gratuite « nRF Connect for Mobile » (Android/iOS) ou équivalent (LightBlue sur iOS). Aucun matériel supplémentaire à acheter pour ce module.
|
||||
:::
|
||||
|
||||
## 3. Tableau récapitulatif quantités globales
|
||||
|
||||
| Catégorie | Nombre de références distinctes |
|
||||
|--------------------------------|-----------------------------------|
|
||||
| Carte et informatique | 3 |
|
||||
| Composants électroniques de base | 7 |
|
||||
| Modules et capteurs | 3 |
|
||||
| Alimentation / énergie | 2 |
|
||||
|
||||
::: {.callout-tip title="Astuce achat groupé"}
|
||||
La plupart de ces composants (hors carte ESP32-S3 et écran OLED/BME280) sont généralement disponibles dans un kit de démarrage Arduino/ESP32 générique, ce qui peut s'avérer plus économique que des achats à l'unité pour un groupe de plusieurs apprenants.
|
||||
:::
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Génère les schémas (SVG + PNG) de tous les modules, ou de ceux passés en argument.
|
||||
|
||||
Usage : python schemas/generer.py [m03 ...]
|
||||
"""
|
||||
import importlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
ICI = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(ICI))
|
||||
|
||||
DOSSIER_SVG = ICI / "svg"
|
||||
DOSSIER_PNG = ICI / "png"
|
||||
|
||||
|
||||
def modules_figures(filtres=()):
|
||||
"""Renvoie {préfixe 'mXX': [Figure, ...]} pour chaque fichier mXX_*.py."""
|
||||
resultat = {}
|
||||
for fichier in sorted(ICI.glob("m[0-9][0-9]_*.py")):
|
||||
prefixe = fichier.stem[:3]
|
||||
if filtres and prefixe not in filtres:
|
||||
continue
|
||||
resultat[prefixe] = importlib.import_module(fichier.stem).FIGURES
|
||||
return resultat
|
||||
|
||||
|
||||
def enregistrer(fig, id_):
|
||||
if hasattr(fig, "fig"): # dessin schemdraw (backend matplotlib)
|
||||
fig = fig.fig
|
||||
fig.savefig(DOSSIER_SVG / f"{id_}.svg", metadata={"Date": None}) # pas de date : diff git stable
|
||||
fig.savefig(DOSSIER_PNG / f"{id_}.png", dpi=200)
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def main():
|
||||
DOSSIER_SVG.mkdir(exist_ok=True)
|
||||
DOSSIER_PNG.mkdir(exist_ok=True)
|
||||
for prefixe, figures in modules_figures(sys.argv[1:]).items():
|
||||
for f in figures:
|
||||
enregistrer(f.dessiner(), f.id)
|
||||
print(f"{f.id} : OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Module 0 — Prérequis & installation."""
|
||||
from outils import boite, chronogramme, fleche, toile
|
||||
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
|
||||
|
||||
|
||||
def chaine_developpement():
|
||||
fig, ax = toile(16, 5.6)
|
||||
# Zones ordinateur / carte
|
||||
boite(ax, 5.0, 3.0, 9.4, 4.6, couleur=GRIS, fond="#f9fafb")
|
||||
boite(ax, 13.2, 3.0, 4.6, 4.6, couleur=BLEU, fond="#f5f8fe")
|
||||
ax.text(0.6, 4.8, "Ordinateur — Arduino IDE", fontsize=9, color=GRIS, fontweight="bold")
|
||||
ax.text(11.2, 4.8, "Carte ESP32-S3", fontsize=9, color=BLEU, fontweight="bold")
|
||||
code = boite(ax, 2.0, 3.3, 2.6, 1.4, "Croquis\n(.ino)", couleur=BLEU)
|
||||
comp = boite(ax, 5.6, 3.3, 2.6, 1.4, "Compilation\n✓ Vérifier", couleur=BLEU)
|
||||
moni = boite(ax, 4.2, 1.4, 5.4, 0.9, "Moniteur série — 115200 bauds", couleur=VERT, taille=8.5)
|
||||
flash = boite(ax, 13.2, 3.3, 3.2, 1.4, "Mémoire Flash\n(programme)", couleur=ORANGE)
|
||||
cpu = boite(ax, 13.2, 1.4, 3.2, 0.9, "Exécution", couleur=ORANGE)
|
||||
fleche(ax, code["d"], comp["g"])
|
||||
fleche(ax, comp["d"], flash["g"], "Téléverser →\n(câble USB)", taille=8)
|
||||
fleche(ax, flash["b"], cpu["h"])
|
||||
fleche(ax, cpu["g"], moni["d"], "Serial.println()", couleur=VERT, taille=8)
|
||||
ax.text(8.0, 0.1, "Le même câble USB sert au téléversement et au moniteur série (USB CDC).",
|
||||
ha="center", fontsize=7.5, color=GRIS, style="italic")
|
||||
return fig
|
||||
|
||||
|
||||
def boot_reset():
|
||||
voies = [
|
||||
("BOOT", [(0, 1), (1, 0), (4, 1)]),
|
||||
("RESET", [(0, 1), (2, 0), (2.6, 1)]),
|
||||
]
|
||||
fig, ax, y = chronogramme(voies, 6, largeur_cm=15, hauteur_voie_cm=1.6,
|
||||
couleurs={"RESET": ORANGE})
|
||||
ax.set_xticks([])
|
||||
ax.spines["bottom"].set_visible(False)
|
||||
for i in range(2):
|
||||
ax.text(6.05, y(i) + 1, "relâché", fontsize=7, color=GRIS, va="center")
|
||||
ax.text(6.05, y(i), "appuyé", fontsize=7, color=GRIS, va="center")
|
||||
etapes = [(1, "① maintenir BOOT"), (2.3, "② appui bref\nsur RESET"), (4, "③ relâcher BOOT")]
|
||||
for t, txt in etapes:
|
||||
ax.axvline(t, color=GRIS, lw=0.8, ls=":")
|
||||
ax.text(t, 3.05, txt, ha="center", va="bottom", fontsize=8, color=ENCRE)
|
||||
ax.text(5.0, -0.75, "→ la carte démarre en mode téléchargement", ha="center", fontsize=8.5,
|
||||
color=VERT, fontweight="bold")
|
||||
ax.set_ylim(-1.1, 4.1)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m00-chaine-developpement", chaine_developpement),
|
||||
Figure("m00-boot-reset", boot_reset),
|
||||
]
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Module 1 — Découverte du microcontrôleur ESP32-S3."""
|
||||
import matplotlib.pyplot as plt
|
||||
import schemdraw
|
||||
import schemdraw.elements as elm
|
||||
|
||||
from outils import boite, fleche, toile
|
||||
from style import BLEU, CM, ENCRE, GRIS, ORANGE, VERT, Figure
|
||||
|
||||
schemdraw.use("matplotlib")
|
||||
|
||||
|
||||
def architecture():
|
||||
fig, ax = toile(16, 8.2)
|
||||
boite(ax, 6.6, 4.2, 12.6, 7.6, couleur=BLEU, fond="#f5f8fe")
|
||||
ax.text(0.6, 7.6, "ESP32-S3 (puce)", fontsize=10, color=BLEU, fontweight="bold")
|
||||
# Cœurs et mémoire
|
||||
boite(ax, 2.6, 6.2, 3.8, 1.0, "Cœur 0\nXtensa LX7", couleur=BLEU, taille=8.5)
|
||||
boite(ax, 2.6, 5.0, 3.8, 1.0, "Cœur 1\nXtensa LX7", couleur=BLEU, taille=8.5)
|
||||
ax.text(2.6, 4.2, "240 MHz max · FreeRTOS", ha="center", fontsize=7.5, color=GRIS)
|
||||
sram = boite(ax, 6.6, 5.6, 3.0, 2.2, "SRAM interne\n≈ 512 Ko\n(données +\ninstructions)", couleur=VERT, taille=8.5)
|
||||
# Radio
|
||||
boite(ax, 10.6, 6.2, 3.6, 1.0, "Wi-Fi 802.11 b/g/n\n2,4 GHz", couleur=ORANGE, taille=8)
|
||||
boite(ax, 10.6, 5.0, 3.6, 1.0, "Bluetooth 5 LE\n(pas de Classic)", couleur=ORANGE, taille=8)
|
||||
# Périphériques
|
||||
periph = ["GPIO", "ADC 12 bits", "LEDC (PWM)", "UART", "I2C", "SPI", "Timers", "USB OTG"]
|
||||
for i, p in enumerate(periph):
|
||||
x = 1.35 + (i % 4) * 2.85
|
||||
yy = 2.55 if i < 4 else 1.35
|
||||
boite(ax, x, yy, 2.55, 0.9, p, couleur=GRIS, taille=8.5)
|
||||
ax.text(0.6, 3.25, "Périphériques intégrés", fontsize=8, color=GRIS, fontweight="bold")
|
||||
# Bus interne
|
||||
ax.plot([0.7, 12.5], [3.8, 3.8], color=BLEU, lw=2.2, alpha=0.5)
|
||||
# Externe
|
||||
flash = boite(ax, 14.75, 5.6, 2.45, 2.2, "Flash\nexterne\n8–16 Mo\n(programme,\nfichiers)", couleur=VERT, taille=7.5)
|
||||
psram = boite(ax, 14.7, 2.9, 2.3, 1.2, "PSRAM\n(option)", couleur=VERT, taille=8, alpha=0.8)
|
||||
ax.text(14.7, 7.15, "sur le module", ha="center", fontsize=7.5, color=GRIS, style="italic")
|
||||
fleche(ax, (12.9, 5.6), flash["g"], double=True)
|
||||
fleche(ax, (12.9, 2.9), psram["g"], double=True)
|
||||
return fig
|
||||
|
||||
|
||||
def uno_vs_s3():
|
||||
fig, ax = plt.subplots(figsize=(16 * CM, 6.2 * CM))
|
||||
criteres = ["Fréquence (MHz)", "RAM (Ko)", "Flash (Ko)"]
|
||||
uno = [16, 2, 32]
|
||||
s3 = [240, 512, 8192]
|
||||
y = range(len(criteres))
|
||||
ax.barh([i + 0.2 for i in y], uno, height=0.38, color=GRIS, label="Arduino Uno (ATmega328)")
|
||||
ax.barh([i - 0.2 for i in y], s3, height=0.38, color=BLEU, label="ESP32-S3")
|
||||
ax.set_xscale("log")
|
||||
ax.set_xlim(1, 300000)
|
||||
ax.set_yticks(list(y), criteres)
|
||||
ax.invert_yaxis()
|
||||
for i, (u, s) in enumerate(zip(uno, s3)):
|
||||
ax.text(u * 1.15, i + 0.2, f"{u:g}", va="center", fontsize=8, color=ENCRE)
|
||||
lib = f"{s:g}" if s < 8000 else "8192 (8 Mo)"
|
||||
ax.text(s * 1.15, i - 0.2, f"{lib} × {s // u}", va="center", fontsize=8, color=BLEU,
|
||||
fontweight="bold")
|
||||
ax.set_xlabel("échelle logarithmique : chaque graduation = × 10")
|
||||
ax.grid(axis="y", visible=False)
|
||||
ax.legend(loc="lower center", bbox_to_anchor=(0.5, 1.0), ncol=2, fontsize=8, frameon=False)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def diviseur_tension():
|
||||
d = schemdraw.Drawing(show=False)
|
||||
d.config(fontsize=11, unit=2.6, color=ENCRE)
|
||||
d += elm.Dot(open=True).label("Signal 5 V\n(module tiers)", loc="left")
|
||||
d += elm.Resistor().right().label("R1 = 10 kΩ").color(ORANGE)
|
||||
d += (noeud := elm.Dot())
|
||||
d += elm.Line().right().length(2.2)
|
||||
d += elm.Dot(open=True).label("GPIO\n(3,3 V max)", loc="right").color(BLEU)
|
||||
d += elm.Resistor().down().at(noeud.center).label("R2 = 20 kΩ", loc="bottom").color(ORANGE)
|
||||
d += elm.Ground()
|
||||
d += elm.Label().at((noeud.center[0] + 1.2, noeud.center[1] - 2.2)).label(
|
||||
r"$V_{GPIO} = 5\,V \times \frac{R2}{R1 + R2} \approx 3{,}3\,V$", fontsize=11, color=VERT)
|
||||
return d.draw(show=False)
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m01-architecture", architecture),
|
||||
Figure("m01-uno-vs-s3", uno_vs_s3),
|
||||
Figure("m01-diviseur-tension", diviseur_tension),
|
||||
]
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Module 2 — Entrées/sorties numériques & LED RGB NeoPixel."""
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import schemdraw
|
||||
import schemdraw.elements as elm
|
||||
|
||||
from outils import chronogramme
|
||||
from style import BLEU, CM, ENCRE, GRIS, ORANGE, ROUGE, VERT, Figure
|
||||
|
||||
schemdraw.use("matplotlib")
|
||||
|
||||
|
||||
def pull_up():
|
||||
d = schemdraw.Drawing(show=False)
|
||||
d.config(fontsize=10.5, unit=2.4, color=ENCRE)
|
||||
d += (vdd := elm.Vdd().label("3V3"))
|
||||
d += (r := elm.Resistor().down().label("pull-up interne\n≈ 45 kΩ", loc="bottom").color(BLEU))
|
||||
d += (n := elm.Dot())
|
||||
d += (lecture := elm.Line().left().length(2.6).label("vers digitalRead()", loc="top", fontsize=9).color(BLEU))
|
||||
d += elm.Line().right().at(n.center).length(2.8)
|
||||
d += (gpio := elm.Dot(open=True).label("GPIO5", loc="top"))
|
||||
d += elm.Line().right().length(1.2)
|
||||
d += elm.Button().down().label("bouton", loc="bottom").color(ORANGE)
|
||||
d += elm.Ground()
|
||||
d += elm.EncircleBox([vdd, r, n, lecture, gpio], padx=0.3, pady=0.5).linestyle("--").color(GRIS)
|
||||
d += elm.Label().at((n.center[0] + 1.6, vdd.end[1] + 0.2)).label("à l'intérieur\nde l'ESP32-S3", color=GRIS, fontsize=9)
|
||||
d += elm.Label().at((gpio.center[0] + 3.6, n.center[1] - 2.0)).label(
|
||||
"relâché → HIGH (3,3 V)\nappuyé → LOW (0 V)", fontsize=10, color=VERT)
|
||||
return d.draw(show=False)
|
||||
|
||||
|
||||
def rebonds():
|
||||
t = [0]
|
||||
niv = [1]
|
||||
# Appui avec rebonds autour de 40 ms, relâchement avec rebonds autour de 140 ms
|
||||
for a, b in ((40, 0), (41.2, 1), (42, 0), (43.5, 1), (44.1, 0), (140, 1), (140.8, 0), (141.9, 1), (142.5, 0), (143.2, 1)):
|
||||
t.append(a)
|
||||
niv.append(b)
|
||||
voies = [("broche (brut)", list(zip(t, niv))),
|
||||
("état retenu", [(0, 1), (54, 0), (153, 1)])]
|
||||
fig, ax, y = chronogramme(voies, 200, largeur_cm=16, hauteur_voie_cm=1.7,
|
||||
graduations=range(0, 201, 25), unite="temps (ms)",
|
||||
couleurs={"état retenu": VERT})
|
||||
for x0, x1, txt in ((40, 44.1, "rebonds\n(quelques ms)"), (140, 143.2, "rebonds")):
|
||||
ax.axvspan(x0 - 1, x1 + 1, color=ORANGE, alpha=0.18)
|
||||
ax.text((x0 + x1) / 2, y(0) + 1.25, txt, ha="center", fontsize=7.5, color=ORANGE)
|
||||
ax.annotate("", xy=(54, y(1) + 0.5), xytext=(44.1, y(1) + 0.5),
|
||||
arrowprops=dict(arrowstyle="<->", color=ENCRE, lw=0.9))
|
||||
ax.text(49, y(1) + 0.62, "attente", ha="center", fontsize=7, color=ENCRE)
|
||||
ax.text(97, y(1) + 0.3, "un seul appui détecté", ha="center", fontsize=8, color=VERT, fontweight="bold")
|
||||
ax.set_ylim(-0.5, 3.9)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def chenillard():
|
||||
d = schemdraw.Drawing(show=False)
|
||||
d.config(fontsize=10.5, unit=2.2, color=ENCRE)
|
||||
pins = [elm.IcPin(name=f"GPIO{g}", side="right", anchorname=f"P{g}") for g in (7, 6, 5, 4)]
|
||||
ic = elm.Ic(pins=pins, size=(3.2, 5.4), pinfontsize=10).label("ESP32-S3", loc="center", fontsize=12).color(BLEU)
|
||||
d += ic
|
||||
couleurs = {4: ROUGE, 5: VERT, 6: BLEU, 7: ORANGE}
|
||||
fins = []
|
||||
for g in (4, 5, 6, 7):
|
||||
d += elm.Line().right().at(getattr(ic, f"P{g}")).length(0.6)
|
||||
d += elm.Resistor().right().label("220 Ω", fontsize=9)
|
||||
d += elm.LED().right().color(couleurs[g])
|
||||
d += elm.Line().right().length(0.6)
|
||||
fins.append(d.here)
|
||||
d += elm.Line().at(fins[0]).to(fins[-1])
|
||||
d += elm.Line().down().at(fins[-1]).length(0.6)
|
||||
d += elm.Ground()
|
||||
d += elm.Label().at((fins[0][0] + 0.4, (fins[0][1] + fins[-1][1]) / 2)).label("GND\ncommun", loc="right", fontsize=9, color=GRIS)
|
||||
return d.draw(show=False)
|
||||
|
||||
|
||||
def ws2812():
|
||||
fig = plt.figure(figsize=(16 * CM, 8.6 * CM))
|
||||
# (a) trame 24 bits
|
||||
ax1 = fig.add_axes([0.04, 0.62, 0.92, 0.3])
|
||||
ax1.axis("off")
|
||||
ax1.set_xlim(0, 24)
|
||||
ax1.set_ylim(0, 1.6)
|
||||
for k, (nom, c) in enumerate((("G", VERT), ("R", ROUGE), ("B", BLEU))):
|
||||
for b in range(8):
|
||||
x = k * 8 + b
|
||||
ax1.add_patch(plt.Rectangle((x, 0.3), 1, 0.8, facecolor=c, alpha=0.18 + 0.06 * (7 - b) / 7,
|
||||
edgecolor=c, lw=1))
|
||||
ax1.text(x + 0.5, 0.7, f"{nom}{7 - b}", ha="center", va="center", fontsize=6.5, color=ENCRE)
|
||||
ax1.text(k * 8 + 4, 1.25, {"G": "Vert (8 bits)", "R": "Rouge (8 bits)", "B": "Bleu (8 bits)"}[nom],
|
||||
ha="center", fontsize=8.5, color=c, fontweight="bold")
|
||||
ax1.text(0, 0.02, "(a) Une couleur = 24 bits, envoyés dans l'ordre G-R-B (d'où NEO_GRB), bit de poids fort en premier",
|
||||
fontsize=8, color=GRIS)
|
||||
# (b) codage d'un bit
|
||||
ax2 = fig.add_axes([0.08, 0.12, 0.88, 0.36])
|
||||
for x0, th, tl, nom in ((0, 0.4, 0.85, "bit 0"), (1.8, 0.8, 0.45, "bit 1")):
|
||||
xs = [x0, x0, x0 + th, x0 + th, x0 + th + tl]
|
||||
ys = [0, 1, 1, 0, 0]
|
||||
ax2.plot(xs, ys, color=BLEU, lw=1.8)
|
||||
ax2.fill_between([x0, x0 + th], 0, 1, color=BLEU, alpha=0.12)
|
||||
ax2.text(x0 + (th + tl) / 2, 1.32, nom, ha="center", fontsize=9, fontweight="bold", color=ENCRE)
|
||||
ax2.annotate("", xy=(x0, 1.1), xytext=(x0 + th, 1.1), arrowprops=dict(arrowstyle="<->", lw=0.8))
|
||||
ax2.text(x0 + th / 2, 1.14, f"{th} µs".replace(".", ","), ha="center", va="bottom", fontsize=7.5)
|
||||
ax2.annotate("", xy=(x0 + th, -0.12), xytext=(x0 + th + tl, -0.12), arrowprops=dict(arrowstyle="<->", lw=0.8))
|
||||
ax2.text(x0 + th + tl / 2, -0.2, f"{tl} µs".replace(".", ","), ha="center", va="top", fontsize=7.5)
|
||||
ax2.text(3.4, 0.55, "période d'un bit : 1,25 µs (800 kHz)\nla différence se joue à ~400 ns près :\n"
|
||||
"impossible avec digitalWrite(),\nla bibliothèque utilise le matériel (RMT)",
|
||||
fontsize=8, color=ENCRE, va="center")
|
||||
ax2.set_xlim(-0.1, 6.2)
|
||||
ax2.set_ylim(-0.5, 1.6)
|
||||
ax2.axis("off")
|
||||
fig.text(0.04, 0.03, "(b) Codage d'un bit : seule la durée de l'état haut change", fontsize=8, color=GRIS)
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m02-pull-up", pull_up),
|
||||
Figure("m02-rebonds", rebonds),
|
||||
Figure("m02-chenillard", chenillard),
|
||||
Figure("m02-ws2812", ws2812),
|
||||
]
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Module 3 — Signaux analogiques : PWM & ADC."""
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import schemdraw
|
||||
import schemdraw.elements as elm
|
||||
|
||||
from style import BLEU, ORANGE, GRIS, ENCRE, VERT, CM, Figure
|
||||
|
||||
schemdraw.use("matplotlib")
|
||||
|
||||
|
||||
def _signal_carre(rapport, periodes=3, points=2000):
|
||||
t = np.linspace(0, periodes, points, endpoint=False)
|
||||
return t, np.where((t % 1) < rapport, 3.3, 0.0)
|
||||
|
||||
|
||||
def pwm_rapport_cyclique():
|
||||
fig, axes = plt.subplots(3, 1, figsize=(16 * CM, 11 * CM), sharex=True)
|
||||
for ax, rapport in zip(axes, (0.25, 0.50, 0.75)):
|
||||
t, v = _signal_carre(rapport)
|
||||
moyenne = 3.3 * rapport
|
||||
ax.fill_between(t, v, step=None, color=BLEU, alpha=0.12)
|
||||
ax.plot(t, v, color=BLEU)
|
||||
ax.axhline(moyenne, color=ORANGE, ls="--", lw=1.8)
|
||||
ax.text(3.04, moyenne, f"moyenne\n{round(moyenne + 1e-9, 2):.2f} V".replace(".", ","),
|
||||
color=ORANGE, va="center", fontsize=9, fontweight="bold")
|
||||
ax.set_ylim(-0.3, 4.1)
|
||||
ax.set_yticks([0, 3.3], ["0 V", "3,3 V"])
|
||||
ax.set_title(f"Rapport cyclique {int(rapport * 100)} % → analogWrite(pin, {round(rapport * 255)})",
|
||||
loc="left", fontsize=10, pad=16 if rapport == 0.25 else 6)
|
||||
ax.grid(axis="x")
|
||||
# Cotes T et t_on sur le premier graphe
|
||||
ax = axes[0]
|
||||
ax.annotate("", xy=(1, 3.75), xytext=(2, 3.75),
|
||||
arrowprops=dict(arrowstyle="<->", color=ENCRE, lw=1))
|
||||
ax.text(1.5, 3.85, "période T", ha="center", va="bottom", fontsize=8.5, color=ENCRE)
|
||||
ax.annotate("", xy=(0, 3.75), xytext=(0.25, 3.75),
|
||||
arrowprops=dict(arrowstyle="<->", color=ENCRE, lw=1))
|
||||
ax.text(0.125, 3.85, "t$_{on}$", ha="center", va="bottom", fontsize=8.5, color=ENCRE)
|
||||
axes[-1].set_xticks([0, 1, 2, 3], ["0", "T", "2T", "3T"])
|
||||
axes[-1].set_xlabel("temps (ex. T = 0,2 ms à 5 kHz)")
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def servo_50hz():
|
||||
fig, axes = plt.subplots(3, 1, figsize=(16 * CM, 9.5 * CM), sharex=True)
|
||||
cas = ((1.0, "0°"), (1.5, "90°"), (2.0, "180°"))
|
||||
t = np.linspace(0, 40, 4000, endpoint=False)
|
||||
for ax, (largeur, angle) in zip(axes, cas):
|
||||
v = np.where((t % 20) < largeur, 3.3, 0.0)
|
||||
ax.plot(t, v, color=BLEU, lw=1.8)
|
||||
ax.fill_between(t, v, color=BLEU, alpha=0.12)
|
||||
ax.set_ylim(-0.3, 4.3)
|
||||
ax.set_yticks([0, 3.3], ["0 V", "3,3 V"])
|
||||
ax.text(22.8, 2.0, f"impulsion {str(largeur).replace('.', ',')} ms → bras à {angle}",
|
||||
fontsize=9, color=ENCRE, va="center")
|
||||
ax.grid(axis="x")
|
||||
ax = axes[0]
|
||||
ax.annotate("", xy=(0, 3.85), xytext=(20, 3.85),
|
||||
arrowprops=dict(arrowstyle="<->", color=ORANGE, lw=1.2))
|
||||
ax.text(10, 3.95, "période = 20 ms (50 Hz)", ha="center", va="bottom",
|
||||
fontsize=9, color=ORANGE, fontweight="bold")
|
||||
axes[-1].set_xticks([0, 5, 10, 15, 20, 25, 30, 35, 40])
|
||||
axes[-1].set_xlabel("temps (ms)")
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def montage_variateur():
|
||||
d = schemdraw.Drawing(show=False)
|
||||
d.config(fontsize=11, unit=2.5, color=ENCRE)
|
||||
ic = elm.Ic(
|
||||
pins=[
|
||||
# schemdraw empile les broches de gauche de bas en haut
|
||||
elm.IcPin(name="GND", side="left", anchorname="GNDL"),
|
||||
elm.IcPin(name="GPIO1 (ADC1)", side="left", anchorname="ADC"),
|
||||
elm.IcPin(name="3V3", side="left", anchorname="V33"),
|
||||
elm.IcPin(name="GPIO4 (PWM)", side="right", anchorname="PWM", slot="1/3"),
|
||||
],
|
||||
size=(6.5, 4.2), pinfontsize=10, lblsize=13,
|
||||
).label("ESP32-S3", loc="top", fontsize=14).color(BLEU)
|
||||
d += ic
|
||||
# Potentiomètre 10 kΩ entre 3V3 et GND, curseur vers GPIO1
|
||||
haut = ic.V33
|
||||
bas = ic.GNDL
|
||||
d += elm.Line().at(haut).left().length(2.2)
|
||||
p_haut = d.here
|
||||
d += elm.Line().at(bas).left().length(2.2)
|
||||
p_bas = d.here
|
||||
d += (pot := elm.Potentiometer().at(p_haut).down().toy(p_bas).label("10 kΩ", loc="top").color(VERT))
|
||||
d += elm.Wire("-").at(pot.tap).to((ic.ADC[0], pot.tap[1])).color(VERT)
|
||||
d += elm.Ground().at(p_bas)
|
||||
# Sortie PWM : résistance 220 Ω puis LED vers GND
|
||||
d += elm.Line().at(ic.PWM).right().length(1)
|
||||
d += elm.Resistor().right().label("220 Ω").color(ORANGE)
|
||||
d += elm.LED().down().label("LED", loc="bottom").color(ORANGE)
|
||||
d += elm.Ground()
|
||||
return d.draw(show=False)
|
||||
|
||||
|
||||
def adc_quantification():
|
||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16 * CM, 7.5 * CM),
|
||||
gridspec_kw={"width_ratios": [1, 1.25]})
|
||||
# (a) principe de la quantification, illustré sur 3 bits
|
||||
v = np.linspace(0, 3.3, 1000)
|
||||
niveaux = 8
|
||||
code = np.minimum(np.floor(v / 3.3 * niveaux), niveaux - 1)
|
||||
ax1.plot(v, v / 3.3 * (niveaux - 1), color=GRIS, ls=":", lw=1.3, label="tension réelle")
|
||||
ax1.step(v, code, where="post", color=BLEU, label="valeur numérique")
|
||||
ax1.set_xlim(0, 3.3)
|
||||
ax1.set_yticks(range(niveaux))
|
||||
ax1.set_xlabel("tension d'entrée (V)")
|
||||
ax1.set_ylabel("valeur lue")
|
||||
ax1.set_title("(a) Principe : la tension est\ndécoupée en paliers", loc="left", fontsize=10)
|
||||
ax1.text(2.35, 0.6, "illustré avec 8 paliers (3 bits)\nl'ESP32-S3 en a 4096 (12 bits)",
|
||||
fontsize=7.5, color=GRIS, ha="center")
|
||||
ax1.legend(fontsize=7.5, loc="upper left", frameon=False)
|
||||
# (b) chaîne de conversion utilisée dans l'exercice
|
||||
ax2.plot([0, 3.3], [0, 4095], color=BLEU)
|
||||
ax2.set_xlim(0, 3.3)
|
||||
ax2.set_ylim(0, 4095)
|
||||
ax2.set_yticks([0, 1024, 2048, 3072, 4095])
|
||||
ax2.set_xlabel("tension d'entrée (V)")
|
||||
ax2.set_ylabel("analogRead()", color=BLEU)
|
||||
sec = ax2.secondary_yaxis("right", functions=(lambda x: x * 255 / 4095, lambda y: y * 4095 / 255))
|
||||
sec.set_ylabel("map() → PWM", color=ORANGE)
|
||||
sec.set_yticks([0, 85, 170, 255])
|
||||
sec.tick_params(colors=ORANGE)
|
||||
sec.spines["right"].set_visible(True)
|
||||
sec.spines["right"].set_color(ORANGE)
|
||||
ax2.plot([1.65, 1.65, 3.3], [0, 2048, 2048], color=ORANGE, ls="--", lw=1.2)
|
||||
ax2.plot(1.65, 2048, "o", color=ORANGE)
|
||||
ax2.text(1.75, 1150, "1,65 V → 2048\nmap() → 127", color=ORANGE, fontsize=8.5, fontweight="bold")
|
||||
ax2.set_title("(b) Lecture puis mise à l'échelle", loc="left", fontsize=10)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m03-pwm-rapport-cyclique", pwm_rapport_cyclique),
|
||||
Figure("m03-servo-50hz", servo_50hz),
|
||||
Figure("m03-montage-variateur", montage_variateur),
|
||||
Figure("m03-adc-quantification", adc_quantification),
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Module 4 — Communication série & débogage."""
|
||||
from outils import boite, chronogramme, fleche, toile
|
||||
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
|
||||
|
||||
|
||||
def trame_uart():
|
||||
# 'A' = 0x41 = 0b01000001, envoyé bit de poids faible en premier
|
||||
donnees = [(0x41 >> k) & 1 for k in range(8)]
|
||||
bits = [1] + [0] + donnees + [1] + [1] # repos, start, D0..D7, stop, repos
|
||||
points = [(i, b) for i, b in enumerate(bits)]
|
||||
fig, ax, y = chronogramme([("TX", points)], 12, largeur_cm=16, hauteur_voie_cm=2.2)
|
||||
ax.set_xticks([])
|
||||
ax.spines["bottom"].set_visible(False)
|
||||
noms = ["repos", "start"] + [f"D{k}" for k in range(8)] + ["stop", "repos"]
|
||||
couleurs = [GRIS, ORANGE] + [BLEU] * 8 + [VERT, GRIS]
|
||||
for i, (n, c) in enumerate(zip(noms, couleurs)):
|
||||
ax.text(i + 0.5, 1.35, n, ha="center", fontsize=8, color=c, fontweight="bold")
|
||||
if 2 <= i <= 9:
|
||||
ax.text(i + 0.5, -0.35, str(bits[i]), ha="center", fontsize=9, color=ENCRE)
|
||||
ax.axvline(i, ymin=0.12, ymax=0.83, color="#e5e7eb", lw=0.7, zorder=0)
|
||||
ax.annotate("", xy=(2, 1.85), xytext=(10, 1.85), arrowprops=dict(arrowstyle="<->", lw=0.9, color=BLEU))
|
||||
ax.text(6, 1.95, "8 bits de données : 'A' = 0x41, bit de poids faible (D0) en premier",
|
||||
ha="center", va="bottom", fontsize=8, color=BLEU)
|
||||
ax.annotate("", xy=(1, -0.75), xytext=(2, -0.75), arrowprops=dict(arrowstyle="<->", lw=0.9))
|
||||
ax.text(2.15, -0.75, "1 bit = 1 / 115200 s ≈ 8,7 µs", va="center", fontsize=8, color=ENCRE)
|
||||
ax.text(12, -0.75, "8N1 : 8 bits, pas de parité, 1 bit de stop", ha="right", va="center",
|
||||
fontsize=8, color=GRIS)
|
||||
ax.set_ylim(-1.1, 2.5)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def cablage_serie():
|
||||
fig, ax = toile(16, 6.8)
|
||||
pc = boite(ax, 1.8, 3.1, 3.0, 2.2, "Ordinateur\n\nMoniteur série", couleur=GRIS, taille=8.5)
|
||||
esp = boite(ax, 8.0, 3.2, 4.0, 4.6, couleur=BLEU, fond="#f5f8fe")
|
||||
ax.text(8.0, 5.1, "ESP32-S3", ha="center", fontsize=10, color=BLEU, fontweight="bold")
|
||||
mod = boite(ax, 14.0, 3.0, 3.0, 3.8, couleur=ORANGE)
|
||||
ax.text(14.0, 4.2, "Module externe\n(GPS, RFID…)", ha="center", va="center", fontsize=8.5, color=ENCRE)
|
||||
# USB natif
|
||||
boite(ax, 7.0, 4.2, 1.6, 0.6, "USB", couleur=GRIS, taille=8)
|
||||
fleche(ax, (3.3, 4.2), (6.2, 4.2), "Serial\n(USB CDC)", double=True, couleur=VERT, taille=8)
|
||||
# Broches Serial2
|
||||
for yy, nom in ((3.0, "GPIO18 (TX)"), (2.0, "GPIO17 (RX)")):
|
||||
ax.text(9.9, yy, nom, ha="right", va="center", fontsize=8.5, color=ENCRE)
|
||||
for yy, nom in ((3.0, "RX"), (2.0, "TX")):
|
||||
ax.text(12.7, yy, nom, ha="left", va="center", fontsize=8.5, color=ENCRE)
|
||||
ax.text(9.9, 1.0, "GND", ha="right", va="center", fontsize=8.5, color=ENCRE)
|
||||
ax.text(12.7, 1.0, "GND", ha="left", va="center", fontsize=8.5, color=ENCRE)
|
||||
fleche(ax, (10.0, 3.0), (12.5, 2.0), couleur=BLEU)
|
||||
fleche(ax, (12.5, 3.0), (10.0, 2.0), couleur=ORANGE)
|
||||
ax.plot([10.0, 12.5], [1.0, 1.0], color=ENCRE, lw=1.3)
|
||||
ax.text(11.25, 3.35, "croisé !", ha="center", fontsize=8.5, color=ORANGE, fontweight="bold")
|
||||
ax.text(11.25, 0.55, "masse commune", ha="center", fontsize=7.5, color=GRIS)
|
||||
ax.text(8.0, 0.15, "Serial2.begin(9600, SERIAL_8N1, 17, 18);", ha="center", fontsize=7.5,
|
||||
color=GRIS, family="monospace")
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m04-trame-uart", trame_uart),
|
||||
Figure("m04-cablage-serie", cablage_serie),
|
||||
]
|
||||
@@ -0,0 +1,117 @@
|
||||
"""Module 5 — Bus I2C et SPI."""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from outils import boite, toile
|
||||
from style import BLEU, CM, ENCRE, GRIS, ORANGE, VERT, Figure
|
||||
|
||||
|
||||
def bus_i2c():
|
||||
fig, ax = toile(16, 6.4)
|
||||
maitre = boite(ax, 1.9, 3.2, 3.0, 2.6, couleur=BLEU)
|
||||
ax.text(1.9, 3.75, "ESP32-S3", ha="center", fontsize=10, color=BLEU, fontweight="bold")
|
||||
ax.text(1.9, 2.95, "maître\n(génère SCL)", ha="center", va="center", fontsize=8, color=ENCRE)
|
||||
y_sda, y_scl = 4.0, 2.4
|
||||
ax.plot([3.4, 15.6], [y_sda, y_sda], color=BLEU, lw=2)
|
||||
ax.plot([3.4, 15.6], [y_scl, y_scl], color=ORANGE, lw=2)
|
||||
ax.text(3.5, y_sda + 0.15, "SDA (GPIO8) — données", fontsize=8.5, color=BLEU, va="bottom")
|
||||
ax.text(3.5, y_scl + 0.15, "SCL (GPIO9) — horloge", fontsize=8.5, color=ORANGE, va="bottom")
|
||||
# Résistances de tirage
|
||||
ax.plot([7.5, 9.5], [5.9, 5.9], color=ENCRE, lw=1.3)
|
||||
ax.text(8.5, 6.05, "3V3", ha="center", fontsize=8, color=ENCRE)
|
||||
for x, yb in ((7.8, y_sda), (9.2, y_scl)):
|
||||
ax.plot([x, x], [5.9, 5.6], color=ENCRE, lw=1.3)
|
||||
ax.add_patch(plt.Rectangle((x - 0.15, 4.7 if yb == y_sda else 4.2), 0.3, 0.9 if yb == y_sda else 1.4,
|
||||
fill=False, ec=ENCRE, lw=1.3))
|
||||
ax.plot([x, x], [4.7 if yb == y_sda else 4.2, yb], color=ENCRE, lw=1.3)
|
||||
ax.plot(x, yb, "o", color=ENCRE, ms=4)
|
||||
ax.text(9.5, 5.2, "résistances de tirage\n(souvent déjà sur les modules)", fontsize=7.5, color=GRIS, va="center")
|
||||
# Esclaves
|
||||
for x, nom, adr in ((6.0, "Écran OLED\nSSD1306", "0x3C"), (11.5, "Capteur\nBME280", "0x76"),
|
||||
(14.6, "autre\nmodule", "0x??")):
|
||||
boite(ax, x, 0.85, 2.7, 1.45, f"{nom}\nadresse {adr}", couleur=VERT, taille=7.2)
|
||||
for dx, yb, c in ((-0.5, y_sda, BLEU), (0.5, y_scl, ORANGE)):
|
||||
ax.plot([x + dx, x + dx], [1.5, yb], color=c, lw=1.5)
|
||||
ax.plot(x + dx, yb, "o", color=c, ms=4)
|
||||
ax.text(1.9, 1.05, "tous sur les 2 mêmes fils :\nchacun répond\nà son adresse", ha="center", va="center",
|
||||
fontsize=7.2, color=GRIS, style="italic")
|
||||
return fig
|
||||
|
||||
|
||||
def trame_i2c():
|
||||
# Écriture vers l'adresse 0x3C (0111100), R/W = 0, ACK, octet 0xAF, ACK
|
||||
adresse = [0, 1, 1, 1, 1, 0, 0]
|
||||
donnee = [1, 0, 1, 0, 1, 1, 1, 1]
|
||||
bits = adresse + [0] + [0] + donnee + [0]
|
||||
roles = ["adr"] * 7 + ["rw"] + ["ack"] + ["data"] * 8 + ["ack"]
|
||||
fig, ax = plt.subplots(figsize=(16 * CM, 5.6 * CM))
|
||||
t0 = 1.0
|
||||
scl_x, scl_y, sda_x, sda_y = [0, t0], [1, 1], [0, 0.5], [1, 1]
|
||||
sda_x += [0.5]
|
||||
sda_y += [0] # START : SDA descend pendant que SCL est haut
|
||||
scl_x += [t0]
|
||||
scl_y += [0]
|
||||
for k, b in enumerate(bits):
|
||||
x = t0 + k
|
||||
scl_x += [x + 0.5, x + 0.5, x + 1, x + 1]
|
||||
scl_y += [0, 1, 1, 0]
|
||||
sda_x += [x + 0.15, x + 0.15]
|
||||
sda_y += [sda_y[-1], b]
|
||||
fin = t0 + len(bits)
|
||||
scl_x += [fin + 0.4, fin + 0.4, fin + 1.4]
|
||||
scl_y += [0, 1, 1]
|
||||
sda_x += [fin + 0.15, fin + 0.15, fin + 0.8, fin + 0.8, fin + 1.4]
|
||||
sda_y += [sda_y[-1], 0, 0, 1, 1] # STOP : SDA monte pendant que SCL est haut
|
||||
ax.plot(scl_x, [v + 2 for v in scl_y], color=ORANGE, lw=1.6)
|
||||
ax.plot(sda_x, sda_y, color=BLEU, lw=1.6)
|
||||
couleurs = {"adr": BLEU, "rw": BLEU, "ack": VERT, "data": BLEU}
|
||||
for k, (b, r) in enumerate(zip(bits, roles)):
|
||||
x = t0 + k
|
||||
ax.axvspan(x, x + 1, ymin=0.08, ymax=0.42, color=couleurs[r], alpha=0.07 if r != "ack" else 0.2, lw=0)
|
||||
ax.text(x + 0.5, -0.35, str(b), ha="center", fontsize=7.5, color=ENCRE)
|
||||
groupes = [(0, 1, "START", ORANGE), (t0, t0 + 7, "adresse 0x3C (7 bits)", BLEU), (t0 + 7, t0 + 8, "R/W", BLEU),
|
||||
(t0 + 8, t0 + 9, "ACK", VERT), (t0 + 9, t0 + 17, "donnée (8 bits)", BLEU),
|
||||
(t0 + 17, t0 + 18, "ACK", VERT), (fin, fin + 1.4, "STOP", ORANGE)]
|
||||
for a, b, txt, c in groupes:
|
||||
ax.annotate("", xy=(a + 0.05, 3.45), xytext=(b - 0.05, 3.45),
|
||||
arrowprops=dict(arrowstyle="|-|", color=c, lw=0.9, mutation_scale=3))
|
||||
ax.text((a + b) / 2, 3.6, txt, ha="center", va="bottom", fontsize=7.3, color=c, fontweight="bold")
|
||||
ax.text(-0.3, 2.5, "SCL", ha="right", va="center", fontsize=9, fontweight="bold", color=ORANGE)
|
||||
ax.text(-0.3, 0.5, "SDA", ha="right", va="center", fontsize=9, fontweight="bold", color=BLEU)
|
||||
ax.text(fin + 1.4, -0.75, "ACK (en vert) : c'est l'esclave qui tire SDA à 0 pour accuser réception",
|
||||
ha="right", fontsize=7.5, color=VERT)
|
||||
ax.set_xlim(-0.4, fin + 1.6)
|
||||
ax.set_ylim(-0.95, 4.2)
|
||||
ax.axis("off")
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def bus_spi():
|
||||
fig, ax = toile(16, 6.4)
|
||||
boite(ax, 2.0, 3.2, 3.2, 4.4, couleur=BLEU)
|
||||
ax.text(2.0, 4.55, "ESP32-S3\nmaître", ha="center", va="center", fontsize=9.5, color=BLEU, fontweight="bold")
|
||||
lignes = [("SCK", 4.2, ORANGE, "horloge"), ("MOSI", 3.5, BLEU, "maître → esclave"),
|
||||
("MISO", 2.8, VERT, "esclave → maître")]
|
||||
for nom, yy, c, role in lignes:
|
||||
ax.plot([3.6, 15.4], [yy, yy], color=c, lw=2)
|
||||
ax.text(3.75, yy + 0.08, f"{nom} — {role}", fontsize=8, color=c, va="bottom")
|
||||
esclaves = [(9.0, "Écran TFT", "CS1"), (13.5, "Carte SD", "CS2")]
|
||||
for i, (x, nom, cs) in enumerate(esclaves):
|
||||
boite(ax, x, 0.8, 2.8, 1.1, nom, couleur=GRIS, taille=8.5)
|
||||
for dx, (_, yy, c, _r) in zip((-0.9, -0.3, 0.3), lignes):
|
||||
ax.plot([x + dx, x + dx], [1.35, yy], color=c, lw=1.4)
|
||||
ax.plot(x + dx, yy, "o", color=c, ms=3.5)
|
||||
ycs = 1.9 - i * 0.45
|
||||
ax.plot([3.6, x + 0.9, x + 0.9], [ycs, ycs, 1.35], color=ENCRE, lw=1.4, ls="--" if i else "-")
|
||||
ax.text(3.75, ycs + 0.06, cs, fontsize=7.8,
|
||||
color=ENCRE, va="bottom")
|
||||
ax.text(15.4, 5.6, "SCK, MOSI, MISO : partagés\nCS (Chip Select) : une ligne par périphérique,\nle maître met à 0 celle de son interlocuteur",
|
||||
ha="right", va="center", fontsize=7.8, color=GRIS, style="italic")
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m05-bus-i2c", bus_i2c),
|
||||
Figure("m05-trame-i2c", trame_i2c),
|
||||
Figure("m05-bus-spi", bus_spi),
|
||||
]
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Module 6 — Interruptions matérielles & timers."""
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from outils import chronogramme
|
||||
from style import BLEU, CM, ENCRE, GRIS, ORANGE, ROUGE, VERT, Figure
|
||||
|
||||
|
||||
def taches_millis():
|
||||
fig, ax = plt.subplots(figsize=(16 * CM, 6.6 * CM))
|
||||
duree = 5200
|
||||
# loop() : itérations très rapides
|
||||
ax.vlines(np.arange(0, duree, 40), 3.1, 3.7, color=GRIS, lw=0.6)
|
||||
# LED toutes les 300 ms
|
||||
t = np.arange(0, duree + 300, 300)
|
||||
niveaux = np.arange(len(t)) % 2
|
||||
ax.step(t, 2.0 + niveaux * 0.6, where="post", color=BLEU, lw=1.6)
|
||||
# NeoPixel toutes les 1000 ms
|
||||
teintes = [ROUGE, VERT, BLEU, "#f5f5f5", ROUGE, VERT]
|
||||
for k in range(6):
|
||||
ax.add_patch(plt.Rectangle((k * 1000, 0.95), min(1000, duree - k * 1000), 0.6,
|
||||
facecolor=teintes[k], edgecolor=GRIS, lw=0.6, alpha=0.85))
|
||||
# Debug toutes les 5000 ms
|
||||
for x in (0, 5000):
|
||||
ax.plot(x, 0.3, marker="v", color=ORANGE, ms=8)
|
||||
ax.text(x + 60, 0.3, "Serial.print", fontsize=7, color=ORANGE, va="center")
|
||||
noms = [(3.4, "loop()", GRIS), (2.3, "LED (300 ms)", BLEU), (1.25, "NeoPixel (1 s)", ENCRE),
|
||||
(0.3, "debug (5 s)", ORANGE)]
|
||||
for yy, n, c in noms:
|
||||
ax.text(-80, yy, n, ha="right", va="center", fontsize=8.5, color=c, fontweight="bold")
|
||||
ax.text(duree, 3.85, "loop() tourne en permanence : à chaque tour, on teste « l'heure est-elle venue ? »",
|
||||
ha="right", fontsize=7.5, color=GRIS, style="italic")
|
||||
ax.set_xlim(-50, duree)
|
||||
ax.set_ylim(-0.1, 4.2)
|
||||
ax.set_yticks([])
|
||||
ax.spines["left"].set_visible(False)
|
||||
ax.set_xticks(range(0, 5001, 1000), [f"{s} s" for s in range(6)])
|
||||
ax.set_xlabel("millis()")
|
||||
ax.grid(axis="y", visible=False)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def deroule_isr():
|
||||
fig, ax = plt.subplots(figsize=(16 * CM, 5.0 * CM))
|
||||
# Signal du bouton
|
||||
ax.step([0, 3.0, 6.0, 10], [2.6, 1.9, 2.6, 2.6], where="post", color=BLEU, lw=1.6)
|
||||
ax.text(-0.15, 2.25, "bouton\n(GPIO5)", ha="right", va="center", fontsize=8.5, fontweight="bold", color=BLEU)
|
||||
ax.annotate("front descendant\n(FALLING)", xy=(3.0, 2.25), xytext=(1.2, 3.25), fontsize=7.5, color=BLEU,
|
||||
arrowprops=dict(arrowstyle="-|>", color=BLEU, lw=0.8), ha="center")
|
||||
# Exécution du processeur
|
||||
y = 0.6
|
||||
ax.broken_barh([(0, 3.0), (3.35, 6.65)], (y, 0.7), facecolors=BLEU, alpha=0.25, edgecolor=BLEU)
|
||||
ax.broken_barh([(3.0, 0.35)], (y, 0.7), facecolors=ROUGE, edgecolor=ROUGE)
|
||||
ax.broken_barh([(5.0, 1.2)], (y, 0.7), facecolors=VERT, alpha=0.55, edgecolor=VERT)
|
||||
ax.text(-0.15, y + 0.35, "processeur", ha="right", va="center", fontsize=8.5, fontweight="bold", color=ENCRE)
|
||||
ax.text(1.5, y + 0.35, "loop()", ha="center", va="center", fontsize=8)
|
||||
ax.text(8.1, y + 0.35, "loop()", ha="center", va="center", fontsize=8)
|
||||
ax.annotate("ISR très courte :\ninterruptionDetectee = true", xy=(3.17, y), xytext=(3.4, -0.55),
|
||||
fontsize=7.5, color=ROUGE, arrowprops=dict(arrowstyle="-|>", color=ROUGE, lw=0.8))
|
||||
ax.text(5.6, y + 0.35, "traitement", ha="center", va="center", fontsize=7.5)
|
||||
ax.text(5.6, y + 0.95, "if (interruptionDetectee) …", ha="center", fontsize=7.2, color=VERT,
|
||||
family="monospace")
|
||||
ax.set_xlim(-1.6, 10)
|
||||
ax.set_ylim(-0.8, 3.9)
|
||||
ax.axis("off")
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def modes_declenchement():
|
||||
pts = [(0, 0), (1.5, 1), (4, 0), (6, 1), (8.5, 0)]
|
||||
fig, ax, y = chronogramme([("signal", pts)], 10, largeur_cm=16, hauteur_voie_cm=3.4)
|
||||
ax.set_xticks([])
|
||||
ax.spines["bottom"].set_visible(False)
|
||||
lignes = [("RISING", [1.5, 6], BLEU), ("FALLING", [4, 8.5], ORANGE), ("CHANGE", [1.5, 4, 6, 8.5], VERT)]
|
||||
for k, (nom, xs, c) in enumerate(lignes):
|
||||
yy = -0.7 - k * 0.7
|
||||
ax.text(-0.25, yy, nom, ha="right", va="center", fontsize=8.5, fontweight="bold", color=c)
|
||||
ax.plot([0, 10], [yy, yy], color="#e5e7eb", lw=0.8)
|
||||
for x in xs:
|
||||
ax.plot(x, yy, marker="o", color=c, ms=7)
|
||||
ax.plot([x, x], [yy, 0], color=c, lw=0.6, ls=":")
|
||||
ax.set_ylim(-3.0, 1.5)
|
||||
ax.text(10, -2.8, "● = instant où l'ISR est appelée", ha="right", fontsize=7.5, color=GRIS)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def encodeur():
|
||||
fig, axes = plt.subplots(1, 2, figsize=(16 * CM, 5.2 * CM))
|
||||
cas = [("Rotation dans un sens", 0.0, "DT = HIGH → compteur++", BLEU),
|
||||
("Rotation dans l'autre sens", 0.5, "DT = LOW → compteur--", ORANGE)]
|
||||
t = np.linspace(0, 4, 2000)
|
||||
for ax, (titre, dephasage, regle, c) in zip(axes, cas):
|
||||
clk = ((t % 2) < 1).astype(float)
|
||||
dt = (((t - 0.5 + dephasage * 2) % 2) < 1).astype(float)
|
||||
ax.plot(t, clk + 1.6, color=BLEU, lw=1.6)
|
||||
ax.plot(t, dt, color=GRIS, lw=1.6)
|
||||
ax.text(-0.1, 2.1, "CLK", ha="right", va="center", fontsize=8.5, fontweight="bold", color=BLEU)
|
||||
ax.text(-0.1, 0.5, "DT", ha="right", va="center", fontsize=8.5, fontweight="bold", color=GRIS)
|
||||
for x in (1, 3):
|
||||
ax.plot([x, x], [-0.25, 2.8], color=c, lw=1, ls="--")
|
||||
etat = dt[np.searchsorted(t, x + 0.01)]
|
||||
ax.plot(x, etat, "o", color=c, ms=6)
|
||||
ax.set_title(titre, fontsize=9, loc="left")
|
||||
ax.text(2, -0.75, regle, ha="center", fontsize=8.5, color=c, fontweight="bold")
|
||||
ax.set_xlim(-0.6, 4)
|
||||
ax.set_ylim(-1.0, 2.9)
|
||||
ax.axis("off")
|
||||
fig.text(0.5, 0.02, "À chaque front descendant de CLK (pointillés), l'ISR lit l'état de DT. "
|
||||
"Le sens réel dépend du câblage.", ha="center", fontsize=7.5, color=GRIS, style="italic")
|
||||
fig.tight_layout(rect=(0, 0.06, 1, 1))
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m06-taches-millis", taches_millis),
|
||||
Figure("m06-deroule-isr", deroule_isr),
|
||||
Figure("m06-modes-declenchement", modes_declenchement),
|
||||
Figure("m06-encodeur", encodeur),
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Module 7 — Wi-Fi : station, point d'accès et client HTTP."""
|
||||
from outils import boite, fleche, sequence, toile
|
||||
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
|
||||
|
||||
|
||||
def modes_wifi():
|
||||
fig, ax = toile(16, 6.4)
|
||||
panneaux = [(2.7, "WIFI_STA\nstation"), (8.0, "WIFI_AP\npoint d'accès"), (13.3, "WIFI_AP_STA\nles deux")]
|
||||
for x, titre in panneaux:
|
||||
boite(ax, x, 3.1, 5.0, 5.6, couleur=GRIS, fond="#fafafa")
|
||||
ax.text(x, 5.4, titre, ha="center", va="center", fontsize=8.5, fontweight="bold", color=ENCRE)
|
||||
# STA
|
||||
box = boite(ax, 2.7, 3.9, 2.2, 0.8, "Box / routeur", couleur=GRIS, taille=8)
|
||||
esp = boite(ax, 1.5, 2.0, 2.0, 0.8, "ESP32-S3", couleur=BLEU, taille=8, gras=True)
|
||||
tel = boite(ax, 3.9, 2.0, 1.8, 0.8, "PC / mobile", couleur=GRIS, taille=8)
|
||||
fleche(ax, esp["h"], (2.3, box["b"][1]), double=True, couleur=BLEU)
|
||||
fleche(ax, tel["h"], (3.1, box["b"][1]), double=True, couleur=GRIS)
|
||||
ax.text(2.7, 4.5, "↑ Internet", ha="center", fontsize=7.5, color=GRIS)
|
||||
ax.text(2.7, 0.7, "IP donnée par la box\nWiFi.localIP()", ha="center", fontsize=7.2, color=BLEU)
|
||||
# AP
|
||||
esp2 = boite(ax, 8.0, 3.9, 2.2, 0.9, "ESP32-S3\n192.168.4.1", couleur=BLEU, taille=8, gras=True)
|
||||
for dx in (-1.3, 1.3):
|
||||
t = boite(ax, 8.0 + dx, 2.0, 1.8, 0.8, "mobile", couleur=GRIS, taille=8)
|
||||
fleche(ax, t["h"], (8.0 + dx * 0.4, esp2["b"][1]), double=True, couleur=ORANGE)
|
||||
ax.text(8.0, 0.7, "réseau « ESP32-Formation »\nsans box ni Internet", ha="center", fontsize=7.2, color=ORANGE)
|
||||
# AP_STA
|
||||
box3 = boite(ax, 13.3, 4.3, 2.2, 0.7, "Box / routeur", couleur=GRIS, taille=8)
|
||||
esp3 = boite(ax, 13.3, 2.95, 2.2, 0.8, "ESP32-S3", couleur=BLEU, taille=8, gras=True)
|
||||
t3 = boite(ax, 13.3, 1.5, 1.8, 0.7, "mobile", couleur=GRIS, taille=8)
|
||||
fleche(ax, esp3["h"], box3["b"], "client", double=True, couleur=BLEU, taille=7)
|
||||
fleche(ax, t3["h"], esp3["b"], "point d'accès", double=True, couleur=ORANGE, taille=7)
|
||||
ax.text(13.3, 0.7, "ex. portail de configuration", ha="center", fontsize=7.2, color=GRIS)
|
||||
return fig
|
||||
|
||||
|
||||
def requete_http():
|
||||
acteurs = ["ESP32-S3", "Box Wi-Fi", "api.open-meteo.com"]
|
||||
messages = [
|
||||
("ESP32-S3", "Box Wi-Fi", "WiFi.begin(ssid, motDePasse)"),
|
||||
("Box Wi-Fi", "ESP32-S3", "connecté : adresse IP attribuée", True),
|
||||
("ESP32-S3", "api.open-meteo.com", "http.GET() → GET /v1/forecast?latitude=…"),
|
||||
("api.open-meteo.com", "ESP32-S3", "200 OK + {\"current_weather\": {\"temperature\": 14.2, …}}", True),
|
||||
("ESP32-S3", "ESP32-S3", "deserializeJson() puis affichage OLED"),
|
||||
("ESP32-S3", "ESP32-S3", "http.end()"),
|
||||
]
|
||||
fig, ax = sequence(acteurs, messages, largeur_cm=16, pas_cm=0.95,
|
||||
couleurs={"Box Wi-Fi": GRIS, "api.open-meteo.com": VERT})
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m07-modes-wifi", modes_wifi),
|
||||
Figure("m07-requete-http", requete_http),
|
||||
]
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Module 8 — Serveur web embarqué."""
|
||||
from matplotlib.patches import Rectangle
|
||||
|
||||
from outils import boite, fleche, sequence, toile
|
||||
from style import BLEU, GRIS, ORANGE, ROUGE, VERT, Figure
|
||||
|
||||
|
||||
def echanges_serveur():
|
||||
acteurs = ["Navigateur (mobile)", "ESP32-S3 — WebServer", "LED NeoPixel"]
|
||||
messages = [
|
||||
("Navigateur (mobile)", "ESP32-S3 — WebServer", "GET / (saisie de http://192.168.1.42)"),
|
||||
("ESP32-S3 — WebServer", "Navigateur (mobile)", "200 OK + page HTML (4 boutons)", True),
|
||||
("Navigateur (mobile)", "ESP32-S3 — WebServer", "clic « Rouge » → GET /rouge"),
|
||||
("ESP32-S3 — WebServer", "LED NeoPixel", "changerCouleur(255, 0, 0)"),
|
||||
("ESP32-S3 — WebServer", "Navigateur (mobile)", "200 OK + page HTML (rechargée)", True),
|
||||
]
|
||||
fig, ax = sequence(acteurs, messages, largeur_cm=16, pas_cm=0.95,
|
||||
couleurs={"Navigateur (mobile)": GRIS, "LED NeoPixel": ROUGE})
|
||||
ax.text(8.0, 0.15, "serveur.on(\"/rouge\", …) associe chaque URL (route) à une fonction ; "
|
||||
"handleClient() traite les requêtes dans loop()", ha="center", fontsize=7.2, color=GRIS, style="italic")
|
||||
return fig
|
||||
|
||||
|
||||
def http_vs_websocket():
|
||||
fig, ax = toile(16, 7.2)
|
||||
for x0, titre, c in ((0.3, "HTTP classique", ORANGE), (8.3, "WebSocket", VERT)):
|
||||
ax.text(x0 + 3.7, 6.85, titre, ha="center", fontsize=10, fontweight="bold", color=c)
|
||||
for dx, nom in ((0.9, "Navigateur"), (6.5, "ESP32-S3")):
|
||||
boite(ax, x0 + dx, 6.2, 1.9, 0.6, nom, couleur=GRIS if nom == "Navigateur" else BLEU, taille=8)
|
||||
ax.plot([x0 + dx] * 2, [0.85, 5.85], color=GRIS, lw=1, ls=(0, (3, 3)))
|
||||
# HTTP : une requête par échange
|
||||
g, d = 1.2, 6.8
|
||||
y = 5.4
|
||||
for req, rep in (("GET /mesure", "200 OK « 21,5 °C »"), ("GET /mesure", "200 OK « 21,6 °C »"),
|
||||
("GET /mesure", "200 OK « 21,6 °C »")):
|
||||
fleche(ax, (g, y), (d, y), req, taille=7.3, decalage=0.05)
|
||||
fleche(ax, (d, y - 0.55), (g, y - 0.55), rep, taille=7.3, pointille=True, decalage=0.05)
|
||||
y -= 1.5
|
||||
ax.text(4.0, 0.15, "le navigateur doit redemander\n(nouvelle requête à chaque fois)", ha="center",
|
||||
fontsize=7.3, color=ORANGE)
|
||||
# WebSocket : une connexion persistante
|
||||
g, d = 9.2, 14.8
|
||||
fleche(ax, (g, 5.4), (d, 5.4), "ouverture (handshake)", taille=7.3, decalage=0.05)
|
||||
fleche(ax, (d, 4.85), (g, 4.85), "connexion acceptée", taille=7.3, pointille=True, decalage=0.05)
|
||||
ax.add_patch(Rectangle((g, 0.75), d - g, 3.75, color=VERT, alpha=0.08))
|
||||
for k, (sens, txt) in enumerate(((1, "21,5 °C"), (1, "21,6 °C"), (-1, "« rouge »"), (1, "21,7 °C"), (1, "21,7 °C"))):
|
||||
yy = 4.1 - k * 0.7
|
||||
a, b = (d, g) if sens == 1 else (g, d)
|
||||
fleche(ax, (a, yy), (b, yy), txt, taille=7.3, couleur=VERT if sens == 1 else ROUGE, decalage=0.05)
|
||||
ax.text(12.0, 0.15, "connexion ouverte en permanence :\nla carte envoie dès qu'une valeur change",
|
||||
ha="center", fontsize=7.3, color=VERT)
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m08-echanges-serveur", echanges_serveur),
|
||||
Figure("m08-http-vs-websocket", http_vs_websocket),
|
||||
]
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Module 9 — Bluetooth Low Energy (BLE)."""
|
||||
from outils import boite, fleche, sequence, toile
|
||||
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
|
||||
|
||||
|
||||
def hierarchie_gatt():
|
||||
fig, ax = toile(16, 7.0)
|
||||
boite(ax, 5.6, 3.5, 10.8, 6.6, couleur=BLEU, fond="#f5f8fe")
|
||||
ax.text(0.5, 6.4, "Serveur BLE — l'ESP32-S3", fontsize=9.5, fontweight="bold", color=BLEU)
|
||||
ax.text(0.5, 6.0, 'BLEDevice::init("ESP32-S3-Formation")', fontsize=7.5, color=GRIS, family="monospace")
|
||||
boite(ax, 5.6, 3.15, 10.0, 5.1, couleur=ORANGE)
|
||||
ax.text(0.9, 5.25, "Service", fontsize=9, fontweight="bold", color=ORANGE)
|
||||
ax.text(0.9, 4.9, "UUID 4fafc201-1fb5-459e-8fcc-c5c9c331914b", fontsize=7.5, color=ENCRE, family="monospace")
|
||||
boite(ax, 5.6, 2.55, 9.2, 3.1, couleur=VERT)
|
||||
ax.text(1.3, 3.75, "Caractéristique", fontsize=9, fontweight="bold", color=VERT)
|
||||
ax.text(1.3, 3.4, "UUID beb5483e-36e1-4688-b7f5-ea07361b26a8", fontsize=7.5, color=ENCRE, family="monospace")
|
||||
for i, (p, c) in enumerate((("READ", BLEU), ("WRITE", ORANGE), ("NOTIFY", VERT))):
|
||||
boite(ax, 2.0 + i * 1.75, 2.6, 1.5, 0.55, p, couleur=c, taille=7.5, gras=True)
|
||||
boite(ax, 8.4, 2.6, 2.6, 0.8, 'valeur : "rouge"', couleur=GRIS, taille=8)
|
||||
ax.text(5.6, 1.4, "(un service peut contenir plusieurs caractéristiques)", ha="center",
|
||||
fontsize=7.3, color=GRIS, style="italic")
|
||||
boite(ax, 14.1, 3.5, 2.8, 2.0, "Client BLE\nsmartphone\n(nRF Connect)", couleur=GRIS, taille=8.5)
|
||||
fleche(ax, (12.7, 3.0), (11.0, 3.0), "écrit", couleur=ORANGE, taille=7.5, decalage=0.08)
|
||||
fleche(ax, (11.0, 2.2), (12.7, 2.2), "notifie", couleur=VERT, taille=7.5, decalage=0.08)
|
||||
fleche(ax, (12.7, 4.1), (11.0, 4.1), "lit", couleur=BLEU, taille=7.5, decalage=0.08)
|
||||
return fig
|
||||
|
||||
|
||||
def sequence_ble():
|
||||
acteurs = ["ESP32-S3 (serveur)", "Smartphone (client)"]
|
||||
messages = [
|
||||
("ESP32-S3 (serveur)", "Smartphone (client)", "annonce (advertising) « ESP32-S3-Formation »", True),
|
||||
("Smartphone (client)", "ESP32-S3 (serveur)", "connexion"),
|
||||
("Smartphone (client)", "ESP32-S3 (serveur)", "découverte des services et caractéristiques"),
|
||||
("Smartphone (client)", "ESP32-S3 (serveur)", "WRITE « rouge »"),
|
||||
("ESP32-S3 (serveur)", "ESP32-S3 (serveur)", "onWrite() → LED rouge"),
|
||||
("ESP32-S3 (serveur)", "Smartphone (client)", "NOTIFY « 21.5 » (température)", True),
|
||||
("ESP32-S3 (serveur)", "Smartphone (client)", "NOTIFY « 21.6 » … toutes les 5 s", True),
|
||||
]
|
||||
fig, ax = sequence(acteurs, messages, largeur_cm=15, pas_cm=0.85,
|
||||
couleurs={"Smartphone (client)": GRIS})
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m09-hierarchie-gatt", hierarchie_gatt),
|
||||
Figure("m09-sequence-ble", sequence_ble),
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Module 10 — Gestion de l'énergie."""
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from style import BLEU, CM, ENCRE, GRIS, ORANGE, ROUGE, VERT, Figure
|
||||
|
||||
|
||||
def consommation_modes():
|
||||
fig, ax = plt.subplots(figsize=(16 * CM, 6.4 * CM))
|
||||
modes = [("Actif\n(Wi-Fi, calcul)", 80, 250, ROUGE, "moins d'1 jour"),
|
||||
("Modem-sleep", 20, 30, ORANGE, "≈ 3 jours"),
|
||||
("Light sleep", 0.7, 0.9, BLEU, "≈ 3 mois"),
|
||||
("Deep sleep", 0.010, 0.150, VERT, "plus d'1 an (théorique)")]
|
||||
for i, (nom, bas, haut, c, autonomie) in enumerate(modes):
|
||||
ax.barh(i, haut - bas, left=bas, height=0.5, color=c, alpha=0.85)
|
||||
ax.text(haut * 1.4, i, autonomie, va="center", fontsize=8.5, color=c, fontweight="bold")
|
||||
ax.set_yticks(range(len(modes)), [m[0] for m in modes])
|
||||
ax.invert_yaxis()
|
||||
ax.set_xscale("log")
|
||||
ax.set_xlim(0.005, 20000)
|
||||
ax.set_xticks([0.01, 0.1, 1, 10, 100, 1000], ["10 µA", "100 µA", "1 mA", "10 mA", "100 mA", "1 A"])
|
||||
ax.set_xlabel("courant consommé (échelle logarithmique)")
|
||||
ax.text(20000, -0.75, "autonomie avec une batterie de 2000 mAh", ha="right", fontsize=7.5,
|
||||
color=GRIS, style="italic")
|
||||
ax.grid(axis="y", visible=False)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
def cycle_deep_sleep():
|
||||
fig, ax = plt.subplots(figsize=(16 * CM, 6.0 * CM))
|
||||
t, i = [0], [0.05]
|
||||
for debut in (0, 60, 120):
|
||||
t += [debut, debut + 0.4, debut + 1.0, debut + 3.0]
|
||||
i += [45, 40, 160, 0.05]
|
||||
t += [150]
|
||||
i += [0.05]
|
||||
ax.step(t, i, where="post", color=BLEU, lw=1.6)
|
||||
ax.fill_between(t, 0.01, i, step="post", color=BLEU, alpha=0.12)
|
||||
ax.set_yscale("log")
|
||||
ax.set_ylim(0.01, 600)
|
||||
ax.set_yticks([0.01, 0.1, 1, 10, 100], ["10 µA", "100 µA", "1 mA", "10 mA", "100 mA"])
|
||||
ax.set_xlim(-3, 150)
|
||||
ax.set_xlabel("temps (s)")
|
||||
ax.set_ylabel("courant")
|
||||
ax.annotate("réveil : setup() repart du début\nmesure BME280, envoi Wi-Fi (≈ 3 s)", xy=(61.5, 160), xytext=(66, 260),
|
||||
fontsize=7.8, color=ROUGE, arrowprops=dict(arrowstyle="-|>", color=ROUGE, lw=0.8))
|
||||
ax.text(30, 0.5, "deep sleep (≈ 57 s)\nseule la RTC reste alimentée", ha="center", fontsize=7.8, color=VERT)
|
||||
ax.text(31.5, 0.16, "esp_sleep_enable_timer_wakeup(60 s)", ha="center", fontsize=7.3, color=ENCRE,
|
||||
family="monospace")
|
||||
ax.text(91, 2.0, "compteurReveils (RTC_DATA_ATTR)\nconservé d'un cycle à l'autre", ha="center", fontsize=7.5,
|
||||
color=GRIS, style="italic")
|
||||
ax.grid(axis="x", visible=False)
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m10-consommation-modes", consommation_modes),
|
||||
Figure("m10-cycle-deep-sleep", cycle_deep_sleep),
|
||||
]
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Module 11 — IoT & Cloud : le protocole MQTT."""
|
||||
from outils import boite, fleche, toile
|
||||
from style import BLEU, ENCRE, GRIS, ORANGE, ROUGE, VERT, Figure
|
||||
|
||||
|
||||
def publish_subscribe():
|
||||
fig, ax = toile(16, 6.6)
|
||||
esp = boite(ax, 2.2, 3.3, 3.4, 2.4, couleur=BLEU)
|
||||
ax.text(2.2, 4.05, "ESP32-S3", ha="center", fontsize=10, fontweight="bold", color=BLEU)
|
||||
ax.text(2.2, 3.0, "BME280 + LED\nNeoPixel", ha="center", va="center", fontsize=8, color=ENCRE)
|
||||
broker = boite(ax, 8.0, 3.3, 3.0, 2.4, couleur=ORANGE)
|
||||
ax.text(8.0, 3.75, "Broker", ha="center", fontsize=10, fontweight="bold", color=ORANGE)
|
||||
ax.text(8.0, 3.0, "Mosquitto,\nHiveMQ…", ha="center", va="center", fontsize=8, color=ENCRE)
|
||||
dash = boite(ax, 13.8, 4.8, 3.6, 1.4, "Tableau de bord\n(Node-RED)", couleur=VERT, taille=8.5)
|
||||
ha = boite(ax, 13.8, 1.8, 3.6, 1.4, "Home Assistant", couleur=VERT, taille=8.5)
|
||||
# Publication de la température
|
||||
fleche(ax, (3.9, 3.9), (6.5, 3.9), "publish", couleur=BLEU, taille=8, fond=True)
|
||||
ax.text(5.3, 5.1, "formation/esp32s3/temperature\n« 21.5 »", ha="center", fontsize=6.8, color=BLEU,
|
||||
family="monospace")
|
||||
fleche(ax, (9.5, 3.9), (12.0, 4.6), couleur=BLEU)
|
||||
fleche(ax, (9.5, 3.7), (12.0, 2.0), couleur=BLEU)
|
||||
ax.text(12.4, 3.35, "abonnés à …/temperature\n→ reçoivent la valeur", ha="center", fontsize=7.2,
|
||||
color=GRIS, style="italic")
|
||||
# Commande en retour
|
||||
fleche(ax, (12.0, 5.2), (9.5, 4.3), couleur=ROUGE)
|
||||
fleche(ax, (6.5, 2.7), (3.9, 2.7), "message", couleur=ROUGE, taille=8, fond=True)
|
||||
ax.text(5.3, 1.45, "formation/esp32s3/commande\n« rouge »", ha="center", fontsize=6.8, color=ROUGE,
|
||||
family="monospace")
|
||||
ax.text(10.9, 5.25, "publish « rouge »", ha="center", fontsize=7.5, color=ROUGE, rotation=20)
|
||||
ax.text(8.0, 0.35, "Les appareils ne se parlent jamais directement : tout passe par le broker.",
|
||||
ha="center", fontsize=7.8, color=GRIS, style="italic")
|
||||
return fig
|
||||
|
||||
|
||||
def arborescence_topics():
|
||||
fig, ax = toile(16, 5.0)
|
||||
noeuds = {
|
||||
"maison": (1.6, 3.6), "salon": (4.9, 4.3), "cuisine": (4.9, 2.9),
|
||||
"temperature ": (8.3, 4.65), "humidite": (8.3, 3.95), "temperature ": (8.3, 2.9),
|
||||
"formation": (1.6, 1.2), "esp32s3": (4.9, 1.2),
|
||||
"temperature": (8.3, 1.55), "commande": (8.3, 0.85),
|
||||
}
|
||||
liens = [("maison", "salon"), ("maison", "cuisine"), ("salon", "temperature "), ("salon", "humidite"),
|
||||
("cuisine", "temperature "), ("formation", "esp32s3"), ("esp32s3", "temperature"),
|
||||
("esp32s3", "commande")]
|
||||
for a, b in liens:
|
||||
(x1, y1), (x2, y2) = noeuds[a], noeuds[b]
|
||||
ax.plot([x1 + 1.15, (x1 + x2) / 2 + 0.3, (x1 + x2) / 2 + 0.3, x2 - 1.15], [y1, y1, y2, y2],
|
||||
color=GRIS, lw=1.1)
|
||||
for nom, (x, y) in noeuds.items():
|
||||
c = BLEU if nom.startswith(("formation", "esp32s3")) or nom in ("temperature", "commande") else GRIS
|
||||
boite(ax, x, y, 2.3, 0.5, nom.strip(), couleur=c, taille=7.2)
|
||||
chemins = [(4.65, "maison/salon/temperature", GRIS), (2.9, "maison/cuisine/temperature", GRIS),
|
||||
(1.55, "formation/esp32s3/temperature", BLEU), (0.85, "formation/esp32s3/commande", BLEU)]
|
||||
for y, txt, c in chemins:
|
||||
ax.text(9.75, y, "→ " + txt, va="center", fontsize=7.8, color=c, family="monospace")
|
||||
ax.text(9.75, 3.95, "→ maison/salon/humidite", va="center", fontsize=7.8, color=GRIS, family="monospace")
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m11-publish-subscribe", publish_subscribe),
|
||||
Figure("m11-arborescence-topics", arborescence_topics),
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Module 12 — Projet final."""
|
||||
from outils import boite, fleche, toile
|
||||
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
|
||||
|
||||
|
||||
def demarche_projet():
|
||||
fig, ax = toile(16, 4.2)
|
||||
etapes = [("Besoin", "quel\nproblème ?", GRIS), ("Cahier des\ncharges", "matériel,\nmodules", BLEU),
|
||||
("MVP", "l'indispensable\nd'abord", BLEU), ("Tests", "sur une durée\nréaliste", ORANGE),
|
||||
("Bonus", "si le temps\nle permet", VERT), ("Présentation", "démo, choix,\ndifficultés", VERT)]
|
||||
xs = [1.3 + k * 2.68 for k in range(len(etapes))]
|
||||
for x, (nom, sous, c) in zip(xs, etapes):
|
||||
boite(ax, x, 2.6, 2.35, 1.1, nom, couleur=c, taille=7.2, gras=True)
|
||||
ax.text(x, 1.85, sous, ha="center", va="top", fontsize=7, color=GRIS)
|
||||
for a, b in zip(xs, xs[1:]):
|
||||
fleche(ax, (a + 1.175, 2.6), (b - 1.175, 2.6))
|
||||
fleche(ax, (xs[3], 3.15), (xs[2], 3.15), "corriger", couleur=ORANGE, taille=7.3, courbe=0.6)
|
||||
return fig
|
||||
|
||||
|
||||
def structure_projet():
|
||||
fig, ax = toile(16, 6.0)
|
||||
main = boite(ax, 8.0, 4.6, 6.2, 1.6, couleur=BLEU)
|
||||
ax.text(8.0, 5.05, "MonProjet.ino", ha="center", fontsize=9.5, fontweight="bold", color=BLEU,
|
||||
family="monospace")
|
||||
ax.text(8.0, 4.3, "setup() et loop() uniquement :\norchestrent les appels", ha="center", va="center",
|
||||
fontsize=7.8, color=ENCRE)
|
||||
onglets = [(2.8, "capteurs.ino", "lireTemperature()\nlireHumidite()", VERT),
|
||||
(8.0, "affichage.ino", "afficherEcran()\nafficherLed()", ORANGE),
|
||||
(13.2, "reseau.ino", "connecterWiFi()\npublierMQTT()", GRIS)]
|
||||
for x, nom, fonctions, c in onglets:
|
||||
boite(ax, x, 1.4, 4.2, 1.9, couleur=c)
|
||||
ax.text(x, 1.95, nom, ha="center", fontsize=9, fontweight="bold", color=c, family="monospace")
|
||||
ax.text(x, 1.1, fonctions, ha="center", va="center", fontsize=7.8, color=ENCRE, family="monospace")
|
||||
fleche(ax, (8.0 + (x - 8.0) * 0.25, main["b"][1]), (x, 2.35), couleur=c)
|
||||
ax.text(15.8, 5.6, "une fonction =\nune responsabilité", ha="right", fontsize=7.8, color=GRIS, style="italic")
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m12-demarche-projet", demarche_projet),
|
||||
Figure("m12-structure-projet", structure_projet),
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Synthèse de la formation (préfixe m13 : document hors numérotation des modules)."""
|
||||
from outils import boite, fleche, toile
|
||||
from style import BLEU, GRIS, ORANGE, ROUGE, VERT, Figure
|
||||
|
||||
|
||||
def parcours_formation():
|
||||
fig, ax = toile(16, 8.8)
|
||||
colonnes = [
|
||||
(1.55, "Prise\nen main", GRIS, [("0", "Installation"), ("1", "Microcontrôleur")]),
|
||||
(4.775, "Entrées /\nsorties", BLEU, [("2", "GPIO & NeoPixel"), ("3", "PWM & ADC"), ("4", "Série & debug")]),
|
||||
(8.0, "Bus\n& temps", ORANGE, [("5", "I2C & SPI"), ("6", "Interruptions")]),
|
||||
(11.225, "Connectivité", VERT, [("7", "Wi-Fi"), ("8", "Serveur web"), ("9", "BLE"), ("11", "MQTT")]),
|
||||
(14.45, "Autonomie", ROUGE, [("10", "Énergie")]),
|
||||
]
|
||||
centres = {}
|
||||
for x, titre, c, modules in colonnes:
|
||||
ax.text(x, 8.2, titre, ha="center", va="center", fontsize=8.5, fontweight="bold", color=c)
|
||||
n = len(modules)
|
||||
for k, (num, nom) in enumerate(modules):
|
||||
y = 6.95 - k * 1.25 - (4 - n) * 0.3
|
||||
boite(ax, x, y, 2.75, 1.0, f"M{num}\n{nom}", couleur=c, taille=7.1)
|
||||
centres[num] = (x, y)
|
||||
for a, b in (("1", "2"), ("4", "5"), ("6", "7"), ("11", "10")):
|
||||
(x1, y1), (x2, y2) = centres[a], centres[b]
|
||||
fleche(ax, (x1 + 1.375, y1), (x2 - 1.375, y2), couleur=GRIS)
|
||||
projet = boite(ax, 8.0, 0.8, 9.6, 1.1, "M12 — Projet final : au moins 3 modules combinés", couleur=BLEU,
|
||||
taille=9, gras=True)
|
||||
for x, *_ in colonnes:
|
||||
cible = (min(max(x, 3.6), 12.4), projet["h"][1])
|
||||
fleche(ax, (x, 2.4), cible, couleur=GRIS, pointille=True)
|
||||
ax.text(14.45, 4.5, "la LED NeoPixel\nsert de fil rouge\ndans tous\nles modules", ha="center", va="center",
|
||||
fontsize=7.3, color=GRIS, style="italic")
|
||||
return fig
|
||||
|
||||
|
||||
FIGURES = [
|
||||
Figure("m13-parcours-formation", parcours_formation),
|
||||
]
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Briques graphiques communes : toile de dessin, boîtes, flèches, chronogrammes, séquences."""
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import FancyBboxPatch
|
||||
|
||||
from style import BLEU, CM, ENCRE, GRIS, GRIS_CLAIR
|
||||
|
||||
CLAIR = { # fonds pâles associés aux couleurs de la charte
|
||||
BLEU: "#e8f0fe", "#e07a1f": "#fdf0e3", "#2e8b57": "#e6f4ea",
|
||||
"#c0392b": "#fce8e6", GRIS: "#f3f4f6",
|
||||
}
|
||||
|
||||
|
||||
def toile(largeur_cm, hauteur_cm, xmax=None, ymax=None):
|
||||
"""Figure sans axes, en coordonnées « cm » (1 unité = 1 cm à l'échelle d'origine)."""
|
||||
fig, ax = plt.subplots(figsize=(largeur_cm * CM, hauteur_cm * CM))
|
||||
ax.set_xlim(0, xmax or largeur_cm)
|
||||
ax.set_ylim(0, ymax or hauteur_cm)
|
||||
ax.set_aspect("equal")
|
||||
ax.axis("off")
|
||||
return fig, ax
|
||||
|
||||
|
||||
def boite(ax, x, y, l, h, texte="", couleur=BLEU, taille=9, gras=False, fond=None, style="round", alpha=1):
|
||||
"""Boîte arrondie centrée sur (x, y). Renvoie un dict des points d'ancrage."""
|
||||
fond = fond if fond is not None else CLAIR.get(couleur, "white")
|
||||
ax.add_patch(FancyBboxPatch((x - l / 2, y - h / 2), l, h,
|
||||
boxstyle=f"{style},pad=0,rounding_size=0.15" if style == "round" else style,
|
||||
facecolor=fond, edgecolor=couleur, linewidth=1.4, alpha=alpha))
|
||||
if texte:
|
||||
ax.text(x, y, texte, ha="center", va="center", fontsize=taille, color=ENCRE,
|
||||
fontweight="bold" if gras else "normal", linespacing=1.3)
|
||||
return {"c": (x, y), "g": (x - l / 2, y), "d": (x + l / 2, y),
|
||||
"h": (x, y + h / 2), "b": (x, y - h / 2)}
|
||||
|
||||
|
||||
def fleche(ax, depart, arrivee, texte="", couleur=ENCRE, taille=8, double=False, pointille=False,
|
||||
cote="haut", decalage=0.18, courbe=0, fond=False):
|
||||
"""Flèche entre deux points, avec étiquette optionnelle au milieu.
|
||||
|
||||
fond=True place l'étiquette sur fond blanc (utile quand elle croise des traits).
|
||||
"""
|
||||
ax.annotate("", xy=arrivee, xytext=depart,
|
||||
arrowprops=dict(arrowstyle="<|-|>" if double else "-|>", color=couleur, lw=1.3,
|
||||
linestyle="--" if pointille else "-", shrinkA=0, shrinkB=0,
|
||||
mutation_scale=11, connectionstyle=f"arc3,rad={courbe}"))
|
||||
if texte:
|
||||
cadre = dict(boxstyle="square,pad=0.1", fc="white", ec="none") if fond else None
|
||||
mx, my = (depart[0] + arrivee[0]) / 2, (depart[1] + arrivee[1]) / 2
|
||||
vertical = abs(arrivee[0] - depart[0]) < abs(arrivee[1] - depart[1])
|
||||
if vertical:
|
||||
ax.text(mx + decalage, my, texte, ha="left", va="center", fontsize=taille, color=couleur,
|
||||
bbox=cadre)
|
||||
else:
|
||||
dy = decalage if cote == "haut" else -decalage
|
||||
ax.text(mx, my + dy, texte, ha="center", va="bottom" if cote == "haut" else "top",
|
||||
fontsize=taille, color=couleur, bbox=cadre)
|
||||
|
||||
|
||||
def chronogramme(voies, duree, largeur_cm=16, hauteur_voie_cm=1.5, graduations=None, unite="",
|
||||
couleurs=None):
|
||||
"""Chronogramme logique.
|
||||
|
||||
voies : liste de (nom, [(t, niveau), ...]) — niveau 0/1, maintenu jusqu'au point suivant.
|
||||
Renvoie (fig, ax, y_voie) où y_voie(i) donne l'ordonnée de base de la voie i.
|
||||
"""
|
||||
n = len(voies)
|
||||
fig, ax = plt.subplots(figsize=(largeur_cm * CM, (n * hauteur_voie_cm + 1.2) * CM))
|
||||
ax.set_xlim(-duree * 0.02, duree * 1.01)
|
||||
ax.set_ylim(-0.6, n * 1.6)
|
||||
ax.spines[["left", "top", "right"]].set_visible(False)
|
||||
ax.set_yticks([])
|
||||
ax.grid(axis="y", visible=False)
|
||||
if graduations is not None:
|
||||
ax.set_xticks(graduations)
|
||||
if unite:
|
||||
ax.set_xlabel(unite)
|
||||
y_voie = lambda i: (n - 1 - i) * 1.6
|
||||
for i, (nom, points) in enumerate(voies):
|
||||
base = y_voie(i)
|
||||
xs, ys = [], []
|
||||
for k, (t, niv) in enumerate(points):
|
||||
fin = points[k + 1][0] if k + 1 < len(points) else duree
|
||||
xs += [t, fin]
|
||||
ys += [base + niv, base + niv]
|
||||
c = (couleurs or {}).get(nom, BLEU)
|
||||
ax.plot(xs, ys, color=c, lw=1.8, solid_joinstyle="miter")
|
||||
ax.fill_between(xs, base, ys, color=c, alpha=0.08)
|
||||
ax.text(-duree * 0.025, base + 0.5, nom, ha="right", va="center", fontsize=9,
|
||||
color=ENCRE, fontweight="bold")
|
||||
return fig, ax, y_voie
|
||||
|
||||
|
||||
def sequence(acteurs, messages, largeur_cm=16, pas_cm=0.85, couleurs=None, notes=()):
|
||||
"""Diagramme de séquence.
|
||||
|
||||
acteurs : liste de noms. messages : liste de (source, cible, texte[, pointillé]).
|
||||
notes : liste de (index_message, acteur, texte) affichées à droite de la ligne de vie.
|
||||
"""
|
||||
n = len(messages)
|
||||
hauteur = n * pas_cm + 2.2
|
||||
fig, ax = toile(largeur_cm, hauteur)
|
||||
xs = {a: largeur_cm * (i + 0.5) / len(acteurs) for i, a in enumerate(acteurs)}
|
||||
haut = hauteur - 0.6
|
||||
for a, x in xs.items():
|
||||
c = (couleurs or {}).get(a, BLEU)
|
||||
largeur = min(largeur_cm / len(acteurs) - 0.3, 0.2 * len(a) + 0.9)
|
||||
boite(ax, x, haut, largeur, 0.8, a, couleur=c, taille=8.5, gras=True)
|
||||
ax.plot([x, x], [0.55, haut - 0.4], color=GRIS, lw=1, ls=(0, (3, 3)), zorder=0)
|
||||
for k, msg in enumerate(messages):
|
||||
src, dst, texte = msg[:3]
|
||||
pointille = len(msg) > 3 and msg[3]
|
||||
y = haut - 1.2 - k * pas_cm
|
||||
if src == dst:
|
||||
# Boucle vers soi-même : petit rectangle ouvert à droite de la ligne de vie
|
||||
x = xs[src]
|
||||
ax.plot([x, x + 0.45, x + 0.45], [y + 0.12, y + 0.12, y - 0.22], color=ENCRE, lw=1.1)
|
||||
fleche(ax, (x + 0.45, y - 0.22), (x + 0.02, y - 0.22))
|
||||
ax.text(x + 0.6, y - 0.05, texte, fontsize=7.8, va="center", color=ENCRE,
|
||||
bbox=dict(boxstyle="square,pad=0.1", fc="white", ec="none"))
|
||||
continue
|
||||
fleche(ax, (xs[src], y), (xs[dst], y), texte, pointille=pointille, taille=7.8, decalage=0.08,
|
||||
fond=True)
|
||||
for k, acteur, texte in notes:
|
||||
y = haut - 1.2 - k * pas_cm
|
||||
ax.text(xs[acteur] + 0.2, y, texte, fontsize=7.5, color=GRIS, style="italic", va="center")
|
||||
return fig, ax
|
||||
|
||||
|
||||
def legende_couleurs(ax, elements, x, y, taille=7.5):
|
||||
"""Petite légende manuelle : elements = [(couleur, texte), ...]."""
|
||||
for i, (c, t) in enumerate(elements):
|
||||
ax.add_patch(plt.Rectangle((x, y - i * 0.45 - 0.12), 0.35, 0.25, color=c))
|
||||
ax.text(x + 0.5, y - i * 0.45, t, fontsize=taille, va="center", color=ENCRE)
|
||||
|
||||
|
||||
__all__ = ["toile", "boite", "fleche", "chronogramme", "sequence", "legende_couleurs",
|
||||
"CLAIR", "GRIS_CLAIR"]
|
||||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 89 KiB |
@@ -0,0 +1,3 @@
|
||||
schemdraw>=0.23
|
||||
matplotlib>=3.11
|
||||
lxml>=6.1
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Charte graphique commune à tous les schémas de la formation."""
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt # noqa: E402
|
||||
|
||||
# Palette
|
||||
BLEU = "#1f5fa8" # signal principal
|
||||
ORANGE = "#e07a1f" # valeur moyenne, mise en évidence
|
||||
VERT = "#2e8b57"
|
||||
ROUGE = "#c0392b"
|
||||
GRIS = "#6b7280"
|
||||
GRIS_CLAIR = "#e5e7eb"
|
||||
ENCRE = "#1f2937" # texte
|
||||
|
||||
CM = 1 / 2.54 # conversion cm -> pouces pour figsize
|
||||
|
||||
plt.rcParams.update({
|
||||
"font.family": "DejaVu Sans",
|
||||
"font.size": 10,
|
||||
"axes.edgecolor": GRIS,
|
||||
"axes.labelcolor": ENCRE,
|
||||
"axes.titlesize": 11,
|
||||
"axes.titleweight": "bold",
|
||||
"axes.titlecolor": ENCRE,
|
||||
"axes.spines.top": False,
|
||||
"axes.spines.right": False,
|
||||
"axes.grid": True,
|
||||
"grid.color": GRIS_CLAIR,
|
||||
"grid.linewidth": 0.8,
|
||||
"xtick.color": GRIS,
|
||||
"ytick.color": GRIS,
|
||||
"lines.linewidth": 2,
|
||||
"svg.fonttype": "path", # texte vectorisé : rendu identique partout
|
||||
"svg.hashsalt": "formation-esp32", # identifiants SVG stables d'une génération à l'autre
|
||||
"savefig.bbox": "tight",
|
||||
"savefig.pad_inches": 0.1,
|
||||
"savefig.facecolor": "white",
|
||||
})
|
||||
|
||||
|
||||
@dataclass
|
||||
class Figure:
|
||||
"""Déclaration d'une figure à générer.
|
||||
|
||||
La légende et l'emplacement sont dans le .qmd :
|
||||
{#fig-<id>}
|
||||
"""
|
||||
id: str # ex. "m03-pwm-duty" ; le préfixe mXX donne le module
|
||||
dessiner: Callable # renvoie une figure matplotlib ou un dessin schemdraw
|
||||
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 53 KiB |
@@ -0,0 +1,940 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="484.918799pt" height="325.741571pt" viewBox="0 0 484.918799 325.741571" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<metadata>
|
||||
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<cc:Work>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Matplotlib v3.11.2, https://matplotlib.org/</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs>
|
||||
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
|
||||
</defs>
|
||||
<g id="figure_1">
|
||||
<g id="patch_1">
|
||||
<path d="M 0 325.741571
|
||||
L 484.918799 325.741571
|
||||
L 484.918799 0
|
||||
L 0 0
|
||||
z
|
||||
" style="fill: #ffffff"/>
|
||||
</g>
|
||||
<g id="axes_1">
|
||||
<g id="patch_2">
|
||||
<path d="M 353.84179 46.025854
|
||||
L 369.194479 56.992061
|
||||
L 353.84179 67.958267
|
||||
L 353.84179 46.025854
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #c0392b; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_3">
|
||||
<path d="M 353.84179 110.360933
|
||||
L 369.194479 121.327139
|
||||
L 353.84179 132.293346
|
||||
L 353.84179 110.360933
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #2e8b57; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_4">
|
||||
<path d="M 353.84179 174.696011
|
||||
L 369.194479 185.662218
|
||||
L 353.84179 196.628425
|
||||
L 353.84179 174.696011
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_5">
|
||||
<path d="M 353.84179 239.03109
|
||||
L 369.194479 249.997297
|
||||
L 353.84179 260.963503
|
||||
L 353.84179 239.03109
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_1">
|
||||
<path d="M 28.145455 271.92971
|
||||
L 168.512899 271.92971
|
||||
L 168.512899 35.059648
|
||||
L 28.145455 35.059648
|
||||
L 28.145455 271.92971
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_2">
|
||||
<path d="M 168.512899 249.997297
|
||||
L 190.445312 249.997297
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_3">
|
||||
<path d="M 168.512899 185.662218
|
||||
L 190.445312 185.662218
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_4">
|
||||
<path d="M 168.512899 121.327139
|
||||
L 190.445312 121.327139
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_5">
|
||||
<path d="M 168.512899 56.992061
|
||||
L 190.445312 56.992061
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_6">
|
||||
<path d="M 190.445312 56.992061
|
||||
L 203.60476 56.992061
|
||||
L 216.764208 56.992061
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_7">
|
||||
<path d="M 216.764208 56.992061
|
||||
L 243.083103 56.992061
|
||||
L 246.738505 46.025854
|
||||
L 254.04931 67.958267
|
||||
L 261.360114 46.025854
|
||||
L 268.670919 67.958267
|
||||
L 275.981723 46.025854
|
||||
L 283.292527 67.958267
|
||||
L 286.94793 56.992061
|
||||
L 313.266825 56.992061
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_8">
|
||||
<path d="M 313.266825 56.992061
|
||||
L 353.84179 56.992061
|
||||
M 369.194479 46.025854
|
||||
L 369.194479 67.958267
|
||||
M 369.194479 56.992061
|
||||
L 409.769443 56.992061
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #c0392b; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_9">
|
||||
<path d="M 364.807996 40.542751
|
||||
L 371.421593 28.968956
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #c0392b; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_6">
|
||||
<path d="M 368.374766 27.227913
|
||||
L 375.774203 21.35189
|
||||
L 374.46842 30.71
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #c0392b"/>
|
||||
</g>
|
||||
<g id="line2d_10">
|
||||
<path d="M 354.93841 40.542751
|
||||
L 361.552007 28.968956
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #c0392b; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_7">
|
||||
<path d="M 358.50518 27.227913
|
||||
L 365.904617 21.35189
|
||||
L 364.598834 30.71
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #c0392b"/>
|
||||
</g>
|
||||
<g id="line2d_11">
|
||||
<path d="M 409.769443 56.992061
|
||||
L 422.928891 56.992061
|
||||
L 436.088339 56.992061
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_12">
|
||||
<path d="M 190.445312 121.327139
|
||||
L 203.60476 121.327139
|
||||
L 216.764208 121.327139
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_13">
|
||||
<path d="M 216.764208 121.327139
|
||||
L 243.083103 121.327139
|
||||
L 246.738505 110.360933
|
||||
L 254.04931 132.293346
|
||||
L 261.360114 110.360933
|
||||
L 268.670919 132.293346
|
||||
L 275.981723 110.360933
|
||||
L 283.292527 132.293346
|
||||
L 286.94793 121.327139
|
||||
L 313.266825 121.327139
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_14">
|
||||
<path d="M 313.266825 121.327139
|
||||
L 353.84179 121.327139
|
||||
M 369.194479 110.360933
|
||||
L 369.194479 132.293346
|
||||
M 369.194479 121.327139
|
||||
L 409.769443 121.327139
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #2e8b57; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_15">
|
||||
<path d="M 364.807996 104.87783
|
||||
L 371.421593 93.304035
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #2e8b57; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_8">
|
||||
<path d="M 368.374766 91.562991
|
||||
L 375.774203 85.686968
|
||||
L 374.46842 95.045079
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #2e8b57"/>
|
||||
</g>
|
||||
<g id="line2d_16">
|
||||
<path d="M 354.93841 104.87783
|
||||
L 361.552007 93.304035
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #2e8b57; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_9">
|
||||
<path d="M 358.50518 91.562991
|
||||
L 365.904617 85.686968
|
||||
L 364.598834 95.045079
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #2e8b57"/>
|
||||
</g>
|
||||
<g id="line2d_17">
|
||||
<path d="M 409.769443 121.327139
|
||||
L 422.928891 121.327139
|
||||
L 436.088339 121.327139
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_18">
|
||||
<path d="M 190.445312 185.662218
|
||||
L 203.60476 185.662218
|
||||
L 216.764208 185.662218
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_19">
|
||||
<path d="M 216.764208 185.662218
|
||||
L 243.083103 185.662218
|
||||
L 246.738505 174.696011
|
||||
L 254.04931 196.628425
|
||||
L 261.360114 174.696011
|
||||
L 268.670919 196.628425
|
||||
L 275.981723 174.696011
|
||||
L 283.292527 196.628425
|
||||
L 286.94793 185.662218
|
||||
L 313.266825 185.662218
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_20">
|
||||
<path d="M 313.266825 185.662218
|
||||
L 353.84179 185.662218
|
||||
M 369.194479 174.696011
|
||||
L 369.194479 196.628425
|
||||
M 369.194479 185.662218
|
||||
L 409.769443 185.662218
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_21">
|
||||
<path d="M 364.807996 169.212908
|
||||
L 371.421593 157.639114
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_10">
|
||||
<path d="M 368.374766 155.89807
|
||||
L 375.774203 150.022047
|
||||
L 374.46842 159.380157
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #1f5fa8"/>
|
||||
</g>
|
||||
<g id="line2d_22">
|
||||
<path d="M 354.93841 169.212908
|
||||
L 361.552007 157.639114
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_11">
|
||||
<path d="M 358.50518 155.89807
|
||||
L 365.904617 150.022047
|
||||
L 364.598834 159.380157
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #1f5fa8"/>
|
||||
</g>
|
||||
<g id="line2d_23">
|
||||
<path d="M 409.769443 185.662218
|
||||
L 422.928891 185.662218
|
||||
L 436.088339 185.662218
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_24">
|
||||
<path d="M 190.445312 249.997297
|
||||
L 203.60476 249.997297
|
||||
L 216.764208 249.997297
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_25">
|
||||
<path d="M 216.764208 249.997297
|
||||
L 243.083103 249.997297
|
||||
L 246.738505 239.03109
|
||||
L 254.04931 260.963503
|
||||
L 261.360114 239.03109
|
||||
L 268.670919 260.963503
|
||||
L 275.981723 239.03109
|
||||
L 283.292527 260.963503
|
||||
L 286.94793 249.997297
|
||||
L 313.266825 249.997297
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_26">
|
||||
<path d="M 313.266825 249.997297
|
||||
L 353.84179 249.997297
|
||||
M 369.194479 239.03109
|
||||
L 369.194479 260.963503
|
||||
M 369.194479 249.997297
|
||||
L 409.769443 249.997297
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_27">
|
||||
<path d="M 364.807996 233.547987
|
||||
L 371.421593 221.974192
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_12">
|
||||
<path d="M 368.374766 220.233148
|
||||
L 375.774203 214.357125
|
||||
L 374.46842 223.715236
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #e07a1f"/>
|
||||
</g>
|
||||
<g id="line2d_28">
|
||||
<path d="M 354.93841 233.547987
|
||||
L 361.552007 221.974192
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_13">
|
||||
<path d="M 358.50518 220.233148
|
||||
L 365.904617 214.357125
|
||||
L 364.598834 223.715236
|
||||
z
|
||||
" clip-path="url(#p7e999a346a)" style="fill: #e07a1f"/>
|
||||
</g>
|
||||
<g id="line2d_29">
|
||||
<path d="M 409.769443 249.997297
|
||||
L 422.928891 249.997297
|
||||
L 436.088339 249.997297
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_30">
|
||||
<path d="M 436.088339 56.992061
|
||||
L 436.088339 153.494679
|
||||
L 436.088339 249.997297
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_31">
|
||||
<path d="M 436.088339 249.997297
|
||||
L 436.088339 263.156744
|
||||
L 436.088339 276.316192
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_32">
|
||||
<path d="M 436.088339 276.316192
|
||||
L 436.088339 293.862123
|
||||
L 425.122132 293.862123
|
||||
L 447.054545 293.862123
|
||||
M 428.411994 299.125902
|
||||
L 443.764683 299.125902
|
||||
M 433.895098 304.389681
|
||||
L 438.28158 304.389681
|
||||
" clip-path="url(#p7e999a346a)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="text_1">
|
||||
<!-- GPIO7 -->
|
||||
<g style="fill: #1f5fa8" transform="translate(118.585675 255.093078) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-2a" d="M 3809 666
|
||||
L 3809 1919
|
||||
L 2778 1919
|
||||
L 2778 2438
|
||||
L 4434 2438
|
||||
L 4434 434
|
||||
Q 4069 175 3628 42
|
||||
Q 3188 -91 2688 -91
|
||||
Q 1594 -91 976 548
|
||||
Q 359 1188 359 2328
|
||||
Q 359 3472 976 4111
|
||||
Q 1594 4750 2688 4750
|
||||
Q 3144 4750 3555 4637
|
||||
Q 3966 4525 4313 4306
|
||||
L 4313 3634
|
||||
Q 3963 3931 3569 4081
|
||||
Q 3175 4231 2741 4231
|
||||
Q 1884 4231 1454 3753
|
||||
Q 1025 3275 1025 2328
|
||||
Q 1025 1384 1454 906
|
||||
Q 1884 428 2741 428
|
||||
Q 3075 428 3337 486
|
||||
Q 3600 544 3809 666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-33" d="M 1259 4147
|
||||
L 1259 2394
|
||||
L 2053 2394
|
||||
Q 2494 2394 2734 2622
|
||||
Q 2975 2850 2975 3272
|
||||
Q 2975 3691 2734 3919
|
||||
Q 2494 4147 2053 4147
|
||||
L 1259 4147
|
||||
z
|
||||
M 628 4666
|
||||
L 2053 4666
|
||||
Q 2838 4666 3239 4311
|
||||
Q 3641 3956 3641 3272
|
||||
Q 3641 2581 3239 2228
|
||||
Q 2838 1875 2053 1875
|
||||
L 1259 1875
|
||||
L 1259 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-2c" d="M 628 4666
|
||||
L 1259 4666
|
||||
L 1259 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-32" d="M 2522 4238
|
||||
Q 1834 4238 1429 3725
|
||||
Q 1025 3213 1025 2328
|
||||
Q 1025 1447 1429 934
|
||||
Q 1834 422 2522 422
|
||||
Q 3209 422 3611 934
|
||||
Q 4013 1447 4013 2328
|
||||
Q 4013 3213 3611 3725
|
||||
Q 3209 4238 2522 4238
|
||||
z
|
||||
M 2522 4750
|
||||
Q 3503 4750 4090 4092
|
||||
Q 4678 3434 4678 2328
|
||||
Q 4678 1225 4090 567
|
||||
Q 3503 -91 2522 -91
|
||||
Q 1538 -91 948 565
|
||||
Q 359 1222 359 2328
|
||||
Q 359 3434 948 4092
|
||||
Q 1538 4750 2522 4750
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-1a" d="M 525 4666
|
||||
L 3525 4666
|
||||
L 3525 4397
|
||||
L 1831 0
|
||||
L 1172 0
|
||||
L 2766 4134
|
||||
L 525 4134
|
||||
L 525 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-2c" transform="translate(137.78125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-32" transform="translate(167.28125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-1a" transform="translate(246 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_2">
|
||||
<!-- GPIO6 -->
|
||||
<g style="fill: #1f5fa8" transform="translate(118.585675 190.757999) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-19" d="M 2113 2584
|
||||
Q 1688 2584 1439 2293
|
||||
Q 1191 2003 1191 1497
|
||||
Q 1191 994 1439 701
|
||||
Q 1688 409 2113 409
|
||||
Q 2538 409 2786 701
|
||||
Q 3034 994 3034 1497
|
||||
Q 3034 2003 2786 2293
|
||||
Q 2538 2584 2113 2584
|
||||
z
|
||||
M 3366 4563
|
||||
L 3366 3988
|
||||
Q 3128 4100 2886 4159
|
||||
Q 2644 4219 2406 4219
|
||||
Q 1781 4219 1451 3797
|
||||
Q 1122 3375 1075 2522
|
||||
Q 1259 2794 1537 2939
|
||||
Q 1816 3084 2150 3084
|
||||
Q 2853 3084 3261 2657
|
||||
Q 3669 2231 3669 1497
|
||||
Q 3669 778 3244 343
|
||||
Q 2819 -91 2113 -91
|
||||
Q 1303 -91 875 529
|
||||
Q 447 1150 447 2328
|
||||
Q 447 3434 972 4092
|
||||
Q 1497 4750 2381 4750
|
||||
Q 2619 4750 2861 4703
|
||||
Q 3103 4656 3366 4563
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-2c" transform="translate(137.78125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-32" transform="translate(167.28125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-19" transform="translate(246 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_3">
|
||||
<!-- GPIO5 -->
|
||||
<g style="fill: #1f5fa8" transform="translate(118.585675 126.422921) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-18" d="M 691 4666
|
||||
L 3169 4666
|
||||
L 3169 4134
|
||||
L 1269 4134
|
||||
L 1269 2991
|
||||
Q 1406 3038 1543 3061
|
||||
Q 1681 3084 1819 3084
|
||||
Q 2600 3084 3056 2656
|
||||
Q 3513 2228 3513 1497
|
||||
Q 3513 744 3044 326
|
||||
Q 2575 -91 1722 -91
|
||||
Q 1428 -91 1123 -41
|
||||
Q 819 9 494 109
|
||||
L 494 744
|
||||
Q 775 591 1075 516
|
||||
Q 1375 441 1709 441
|
||||
Q 2250 441 2565 725
|
||||
Q 2881 1009 2881 1497
|
||||
Q 2881 1984 2565 2268
|
||||
Q 2250 2553 1709 2553
|
||||
Q 1456 2553 1204 2497
|
||||
Q 953 2441 691 2322
|
||||
L 691 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-2c" transform="translate(137.78125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-32" transform="translate(167.28125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-18" transform="translate(246 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_4">
|
||||
<!-- GPIO4 -->
|
||||
<g style="fill: #1f5fa8" transform="translate(118.585675 62.087842) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-17" d="M 2419 4116
|
||||
L 825 1625
|
||||
L 2419 1625
|
||||
L 2419 4116
|
||||
z
|
||||
M 2253 4666
|
||||
L 3047 4666
|
||||
L 3047 1625
|
||||
L 3713 1625
|
||||
L 3713 1100
|
||||
L 3047 1100
|
||||
L 3047 0
|
||||
L 2419 0
|
||||
L 2419 1100
|
||||
L 313 1100
|
||||
L 313 1709
|
||||
L 2253 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-2c" transform="translate(137.78125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-32" transform="translate(167.28125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-17" transform="translate(246 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_5">
|
||||
<!-- ESP32-S3 -->
|
||||
<g style="fill: #1f5fa8" transform="translate(74.071284 153.476009) scale(0.12 -0.12)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-28" d="M 628 4666
|
||||
L 3578 4666
|
||||
L 3578 4134
|
||||
L 1259 4134
|
||||
L 1259 2753
|
||||
L 3481 2753
|
||||
L 3481 2222
|
||||
L 1259 2222
|
||||
L 1259 531
|
||||
L 3634 531
|
||||
L 3634 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-36" d="M 3425 4513
|
||||
L 3425 3897
|
||||
Q 3066 4069 2747 4153
|
||||
Q 2428 4238 2131 4238
|
||||
Q 1616 4238 1336 4038
|
||||
Q 1056 3838 1056 3469
|
||||
Q 1056 3159 1242 3001
|
||||
Q 1428 2844 1947 2747
|
||||
L 2328 2669
|
||||
Q 3034 2534 3370 2195
|
||||
Q 3706 1856 3706 1288
|
||||
Q 3706 609 3251 259
|
||||
Q 2797 -91 1919 -91
|
||||
Q 1588 -91 1214 -16
|
||||
Q 841 59 441 206
|
||||
L 441 856
|
||||
Q 825 641 1194 531
|
||||
Q 1563 422 1919 422
|
||||
Q 2459 422 2753 634
|
||||
Q 3047 847 3047 1241
|
||||
Q 3047 1584 2836 1778
|
||||
Q 2625 1972 2144 2069
|
||||
L 1759 2144
|
||||
Q 1053 2284 737 2584
|
||||
Q 422 2884 422 3419
|
||||
Q 422 4038 858 4394
|
||||
Q 1294 4750 2059 4750
|
||||
Q 2388 4750 2728 4690
|
||||
Q 3069 4631 3425 4513
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-16" d="M 2597 2516
|
||||
Q 3050 2419 3304 2112
|
||||
Q 3559 1806 3559 1356
|
||||
Q 3559 666 3084 287
|
||||
Q 2609 -91 1734 -91
|
||||
Q 1441 -91 1130 -33
|
||||
Q 819 25 488 141
|
||||
L 488 750
|
||||
Q 750 597 1062 519
|
||||
Q 1375 441 1716 441
|
||||
Q 2309 441 2620 675
|
||||
Q 2931 909 2931 1356
|
||||
Q 2931 1769 2642 2001
|
||||
Q 2353 2234 1838 2234
|
||||
L 1294 2234
|
||||
L 1294 2753
|
||||
L 1863 2753
|
||||
Q 2328 2753 2575 2939
|
||||
Q 2822 3125 2822 3475
|
||||
Q 2822 3834 2567 4026
|
||||
Q 2313 4219 1838 4219
|
||||
Q 1578 4219 1281 4162
|
||||
Q 984 4106 628 3988
|
||||
L 628 4550
|
||||
Q 988 4650 1302 4700
|
||||
Q 1616 4750 1894 4750
|
||||
Q 2613 4750 3031 4423
|
||||
Q 3450 4097 3450 3541
|
||||
Q 3450 3153 3228 2886
|
||||
Q 3006 2619 2597 2516
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-15" d="M 1228 531
|
||||
L 3431 531
|
||||
L 3431 0
|
||||
L 469 0
|
||||
L 469 531
|
||||
Q 828 903 1448 1529
|
||||
Q 2069 2156 2228 2338
|
||||
Q 2531 2678 2651 2914
|
||||
Q 2772 3150 2772 3378
|
||||
Q 2772 3750 2511 3984
|
||||
Q 2250 4219 1831 4219
|
||||
Q 1534 4219 1204 4116
|
||||
Q 875 4013 500 3803
|
||||
L 500 4441
|
||||
Q 881 4594 1212 4672
|
||||
Q 1544 4750 1819 4750
|
||||
Q 2544 4750 2975 4387
|
||||
Q 3406 4025 3406 3419
|
||||
Q 3406 3131 3298 2873
|
||||
Q 3191 2616 2906 2266
|
||||
Q 2828 2175 2409 1742
|
||||
Q 1991 1309 1228 531
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-10" d="M 313 2009
|
||||
L 1997 2009
|
||||
L 1997 1497
|
||||
L 313 1497
|
||||
L 313 2009
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-28"/>
|
||||
<use xlink:href="#DejaVuSans-36" transform="translate(63.1875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(126.671875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-16" transform="translate(186.96875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-15" transform="translate(250.59375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-10" transform="translate(314.21875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-36" transform="translate(350.296875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-16" transform="translate(413.78125 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_6">
|
||||
<!-- 220 Ω -->
|
||||
<g style="fill: #1f2937" transform="translate(251.557001 40.415231) scale(0.09 -0.09)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-13" d="M 2034 4250
|
||||
Q 1547 4250 1301 3770
|
||||
Q 1056 3291 1056 2328
|
||||
Q 1056 1369 1301 889
|
||||
Q 1547 409 2034 409
|
||||
Q 2525 409 2770 889
|
||||
Q 3016 1369 3016 2328
|
||||
Q 3016 3291 2770 3770
|
||||
Q 2525 4250 2034 4250
|
||||
z
|
||||
M 2034 4750
|
||||
Q 2819 4750 3233 4129
|
||||
Q 3647 3509 3647 2328
|
||||
Q 3647 1150 3233 529
|
||||
Q 2819 -91 2034 -91
|
||||
Q 1250 -91 836 529
|
||||
Q 422 1150 422 2328
|
||||
Q 422 3509 836 4129
|
||||
Q 1250 4750 2034 4750
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-3" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-33d" d="M 4647 556
|
||||
L 4647 0
|
||||
L 2772 0
|
||||
L 2772 556
|
||||
Q 3325 859 3634 1378
|
||||
Q 3944 1897 3944 2528
|
||||
Q 3944 3278 3531 3731
|
||||
Q 3119 4184 2444 4184
|
||||
Q 1769 4184 1355 3729
|
||||
Q 941 3275 941 2528
|
||||
Q 941 1897 1250 1378
|
||||
Q 1563 859 2119 556
|
||||
L 2119 0
|
||||
L 244 0
|
||||
L 244 556
|
||||
L 1241 556
|
||||
Q 747 991 519 1456
|
||||
Q 294 1922 294 2497
|
||||
Q 294 3491 894 4106
|
||||
Q 1491 4722 2444 4722
|
||||
Q 3391 4722 3994 4106
|
||||
Q 4594 3494 4594 2528
|
||||
Q 4594 1922 4372 1459
|
||||
Q 4150 997 3647 556
|
||||
L 4647 556
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-15"/>
|
||||
<use xlink:href="#DejaVuSans-15" transform="translate(63.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-13" transform="translate(127.25 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(190.875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33d" transform="translate(222.65625 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_7">
|
||||
<!-- 220 Ω -->
|
||||
<g style="fill: #1f2937" transform="translate(251.557001 104.75031) scale(0.09 -0.09)">
|
||||
<use xlink:href="#DejaVuSans-15"/>
|
||||
<use xlink:href="#DejaVuSans-15" transform="translate(63.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-13" transform="translate(127.25 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(190.875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33d" transform="translate(222.65625 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_8">
|
||||
<!-- 220 Ω -->
|
||||
<g style="fill: #1f2937" transform="translate(251.557001 169.085388) scale(0.09 -0.09)">
|
||||
<use xlink:href="#DejaVuSans-15"/>
|
||||
<use xlink:href="#DejaVuSans-15" transform="translate(63.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-13" transform="translate(127.25 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(190.875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33d" transform="translate(222.65625 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_9">
|
||||
<!-- 220 Ω -->
|
||||
<g style="fill: #1f2937" transform="translate(251.557001 233.420467) scale(0.09 -0.09)">
|
||||
<use xlink:href="#DejaVuSans-15"/>
|
||||
<use xlink:href="#DejaVuSans-15" transform="translate(63.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-13" transform="translate(127.25 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(190.875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33d" transform="translate(222.65625 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_10">
|
||||
<!-- GND -->
|
||||
<g style="fill: #6b7280" transform="translate(447.702393 147.657021) scale(0.09 -0.09)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-31" d="M 628 4666
|
||||
L 1478 4666
|
||||
L 3547 763
|
||||
L 3547 4666
|
||||
L 4159 4666
|
||||
L 4159 0
|
||||
L 3309 0
|
||||
L 1241 3903
|
||||
L 1241 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-27" d="M 1259 4147
|
||||
L 1259 519
|
||||
L 2022 519
|
||||
Q 2988 519 3436 956
|
||||
Q 3884 1394 3884 2338
|
||||
Q 3884 3275 3436 3711
|
||||
Q 2988 4147 2022 4147
|
||||
L 1259 4147
|
||||
z
|
||||
M 628 4666
|
||||
L 1925 4666
|
||||
Q 3281 4666 3915 4102
|
||||
Q 4550 3538 4550 2338
|
||||
Q 4550 1131 3912 565
|
||||
Q 3275 0 1925 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-31" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-27" transform="translate(152.296875 0)"/>
|
||||
</g>
|
||||
<!-- commun -->
|
||||
<g style="fill: #6b7280" transform="translate(438.322705 155.837177) scale(0.09 -0.09)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-46" d="M 3122 3366
|
||||
L 3122 2828
|
||||
Q 2878 2963 2633 3030
|
||||
Q 2388 3097 2138 3097
|
||||
Q 1578 3097 1268 2742
|
||||
Q 959 2388 959 1747
|
||||
Q 959 1106 1268 751
|
||||
Q 1578 397 2138 397
|
||||
Q 2388 397 2633 464
|
||||
Q 2878 531 3122 666
|
||||
L 3122 134
|
||||
Q 2881 22 2623 -34
|
||||
Q 2366 -91 2075 -91
|
||||
Q 1284 -91 818 406
|
||||
Q 353 903 353 1747
|
||||
Q 353 2603 823 3093
|
||||
Q 1294 3584 2113 3584
|
||||
Q 2378 3584 2631 3529
|
||||
Q 2884 3475 3122 3366
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-52" d="M 1959 3097
|
||||
Q 1497 3097 1228 2736
|
||||
Q 959 2375 959 1747
|
||||
Q 959 1119 1226 758
|
||||
Q 1494 397 1959 397
|
||||
Q 2419 397 2687 759
|
||||
Q 2956 1122 2956 1747
|
||||
Q 2956 2369 2687 2733
|
||||
Q 2419 3097 1959 3097
|
||||
z
|
||||
M 1959 3584
|
||||
Q 2709 3584 3137 3096
|
||||
Q 3566 2609 3566 1747
|
||||
Q 3566 888 3137 398
|
||||
Q 2709 -91 1959 -91
|
||||
Q 1206 -91 779 398
|
||||
Q 353 888 353 1747
|
||||
Q 353 2609 779 3096
|
||||
Q 1206 3584 1959 3584
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-50" d="M 3328 2828
|
||||
Q 3544 3216 3844 3400
|
||||
Q 4144 3584 4550 3584
|
||||
Q 5097 3584 5394 3201
|
||||
Q 5691 2819 5691 2113
|
||||
L 5691 0
|
||||
L 5113 0
|
||||
L 5113 2094
|
||||
Q 5113 2597 4934 2840
|
||||
Q 4756 3084 4391 3084
|
||||
Q 3944 3084 3684 2787
|
||||
Q 3425 2491 3425 1978
|
||||
L 3425 0
|
||||
L 2847 0
|
||||
L 2847 2094
|
||||
Q 2847 2600 2669 2842
|
||||
Q 2491 3084 2119 3084
|
||||
Q 1678 3084 1418 2786
|
||||
Q 1159 2488 1159 1978
|
||||
L 1159 0
|
||||
L 581 0
|
||||
L 581 3500
|
||||
L 1159 3500
|
||||
L 1159 2956
|
||||
Q 1356 3278 1631 3431
|
||||
Q 1906 3584 2284 3584
|
||||
Q 2666 3584 2933 3390
|
||||
Q 3200 3197 3328 2828
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-58" d="M 544 1381
|
||||
L 544 3500
|
||||
L 1119 3500
|
||||
L 1119 1403
|
||||
Q 1119 906 1312 657
|
||||
Q 1506 409 1894 409
|
||||
Q 2359 409 2629 706
|
||||
Q 2900 1003 2900 1516
|
||||
L 2900 3500
|
||||
L 3475 3500
|
||||
L 3475 0
|
||||
L 2900 0
|
||||
L 2900 538
|
||||
Q 2691 219 2414 64
|
||||
Q 2138 -91 1772 -91
|
||||
Q 1169 -91 856 284
|
||||
Q 544 659 544 1381
|
||||
z
|
||||
M 1991 3584
|
||||
L 1991 3584
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-51" d="M 3513 2113
|
||||
L 3513 0
|
||||
L 2938 0
|
||||
L 2938 2094
|
||||
Q 2938 2591 2744 2837
|
||||
Q 2550 3084 2163 3084
|
||||
Q 1697 3084 1428 2787
|
||||
Q 1159 2491 1159 1978
|
||||
L 1159 0
|
||||
L 581 0
|
||||
L 581 3500
|
||||
L 1159 3500
|
||||
L 1159 2956
|
||||
Q 1366 3272 1645 3428
|
||||
Q 1925 3584 2291 3584
|
||||
Q 2894 3584 3203 3211
|
||||
Q 3513 2838 3513 2113
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-46"/>
|
||||
<use xlink:href="#DejaVuSans-52" transform="translate(54.984375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-50" transform="translate(116.171875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-50" transform="translate(213.578125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-58" transform="translate(310.984375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-51" transform="translate(374.359375 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="p7e999a346a">
|
||||
<rect x="7.2" y="7.2" width="460.8" height="311.341571"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 75 KiB |
@@ -0,0 +1,719 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="497.702001pt" height="242.282279pt" viewBox="0 0 497.702001 242.282279" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<metadata>
|
||||
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<cc:Work>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Matplotlib v3.11.2, https://matplotlib.org/</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs>
|
||||
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
|
||||
</defs>
|
||||
<g id="figure_1">
|
||||
<g id="patch_1">
|
||||
<path d="M -0 242.282279
|
||||
L 497.702001 242.282279
|
||||
L 497.702001 0
|
||||
L -0 0
|
||||
z
|
||||
" style="fill: #ffffff"/>
|
||||
</g>
|
||||
<g id="axes_1">
|
||||
<g id="patch_2">
|
||||
<path d="M 444.794366 164.385404
|
||||
L 437.451525 174.665381
|
||||
L 430.108684 164.385404
|
||||
L 444.794366 164.385404
|
||||
z
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_1">
|
||||
<path d="M 129.052194 147.496869
|
||||
L 319.966065 147.496869
|
||||
L 319.966065 24.137136
|
||||
L 129.052194 24.137136
|
||||
L 129.052194 147.496869
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_2">
|
||||
<path d="M 129.052194 132.811186
|
||||
L 114.366512 132.811186
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_3">
|
||||
<path d="M 129.052194 85.817003
|
||||
L 114.366512 85.817003
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_4">
|
||||
<path d="M 129.052194 38.822819
|
||||
L 114.366512 38.822819
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_5">
|
||||
<path d="M 319.966065 132.811186
|
||||
L 334.651748 132.811186
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f5fa8; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_6">
|
||||
<path d="M 114.366512 38.822819
|
||||
L 82.05801 38.822819
|
||||
L 49.749509 38.822819
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_7">
|
||||
<path d="M 114.366512 132.811186
|
||||
L 82.05801 132.811186
|
||||
L 49.749509 132.811186
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_8">
|
||||
<path d="M 49.749509 38.822819
|
||||
L 49.749509 71.13132
|
||||
L 57.09235 73.578934
|
||||
L 42.406668 78.474161
|
||||
L 57.09235 83.369389
|
||||
L 42.406668 88.264616
|
||||
L 57.09235 93.159844
|
||||
L 42.406668 98.055071
|
||||
L 49.749509 100.502685
|
||||
L 49.749509 132.811186
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #2e8b57; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_9">
|
||||
<path d="M 70.896892 85.817003
|
||||
L 64.435191 85.817003
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #2e8b57; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_3">
|
||||
<path d="M 64.435191 88.019855
|
||||
L 57.09235 85.817003
|
||||
L 64.435191 83.61415
|
||||
z
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: #2e8b57"/>
|
||||
</g>
|
||||
<g id="line2d_10">
|
||||
<path d="M 70.896892 85.817003
|
||||
L 114.366512 85.817003
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #2e8b57; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_11">
|
||||
<path d="M 49.749509 132.811186
|
||||
L 49.749509 144.559732
|
||||
L 42.406668 144.559732
|
||||
L 57.09235 144.559732
|
||||
M 44.60952 148.084296
|
||||
L 54.889498 148.084296
|
||||
M 48.280941 151.60886
|
||||
L 51.218077 151.60886
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_12">
|
||||
<path d="M 334.651748 132.811186
|
||||
L 349.33743 132.811186
|
||||
L 364.023113 132.811186
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_13">
|
||||
<path d="M 364.023113 132.811186
|
||||
L 386.051636 132.811186
|
||||
L 388.49925 125.468345
|
||||
L 393.394477 140.154028
|
||||
L 398.289705 125.468345
|
||||
L 403.184932 140.154028
|
||||
L 408.08016 125.468345
|
||||
L 412.975387 140.154028
|
||||
L 415.423001 132.811186
|
||||
L 437.451525 132.811186
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_14">
|
||||
<path d="M 437.451525 132.811186
|
||||
L 437.451525 164.385404
|
||||
M 444.794366 174.665381
|
||||
L 430.108684 174.665381
|
||||
M 437.451525 174.665381
|
||||
L 437.451525 206.239598
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="line2d_15">
|
||||
<path d="M 448.465787 171.728245
|
||||
L 456.215461 176.15663
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_4">
|
||||
<path d="M 457.381244 174.116512
|
||||
L 461.315759 179.071086
|
||||
L 455.049679 178.196749
|
||||
z
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: #e07a1f"/>
|
||||
</g>
|
||||
<g id="line2d_16">
|
||||
<path d="M 448.465787 165.119688
|
||||
L 456.215461 169.548073
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #e07a1f; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="patch_5">
|
||||
<path d="M 457.381244 167.507954
|
||||
L 461.315759 172.462529
|
||||
L 455.049679 171.588192
|
||||
z
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: #e07a1f"/>
|
||||
</g>
|
||||
<g id="line2d_17">
|
||||
<path d="M 437.451525 206.239598
|
||||
L 437.451525 217.988144
|
||||
L 430.108684 217.988144
|
||||
L 444.794366 217.988144
|
||||
M 432.311536 221.512708
|
||||
L 442.591514 221.512708
|
||||
M 435.982956 225.037272
|
||||
L 438.920093 225.037272
|
||||
" clip-path="url(#p4639aee0bc)" style="fill: none; stroke: #1f2937; stroke-width: 2; stroke-linecap: round"/>
|
||||
</g>
|
||||
<g id="text_1">
|
||||
<!-- GND -->
|
||||
<g style="fill: #1f5fa8" transform="translate(133.457899 137.906968) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-2a" d="M 3809 666
|
||||
L 3809 1919
|
||||
L 2778 1919
|
||||
L 2778 2438
|
||||
L 4434 2438
|
||||
L 4434 434
|
||||
Q 4069 175 3628 42
|
||||
Q 3188 -91 2688 -91
|
||||
Q 1594 -91 976 548
|
||||
Q 359 1188 359 2328
|
||||
Q 359 3472 976 4111
|
||||
Q 1594 4750 2688 4750
|
||||
Q 3144 4750 3555 4637
|
||||
Q 3966 4525 4313 4306
|
||||
L 4313 3634
|
||||
Q 3963 3931 3569 4081
|
||||
Q 3175 4231 2741 4231
|
||||
Q 1884 4231 1454 3753
|
||||
Q 1025 3275 1025 2328
|
||||
Q 1025 1384 1454 906
|
||||
Q 1884 428 2741 428
|
||||
Q 3075 428 3337 486
|
||||
Q 3600 544 3809 666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-31" d="M 628 4666
|
||||
L 1478 4666
|
||||
L 3547 763
|
||||
L 3547 4666
|
||||
L 4159 4666
|
||||
L 4159 0
|
||||
L 3309 0
|
||||
L 1241 3903
|
||||
L 1241 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-27" d="M 1259 4147
|
||||
L 1259 519
|
||||
L 2022 519
|
||||
Q 2988 519 3436 956
|
||||
Q 3884 1394 3884 2338
|
||||
Q 3884 3275 3436 3711
|
||||
Q 2988 4147 2022 4147
|
||||
L 1259 4147
|
||||
z
|
||||
M 628 4666
|
||||
L 1925 4666
|
||||
Q 3281 4666 3915 4102
|
||||
Q 4550 3538 4550 2338
|
||||
Q 4550 1131 3912 565
|
||||
Q 3275 0 1925 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-31" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-27" transform="translate(152.296875 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_2">
|
||||
<!-- GPIO1 (ADC1) -->
|
||||
<g style="fill: #1f5fa8" transform="translate(133.457899 90.205128) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-33" d="M 1259 4147
|
||||
L 1259 2394
|
||||
L 2053 2394
|
||||
Q 2494 2394 2734 2622
|
||||
Q 2975 2850 2975 3272
|
||||
Q 2975 3691 2734 3919
|
||||
Q 2494 4147 2053 4147
|
||||
L 1259 4147
|
||||
z
|
||||
M 628 4666
|
||||
L 2053 4666
|
||||
Q 2838 4666 3239 4311
|
||||
Q 3641 3956 3641 3272
|
||||
Q 3641 2581 3239 2228
|
||||
Q 2838 1875 2053 1875
|
||||
L 1259 1875
|
||||
L 1259 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-2c" d="M 628 4666
|
||||
L 1259 4666
|
||||
L 1259 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-32" d="M 2522 4238
|
||||
Q 1834 4238 1429 3725
|
||||
Q 1025 3213 1025 2328
|
||||
Q 1025 1447 1429 934
|
||||
Q 1834 422 2522 422
|
||||
Q 3209 422 3611 934
|
||||
Q 4013 1447 4013 2328
|
||||
Q 4013 3213 3611 3725
|
||||
Q 3209 4238 2522 4238
|
||||
z
|
||||
M 2522 4750
|
||||
Q 3503 4750 4090 4092
|
||||
Q 4678 3434 4678 2328
|
||||
Q 4678 1225 4090 567
|
||||
Q 3503 -91 2522 -91
|
||||
Q 1538 -91 948 565
|
||||
Q 359 1222 359 2328
|
||||
Q 359 3434 948 4092
|
||||
Q 1538 4750 2522 4750
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-14" d="M 794 531
|
||||
L 1825 531
|
||||
L 1825 4091
|
||||
L 703 3866
|
||||
L 703 4441
|
||||
L 1819 4666
|
||||
L 2450 4666
|
||||
L 2450 531
|
||||
L 3481 531
|
||||
L 3481 0
|
||||
L 794 0
|
||||
L 794 531
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-3" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-b" d="M 1984 4856
|
||||
Q 1566 4138 1362 3434
|
||||
Q 1159 2731 1159 2009
|
||||
Q 1159 1288 1364 580
|
||||
Q 1569 -128 1984 -844
|
||||
L 1484 -844
|
||||
Q 1016 -109 783 600
|
||||
Q 550 1309 550 2009
|
||||
Q 550 2706 781 3412
|
||||
Q 1013 4119 1484 4856
|
||||
L 1984 4856
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-24" d="M 2188 4044
|
||||
L 1331 1722
|
||||
L 3047 1722
|
||||
L 2188 4044
|
||||
z
|
||||
M 1831 4666
|
||||
L 2547 4666
|
||||
L 4325 0
|
||||
L 3669 0
|
||||
L 3244 1197
|
||||
L 1141 1197
|
||||
L 716 0
|
||||
L 50 0
|
||||
L 1831 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-26" d="M 4122 4306
|
||||
L 4122 3641
|
||||
Q 3803 3938 3442 4084
|
||||
Q 3081 4231 2675 4231
|
||||
Q 1875 4231 1450 3742
|
||||
Q 1025 3253 1025 2328
|
||||
Q 1025 1406 1450 917
|
||||
Q 1875 428 2675 428
|
||||
Q 3081 428 3442 575
|
||||
Q 3803 722 4122 1019
|
||||
L 4122 359
|
||||
Q 3791 134 3420 21
|
||||
Q 3050 -91 2638 -91
|
||||
Q 1578 -91 968 557
|
||||
Q 359 1206 359 2328
|
||||
Q 359 3453 968 4101
|
||||
Q 1578 4750 2638 4750
|
||||
Q 3056 4750 3426 4639
|
||||
Q 3797 4528 4122 4306
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-c" d="M 513 4856
|
||||
L 1013 4856
|
||||
Q 1481 4119 1714 3412
|
||||
Q 1947 2706 1947 2009
|
||||
Q 1947 1309 1714 600
|
||||
Q 1481 -109 1013 -844
|
||||
L 513 -844
|
||||
Q 928 -128 1133 580
|
||||
Q 1338 1288 1338 2009
|
||||
Q 1338 2731 1133 3434
|
||||
Q 928 4138 513 4856
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-2c" transform="translate(137.78125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-32" transform="translate(167.28125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-14" transform="translate(246 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(309.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-b" transform="translate(341.40625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-24" transform="translate(380.421875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-27" transform="translate(448.828125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-26" transform="translate(525.828125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-14" transform="translate(595.65625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-c" transform="translate(659.28125 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_3">
|
||||
<!-- 3V3 -->
|
||||
<g style="fill: #1f5fa8" transform="translate(133.457899 43.9186) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-16" d="M 2597 2516
|
||||
Q 3050 2419 3304 2112
|
||||
Q 3559 1806 3559 1356
|
||||
Q 3559 666 3084 287
|
||||
Q 2609 -91 1734 -91
|
||||
Q 1441 -91 1130 -33
|
||||
Q 819 25 488 141
|
||||
L 488 750
|
||||
Q 750 597 1062 519
|
||||
Q 1375 441 1716 441
|
||||
Q 2309 441 2620 675
|
||||
Q 2931 909 2931 1356
|
||||
Q 2931 1769 2642 2001
|
||||
Q 2353 2234 1838 2234
|
||||
L 1294 2234
|
||||
L 1294 2753
|
||||
L 1863 2753
|
||||
Q 2328 2753 2575 2939
|
||||
Q 2822 3125 2822 3475
|
||||
Q 2822 3834 2567 4026
|
||||
Q 2313 4219 1838 4219
|
||||
Q 1578 4219 1281 4162
|
||||
Q 984 4106 628 3988
|
||||
L 628 4550
|
||||
Q 988 4650 1302 4700
|
||||
Q 1616 4750 1894 4750
|
||||
Q 2613 4750 3031 4423
|
||||
Q 3450 4097 3450 3541
|
||||
Q 3450 3153 3228 2886
|
||||
Q 3006 2619 2597 2516
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-39" d="M 1831 0
|
||||
L 50 4666
|
||||
L 709 4666
|
||||
L 2188 738
|
||||
L 3669 4666
|
||||
L 4325 4666
|
||||
L 2547 0
|
||||
L 1831 0
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-16"/>
|
||||
<use xlink:href="#DejaVuSans-39" transform="translate(63.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-16" transform="translate(132.03125 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_4">
|
||||
<!-- GPIO4 (PWM) -->
|
||||
<g style="fill: #1f5fa8" transform="translate(222.475673 137.199311) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-17" d="M 2419 4116
|
||||
L 825 1625
|
||||
L 2419 1625
|
||||
L 2419 4116
|
||||
z
|
||||
M 2253 4666
|
||||
L 3047 4666
|
||||
L 3047 1625
|
||||
L 3713 1625
|
||||
L 3713 1100
|
||||
L 3047 1100
|
||||
L 3047 0
|
||||
L 2419 0
|
||||
L 2419 1100
|
||||
L 313 1100
|
||||
L 313 1709
|
||||
L 2253 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-3a" d="M 213 4666
|
||||
L 850 4666
|
||||
L 1831 722
|
||||
L 2809 4666
|
||||
L 3519 4666
|
||||
L 4500 722
|
||||
L 5478 4666
|
||||
L 6119 4666
|
||||
L 4947 0
|
||||
L 4153 0
|
||||
L 3169 4050
|
||||
L 2175 0
|
||||
L 1381 0
|
||||
L 213 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-30" d="M 628 4666
|
||||
L 1569 4666
|
||||
L 2759 1491
|
||||
L 3956 4666
|
||||
L 4897 4666
|
||||
L 4897 0
|
||||
L 4281 0
|
||||
L 4281 4097
|
||||
L 3078 897
|
||||
L 2444 897
|
||||
L 1241 4097
|
||||
L 1241 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2a"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(77.484375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-2c" transform="translate(137.78125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-32" transform="translate(167.28125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-17" transform="translate(246 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(309.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-b" transform="translate(341.40625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(380.421875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3a" transform="translate(440.71875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-30" transform="translate(539.59375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-c" transform="translate(625.875 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_5">
|
||||
<!-- ESP32-S3 -->
|
||||
<g style="fill: #1f5fa8" transform="translate(191.090692 19.295781) scale(0.14 -0.14)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-28" d="M 628 4666
|
||||
L 3578 4666
|
||||
L 3578 4134
|
||||
L 1259 4134
|
||||
L 1259 2753
|
||||
L 3481 2753
|
||||
L 3481 2222
|
||||
L 1259 2222
|
||||
L 1259 531
|
||||
L 3634 531
|
||||
L 3634 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-36" d="M 3425 4513
|
||||
L 3425 3897
|
||||
Q 3066 4069 2747 4153
|
||||
Q 2428 4238 2131 4238
|
||||
Q 1616 4238 1336 4038
|
||||
Q 1056 3838 1056 3469
|
||||
Q 1056 3159 1242 3001
|
||||
Q 1428 2844 1947 2747
|
||||
L 2328 2669
|
||||
Q 3034 2534 3370 2195
|
||||
Q 3706 1856 3706 1288
|
||||
Q 3706 609 3251 259
|
||||
Q 2797 -91 1919 -91
|
||||
Q 1588 -91 1214 -16
|
||||
Q 841 59 441 206
|
||||
L 441 856
|
||||
Q 825 641 1194 531
|
||||
Q 1563 422 1919 422
|
||||
Q 2459 422 2753 634
|
||||
Q 3047 847 3047 1241
|
||||
Q 3047 1584 2836 1778
|
||||
Q 2625 1972 2144 2069
|
||||
L 1759 2144
|
||||
Q 1053 2284 737 2584
|
||||
Q 422 2884 422 3419
|
||||
Q 422 4038 858 4394
|
||||
Q 1294 4750 2059 4750
|
||||
Q 2388 4750 2728 4690
|
||||
Q 3069 4631 3425 4513
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-15" d="M 1228 531
|
||||
L 3431 531
|
||||
L 3431 0
|
||||
L 469 0
|
||||
L 469 531
|
||||
Q 828 903 1448 1529
|
||||
Q 2069 2156 2228 2338
|
||||
Q 2531 2678 2651 2914
|
||||
Q 2772 3150 2772 3378
|
||||
Q 2772 3750 2511 3984
|
||||
Q 2250 4219 1831 4219
|
||||
Q 1534 4219 1204 4116
|
||||
Q 875 4013 500 3803
|
||||
L 500 4441
|
||||
Q 881 4594 1212 4672
|
||||
Q 1544 4750 1819 4750
|
||||
Q 2544 4750 2975 4387
|
||||
Q 3406 4025 3406 3419
|
||||
Q 3406 3131 3298 2873
|
||||
Q 3191 2616 2906 2266
|
||||
Q 2828 2175 2409 1742
|
||||
Q 1991 1309 1228 531
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-10" d="M 313 2009
|
||||
L 1997 2009
|
||||
L 1997 1497
|
||||
L 313 1497
|
||||
L 313 2009
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-28"/>
|
||||
<use xlink:href="#DejaVuSans-36" transform="translate(63.1875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33" transform="translate(126.671875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-16" transform="translate(186.96875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-15" transform="translate(250.59375 0)"/>
|
||||
<use xlink:href="#DejaVuSans-10" transform="translate(314.21875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-36" transform="translate(350.296875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-16" transform="translate(413.78125 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_6">
|
||||
<!-- 10 kΩ -->
|
||||
<g style="fill: #2e8b57" transform="translate(7.2 89.91794) scale(0.11 -0.11)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-13" d="M 2034 4250
|
||||
Q 1547 4250 1301 3770
|
||||
Q 1056 3291 1056 2328
|
||||
Q 1056 1369 1301 889
|
||||
Q 1547 409 2034 409
|
||||
Q 2525 409 2770 889
|
||||
Q 3016 1369 3016 2328
|
||||
Q 3016 3291 2770 3770
|
||||
Q 2525 4250 2034 4250
|
||||
z
|
||||
M 2034 4750
|
||||
Q 2819 4750 3233 4129
|
||||
Q 3647 3509 3647 2328
|
||||
Q 3647 1150 3233 529
|
||||
Q 2819 -91 2034 -91
|
||||
Q 1250 -91 836 529
|
||||
Q 422 1150 422 2328
|
||||
Q 422 3509 836 4129
|
||||
Q 1250 4750 2034 4750
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-4e" d="M 581 4863
|
||||
L 1159 4863
|
||||
L 1159 1991
|
||||
L 2875 3500
|
||||
L 3609 3500
|
||||
L 1753 1863
|
||||
L 3688 0
|
||||
L 2938 0
|
||||
L 1159 1709
|
||||
L 1159 0
|
||||
L 581 0
|
||||
L 581 4863
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-33d" d="M 4647 556
|
||||
L 4647 0
|
||||
L 2772 0
|
||||
L 2772 556
|
||||
Q 3325 859 3634 1378
|
||||
Q 3944 1897 3944 2528
|
||||
Q 3944 3278 3531 3731
|
||||
Q 3119 4184 2444 4184
|
||||
Q 1769 4184 1355 3729
|
||||
Q 941 3275 941 2528
|
||||
Q 941 1897 1250 1378
|
||||
Q 1563 859 2119 556
|
||||
L 2119 0
|
||||
L 244 0
|
||||
L 244 556
|
||||
L 1241 556
|
||||
Q 747 991 519 1456
|
||||
Q 294 1922 294 2497
|
||||
Q 294 3491 894 4106
|
||||
Q 1491 4722 2444 4722
|
||||
Q 3391 4722 3994 4106
|
||||
Q 4594 3494 4594 2528
|
||||
Q 4594 1922 4372 1459
|
||||
Q 4150 997 3647 556
|
||||
L 4647 556
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-14"/>
|
||||
<use xlink:href="#DejaVuSans-13" transform="translate(63.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(127.25 0)"/>
|
||||
<use xlink:href="#DejaVuSans-4e" transform="translate(159.03125 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33d" transform="translate(216.9375 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_7">
|
||||
<!-- 220 Ω -->
|
||||
<g style="fill: #e07a1f" transform="translate(384.288022 121.035037) scale(0.11 -0.11)">
|
||||
<use xlink:href="#DejaVuSans-15"/>
|
||||
<use xlink:href="#DejaVuSans-15" transform="translate(63.625 0)"/>
|
||||
<use xlink:href="#DejaVuSans-13" transform="translate(127.25 0)"/>
|
||||
<use xlink:href="#DejaVuSans-3" transform="translate(190.875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-33d" transform="translate(222.65625 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="text_8">
|
||||
<!-- LED -->
|
||||
<g style="fill: #e07a1f" transform="translate(468.952313 173.535236) scale(0.11 -0.11)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-2f" d="M 628 4666
|
||||
L 1259 4666
|
||||
L 1259 531
|
||||
L 3531 531
|
||||
L 3531 0
|
||||
L 628 0
|
||||
L 628 4666
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-2f"/>
|
||||
<use xlink:href="#DejaVuSans-28" transform="translate(55.71875 0)"/>
|
||||
<use xlink:href="#DejaVuSans-27" transform="translate(118.90625 0)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="p4639aee0bc">
|
||||
<rect x="21.461213" y="14.09213" width="460.8" height="220.990149"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 357 KiB |
|
After Width: | Height: | Size: 644 KiB |
|
After Width: | Height: | Size: 49 KiB |