Releases: Phildem/Cath
Version 1.5 enhanced by Grok not tested yet
I asked to Grok IA to improve my code, here is the result.
Explication du Scheduling Cath
Le Cath (contraction de "Cathodique" ou simplement un nom inventé par l'auteur Philippe Demerliac, alias Cyrob) est un système de planification de tâches léger conçu pour les microcontrôleurs Arduino. Il permet de gérer plusieurs tâches (ou "jobs") de manière non-bloquante, en s'appuyant sur le timing système (millis()) pour exécuter du code périodiquement, sans utiliser d'interruptions complexes ou de bibliothèques lourdes comme RTOS (Real-Time Operating System). C'est idéal pour des projets embarqués simples, comme clignoter des LEDs, débouncer des boutons ou traiter des signaux logiques, tout en minimisant la consommation mémoire et CPU.
Cath est open-source (licence MIT-like) et optimisé pour des contraintes comme celles d'un Arduino Uno (ATMega328P). Il supporte jusqu'à 9 tâches par défaut (configurable), avec des périodes en millisecondes (ms). Voici une explication détaillée de son fonctionnement, étape par étape.
1. Principes de Base
- Modèle de Tâche : Chaque tâche est une classe dérivée de la classe abstraite
Cath. Vous devez implémenter deux méthodes virtuelles :SetUp(): Appelée une seule fois au démarrage (équivalent d'unsetup()par tâche). Utilisée pour initialiser les pins, variables, etc.Loop(): Appelée périodiquement selon la période définie. C'est le cœur de votre logique (ex. : toggle une LED).
- Timing Non-Bloquant : Cath n'utilise pas de
delay()(qui bloque tout le programme). Au lieu de cela, il décompte un compteur par ms pour chaque tâche, en se basant surmillis()(le compteur système Arduino en ms depuis le démarrage). - Précision : Le scheduling est précis à ~1 ms, car il vérifie le changement du bit le plus bas de
millis()(low byte). Pas de jitter important sur des périodes courtes. - Option de Compteur : Par défaut (
__CathOpt_SmallCounter__), les compteurs sont en 16 bits (max ~65 secondes). Sans cette option, 32 bits (max ~49 jours).
2. Architecture Interne
La classe Cath gère tout via des membres statiques :
S_CathTasks[]: Un tableau fixe (taillekMaxCathTask = 9) de pointeurs vers les objets tâches.S_NbTask: Nombre de tâches enregistrées (commence à 0).S_LastMilli: Dernière valeur du low byte demillis()(pour détecter chaque ms).
Flux de Données :
-
Enregistrement (Registration) : Dans le constructeur de votre classe dérivée, appelez
Cath::S_Register(this, Période, Offset).Période: Intervalle en ms entre deux appels deLoop()(ex. : 100 pour 10 Hz).Offset: Délai initial avant le premier appel (ex. : 1 ms par défaut, pour éviter un appel immédiat).- Cela initialise
m_CurCounter = Offsetetm_LoopDelay = Périodepour la tâche, et l'ajoute au tableau.
-
Initialisation Globale (dans
setup()) :Cath::S_SetUp(); // Boucle sur toutes les tâches et appelle SetUp() pour chacune.
- Exécuté une seule fois au démarrage.
-
Exécution Périodique (dans
loop()) :Cath::S_Loop(); // Vérifie et exécute les tâches dues.
- Logique Interne :
- Récupère
CurMilli = millis() & 0xFF(seulement les 8 bits bas, qui changent tous les ms). - Si
CurMilli != S_LastMilli(i.e., une nouvelle ms est passée) :- Met à jour
S_LastMilli. - Pour chaque tâche dans le tableau :
- Décremente
m_CurCounter--. - Si
m_CurCounter == 0:- Appelle
Loop(). - Reset
m_CurCounter = m_LoopDelay(prochaine exécution dans Période ms).
- Appelle
- Décremente
- Met à jour
- Important :
S_Loop()est non-bloquant et rapide (~quelques µs par ms). Vous pouvez y ajouter d'autres codes dansloop(), mais Cath gère le timing.
- Récupère
- Logique Interne :
3. Exemple Visuel du Scheduling
Imaginons 3 tâches avec des périodes différentes (enregistrées à t=0) :
| Temps (ms) | Tâche 1 (Période=100, Offset=50) | Tâche 2 (Période=500) | Tâche 3 (Période=20) |
|---|---|---|---|
| 0 | Compteur=50 (pas d'exécution) | Compteur=1 | Compteur=1 |
| 1 | 49 | 0 → Loop() ; reset à 500 | 0 → Loop() ; reset à 20 |
| 20 | 30 | 480 | 0 → Loop() ; reset à 20 |
| 50 | 0 → Loop() ; reset à 100 | 450 | 0 → Loop() ; reset à 20 |
| 100 | 0 → Loop() ; reset à 100 | 400 | ... |
| ... | ... | ... | ... |
- Synchronisation : Les tâches sont indépendantes, mais alignées sur le tick ms global. Pas de priorité (FIFO d'enregistrement).
- Dynamique : Dans
Loop(), vous pouvez modifierm_CurCounterpour des timings variables (ex. : pulse court dansABlinker).
4. Avantages et Limites
-
Avantages :
- Léger : ~200-300 octets de code, pas de heap allocation.
- Simple : Pas de yield() ou de mutex ; tout est déterministe.
- Flexible : Supporte des offsets pour déphaser les tâches (éviter les pics CPU).
- Débogage : Option
CATH_DEBUGpour logs série (ex. : avertissement si trop de tâches).
-
Limites :
- Pas de Priorités : Toutes les tâches sont traitées dans l'ordre d'enregistrement.
- Max 9 Tâches : Augmentez
kMaxCathTasksi besoin (mais surveillez la RAM). - Précision Limité : Sur des périodes >65s (avec 16 bits), overflow possible. Utilisez 32 bits pour du long terme.
- Pas Multitâche Vraie : Sur un seul core, c'est du "cooperative multitasking" (les tâches doivent être courtes pour ne pas bloquer les autres).
5. Exemple d'Utilisation (du Code Amélioré)
Dans le code fourni :
Blinker: Clignote une LED toutes les X ms.PushBut: Débouce un bouton à 20 ms.PinAorB, etc. : Appliquent des logiques booléennes (OR, AND, XOR) sur les états des boutons.
Pour ajouter une tâche :
class MaTache : public Cath {
public:
MaTache() { S_Register(this, 1000, 100); } // Toutes les 1s, offset 100ms
void SetUp() override { /* Init */ }
void Loop() override { /* Logique */ }
};
MaTache instance; // Instance globale6. Conseils Pratiques
- Test : Utilisez
Serial.print(millis())dansLoop()pour vérifier les timings. - Optimisations : Pour des boards plus puissants (ESP32), combinez avec FreeRTOS si besoin de vraies priorités.
- Ressources : Vidéo de présentation (français) : YouTube. Code source sur GitHub (recherchez "Cyrob Cath Arduino").
Si vous voulez un schéma UML, un exemple plus avancé, ou des clarifications sur une partie spécifique (ex. : le bug XOR fixé), dites-le-moi ! 😊