logo_elektromys.eu

/ Stavový automat |

Tento text navazuje na předchozí tutoriál o "multitaskingu". Tam naleznete postup (a soubory) pro implementování funkce millis, kterou budeme v tomto tutoriálu využívat.

Stavový (či konečný) automat je dobře přehledný programovací model, kterým můžete řešit spoustu úloh. Spočívá v tom, že si činnost programu či zařízení rozdělíte do "stavů" v nichž se může nacházet a stanovíte si podmínky kdy a jak zařízení přechází z jednoho stavu do jiného. Psaní stavového automatu vás tak donutí dobře specifikovat co přesně má zařízení dělat a jak má reagovat. A to je vhodné si pečlivě rozmyslet před tím než začnete psát program a dvakrát tak pečlivě pokud děláte něco na zakázku. Jinak vám totiž hrozí, že uprostřed rozdělané práce narazíte na situaci kterou jste se zadavatelem neprobrali a nevíte co má aplikace udělat. Dozvíte se to a typicky vám to vůbec nebude pasovat do programu a budete muset začít znovu. Ale zpět k jádru věci - pojďme si stavový automat vysvětlit na nějakém konkrétním příkladě.

/ Příklad 1 |

Vezměte si třeba jednoduchý tříbarevný semafor. Ten se může nacházet například v těchto pěti stavech:

Asi chápete, že semafor nemůže mezi jednotlivými stavy přecházet libovolně. Prostě nemůže svítit zelená, a hned na to červená. Případně nemůže svítit oranžová a potom oranžová a červená. A tak podobně. Semafor tedy smí přecházet jen mezi určitými stavy. Například z červené může přejít buď do stavu oranžové nebo do stavu "zhasnuto" (když ho prostě vypnou), ale nikam jinam. Ze zelené lze přejít jen do oranžové a tak podobně. Přechod z jednoho stavu do druhého je řízen nějakou podmínkou či událostí. Například přechod ze stavu červená do stavu červená+oranžová se odehraje po uplynutí určitého času. Případně přechod z libovolného stavu do stavu "nesvítí" se odehraje když obsluha semafor vypne (například kvůli malému provozu). Stavové automaty se dají dobře nakreslit ... třeba ten co jsme částečně popsali vypadá následovně:

Příklad stavového automatu semaforu

Časy T1 až T4 vyjadřují jak dlouho má semafor setrvat ve vybraném stavu. Například čas T1 vyjadřuje jak dlouho má na semaforu svítit červená. Jakmile tento čas uplyne, přejde semafor do stavu červená+oranžová (a přirozeně rozsvítí červené a oranžové světlo). V běžném provozu semafor střídá stavy po směru hodinových ručiček. Přechody označené vypnutí představují událost kdy obsluha stiskne tlačítko "vypnout". Analogicky přechod zapnuti představuje událost kdy obsluha stiskne tlačítko "zapnout" s úmyslem semafor rozběhnout. Jak naznačuje diagram, semafor se rozběhne nejprve ze stavu červená. Jednoduchý program který řídí trojici LEDek v tomto stylu (bez možnosti zapnutí a vypnutí semaforu) si můžete prohlédnout a vyzkoušet ze zdrojového kódu. Až si ho prohlédnete, zkusíme si vše raději ještě jednou na následujícím příkladu.

Cvičení 1: Naprogramujte dvojici dvoubarevných semaforů, které řídí dopravu v jednosměrné ulici. Program musí respektovat i nějaký čas, který potřebují auta aby dokončila cestu ulicí - je tedy nutné aby nějaký čas svítila červená na obou stranách. Chování aplikace by mělo odpovídat následujícímu stavovému diagramu (v každé buňce je popsáno co svítí na obou semaforech). Čas jaký má semafor setrvat v jednom stavu je znázorněn u šipek (v sekundách). rozbíhat se semafor může z libovolného stavu.

Takto má fungovat dvojice vašich semaforů

Program lze přirozeně napsat mnoha různými způsoby. Já to udělal následovně. K definování stavů jsem využil výčtový typ (enum), ale nebude žádný problém pokud pro jednoduchost použijete makra tak jako v předchozím příkladě. V praxi je použití enum výhodnější, protože snižuje riziko, že někde omylem změníte hodnotu stavu na nějaký nesmysl. Časy přechodů jednotlivých stavů jsem volil místo maker jako proměnnou typu const. Program bude pracovat stejně, ale stačí třídu const umazat a program bude moct za chodu časy upravovat dle aktuální potřeby.

#include "stm8s.h"
#include "millis.h"

// makra definující piny LEDek
#define LEDR1_GPIO GPIOG
#define LEDR1_PIN GPIO_PIN_7
#define LEDG1_GPIO GPIOG
#define LEDG1_PIN GPIO_PIN_6
#define LEDR2_GPIO GPIOG
#define LEDR2_PIN GPIO_PIN_5
#define LEDG2_GPIO GPIOG
#define LEDG2_PIN GPIO_PIN_4

// makra pro čitelnější a snadnější ovládání semaforu 1
#define LEDR1_ON     GPIO_WriteHigh(LEDR1_GPIO,LEDR1_PIN)
#define LEDG1_ON     GPIO_WriteHigh(LEDG1_GPIO,LEDG1_PIN)
#define LEDR1_OFF   GPIO_WriteLow(LEDR1_GPIO,LEDR1_PIN)
#define LEDG1_OFF   GPIO_WriteLow(LEDG1_GPIO,LEDG1_PIN)
// makra pro čitelnější a snadnější ovládání semaforu 2
#define LEDR2_ON     GPIO_WriteHigh(LEDR2_GPIO,LEDR2_PIN)
#define LEDG2_ON     GPIO_WriteHigh(LEDG2_GPIO,LEDG2_PIN)
#define LEDR2_OFF   GPIO_WriteLow(LEDR2_GPIO,LEDR2_PIN)
#define LEDG2_OFF   GPIO_WriteLow(LEDG2_GPIO,LEDG2_PIN)

// časy jak dlouho má být semafor v jednotlivých stavech (v milisekundách)
const uint16_t trvani_stop1=3000;
const uint16_t trvani_smera=6000;
const uint16_t trvani_stop2=3000;
const uint16_t trvani_smerb=6000;

void process_semaphore(void);

void main(void){
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // taktujeme na 16MHz
// nastavní pinů pro LEDky jako výstupy + počáteční rozsvícení obou červených
GPIO_Init(LEDR1_GPIO,LEDR1_PIN,GPIO_MODE_OUT_PP_HIGH_SLOW);
GPIO_Init(LEDG1_GPIO,LEDG1_PIN,GPIO_MODE_OUT_PP_LOW_SLOW);
GPIO_Init(LEDR2_GPIO,LEDR2_PIN,GPIO_MODE_OUT_PP_HIGH_SLOW);
GPIO_Init(LEDG2_GPIO,LEDG2_PIN,GPIO_MODE_OUT_PP_LOW_SLOW);

init_millis(); // spouštíme millis časovač

  while (1){
    process_semaphore(); // obsloužíme semafor
    // obsluhujeme jiné (momentálně nepřítomné) činnosti
  }
}

void process_semaphore(void){
static uint32_t last_time=0; // ukládáme si poslední čas změny stavu
static enum stavy_semaforu{ // pro definici stavů použivejte výčtový typ
  STOP1, // A: oba směry zavřené (dojíždějí auta ze směru B)
  SMERA, // B: směr A má volno(zelená pro auta ze směru A - semafor 1)
  STOP2, // C: oba směry zavřené (dojíždějí auta ze směru A)
  SMERB  // D: směr B má volno (zelená pro auta ze směru B - semafor 2)
} stav=STOP1;   // začínáme ve stavu STOP1 (už máme rozsvícené červené LED)

// na větvení se výborně hodí switch/case
switch (stav){
  case STOP1: // ve stavu STOP1 čekáme až vyprší čas
    if(millis() - last_time >= trvani_stop1){
      last_time = millis();
      LEDR1_OFF; //rozsvítíme zelenou autům ze směru A
      LEDG1_ON;
      LEDR2_ON;
      LEDG2_OFF;
      stav = SMERA; // a přejdeme do odpovídajícího stavu
    }
    break;
    
  case SMERA:
    if(millis() - last_time >= trvani_smera){
      last_time = millis();
      LEDR1_ON; // rozsvítíme červenou oběma směrům 
      LEDG1_OFF;
      LEDR2_ON;
      LEDG2_OFF;
      stav = STOP2; // a přejdeme do odpovídajícího stavu
    }
  break;
  
  case STOP2:
    if(millis() - last_time >= trvani_stop2){
      last_time = millis();
      LEDR1_ON; 
      LEDG1_OFF;
      LEDR2_OFF;
      LEDG2_ON; //rozsvítíme zelenou autům ze směru B
      stav = SMERB; // a přejdeme do odpovídajícího stavu
    }  
  break;
  
  case SMERB:
    if(millis() - last_time >= trvani_smerb){
      last_time = millis();
      LEDR1_ON; // rozsvítíme červenou oběma směrům 
      LEDG1_OFF;
      LEDR2_ON;
      LEDG2_OFF;
      stav = STOP1; // a přejdeme do odpovídajícího stavu (zpět na začátek)
    }    
  break;
  
  default: // to by nikdy nastat nemělo, ale pro všechny případy (píšeme defenzivně)
   stav = STOP1;
  break;
  }
}

Teď se budu opakovat, ale všimněte si že funkce process_semaphore() je neblokující. Tedy přepínání stavů semaforu vykonává velice rychle a pokud nemá nic na práci (nevypršel požadovaný čas) velmi rychle končí. V hlavní smyčce pak můžeme přidávat další a další takové procesy (klidně z předchozího tutoriálu). Mikrokontrolér tím v podstatě vůbec nezatížíme a vše hladce poběží. Funkce trvá typicky (když nemá nic na práci) 13us. Když zpracovává přepnutí stavu (což se stává velmi zřídka) tak trvá 19us. Při taktování 16MHz a překladu bez optimalizace. S optimalizací na rychlost pak 11us a 17us. Tyto časy jsou důležité protože nám říkají na jak dlouho funkce "zablokuje" mikrokontrolér. Když je úloh více, programátor musí počítat s tím, že nějakou dobu trvá než se na každou úlohu dostane řada. Každopádně jednotky mikrosekund jako v tomto případě jsou velmi krátké úlohy.

| Odkazy /

Home
| V1.00 8.5.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /