Se întâmplă adesea ca programul să se comporte diferit în funcție de debutul unor evenimente, de timpul care a trecut de la începutul lucrării etc.
De exemplu, să aruncăm o privire asupra dispozitivului, ca un ghirlandă. Să presupunem că dorim ca LED-urile să fie conectate la Arduino, al cărui mod strălucitor ar putea fi schimbat prin apăsarea butonului. Modul poate fi de trei:
LED-urile sunt aprinse continuu
LED-urile se opresc apoi se aprinde la fiecare 2 secunde
LED-urile cântăresc ușor luminozitatea de la zero la maxim în 10 secunde, ieșesc și încep să câștige din nou luminozitatea
Apăsați butonul, ne dorim să trecem la modul următor: 1 → 2, 2 → 3, 3 → 1. De fapt, la fel cum se întâmplă în multe ghirlande de brad.
Nu vom gestiona fiecare LED individual. Doar presupuneți că toți sunt conectați imediat la unul dintre arduino-urile prin intermediul unui MOSFET sau alt switch. La un alt pin, este conectat un buton de ceas pentru a schimba modul.
În această situație, o schiță care face toată lucrarea poate arăta astfel:
Rezultatul nu este cel mai simplu program. Dar avem o mulțime de logică. Pas cu pas vom înțelege ceea ce este scris aici. Să începem cu unele macrocomenzi.
După cum am menționat deja, avem 3 moduri: stralucirea constantă, intermitent, luminozitatea crescândă. Dar cum pot fi desemnați să spună procesorului care este cel actual, care este următorul, etc.? După cum știm deja, tot ceea ce procesorul funcționează este numere întregi. Știind acest lucru, putem număra pur și simplu toate regimurile posibile:
0 - modul stralucitor constant, îl numim STATE_SHINE
1 - modul clipește, l-am denumit STATE_BLINK
2 - modul de creștere a luminozității, îl numim STATE_FADE
Utilizarea unui prefix comun în numele, desigur, nu este necesar - acesta este doar un macro - ci pentru gruparea valorilor aferente, în sensul prefixe sunt destul de confortabile, și le puteți întâlni destul de des.
În fiecare mod, dispozitivul nostru face ceva unic, diferit de ceea ce se întâmplă în alte moduri. Numai un singur mod poate fi actual. Iar posibilele tranziții sunt clar definite: fiecare următoarea este activată când butonul este apăsat. Astfel de sisteme se numesc automate finite. iar modurile lor se numesc state.
În mod formal, un automat finit este un sistem cu un număr de state finite, cunoscute, condițiile de tranziții între care sunt fixe și cunoscute, iar cel curent este întotdeauna exact un singur stat.
Ei bine, facem mașina finită de stat - excelentă. Starea actuală, adică Modul de strălucire va fi stocat într-o variabilă numită ledState. care este setat inițial la valoarea STATE_SHINE. Astfel, atunci când porniți sistemul Arduino va fi într-un mod constant de strălucire.
Scopul altor macrocomenzi și variabile va fi discutat în cursul acestei chestiuni.
Lanțul tranzițiilor dintre state
Să ne uităm la funcția de buclă. Mai exact, prima sa parte și definițiile care o privesc:
Mai întâi de toate, am citit starea butonului în variabila logică isButtonDown. Iar imediat după aceea, verificăm condiția ca aceasta să fie apăsată chiar acum, și nu în această stare din apelul precedent. Puteti afla o tehnica tipica pentru determinarea click-ului unui buton. Asta este, deci apelul de întârziere și alocarea butonului ButtonDown la sfârșit trebuie să vă fie clar. Concentrați-vă asupra a ceea ce se întâmplă în declarația if.
Operatorul de egalitate
Esența sa este de a stabili în loc de starea actuală următoarele. Condițiile sunt determinate de starea curentă, iar starea corespunzătoare este stabilită în codul ramurii.
Rețineți că în egalitatea C ++ este verificată prin simbolul ==. și anume semnul dublu egal. Deci operatorul "egal" este scris. Valoarea expresiei logice va fi adevărată dacă valorile din stânga și din dreapta lui == sunt egale.
O eroare tipică în programare este de a confunda operatorul de egalitate == cu operatorul de atribuire =. Dacă scrieți:
programul va fi absolut corect din punctul de vedere al compilatorului C ++, dar nu va fi ceea ce se așteaptă: în primul rând, variabila ledState va fi setată la STATE_SHINE. dar numai atunci va fi verificată adevărata sa valoare.
De exemplu, în cazul nostru, dacă am făcut o astfel de eroare, atunci când am trecut acest cod, ledState va fi întotdeauna suprascris cu valoarea STATE_SHINE. care la rândul său este declarată ca 0. 0 în condiția este echivalentă cu falsul. și, prin urmare, în interiorul blocului de cod nu am fi ajuns niciodată.
Deci, dacă ne uităm la lanțul nostru, dacă, în general, putem vedea că încercăm să înțelegem ce stat este instalat acum și pe această bază, actualizăm valoarea ledState. setându-l în următorul mod de proiectare.
Logica sa în fiecare stat
Am făcut tot ce este necesar pentru ca variabila ledState să poată înțelege exact ce este necesar să facă cu LED-urile chiar acum. Rămâne doar să punem în aplicare această logică în practică.
Luați în considerare a doua jumătate a codului funcției buclă și definițiile care sunt utilizate în ea.
În corpul bucla, vedem un șir de expresii condiționate, unde în fiecare dintre condiții comparăm starea curentă a stării de stare una câte una cu toate cele posibile. Esența este exact la fel ca înainte, când comutarea stărilor apăsând butonul: în funcție de valoarea curentă a ledState executați un anumit bloc de cod.
În acest cod, puteți vedea în mod clar 3 blocuri de cod: unul pentru fiecare mod strălucitor. Primul dintre ele este executat dacă ledState este STATE_SHINE. și anume dacă modul curent este o strălucire continuă. Blocul de coduri este primitiv în acest caz, trebuie doar să ne asigurăm că LED-urile sunt aprinse:
Este clar că trecerea buclei data viitoare, procesorul va cădea din nou în această ramură și încă o dată va activa LED-urile deja incluse. Dar nu este nimic de îngrijorat, este absolut normal, fără efecte secundare.
Mod intermitent
E mai interesant. Dacă starea curentă a fost setată la STATE_BLINK. și anume clipind la fiecare 2 secunde, ramura cu codul este executata:
Știți că pentru a bloca LED-ul pe Arduino, e suficient să sunați digitalWrite și să întârziați secvențial. apoi includeți oprirea pinului:
Dar am acționat diferit și oarecum mai complicat. De ce?
Faptul este că întârzierea de apelare duce la un somn al procesorului și pur și simplu "îngheață" apelul pentru o anumită perioadă de timp. El nu poate face altceva decât să doarmă. Și acum, rețineți că ghirlanda noastră mereu "simultan" are două lucruri:
Controlează iluminarea LED-urilor
Prindeți momentul apăsării butonului pentru a schimba modurile
Astfel, în cazul în care modul intermitent procesorul a fost adormit timp de 2 secunde, iar în acest timp facem clic pe comutatorul de buton, apăsându-l va trece neobservat și trecerea la modul următor nu se va întâmpla. Și nu vrem deloc acest lucru.
Cum de a rezolva problema? Nu folosiți deloc întârziere! În schimb, de fiecare dată când ajungem la filiala noastră, putem recalcula: dacă ghirlanda va fi pornit sau oprit la ora actuală.
Pentru aceasta folosim o expresie aritmetică mică. (milis () / BLINK_DELAY)% 2.
Apoi, pur și simplu luăm restul de împărțind cu 2 din acest rezultat intermediar prin. Astfel, valoarea finală este aceea că 0. atunci se comută la fiecare 2 secunde. Ce avem nevoie! Și noi doar trecem valoarea calculată ca argument atunci când sunăm digitalWrite.
Modul de creștere a luminozității
Dacă starea curentă a ghirlandei noastre este STATE_FADE. va fi executată a treia unitate, ceea ce va determina reluarea ușoară a luminozității LED-urilor în decurs de 10 secunde, ieșirea și luminozitatea din nou:
Esența este cam aceeași ca și când clipește. Folosim doar o expresie ușor diferită pentru calcule și sunați analogWrite în loc de digitalWrite.
Sarcina noastră este aceea de a forța LED-urile să obțină luminozitate de la zero la maxim în exact 10 secunde sau 10 000 milisecunde. Funcția analogWrite ca parametru de luminanță are valori cuprinse între 0 și 255, adică doar 256 gradări. Prin urmare, pentru a crește luminozitatea cu o gradare, trebuie să treacă 10000 ms-256 ≈ 39 ms. Acestea sunt valorile determinate la începutul programului:
Deci, valoarea expresiei millis () / FADE_STEP_DELAY va deveni o dată la fiecare trecere de 39 ms.
Observați parantezele din definiția FADE_STEP_DELAY. Deoarece valorile definițiilor macro sunt înlocuite în program așa cum este, obținem millis () / (10000/256); și dacă nu există paranteze, s-ar fi dovedit milis () / 10000 / 256. Ceea ce nu este exact același din punct de vedere al matematicii. Prin urmare, adăugați paranteze în jurul oricărei expresii aritmetice atunci când le folosiți ca valori pentru macrocomenzi.
În cele din urmă, valorile intermediare Millis () / FADE_STEP_DELAY obținem restul divizării cu 256. Astfel, totul va începe peste tot din nou, de fiecare dată când o valoare intermediară va deveni un multiplu de 256. Ce ai nevoie!
Aritmetică de stat
Să aruncăm o privire din nou la codul care ne-a oferit includerea următoarei stări la apăsarea butonului:
Dacă nu aveam 3 state, ci 33, codul ar fi întins la mai multe linii, dar nimic nou și unic nu s-ar adăuga la program: ar rămâne același. Dacă credeți că atunci când scrieți o schiță că unele dintre locurile sale sunt reduse la un set monotonic de instrucțiuni care diferă puțin unul de celălalt, este aproape sigur o modalitate de a simplifica acest cod, de ao face mai ușor, mai ușor de înțeles și mai compact. Este necesar să gândim doar.
Ce puteți face cu lanțul nostru? Amintiți-vă că stările pentru procesor sunt doar întregi. Le-am definit folosind macro-uri.
Aceste numere am determinat în mod consecvent. Prin urmare, comutarea ledState la următoarea valoare nu este altceva decât adăugarea unei singure. Singurul obstacol este trecerea de la ultima stare la prima. Dar deja cunoaștem operatorul restului diviziei și, cu ajutorul acesteia, este ușor să luăm în considerare acest scenariu. Apoi, tot codul pentru includerea următoarei stări poate fi scris fără nicio ramificare:
Unde TOTAL_STATES este numărul total de state pe care le putem defini ca:
Enumerări enum
Din punctul de vedere al compilării, aceasta nu este diferită de:
Dar în cazul enum-ului, nu trebuie să notăm în mod explicit valorile 0, 1, 2, 3 etc. În enumerări, prima constantă primește automat valoarea 0, iar fiecare urmă este mai mare cu una.
Și ceea ce este chiar mai bun este enumerarea: putem folosi numele său (Statul în cazul nostru) ca un tip de date, precum și int. bool și altele asemenea. Asta este, putem defini variabila de stare curentă după cum urmează:
În spatele frameState a rămas același număr ca înainte, însă programul în sine a devenit mai clar și mai clar: am indicat clar ce vom stoca în variabila noastră.
După ce ne-am adunat împreună, vom primi versiunea actualizată a programului nostru:
Comutați expresia de selecție
Pentru a înțelege ce să facem cu luminile ghirlandei în acest moment, am folosit un lanț de declarații dacă. unde alternativ ledState a fost comparat cu toate stările posibile. Acesta este un script destul de comun, iar pentru el în C ++ există o expresie pentru alegerea comutatorului. Putem folosi în scopul nostru:
Să nu spun că codul a devenit mai compact, dar a devenit puțin mai vizibil.
Esența comutatorului de expresie este după cum urmează. În primul rând, valoarea expresiei aritmetice scrise în paranteze este evaluată. În cazul nostru, este doar obținerea valorii de ledState. Apoi, în blocul de cod dintre cotierele curbate, se caută eticheta casetei. a căror valoare este egală cu cea calculată. Executarea codului începe cu ea și merge secvențial la sfârșitul blocului (nu până în următorul caz). Executarea blocului poate fi finalizată înainte de termen cu ruperea expresiei.
O greșeală obișnuită de lipsa de atenție este să uiți să pui o pauză. În acest caz, procesorul va executa codul aparținând altor etichete, iar cel mai adesea nu este ceea ce aveți nevoie. Da, aceasta este o caracteristică neplăcută a C ++, dar există o poveste. Inițial, acest lucru a fost făcut pentru a putea lista mai multe valori simultan pentru o ramură. De exemplu, dacă trebuia să facem același lucru pentru STATE_FADE și STATE_BLINK, am putea scrie:
Tot în comutator. în ultimul loc puteți specifica o etichetă cu numele implicit al numelui. Acesta va fi executat dacă nici o altă etichetă de caz nu a corespuns valorii.
Deci, te-ai familiarizat cu automate finite, state, tranziții, principiile de execuție simultană a mai multor sarcini. În exemplul nostru, pentru diferite state am folosit o logică destul de simplă: un apel la digitalWrite cu o simplă expresie aritmetică; și pentru a schimba stările și a folosit tranziții succesive la apăsarea unui buton.
Nimic nu vă împiedică să dezvoltați acest subiect pentru a crea un dispozitiv mult mai inteligent. De exemplu, un robot de cameră poate avea stări de patrulare pentru detectarea pisicilor, starea de hărțuire a pisicilor, indicarea stării bateriei scăzute. Comutările vor avea loc pe semnalele senzorilor și nu vor fi atât de simple. Logica statelor în sine poate fi destul de complicată: trebuie să obțineți valori de la mai mulți senzori, să alegeți direcția mișcării, să rotiți motoarele etc.
Ca și în orice altceva, nimic nu împiedică mașinile de stat finite imbricate: adică stările care sunt ele însele automate finite. Principalul lucru este să păstrați codul subțire și lizibil, atunci veți reuși!
Cu excepția cazurilor în care se specifică altfel, conținutul acestui wiki este licențiat sub următoarea licență: CC Attribution-Noncommercial-Share Alike 3.0 Unported