Les machines d'état
Une autre technique pour orchestrer plusieurs tâches avec un calendrier donné consiste à utiliser des machines d’état cadencées à une fréquence précise.
Si on reprend l’exemple du chapitre précédent avec une LED qui doit clignoter avec une période de 1000ms (LED1) et une autre avec une période de 1500ms (LED2).
Pour la LED1, on pourrait faire une machine d’état cadencée à 500ms qui inverse la LED à chaque deuxième tick :
Et pour la LED2, on fait aussi une machine d’état cadencée à 500ms, mais qui inverse la LED à chaque troisième tick :
On peut généraliser le machine d’état pour inverser la LED tous les \(n\) ticks avec un compteur \(i\) :
On peut implémenter cette technique en C++ avec une classe abstraite pour représenter une machine d’état:
class StateMachine {
public:
virtual void Tick() = 0;
};
Une machine d’états pour inverser l’état des LEDs tous les \(n\) ticks peut être implémentée ainsi :
static constexpr int kLedOff = 1;
static constexpr int kLedOn = 0;
class Led : public StateMachine {
public:
Led(PinName pin, int n) : led_(pin, kLedOff), n_{n}, state_{0} {}
void Tick() override
{
state_++;
if (state_ == n_) {
led_ = !led_;
state_ = 0;
}
}
private:
DigitalOut led_;
int n_;
int state_;
};
Pour un système composé de plusieurs machines d’états, on peut stocker ces machines dans un vecteur :
#include <vector>
class System {
public:
void AppendStateMachine(StateMachine* state_machine)
{
stateMachines_.push_back(state_machine);
}
void Tick()
{
for (const auto& s : stateMachines_) {
s->Tick();
}
}
private:
vector<StateMachine*> stateMachines_;
};
Le programme principal est le suivant :
int main()
{
System system;
system.AppendStateMachine(new Led(LED1, 2));
system.AppendStateMachine(new Led(LED2, 3));
Ticker clock;
clock.attach(callback(&system, &System::Tick), 500ms);
while (true) {
ThisThread::sleep_for(Kernel::wait_for_u32_forever);
}
}
Note
On aurait aussi pu implémenter le System
comme une classe dérivée de StateMachine
mais ça n’apporte pas grand-chose ici.
On cadence le système avec une période \(p\) égal au plus grand diviseur commun des périodes \(T_i\) de toutes les machines d’états:
Pour que le système fonctionne, nous devons encore nous assurer que toutes les tâches peuvent terminer pendant la période \(p\). Si le temps pour effectuer la tâche \(i\) est égal à \(C_i\), le système fonctionne si:
Si cette dernière condition n’est pas remplie, on doit fractionner les tâches, ou alors choisir des périodes \(T_i\) qui donnent un plus grand diviseur commun.