Pourquoi Encore un Projet M5Stack ?
Quand j’ai reçu mon M5StickC-Plus2, j’étais excité à l’idée de construire quelque chose de cool. Mais comme beaucoup de développeurs, j’ai rapidement buté sur… du travail de configuration ennuyeux.
Vous connaissez le refrain : configurer les boutons, gérer les coordonnées d’affichage, gérer les menus, s’occuper de la gestion d’alimentation, mettre en place des timers. Avant même de pouvoir commencer la partie intéressante de mon projet, je devais écrire des centaines de lignes de code d’infrastructure.
Les bibliothèques aident, mais elles viennent avec leurs propres problèmes :
- Boîtes noires : Vous ne pouvez pas voir ou modifier leur fonctionnement interne
- Sur-abstraction : Parfois vous avez besoin d’un contrôle précis
- Courbe d’apprentissage : Chaque bibliothèque a sa propre API à apprendre
- Dépendances : Une bibliothèque en tire cinq autres
Je voulais quelque chose de différent : un starter kit où vous possédez tout le code.
La Philosophie : Une Fondation, Pas un Framework
Ce projet n’est pas une bibliothèque que vous importez. C’est un point de départ que vous personnalisez.
Pensez-y comme ceci :
- Bibliothèque : “Voici un système de menu, utilisez ces méthodes”
- Ce starter : “Voici comment j’ai construit un système de menu, changez ce que vous voulez”
Vous obtenez :
- ✅ Code source complet que vous pouvez lire et comprendre
- ✅ Exemples fonctionnels que vous pouvez modifier
- ✅ Patterns architecturaux que vous pouvez étendre
- ✅ Aucune dépendance cachée ou magie
Vous n’aimez pas le fonctionnement du défilement du menu ? Changez-le. Vous voulez des couleurs différentes ? Modifiez le gestionnaire d’affichage. Besoin d’une disposition de boutons différente ? Mettez à jour les contrôles.
Le Parcours : De l’IDE Arduino à PlatformIO
J’ai commencé ce projet dans l’IDE Arduino (comme beaucoup le font), mais j’ai rapidement basculé vers VSCode + PlatformIO. Voici pourquoi :
Points Faibles de l’IDE Arduino
// Où est définie cette fonction ?
// De quelle bibliothèque vient-elle ?
// Bonne chance pour la trouver...
M5.Lcd.setCursor(10, 80);
Avantages de PlatformIO
- IntelliSense : Auto-complétion qui fonctionne vraiment
- Aller à la Définition : Accéder à la source de n’importe quelle fonction
- Structure de Projet : Organisation de fichiers appropriée (le plus gros problème pour moi)
- Gestion des Bibliothèques : Gestion claire des dépendances
- C++ Moderne : Support complet du C++11/14/17
Le changement a pris une heure. Il m’a fait gagner des dizaines d’heures par la suite.
Décisions Architecturales Clés
1. Le Système de Pages : Gestion du Cycle de Vie
Très tôt, j’ai réalisé que j’avais besoin de plusieurs “écrans” ou “pages”. Mais basculer entre eux était chaotique :
// ❌ La mauvaise façon
if (currentPage == 0) {
drawClock();
} else if (currentPage == 1) {
drawMenu();
} else if (currentPage == 2) {
drawSettings();
}
J’avais besoin d’un cycle de vie approprié. Voici le PageManager :
class PageBase {
virtual void setup() = 0; // Appelé à l'entrée de la page
virtual void loop() = 0; // Appelé à chaque frame
virtual void cleanup() = 0; // Appelé en quittant la page
};
Maintenant chaque page se gère elle-même :
void ClockPage::setup() {
display->clearScreen();
clockHandler->drawClock(0);
}
void ClockPage::loop() {
if (hasActiveMenu()) return; // Pause si le menu est ouvert
// Mise à jour de l'horloge chaque seconde
}
Leçon apprise : Donnez à chaque composant son propre cycle de vie. Ne gérez pas tout depuis main().
2. La Pile de Menus : Menus Imbriqués Bien Faits
Les menus étaient étonnamment difficiles. Je voulais :
- Un menu principal
- Des sous-menus (Paramètres → Paramètres d’Affichage → Luminosité)
- Un bouton “retour” qui fonctionne correctement
La solution ? Une pile (Dernier Entré, Premier Sorti) :
class MenuManager {
private:
MenuHandler* menuStack[MAX_MENU_STACK];
int stackSize;
public:
void pushMenu(MenuHandler* menu) {
menuStack[stackSize++] = menu;
menu->draw();
}
void popMenu() {
stackSize--;
if (stackSize > 0) {
menuStack[stackSize - 1]->draw(); // Redessine le menu précédent
}
}
};
Maintenant les sous-menus fonctionnent naturellement :
Page Horloge
→ Ouvrir Menu
→ Paramètres
→ Affichage
→ [Retour]
→ [Retour]
→ [Retour]
Page Horloge (restaurée)
Leçon apprise : Choisissez la bonne structure de données. Une pile gère naturellement la navigation imbriquée.
3. La Saga Pointeur de Fonction vs std::function
C’était une session de débogage de 4 heures qui m’a enseigné une leçon cruciale en C++.
Je voulais des callbacks de menu pouvant accéder aux membres de classe :
// Je voulais faire ceci :
mainMenu->addItem("Démarrer Timer", [this]() {
clockHandler->startTimer(); // Accéder au membre de classe
});
Mais j’obtenais des erreurs :
error: no suitable conversion from lambda to void (*)()
Le problème ? Ma structure MenuItem utilisait d’anciens pointeurs de fonction style C :
// ❌ Ancienne façon (style C)
struct MenuItem {
void (*callback)(); // Ne peut pas capturer 'this' !
};
La solution ? Le std::function moderne du C++ :
// ✅ Nouvelle façon (C++11)
struct MenuItem {
std::function<void()> callback; // Peut tout capturer !
};
Maintenant ceci fonctionne :
mainMenu->addItem("Paramètres", [this]() {
openSettingsSubmenu(); // 'this' capturé, fonctionne parfaitement
});
Leçon apprise : Utilisez std::function pour les callbacks en C++ moderne. C’est plus flexible et gère les lambdas avec captures.
4. Positionnement d’Affichage : Fini les Nombres Magiques
Le code initial ressemblait à ceci :
// ❌ Qu'est-ce que cela signifie ?
M5.Lcd.setCursor(10, 80);
M5.Lcd.setTextSize(3);
M5.Lcd.print("Bonjour");
J’ai créé le DisplayHandler pour abstraire les positions :
// ✅ Sémantique et clair
display->displayMainTitle("Bonjour");
display->displaySubtitle("Sous-titre");
display->displayStatus("Prêt", MSG_SUCCESS);
En coulisses :
void displayMainTitle(const char* text, MessageType type) {
M5.Lcd.setTextSize(SIZE_TITLE); // Taille cohérente
M5.Lcd.setTextColor(getColorForType(type));
int x = (SCREEN_WIDTH - textWidth) / 2; // Auto-centrage
int y = ZONE_CENTER_Y - 20;
M5.Lcd.setCursor(x, y);
M5.Lcd.print(text);
}
Leçon apprise : Abstraire les détails bas niveau. Votre futur moi vous remerciera.
5. Deep Sleep : Le Bug de Gestion d’Alimentation de 3 Heures
Le M5StickC-Plus2 a une excellente autonomie de batterie… si vous utilisez le deep sleep correctement.
Ma première tentative :
// ❌ Ceci plante l'appareil au réveil
esp_deep_sleep_start();
Après avoir plongé dans la documentation et les forums, j’ai trouvé le problème : GPIO4 doit rester HIGH pendant le sommeil sinon l’appareil perd l’alimentation.
La solution qui fonctionne :
void M5deepSleep(uint64_t microseconds) {
// CRITIQUE : Garder la broche d'alimentation haute
pinMode(4, OUTPUT);
digitalWrite(4, HIGH);
gpio_hold_en(GPIO_NUM_4);
gpio_deep_sleep_hold_en();
esp_sleep_enable_timer_wakeup(microseconds);
esp_deep_sleep_start();
}
Ceci alimente mon timer Pomodoro : 25 minutes de sommeil, réveil, alarme sonore, affichage de l’horloge.
Leçon apprise : Les particularités spécifiques au matériel nécessitent des solutions spécifiques au matériel. Ne supposez pas toujours que les API standard fonctionnent immédiatement.
Contrôles des Boutons : Trouver le L’ergonomie Optimal
Le M5StickC-Plus2 a trois boutons :
______PWR (côté)
A (face)
___B_____ (côté, opposé)
Après avoir testé différentes dispositions, je me suis arrêté sur :
Aucun menu actif :
- PWR : Changer de page
- A : Ouvrir le menu
- B : Action spécifique à la page (ex: démarrer le timer sur double clic)
Menu actif :
- PWR : Naviguer vers le bas
- A : Sélectionner l’élément
- B (court) : Naviguer vers le haut
- B (appui long) : Fermer le menu
Pourquoi cette disposition ?
- Boutons latéraux pour la navigation : Plus facile à presser en tenant l’appareil
- Bouton central pour les actions : Bouton le plus important en position privilégiée
- Appui long pour “retour” : Empêche les sorties accidentelles
Leçon apprise : L’ergonomie compte. Testez sur du matériel réel, pas seulement dans votre tête.
La Stack Technique
- Plateforme : M5StickC-Plus2
- IDE : VSCode + PlatformIO
- Langage : C++ (fonctionnalités C++11)
- Bibliothèques : M5Unified
- Architecture : POO avec pattern de composition
lib/
├── display_handler.h # Abstraction d'affichage
├── menu_handler.h # Logique de menu individuel
├── menu_manager.h # Pile de menus
├── page_manager.h # Cycle de vie des pages
├── clock_handler.h # Temps & timers
├── battery_handler.h # Gestion d'alimentation
└── pages/
├── page_base.h # Classe de base abstraite
└── clock_page.h # Page horloge par défaut
Ce que je Ferais Différemment
1. Commencer avec std::function
N’utilisez pas de pointeurs de fonction style C pour les callbacks. Passez directement à std::function<void()>. Bien que ce soit plus consommateur que les pointeurs, je n’ai pas eu le temps de trouver une meilleure option (pour l’instant).
2. Tester le Deep Sleep Tôt
N’attendez pas la fin pour tester la gestion d’alimentation. C’est dépendant du matériel et peut tout casser.
3. Concevoir la Disposition des Boutons sur Papier
Esquissez la disposition des boutons avant d’écrire le code. La changer plus tard affecte tout.
4. Utiliser PlatformIO dès le Jour 1
Ne commencez pas dans l’IDE Arduino. La migration prend du temps et casse des choses.
Ce qui a Vraiment Bien Marché
1. Composition Plutôt qu’Héritage
Chaque page reçoit un DisplayHandler* et un MenuManager*. Elles n’héritent pas de la logique d’affichage—elles la composent.
2. Séparation Claire des Préoccupations
*_handler.h : Composants focalisés et réutilisables
*_manager.h : Orchestration complexe
*_utils.h : Fonctions utilitaires
3. Callbacks Lambda
Pouvoir écrire [this]() { myMethod(); } en ligne rend le code tellement plus propre que des fonctions de callback séparées.
4. Le Pattern de Page de Base
Chaque page hérite de PageBase, qui fournit la gestion de menu gratuitement. Pas de code dupliqué.
Essayez par Vous-Même
Le starter kit complet est disponible sur GitHub. Clonez-le, téléversez-le sur votre M5StickC-Plus2, et vous aurez :
- ✅ Une page horloge fonctionnelle
- ✅ Indicateur de batterie
- ✅ Système de menu avec sous-menus
- ✅ Navigation entre pages
- ✅ Timer Pomodoro
- ✅ Tout le code à modifier
Vous voulez construire un tracker de fitness ? Gardez le système de pages, remplacez la logique de l’horloge.
Vous construisez un jeu ? Utilisez le système de menu pour vos paramètres, insérez votre boucle de jeu.
Vous créez un tableau de bord IoT ? Le gestionnaire d’affichage abstrait tout le positionnement pour vous.
Réflexions Finales
Construire ce starter kit m’a appris qu’une bonne architecture est invisible. Quand elle fonctionne, vous ne pensez pas aux pages ou aux menus—vous construisez juste des fonctionnalités.
C’est l’objectif : vous donner les choses ennuyeuses pour que vous puissiez vous concentrer sur les choses intéressantes.
Le M5StickC-Plus2 est un appareil fantastique. Avec la bonne fondation, vous pouvez construire quelque chose d’incroyable en un week-end au lieu de passer ce week-end à configurer l’infrastructure.
Maintenant allez construire quelque chose de cool. 🚀