Stavový automat je nějaký abstraktní model obsahující konečný počet stavů něčeho. Používá se k reprezentaci a řízení toku provádění jakýchkoli příkazů. Stavový automat je ideální pro implementaci umělé inteligence ve hrách a získává úhledné řešení bez psaní těžkopádného a složitého kódu. V tomto článku se budeme zabývat teorií a také se naučíme, jak používat jednoduchý a zásobníkový stavový automat.

O psaní umělé inteligence pomocí stavového automatu jsme již publikovali sérii článků. Pokud jste tuto sérii ještě nečetli, můžete tak učinit nyní:

Co je to konečný automat?

Konečný automat (nebo jednoduše FSM - Finite-state machine) je výpočtový model založený na hypotetickém automatu. V jednu chvíli může být aktivní pouze jeden stav. Proto, aby mohl stroj provést jakoukoli akci, musí změnit svůj stav.

Státní stroje se obvykle používají k organizaci a reprezentaci toku provádění něčeho. To je užitečné zejména při implementaci AI do her. Například, abych napsal "mozek" nepřítele: každý stát představuje nějaký druh akce (útok, úskok atd.).

Konečný automat lze znázornit jako graf, jehož vrcholy jsou stavy a hrany jsou přechody mezi nimi. Každá hrana má štítek označující, kdy má dojít k přechodu. Například na obrázku výše můžete vidět, že automat změní stav z „putování“ na stav „útok“, za předpokladu, že je hráč poblíž.

Plánovací stavy a jejich přechody

Implementace konečného automatu začíná identifikací jeho stavů a ​​přechodů mezi nimi. Představte si stavový stroj, který popisuje činnost mravence, který nese listy do mraveniště:

Výchozím bodem je stav „najít list“, který zůstává aktivní, dokud mravenec list nenajde. Když se tak stane, stav se změní na „jdi domů“. Stejný stav zůstane aktivní, dokud se náš mravenec nedostane do mraveniště. Poté se stav změní zpět na "najít list".

Pokud je aktivní stav "najít list", ale kurzor myši je vedle mravence, pak se stav změní na "uteč". Jakmile je mravenec v dostatečné bezpečné vzdálenosti od kurzoru myši, stav se změní zpět na „najít list“.

Vezměte prosím na vědomí, že při cestě domů nebo z domu se mravenec nebude bát kurzoru myši. Proč? A protože neexistuje žádný odpovídající přechod.

Implementace jednoduchého stavového automatu

Stavový automat lze implementovat pomocí jediné třídy. Říkejme tomu FSM. Cílem je implementovat každý stav jako metodu nebo funkci. K určení aktivního stavu použijeme také vlastnost activeState.

Public class FSM ( private var activeState:Function; // ukazatel na aktivní stav automatu public function FSM() ( ) public function setState(state:Function) :void ( activeState = state; ) public function update() :void ( if ( activeState != null) ( activeState(); ) ) )

Každý stav je funkce. Navíc taková, že bude volána při každé aktualizaci herního rámce. Jak již bylo zmíněno, activeState bude ukládat ukazatel na funkci aktivního stavu.

Metoda update() třídy FSM musí být volána každý snímek hry. A on zase zavolá funkci toho stavu, který v tento moment je aktivní.

Metoda setState() nastaví nový aktivní stav. Navíc každá funkce, která definuje nějaký stav automatu, nemusí patřit do třídy FSM – tím je naše třída univerzálnější.

Pomocí stavového automatu

Pojďme implementovat mravenčí AI. Výše jsme si již ukázali sadu jeho stavů a ​​přechodů mezi nimi. Pojďme si je znovu ilustrovat, ale tentokrát se zaměříme na kód.

Náš mravenec je zastoupen třídou Ant, která má mozkové pole. Toto je pouze instance třídy FSM.

Public class Ant ( public var position:Vector3D; public var velocity:Vector3D; public var brain:FSM; public function Ant(posX:Number, posY:Number) (position = new Vector3D(posX, posY); velocity = new Vector3D( -1, -1); mozek = new FSM(); // Začněte hledáním listu. mozek. setState(findLeaf); ) /** * Stav „findLeaf“. * Přiměje mravence hledat listy. */ public function findLeaf( ) :void ( ) /** * Stav "goHome" * Způsobí, že mravenec odejde do mraveniště */ veřejná funkce goHome() :void ( ) /** * Stav "runAway" * Způsobí mravenec utéct před kurzorem myši * / veřejná funkce runAway() :void ( ) public function update():void ( // Aktualizace stavového automatu. Tato funkce zavolá // funkci aktivního stavu: findLeaf(), goHome () nebo runAway(). brain.update( ); // Použití rychlosti na pohyb mravence. moveBasedOnVelocity(); ) (...) )

Třída Ant také obsahuje vlastnosti rychlosti a polohy. Tyto proměnné budou použity k výpočtu pohybu pomocí Eulerovy metody. Funkce update() je volána při každé aktualizaci rámce hry.

Níže je uvedena implementace každé z metod, počínaje findLeaf(), stavem zodpovědným za hledání listů.

Veřejná funkce findLeaf() :void ( // Přesune mravence na list. velocity = new Vector3D(Hra.instance.leaf.x - poloha.x, Hra.instance.leaf.y - poloha.y); if (vzdálenost (Hra .instance.leaf, toto)<= 10) { // Муравей только что подобрал листок, время // возвращаться домой! brain.setState(goHome); } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши находится рядом. Бежим! // Меняем состояние автомата на runAway() brain.setState(runAway); } }

Stav goHome() se používá k tomu, aby se mravenec vrátil domů.

Veřejná funkce goHome() :void ( // Přesune mravence na domovskou rychlost = new Vector3D(Hra.instance.home.x - poloha.x, Hra.instance.home.y - poloha.y); if (vzdálenost( Hra. instance.home, toto)<= 10) { // Муравей уже дома. Пора искать новый лист. brain.setState(findLeaf); } }

A nakonec stav runAway() – používá se při uhýbání kurzoru myši.

Veřejná funkce runAway() :void ( // Přesune mravence pryč od kurzoru rychlost = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y); // Je kurzor stále kolem ? if ( distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) ( // Ne, už je to daleko. Je čas vrátit se k hledání listů. brain.setState(findLeaf); ) )

Vylepšení FSM: Stack Based Automaton

Představte si, že mravenec na cestě domů také potřebuje utéct před kurzorem myši. Takto budou vypadat státy FSM:

Vypadá to jako triviální změna. Ne, taková změna nám dělá problém. Představte si, že současný stav je „uteklý“. Pokud se kurzor myši vzdálí od mravence, co by měl udělat: jít domů nebo hledat list?

Řešením tohoto problému je stavový automat na bázi zásobníku. Na rozdíl od jednoduchého FSM, který jsme implementovali výše, tento druh FSM používá ke správě stavů zásobník. V horní části zásobníku je aktivní stav a přechody nastávají, když jsou stavy přidávány/odebírány ze zásobníku.

A zde je názorná ukázka fungování stavového automatu založeného na zásobníku:

Implementace FSM založeného na zásobníku

Takový konečný automat lze implementovat stejným způsobem jako jednoduchý. Rozdíl bude v použití pole ukazatelů na požadované stavy. Vlastnost activeState již nebudeme potřebovat, protože horní část zásobníku již bude ukazovat na aktivní stav.

Veřejná třída StackFSM ( private var stack:Array; veřejná funkce StackFSM() ( this.stack = new Array(); ) public function update() :void ( var currentStateFunction:Function = getCurrentState(); if (currentStateFunction != null) ( currentStateFunction(); ) ) veřejná funkce popState() :Function ( return stack.pop(); ) veřejná funkce pushState(state:Function) :void ( if (getCurrentState() != state) ( stack.push(state) ; ) ) veřejná funkce getCurrentState() :Function ( return stack.length > 0 ? stack : null; ) )

Všimněte si, že metoda setState() byla nahrazena metodami pushState() (přidání nového stavu na vrchol zásobníku) a popState() (odstranění stavu z vrcholu zásobníku).

Použití FSM založeného na zásobníku

Je důležité poznamenat, že při použití stavového automatu založeného na zásobníku je každý stav zodpovědný za odstranění ze zásobníku, když není potřeba. Například stav attack() by se měl sám odstranit ze zásobníku, pokud již byl nepřítel zničen.

Public class Ant ( (...) public var brain:StackFSM; public function Ant(posX:Number, posY:Number) ( (...) brain = new StackFSM(); // Začněte hledáním listového mozku. pushState( findLeaf); (...) ) /** * Stav "findLeaf". * Přiměje mravence hledat listy. */ veřejná funkce findLeaf() :void ( // Přesune mravence na list. velocity = new Vector3D(Game.instance. leaf.x - position.x, Game.instance.leaf.y - position.y); if (vzdálenost(Game.instance.leaf, this)<= 10) { //Муравей только что подобрал листок, время // возвращаться домой! brain.popState(); // removes "findLeaf" from the stack. brain.pushState(goHome); // push "goHome" state, making it the active state. } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши рядом. Надо бежать! // Состояние "runAway" добавляется перед "findLeaf", что означает, // что состояние "findLeaf" вновь будет активным при завершении состояния "runAway". brain.pushState(runAway); } } /** * Состояние "goHome". * Заставляет муравья идти в муравейник. */ public function goHome() :void { // Перемещает муравья к дому velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y); if (distance(Game.instance.home, this) <= 10) { // Муравей уже дома. Пора искать новый лист. brain.popState(); // removes "goHome" from the stack. brain.pushState(findLeaf); // push "findLeaf" state, making it the active state } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши рядом. Надо бежать! // Состояние "runAway" добавляется перед "goHome", что означает, // что состояние "goHome" вновь будет активным при завершении состояния "runAway". brain.pushState(runAway); } } /** * Состояние "runAway". * Заставляет муравья убегать от курсора мыши. */ public function runAway() :void { // Перемещает муравья подальше от курсора velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y); // Курсор все еще рядом? if (distance(Game.mouse, this) >MOUSE_THREAT_RADIUS) ( // Ne, už je to daleko. Je čas vrátit se k hledání listů. brain.popState(); ) ) (...) )

Závěr

Stavové automaty jsou jistě užitečné pro implementaci logiky umělé inteligence do her. Lze je snadno znázornit jako graf, který umožňuje vývojáři vidět všechny možné možnosti.

Implementace stavového automatu se stavovými funkcemi je jednoduchá, ale výkonná technika. Pomocí FSM lze implementovat i složitější stavové zapletení.

Jedním z kritérií složitosti konečného automatu je počet jeho stavů. Čím menší je toto číslo, tím jednodušší je diskrétní zařízení, které tento automat implementuje. Proto je jedním z důležitých problémů teorie konečných automatů konstrukce automatu s nejmenším počtem stavů.

Protože v moderních počítačích jsou jakékoli informace reprezentovány ve formě binárních kódů, můžete k sestavení automatu použít prvky, které mají pouze dva různé stabilní stavy, z nichž jeden odpovídá číslu 0 a druhý číslu 1.

Zde je několik příkladů konečných automatů.

Příklad 1. Prvek zpoždění (paměťový prvek).

Zpožďovací prvky jsou zařízení s jedním vstupem a jedním výstupem. Navíc hodnota výstupního signálu v daném čase t se shoduje s hodnotou signálu v předchozím okamžiku. Schematicky může být zpožďovací prvek znázorněn následovně (obr. 2).

Předpokládejme, že vstupní a potažmo výstupní abeceda je X ={0, 1}, Y =(0,1). Pak Q =(0,1). Pod stavem zpožďovacího prvku v daném okamžiku t obsah paměťového prvku v daném okamžiku je chápán. Takto q (t )= X (t 1), a Y (t )= q (t )=X (t 1).

Prvek zpoždění nastavíme tabulkou, kde A 1 =0, A 2 =1, q 1 =0, q 2 =1,

(A 1 , q 1)= (0, 0)=0, (A 1 , q 1)= (0, 0)=0;

(A 1 , q 2)= (0, 1)=0, (A 1 , q 2)= (0, 1)=1;

(A 2 , q 1)= (1, 0)=1, (A 2 , q 1)= (1, 0)=0;

(A 2 , q 2)= (1, 1)=1, (A 2 , q 2)= (1, 1)=1;

q

A

=0, =0

=0, =1

=1, =0

=1, =1

Mooreův diagram je znázorněn na Obr. 3

K reprezentaci tohoto automatu systémem booleovských funkcí používáme tabulku automatů a výše uvedený algoritmus. V tomto případě není kódování nutné, protože vstupní a výstupní abeceda a stavy jsou již zakódovány.

Věnujme pozornost tomu, že m=p=p =2. Pak k =r =s =1, a proto je zpožďovací prvek dán dvěma funkcemi a . Pravdivostní tabulka těchto funkcí obsahuje 2 k + r =2 2 =4 řádky a k +r +r +s =4 sloupce:

X

z

Příklad 2. Binární sekvenční sčítačka.

Tato sériová sčítačka je zařízení, které sčítá dvě čísla ve dvojkové soustavě. Čísla se přivádějí na vstupy sčítačky X 1 a X 2, počínaje nejméně významnými číslicemi. Na výstupu se vytvoří sekvence odpovídající zadání čísla X 1 +X 2 v binárním systému výpočtu (obr. 4).

Vstupní a výstupní abecedy jsou jednoznačně definovány: X ={00; 01; 10; 11}, Y =(0,1). Množina stavů je určena hodnotou přenosu při sčítání odpovídajících bitů čísel X 1 a X 2. Pokud se při sčítání některých číslic vytvoří přenos, pak budeme předpokládat, že sčítačka přešla do stavu q jeden . Pokud nedojde k přenosu, budeme předpokládat, že sčítačka je ve stavu q 0 .

Sčítačka je dána tabulkou.

q

A

q 0

q 1

q 0 , 0

q 0 , 1

q 0 , 1

q 1 , 0

q 0 , 1

q 1 , 0

q 1 , 0

q 1 , 1

Mooreův diagram sekvenční sčítačky je znázorněn na Obr. 5.

Všimněte si, že vstupní a výstupní znaky jsou již zakódovány. Stavy zakódujeme následovně: (q 0)=0, (q 1) = 1. Proto je sekvenční sčítačka dána dvěma booleovskými funkcemi, jejichž pravdivostní tabulka je následující:

X 1

X 2

z

Příklad 3. Schéma porovnání rovnosti.

Porovnávací obvod rovnosti je zařízení, které porovnává dvě čísla. X 1 a X 2, daný ve dvojkové soustavě. Toto zařízení funguje následovně. Na vstupu zařízení se postupně, počínaje nejvyšší, přivádějí číslice čísel X 1 a X 2. Tyto pořadí se porovnávají. Pokud se bity shodují, vytvoří se na výstupu obvodu výstupní signál 0, jinak se na výstupu objeví signál 1. Je jasné, že výskyt 1 ve výstupní sekvenci znamená, že porovnávaná čísla X 1 a X 2 odlišný. Pokud je výstupní sekvence nulová a její délka odpovídá počtu číslic porovnávaných čísel, pak X 1 a X 2 .

Pro tento stroj X ={00, 01, 10, 11}; Y ={0,1}.

Fungování obvodu je určeno dvěma stavy. Stát q 0 odpovídá rovnosti aktuálně porovnávaných bitů. V tomto případě zůstane stroj ve stejném stavu. Pokud se v příštím okamžiku budou porovnávané číslice lišit, automat přejde do nového stavu q 1 a zůstane v něm, protože to znamená, že čísla jsou různá. Srovnávací schéma tedy může být specifikováno tabulkou:

q

X

q 0

q 1

q 0 , 0

q 1 , 1

q 1 , 1

q 1 , 1

q 1 , 1

q 1 , 1

q 0 , 0

q 1 , 1

Moorův diagram srovnávacího schématu pro rovnost je znázorněn na Obr. 6.

Stavy zakódujeme následovně: (q 0)=0, (q 1) = 1. Automat dostane dvě funkce.

X 1

X 2

z

Příklad 4. Schéma srovnání nerovností.

Porovnávací obvod nerovností je zařízení, které umožňuje zjistit, zda je porovnán X 1 a X 2 , a pokud nejsou stejné, zjistěte, který z nich je větší než druhý. Toto zařízení má dva vstupy a dva výstupy. Výstupní signály y 1 (t ) a y 2 (t ) se určují podle následujících pravidel:

y 1 (t )=y 2 (t )=0 pokud X 1 (t )=X 2 (t );

y 1 (t )=1, y 2 (t )=0 pokud X 1 (t )>X 2 (t ), tzn X 1 (t )=1, X 2 (t )=0;

y 1 (t )=0, y 2 (t )=1 pokud X 1 (t )<X 2 (t ), tzn X 1 (t )=0, X 2 (t )=1.

Tedy při aplikaci na vstup srovnávacího obvodu pro nerovnost čísel X 1 =X 1 (l)… X 1 (t ) a X 2 =X 2 (l)… X 2 (t ) číslice těchto čísel jsou postupně porovnávány, počínaje nejvyššími. Výstupní signály jsou formulovány podle výše uvedených pravidel. Navíc, pokud se výstupní sekvence skládá z nulových párů, pak X 1 =X 2. Pokud se první liší od nuly, má dvojice tvar , () pak X 1 >X 2 (X 1 <X 2).

Z popisu obvodu vyplývá, že

X ={00, 01, 10, 11}, Y ={00, 01, 10}.

Stav schématu je definován následovně. Předpokládáme, že v počáteční době t =1 stroj je ve stavu q 1 . Pokud se porovnávají číslice čísel X 1 a X 2, pak stroj zůstane v tomto stavu. Všimněte si, že na výstupu se objeví signál 00. Pokud je číslice čísla X 1 bude menší (větší než) odpovídající číslice čísla X 2, pak stroj přejde do stavu q 2 (q 3). V tomto případě se na výstupu objeví signál 01 (10). V budoucnu při zadávání zbývajících číslic čísel X 1 a X 2 do vstupů automatu, automat zůstane ve stavu q 2 (q 3) a vygenerujte výstupní symbol 10 (01). Z výše uvedeného vyplývá, že srovnávací schéma pro nerovnost lze upřesnit tabulkou:

q

X

q 1

q 2

q 3

q 1 , 00

q 2 , 01

q 3 , 10

q 2 , 01

q 2 , 01

q 3 , 10

q 3 , 10

q 2 , 01

q 3 , 10

q 1 , 00

q 2 , 01

q 3 , 10

Odpovídající Mooreův diagram je znázorněn na Obr. 7.

Vstupní a výstupní abeceda je zde již zakódována. státy q 1 , q 2 a q 3 zakódovat: 1 (q 1)=00, (q 2)=01, (q 3)=10.

Proto lze tento obvod definovat systémem sestávajícím ze čtyř booleovských funkcí, které závisí na čtyřech proměnných. Tyto funkce jsou částečně definovány a dány pravdivostní tabulkou

X 1

X 2

z 1

z 2

Symboly * v tabulce označují sady proměnných X 1 , X 2 , z 1 , z 2, na kterém jsou funkce 1 , 2 , 1 , 2 nejsou definovány. Položme funkční hodnoty 1 , 2 , 1 , 2 na těchto sadách se rovná 1.

Článek pojednává o jednoduchých stavových automatech a jejich implementaci v C++ pomocí konstrukcí přepínačů, runtime tabulek a knihovny Boost Statechart.

Úvod

Zhruba řečeno, konečný automat (Finite State Machine) je v očích uživatele černou skříňkou, do které můžete něco přenést a něco odtud přijímat. Jedná se o velmi pohodlnou abstrakci, která vám umožňuje skrýt složitý algoritmus, navíc jsou stavové automaty velmi efektivní.

Konečné automaty jsou znázorněny jako diagramy sestávající ze stavů a ​​přechodů. Dovolte mi to vysvětlit na jednoduchém příkladu:

Jak asi tušíte, jedná se o stavový diagram žárovky. Počáteční stav je indikován černým kroužkem, přechody šipkami, některé šipky jsou podepsané - to jsou události, po kterých se stroj přepne do jiného stavu. Takže hned z výchozího stavu se dostáváme do stavu zhasnout- lampa nesvítí. Pokud stisknete tlačítko, stroj změní svůj stav a bude postupovat podle označené šipky stiskněte tlačítko, na stát rozsvítit- lampa svítí. Z tohoto stavu přejdete opět šipkou po stisknutí tlačítka do stavu zhasnout.

Široce používané jsou také skokové stoly:

Praktická aplikace strojů

Stavové stroje jsou široce používány v programování. Velmi vhodné je například představit si provoz zařízení ve formě automatu. Díky tomu bude kód jednodušší a bude se s ním snadněji experimentovat a udržovat.

Také konečné automaty se používají k psaní všemožných parserů a analyzátorů textu, pomocí kterých lze efektivně vyhledávat podřetězce, regulární výrazy jsou také překládány do konečného automatu.

Implementujeme například automat na počítání čísel a slov v textu. Pro začátek se dohodneme, že číslo bude posloupnost číslic od 0 do 9 libovolné délky, obklopená mezerami (mezera, tabulátor, posun od řádku). Slovo bude považováno za posloupnost libovolné délky sestávající z písmen a také obklopená mezerami.

Zvažte schéma:

Z výchozího stavu se dostáváme do stavu Start. Zkontrolujeme aktuální znak, a pokud je to písmeno, přejděte do stavu Slovo podél šipky označené jako dopis. Tento stav předpokládá, že v okamžiku, kdy uvažujeme o slovu, analýza dalších symbolů tento předpoklad buď potvrdí, nebo vyvrátí. Zvažte tedy další znak, pokud je to písmeno, pak se stav nemění (všimněte si kruhové šipky označené jako dopis). Pokud znak není písmeno, ale odpovídá znaku mezery, znamená to, že předpoklad byl správný a slovo jsme našli (pohybujeme se po šipce Prostor do stavu Start). Pokud symbol není ani písmeno, ani mezera, pak jsme udělali chybu v předpokladu a sekvence, kterou zvažujeme, není slovo (sledujeme šipku neznámý do stavu Přeskočit).

Schopný Přeskočit zůstaneme, dokud nenarazíme na vesmírnou postavu. Po nalezení mezery postupujeme podle šipky Prostor do stavu Start. To je nutné k úplnému přeskočení řádku, který neodpovídá našemu vyhledávacímu vzoru.

Po přechodu do stavu Start, cyklus vyhledávání se opakuje od začátku. Větev rozpoznávání čísel je podobná větvi rozpoznávání slov.

Automat pomocí instrukcí přepínače

První jsou možné stavy:

Poté iterujeme přes řetězec a vložíme aktuální znak do automatu. Samotný automat je příkaz switch, který nejprve provede přechod do sekce aktuálního stavu. Uvnitř sekce je konstrukce if-else, která v závislosti na události (příchozím znaku) mění aktuální stav:

const size_t length = text.length () ; for (velikost_t i = 0 ; i != délka; ++ i) ( const char current = text[ i] ; switch (stav) ( case State_Start: if (std:: isdigit (current) ) ( state = State_Number; ) else if (std:: isalpha (aktuální) ) ( stav = Stavové_slovo; ) break ; case Číslo_stavu: if (std:: isspace (aktuální) ) (

Zde se podíváme na schéma - aktuální stav číslo, událost Prostor(byl nalezen znak mezery), což znamená, že bylo nalezeno číslo:

FoundNumber() ; stav = State_Start; ) else if (! std:: isdigit (aktuální) ) ( state = State_Skip; ) break ; case State_Word: if (std:: isspace (aktuální) ) ( FoundWord() ; stav = State_Start; ) else if (! std:: isalpha (aktuální) ) ( stav = State_Skip; ) break ; case State_Skip: if (std::isspace (aktuální) ) ( stav = State_Start; ) break ; ))

Výsledek:

Vysoká účinnost

Snadná implementace pro jednoduché algoritmy

- náročné na údržbu

Interpretace za běhu

Myšlenka tohoto přístupu je jednoduchá - musíte vytvořit tabulku přechodů, vyplnit ji a poté, když dojde k události, najít v tabulce následující stav a provést přechod:

enum Events( Event_Space, Event_Digit, Event_Letter, Event_Unknown ) ; void ProcessEvent(událost událostí) ; ... struct Transition ( States BaseState_; Events Event_; States TargetState_; ) ; void AddTransition(States fromState, Events Event, States toState) ; ... typedef std::vector< transition>TransitionTable; TransitionTable Přechody_; Stavy CurrentState_;

Vyplnění tabulky:

AddTransition(State_Start, Event_Digit, State_Number) ; AddTransition(State_Start, Event_Letter, State_Word) ; AddTransition(číslo_stavu, prostor_událostí, začátek_stavu) ; AddTransition(číslo_stavu, písmeno_události, přeskočení_stavu) ; AddTransition(Číslo_stavu, Neznámá_Událost, Přeskočit_Stav) ; AddTransition(Stav_slovo, prostor_udalosti, stav_začátek) ; AddTransition(State_Word, Event_Digit, State_Skip) ; AddTransition(Stav_slovo, Udalost_Neznámá, Stav_Přeskočit) ; AddTransition(State_Skip, Event_Space, State_Start) ;

Ukázalo se to docela jasně. Odplatou za přehlednost bude pomalejší chod stroje, což ostatně často nevadí.

Aby automat při výskytu určitých událostí mohl upozornit nějaký kód, můžete do struktury přidat informace o přechodu ( přechod) ukazatel funkce ( akce), který se bude jmenovat:

typedef void (DynamicMachine:: * Akce) () ; struct Transition ( States BaseState_; Events Event_; States TargetState_; Action Action_; ); ... void AddTransition(States fromState, Events Event, States toState, Action action) ; ... AddTransition (State_Number, Event_Space, State_Start a DynamicMachine:: FoundNumber ) ;

Výsledek:

Flexibilita a viditelnost

Jednodušší na údržbu

- Nižší výkon ve srovnání s bloky přepínačů

Interpretace doby provedení. Optimalizace rychlosti

Lze viditelnost kombinovat s rychlostí? Je nepravděpodobné, že automat bude stejně účinný jako automat založený na spínacích blocích, ale je možné tuto mezeru zacelit. K tomu je nutné sestavit matici z tabulky tak, že pro získání informací o přechodu neprovádějte vyhledávání, ale výběr podle čísla stavu a události:

Výsledek je dosažen na úkor spotřeby paměti.

Výsledek:

Flexibilita, přehlednost

Efektivní

- spotřeba paměti (pravděpodobně nevýznamná)

Boost Statechart

Probrali jsme několik způsobů, jak sami implementovat stavový automat. Pro změnu navrhuji zvážit knihovnu pro stavební automaty od Boost. Tato knihovna je velmi výkonná, navrhované funkce vám umožňují stavět velmi složité konečné automaty. Podíváme se na to spíše krátce.

Nejprve tedy definujme události:

jmenný prostor Události( struct Číslice: boost::statechart::event< Digit>( ); struct Letter : boost::statechart::event< Letter>( ); struct Space : boost::statechart::event< Space>( ); structUnknown : boost::statechart::event< Unknown> { } ; }

Samotný stroj (všimněte si, že druhý parametr šablony je počáteční stav):

struct Machine : boost::statechart::state_machine< Machine, States:: Start > { } ;

A státy samotné. Uvnitř stavů je nutné definovat typ, který popisuje přechody ( reakce), a pokud existuje několik přechodů, uveďte je v seznamu typů boost::mpl::list. Druhý parametr šablony jednoduchý_stav– typ popisující vůz. Přechody jsou popsány pomocí parametrů šablony přechodu, dvojice Událost - Další stav:

jmenný prostor States ( struct Start : boost::statechart::simple_state< Start, Machine> < boost:: statechart :: transition < Events:: Digit , States:: Number >< Events:: Letter , States:: Word >> reakce; ); struct Číslo : boost::statechart::simple_state< Number, Machine>( typedef boost::mpl::list< boost:: statechart :: transition < Events:: Space , States:: Start >, boost::statechart::transition< Events:: Letter , States:: Skip >, boost::statechart::transition< Events:: Unknown , States:: Skip >> reakce; ); struct Word : boost::statechart::simple_state< Word, Machine>( typedef boost::mpl::list< boost:: statechart :: transition < Events:: Space , States:: Start >, boost::statechart::transition< Events:: Digit , States:: Skip >, boost::statechart::transition< Events:: Unknown , States:: Skip >> reakce; ); struct Přeskočit : boost::statechart::simple_state< Skip, Machine>( typedef boost::statechart::transition< Events:: Space , States:: Start >reakce; ); )

Stroj je postaven, zbývá jej pouze inicializovat a můžete použít:

Strojní stroj; machine.initiate(); ...stroj .process_event(Události::Space() ) ;

Výsledek:

Flexibilita, rozšiřitelnost

- Účinnost

Výkon

Napsal jsem testovací program pro kontrolu rychlosti konstruovaných automatů. Přes stroje jsem prohnal text o velikosti ~ 17 MB. Zde jsou výsledky běhu:

Načítání textu Délka textu: 17605548 bajtů 0,19 s Běžící slova BoostMachine: 998002, čísla: 6816 0,73 s Běžící slova DynamicMachine: 998002, čísla: 6816 0,56 s Běžící čísla FastDynamic8,8006 Simple s

Co zůstává nezkontrolováno

Několik dalších implementací konečných automatů zůstalo nepokryto (doporučuji http://www.rsdn.ru/article/alg/Static_Finite_State_Machine.xml a http://www.rsdn.ru/article/alg/FiniteStateMachine.xml), generátory, které sestavení automatů z popisů, knihovny Meta State Machine z Boost verze 1.44.0 a také formálních popisů stavových automatů. Zvědavý čtenář se může seznámit se vším výše uvedeným.

Teorie automatů je část diskrétní matematiky, která studuje modely diskrétních převaděčů informací. Takovými převodníky jsou jak skutečná zařízení (počítače, živé organismy), tak imaginární zařízení (axiomatické teorie, matematické stroje). V podstatě lze konečný automat popsat jako zařízení M , který má vstupní a výstupní kanály, přičemž v každém z diskrétních časových momentů, nazývaných hodinové momenty, je v jednom z konečných stavů.

Na vstupním kanálu kdykoli t =1, 2, ... do zařízení M vstupní signály přicházejí (z nějaké konečné množiny signálů). Zákon změny stavu na další časový okamžik je nastaven v závislosti na vstupním signálu a stavu zařízení v aktuálním časovém okamžiku. Výstupní signál závisí na stavu a vstupním signálu v aktuálním čase (obr. 1).

Stavový automat je matematický model skutečných diskrétních zařízení pro zpracování informací.

státní automat nazval systém A= (X , Q , Y , , ), kde X , Q , Y jsou libovolné neprázdné konečné množiny a a - funkce, z toho:

    hodně X ={A 1 , ..., A m ) je nazýván vstupní abeceda a jeho prvky jsou vstupní signály , jejich sekvence jsou v slogany ;

    hodně Q ={q 1 , ..., q n ) je nazýván mnoho států automat a jeho prvky - státy ;

    hodně Y ={b 1 , ..., b p ) je nazýván výstupní abeceda , jeho prvky jsou výstupní signály , jejich sekvence jsou výstupní slova ;

    funkce : X Q Q volala přechodová funkce ;

    funkce :X Q Y volala výstupní funkce .

Takto, (X , q )Q , (X , q )Y pro  X X , q Q .

Se stavovým automatem je spojeno imaginární zařízení, které funguje následovně. Může být ve stavu ze sady Q , přijímat signály z přístroje X a vydávat signály ze sady Y .

2. Metody pro definování konečného automatu

Existuje několik ekvivalentních způsobů, jak definovat abstraktní automaty, z nichž tři jsou: tabelární , geometrický a funkční .

2.1 Tabulkové přiřazení stroje

Z definice automatu vyplývá, že jej lze vždy definovat tabulkou se dvěma vstupy obsahujícími t linky a P kolony, kde na průsečíku kolony q a linky A funkční hodnoty stojí za to (A i , q j ), (A i , q j ).

q

A

q 1

q j

q n

A 1

(A 1 , q 1), (A 1 , q 1)

(A 1 , q j ), (A 1 , q j )

(A 1 , q n ), (A 1 , q n )

A i

(A i , q 1), (A i , q 1)

(A i , q j ), (A i , q j )

(A i , q n ), (A i , q n )

A m

(A m , q 1), (A m , q 1)

(A m , q j ), (A m , q j )

(A m , q n ), (A m , q n )

2.2. Definování automatu pomocí Moorova diagramu

Dalším způsobem, jak určit konečný automat, je grafický, tedy pomocí grafu. Automat je reprezentován jako označený orientovaný graf G(Q , D ) s mnoha vrcholy Q a mnoho oblouků D ={(q j , (A i , q j ))| q j Q , A i X ), zatímco oblouk ( q j , (A i , q j )) je označena dvojicí ( A i , (A i , q j )). Při této metodě jsou tedy stavy automatu znázorněny kroužky, do kterých jsou zapsány symboly stavů q j (j = 1, …, n ). Z každého kruhu se provádí t šipky (směrované hrany) jedna ku jedné odpovídající znakům vstupní abecedy X ={A 1 , ..., A m ). Šipka odpovídající písmenu A i X a opuštění kruhu q j Q , dvojice ( A i , (A i , q j )) a tato šipka vede ke kruhu odpovídajícímu (A i , q j ).

Výsledný výkres se nazývá automatový graf nebo, Moorův diagram . Pro nepříliš složité automaty je tato metoda názornější než tabelární.

Dnes bude řeč o kulometech, ale v žádném případě ne o těch, které drží vojáci v rukou ruská armáda. Budeme mluvit o tak zajímavém stylu programování mikrokontrolérů, jako je automatické programování. Přesněji se ani nejedná o programovací styl, ale o celý koncept, díky kterému si programátor mikrokontrolérů může výrazně usnadnit život. Díky tomu je mnoho úloh předkládaných programátorovi vyřešeno mnohem snadněji a jednodušeji, což programátorovi uleví od bolesti hlavy. Mimochodem, automatické programování se často nazývá Technologie SWITCH.

Chci poznamenat, že podnětem k napsání tohoto příspěvku bylo série článků o technologii SWITCH Vladimír Tatarchevskij. Série článků se jmenuje „Uplatnění SWITCH-technologie ve vývoji aplikovaných software pro mikrokontroléry ”V tomto článku se tedy z větší části pokusím uvést příklad funkčního kódu a jeho popis.

Mimochodem, naplánoval jsem sérii článků o programování, ve kterých se budu podrobně zabývat programovacími technikami pro mikrokontroléry ABP, Nenechte si ujít…. Tak pojďme!

Program postupně vykonává příkazy stanovené programátorem. Pro pravidelné počítačový program je zcela normální, když program dokončí a zastaví své provádění, zatímco zobrazuje výsledky své práce na monitoru.

Program mikrokontroléru nemůže jednoduše ukončit své provádění. Představte si, že jste zapnuli přehrávač nebo magnetofon. Stiskli jste tlačítko napájení, vybrali požadovanou skladbu a vychutnejte si hudbu. Když vám však hudba přestala drnčet v ušním bubínku, přehrávač zamrzne a nijak nereaguje na mačkání tlačítek a tím spíše na váš tanec s tamburínou.

a co to je? Vše je v pořádku – ovladač, ten v hloubi vašeho přehrávače, právě dokončil svůj program. Zde můžete vidět, jak nepříjemné to dopadá.

Odtud tedy docházíme k závěru, že program pro mikrokontrolér by se prostě neměl zastavit. V podstatě by se mělo jednat o nekonečnou smyčku – pouze v tomto případě by náš přehrávač fungoval správně. Dále vám ukážu, co jsou designy programový kód u mikrokontrolérů to ani nejsou návrhy, ale nějaké styly programování.

Styly programování.

„Styly programování“ zní poněkud nesrozumitelně, ale dobře. Co tím chci říct Představme si, že člověk ještě nikdy neprogramoval, tedy obecně naprostá atrapa.

Tato osoba přečetla mnoho knih o programování, naučila se všechny základní konstrukce jazyka.Sbíral informace kousek po kousku, protože přístup k informacím je nyní neomezený. To vše je dobré, ale jak budou vypadat jeho první programy? Zdá se mi, že nebude filozofovat, ale půjde cestou od jednoduchého ke složitému.

Tyto styly jsou tedy kroky vedoucí od jednoduché úrovně ke složitější, ale zároveň efektivnější.

Zpočátku jsem o žádném nepřemýšlel Designové vlastnosti programy. Právě jsem vytvořil logiku programu - nakreslil jsem vývojový diagram a napsal kód. Z toho, co neustále naráželo na hrábě. Ale bylo to poprvé, kdy jsem si nedal parní lázeň a použil styl „jednoduché smyčky“, pak jsem začal používat přerušení, pak byly automaty a jeli jsme ...

1. Jednoduchá smyčka. Program se v tomto případě zacyklí bez jakýchkoliv složitostí, a to má své klady i zápory. Navíc jen v jednoduchosti přístupu, nepotřebujete vymýšlet mazané návrhy, píšete, jak myslíte (postupně si kopete vlastní hrob).

Void main(void) ( initial_AL(); //inicializace periferie while(1) ( Leds_BLINK(); //funkce LED blikače signal_on(); //funkce zapnutí signálu signal_off(); // funkce vypnutí signálu l=button( ); //proměnná zodpovědná za stisknutí tlačítek switch(l) //V závislosti na hodnotě proměnné se provede jedna nebo druhá akce (případ 1: ( Deistvie1(); / /Místo funkce může existovat podmíněný operátor deistvie2(); //nebo několik dalších větví switch case Deistvie3(); deistvie4(); deistvie5(); ); případ 2: ( Deistvie6(); Deistvie7(); Deistvie8(); Deistvie9(); Deistvie10(); ); . . . . . . . . )))

Pracovní bod programu se pohybuje v pořadí. V tomto případě se všechny akce, podmínky a cykly provádějí postupně. Kód se začne zpomalovat, musíte vložit spoustu podmínek navíc, a tím komplikovat vnímání.

To vše program značně mate a vytváří z kódu spleť podmínek. Výsledkem je, že tento kód nelze přidat ani odebrat, stává se jako monolitický kus. Samozřejmě, když objem není velký, lze kód upravit, ale čím dále, tím obtížněji.

S tímto přístupem jsem napsal několik programů, nebyly velké a docela funkční, ale viditelnost zůstala hodně žádoucí. Abych přidal nějakou novou podmínku, musel jsem odhodit celý kód, protože všechno bylo svázané. To vedlo k mnoha chybám a bolestem hlavy. Kompilátor se zapřisáhl, jakmile to půjde, odladění takového programu se změnilo v peklo.

2. Cyklus + přerušení.

Nekonečný brzdný cyklus můžete částečně vyřešit pomocí přerušení. Přerušení pomáhají prolomit začarovaný kruh, pomáhají vám nezmeškat důležitou událost, přidávají další funkce (přerušení od časovačů, externí přerušení).

Řekněme, že můžete zavěsit zpracování tlačítek nebo sledování důležité události na přerušení. V důsledku toho se program stává vizuálnějším, ale ne méně matoucím.

Přerušení vás bohužel nezachrání před nepořádkem, do kterého se program promění. To, co je jediným celkem, nebude možné rozdělit na části.

3. Automatické programování.

Tím se dostáváme k hlavnímu tématu tohoto článku. Programování v konečných automatech šetří program od nedostatků obsažených v prvních dvou příkladech. Program se stává jednodušším, lze jej snadno upravovat.

Program napsaný ve stylu automatu je jako spínač, který se v závislosti na podmínkách přepne do jednoho nebo druhého stavu. Počet stavů je programátorovi zpočátku znám.

Zhruba je to jako vypínač. Existují dva stavy zapnuto a vypnuto a dvě podmínky zapnuto a vypnuto. No, první věci.

Implementace multitaskingu v technologii switchů.

Mikrokontrolér je schopen ovládat zátěž, blikat LED diody, sledovat stisk kláves a mnoho dalšího. Ale jak to všechno udělat zároveň? Existuje mnoho řešení tohoto problému. Nejjednodušší z nich, které jsem již zmínil, je použití přerušení.

Během programu, když dojde k přerušení, je kontrolér odveden od provádění programového kódu a krátce provede další část programu, za kterou je přerušení zodpovědné. Přerušení bude fungovat, pak bude pracovní bod programu pokračovat od místa, odkud byl řadič přerušením přerušen (samotné slovo značí, že řadič je přerušen).

Dalším způsobem, jak implementovat multitasking, je použití operační systémy. Ano, již se začaly objevovat malé OS, které lze použít na nízkopříkonovém ovladači. Často se ale tato metoda ukáže jako poněkud nadbytečná. Koneckonců, proč plýtvat zdroji kontroléru zbytečnou prací, když je docela možné vystačit s malým krveprolitím.

V programech napsaných pomocí technologie switch je taková „iluze“ multitaskingu získána díky systému zpráv. Napsal jsem "iluzi", protože to tak skutečně je, protože program fyzicky nemůže provádět různé části kódu současně. O systému zpráv budu mluvit o něco dále.

Systém zpráv.

Pomocí systému zpráv můžete zničit více procesů a vytvořit iluzi multitaskingu.

Řekněme, že potřebujeme program, ve kterém se LED spíná. Zde máme dva stroje, říkejme jim LEDON - stroj zodpovědný za rozsvícení LED a stroj LEDOFF - stroj zodpovědný za vypnutí LED.

Každý z automatů má dva stavy, to znamená, že automat může být v aktivním nebo neaktivním stavu, jako je nožový spínač zapnutý nebo vypnutý.

Když je aktivován jeden stroj, LED se rozsvítí, když je aktivován druhý stroj, LED zhasne. Zvažte malý příklad:

Int main(void) ( INIT_PEREF(); //inicializace periferií (LED) InitGTimers(); //inicializace časovačů InitMessages(); //inicializace mechanismu zpracování zpráv InitLEDON(); //inicializace automatu LEDON InitLEDOFF(); // inicializace automatu LEDOFF SendMessage(MSG_LEDON_ACTIVATE); //aktivace automatu LEDON sei(); //Povolení přerušení //Naprogramujte hlavní smyčku while(1) ( ProcessLEDON(); //iterace LEDON automat ProcessLEDOFF(); //iterace automatu LEDOFF ProcessMessages (); //zpracování zpráv ); )

V řádcích 3-7 dochází k různým inicializacím, takže nás to nyní nijak zvlášť nezajímá. Pak se ale stane následující: před spuštěním hlavní smyčky (zatímco (1)) odešleme zprávu automatu

Odeslat zprávu (MSG_LEDON_ACTIVATE)

zodpovědný za rozsvícení LED. Bez tohoto malého krůčku se naše hurdiska neobejde. Dále hlavní nekonečná smyčka while dělá většinu práce.

Malá odbočka:

Zpráva má tři stavy. Konkrétně stav zprávy může být neaktivní, nastavený, ale neaktivní a aktivní.

Ukázalo se, že zpráva byla zpočátku neaktivní, když jsme zprávu odeslali, obdržela stav „nainstalovaná, ale neaktivní“. A to nám dává následující. Když je program spouštěn postupně, automat LEDON neobdrží zprávu. Nastává nečinná iterace automatu LEDON, ve které zprávu jednoduše nelze přijmout. Protože zpráva má stav „nainstalovaná, ale neaktivní“, program pokračuje ve svém provádění.

Poté, co jsou všechny automaty nečinné, fronta dosáhne funkce ProcessMessages(). Tato funkce je vždy umístěna na konec cyklu po dokončení všech iterací automatu. Funkce ProcessMessages() jednoduše změní zprávu ze stavu „set but inactive“ do stavu „active“.

Když nekonečná smyčka dokončí druhé kolo, obraz je již úplně jiný. Iterace automatu ProcessLEDON již nebude nečinná. Stroj bude schopen přijmout zprávu, přepnout se do rozsvíceného stavu a také zprávu postupně odeslat. Bude adresováno stroji LEDOFF a životní cyklus zprávy se budou opakovat.

Chci poznamenat, že zprávy, které mají „aktivní“ stav, jsou zničeny, když se setkají s funkcí ProcessMessages. Zprávu tedy může přijmout pouze jeden automat. Existuje ještě jeden typ zpráv - jsou to zprávy odvysílané, ale nebudu je zvažovat, jsou také dobře pokryty v článcích Tatarchevského.

Časovače

Při správném zasílání zpráv můžeme řídit pořadí, ve kterém stavové automaty pracují, ale bez zpráv se jen tak neobejdeme.

Možná jste si všimli, že předchozí ukázkový fragment kódu nebude fungovat podle očekávání. Stroje si budou vyměňovat zprávy, LED diody se přepínají, ale toho se nedočkáme. Uvidíme pouze slabě svítící LED.

Je to proto, že jsme nemysleli na kompetentní zpracování zpoždění. Nestačí nám totiž střídavě zapínat a vypínat LEDky, LEDka musí v každém stavu setrvat třeba vteřinu.

Algoritmus bude následující:

Kliknutím zvětšíte

Na toto blokové schéma jsem zapomněl dodat, že když tiká časovač, samozřejmě se provede akce - rozsvícení LED nebo zhasnutí.

1. Do stavu vstupujeme přijetím zprávy.

2. Zkontrolujeme odečty časovače/počítadla, pokud tiká, pak provedeme akci, jinak si jen pošleme zprávu sami sobě.

3. Odešleme zprávu dalšímu automatu.

4. Konec

V dalším záznamu se vše opakuje.

Program technologie SWITCH. Tři kroky.

A napišme program v konečných automatech a k tomu budeme potřebovat pouze tři jednoduché kroky. Program bude jednoduchý, ale vyplatí se začít s jednoduchými věcmi. Nás vhodný program se spínací LED. Toto je velmi dobrý příklad tak nevymýšlejme nic nového.

Program napíšu v jazyce C, ale to vůbec neznamená, že v konečných automatech musíte psát pouze v C, je docela možné použít jakýkoli jiný programovací jazyk.

Program bude modulární, a proto bude rozdělen do několika souborů. Naše moduly budou:

  • Modul hlavní smyčky programu obsahuje soubory leds_blink.c, HAL.c, HAL.h
  • Modul časovače obsahuje soubory timers.c, timers.h
  • Modul zpracování zpráv obsahuje soubory messages.c, messages.h
  • Modul stroje 1 obsahuje soubory ledon.c, ledon.h
  • Modul stroje 2 obsahuje soubory ledoff.c, ledoff .h

Krok 1.

Vytvoříme projekt a ihned k němu připojíme soubory našich statických modulů: timers.c, timers.h, messages.c, messages.h.

Soubor leds_blink.c modulu hlavního cyklu programu.

#include "hal.h" #include "messages.h" //modul zpracování zpráv #include "timers.h" //modul časovačů //Přerušení časovače //############# # ################################################## ############################# ISR(TIMER0_OVF_vect) // Přerušení vektorového přechodu (přetečení časovače T0) ( ProcessTimers(); / /obslužný nástroj přerušení časovače) //########################################### ################################################ int main (void) ( INIT_PEREF(); //inicializace periferie (LED) InitGTimers(); //inicializace časovačů InitMessages(); //inicializace mechanismu zpracování zpráv InitLEDON(); //inicializace automatu LEDON InitLEDOFF(); StartGTimer( TIMER_SEK); //Spuštění časovače SendMessage(MSG_LEDON_ACTIVATE); //aktivace automatu FSM1 sei(); //Povolení přerušení //Naprogramujte hlavní smyčku while(1) ( ProcessLEDON(); //iterujte automat LEDON ProcessLEDOFF(); ProcessMessages(); //zpracování zpráv; )

V prvních řádcích jsou zbývající moduly připojeny k hlavnímu programu. Zde vidíme, že modul časovače a modul zpracování zpráv jsou propojeny. Další v programu je vektor přerušení přetečení.

Z řádku int main (void) můžeme říci, že hlavní program začíná. A začíná to inicializací všeho a všeho. Zde inicializujeme periferie, to znamená, že nastavíme počáteční hodnoty na vstupní / výstupní porty komparátoru a veškerý další obsah ovladače. To vše dělá funkce INIT_PEREF, spouštíme ji zde, ačkoli její hlavní tělo je v souboru hal.c.

Dále vidíme inicializaci časovačů, modul zpracování zpráv a inicializaci automatů. Zde se tyto funkce také jednoduše spouštějí, ačkoli samotné funkce jsou zapsány v souborech jejich modulů. Podívejte se, jak pohodlné. Hlavní text programu zůstává snadno čitelný a není zahlcen nadbytečným kódem, který vám zlomí nohu.

Hlavní inicializace jsou u konce, nyní musíme spustit hlavní smyčku. Za tímto účelem odešleme úvodní zprávu a kromě toho spustíme naše hodinky - spustíme časovač.

StartGTimer(TIMER_SEK); //Spustit časovač SendMessage(MSG_LEDON_ACTIVATE); //aktivace stroje FSM1

A hlavní smyčka, jak jsem řekl, vypadá velmi jednoduše. Funkce všech automatů zapisujeme, jen je zapisujeme do sloupce, bez dodržení pořadí. Tyto funkce jsou manipulátory automatů a jsou umístěny v modulech automatů. Funkce modulu zpracování zpráv doplňuje tuto pyramidu automatu. Samozřejmě jsem to již řekl dříve, když jsem se zabýval systémem zpráv. Nyní se můžete podívat, jak vypadají další dva soubory modulu hlavní programové smyčky

Hal.h je hlavičkový soubor modul hlavní smyčky programu.

#ifndef HAL_h #define HAL_h #include #zahrnout //Standardní knihovna včetně přerušení #define LED1 0 #define LED2 1 #define LED3 2 #define LED4 3 #define Komparator ACSR //comparator #define ViklKomparator 1<

Jak můžete vidět, tento soubor ze své podstaty neobsahuje jediný řádek spustitelného kódu – to jsou všechny substituce maker a připojení knihoven. S tímto souborem je život velmi dobrý, zlepšuje viditelnost.

Ale soubor Hal.c je již spustitelný soubor a jak jsem již zmínil, obsahuje různé periferní inicializace.

#include "hal.h" void INIT_PEREF(void) ( //Inicializace I/O portů //########################### ################################################## # ##### Komparator = ViklKomparator; //inicializace komparátoru - vypnout DDRD = 1<

Dobře, ukázal jsem modul hlavního cyklu programu, nyní musíme udělat poslední krok, musíme napsat moduly samotných automatů.

Krok 3

Zbývá nám napsat moduly konečných automatů, v našem případě automat LEDON a automat LEDOFF. Pro začátek uvedu text programu pro stroj, který svítí LED, soubor ledon.c.

//soubor ledon.c #include "ledon.h" #include "times.h" #include "messages.h" unsigned char ledon_state; //stavová proměnná void InitLEDON(void) ( ledon_state=0; //zde můžete inicializovat další proměnné automatu, pokud existují ) void ProcessLEDON(void) ( switch(ledon_state) ( případ 0: //neaktivní stav if(GetMessage (MSG_LEDON_ACTIVATE) )) //pokud je zde zpráva, bude přijata ( //a časovač bude zkontrolován if(GetGTimer(TIMER_SEK)==one_sek) //pokud má časovač nastavenu 1s, tak proveďte ( StopGTimer(TIMER_SEK); PORTD = 1<

Zde se v prvních řádcích jako vždy propojují knihovny a deklarují proměnné. Dále jsme již prošli funkcemi, se kterými jsme se již setkali. Toto je inicializační funkce automatu InitLEDON a funkce samotného ovladače automatu ProcessLEDON.

V těle handleru se již zpracovávají funkce z modulu časovače a modulu zpráv. A logika samotného automatu je založena na konstrukci skříně spínače. A zde můžete vidět, že obsluha automatu může být také komplikovaná přidáním několika přepínačů.

Hlavičkový soubor pro automat by byl ještě jednodušší:

//soubor fsm1 #ifndef LEDON_h #define LEDON_h #include "hal.h" void InitLEDON(void); void ProcessLEDON(void); #endif

Zde připojíme odkazový soubor hal.h a také specifikujeme prototypy funkcí.

Soubor zodpovědný za vypnutí LED bude vypadat téměř stejně pouze v zrcadlovém obraze, takže ho zde nebudu zobrazovat - neochota 🙂

Všechny soubory projektu si můžete stáhnout zde na tomto odkazu ====>>> ODKAZ.

Zde jsou jen tři kroky a náš program získal hotovou podobu, což znamená, že moje dnešní mise skončila a je čas to zabalit. Zdá se mi, že informace uvedené v tomto článku pro vás budou velmi užitečné. Skutečný užitek ale přinese až tehdy, když tyto znalosti uvedete do praxe.

Mimochodem, naplánoval jsem řadu zajímavých projektů, které budou obzvlášť zajímavé, tak určitě přihlásit k odběru nových článků . Plánuji také rozesílat další materiály, takže mnoho lidí se již přihlašuje přes hlavní stránku webu. Přihlásit se můžete i zde.

No a teď už mám opravdu všechno, tak přeji hodně štěstí, dobrou náladu a zase na viděnou.

N/A Vladimír Vasiliev