Capitolele anterioare au tratat, pe rând, componentele unui microcontroler: porturi, întreruperi, timere, ADC, UART, SPI, I²C. Un inginer care le stăpânește pe toate nu știe însă, prin asta, să facă un produs. Între „am aprins un LED cu PWM” și „am livrat un sistem care funcționează nesupravegheat doi ani într-o seră” există o distanță care nu se acoperă cu cunoștințe suplimentare despre registre, ci cu metodă - obiectul acestui curs, urmărit pe un singur exemplu, de la cerință până la planul de testare.
1Obiectul și structura cursului6 min
Proiectele cu microcontrolere eșuează aproape întotdeauna din motive de metodă, nu de programare - rareori pentru că cineva nu a înțeles un prescaler, aproape întotdeauna pentru că nimeni nu a scris limpede ce trebuie să facă sistemul, pentru că partajarea pe module s-a făcut din mers, sau pentru că testarea a fost lăsată la sfârșit. Vom urmări întregul drum, de la cerință vagă la sistem documentat, pe un singur exemplu de referință: un sistem de udare automată pentru o seră, reluat identic în fiecare secțiune.
- SPI și I²C rezolvă comunicația cu perifericele apropiate - rămâne deschisă întrebarea cum se organizează, în ansamblu, un program care le folosește pe toate coerent (Cursul 09)
- Să explicați cele trei cauze principale pentru care eșuează proiectele cu microcontrolere
- Să transformați o cerință vagă într-o cerință verificabilă, cu criteriu de acceptare numeric
- Să partiționați un sistem în subsisteme cu interfețe fixate înainte de implementare
- Să descrieți logica unei aplicații ca mașină cu stări finite, pornind de la un tabel de tranziții complet
- Să distingeți nivelurile de testare (unitar, pe modul, de sistem, la limite, de robustețe) și să scrieți un plan de testare legat de caietul de sarcini
- Să aplicați cele trei principii ale depanării sistematice: caz minim reproductibil, măsoară nu ghici, separă hardware de software
2De ce eșuează un proiect: cele trei cauze11 min
Prima cauză: cerința neclară. Beneficiarul spune „vreau ca sistemul să reacționeze repede” sau „vreau să fie sigur”. Nimeni nu contrazice asemenea afirmații, pentru că nimeni nu știe ce înseamnă. La recepție, beneficiarul constată că sistemul reacționează în 300 ms și afirmă că nu este „repede”, proiectantul răspunde că nici nu s-a cerut altceva, iar discuția nu are cum să se încheie - nu există un criteriu obiectiv la care ambele părți să se raporteze. O cerință care nu poate fi verificată printr-o măsurătoare nu este o cerință, ci o dorință.
A doua cauză: delimitarea proastă a subsistemelor. Când codul crește fără o structură decisă dinainte, funcțiile încep să comunice prin variabile globale, fiecare modificând starea celeilalte. Un asemenea program funcționează, de obicei, până la prima modificare - atunci se descoperă că schimbarea unui prag impune modificări în rutina releului, care strică afișarea, iar corecția afișării reintroduce eroarea inițială. Sistemul devine imposibil de întreținut nu pentru că ar fi complex, ci pentru că nu are granițe interne.
A treia cauză, cea mai costisitoare: testarea amânată. Este tentant să scrii tot programul și abia apoi să îl încerci, pentru că testarea pe parcurs pare o pierdere de timp. În realitate, când integrezi zece module netestate și rezultatul nu funcționează, nu ai nicio informație despre locul erorii - oricare dintre cele zece, sau oricare interacțiune dintre ele, poate fi vinovat. Dacă fiecare modul a fost verificat separat și sistemul se strică la adăugarea celui de-al zecelea, spațiul de căutare s-a redus la un singur modul.
Cele două etape și ciclul dintre ele
Dezvoltarea se desfășoară în două etape mari: proiectarea, care începe cu descrierea sistemului și se încheie cu primul draft de cod, și testarea, care începe cu simularea și se încheie cu documentarea. Reprezentarea pașilor ca o listă e înșelătoare, pentru că sugerează o curgere într-un singur sens - în realitate există o buclă de reacție: dacă la testare sistemul nu se comportă conform caietului de sarcini, nu se corectează doar codul, ci se reia proiectarea de la nivelul la care s-a produs greșeala, uneori chiar caietul de sarcini însuși. Un proiect sănătos parcurge acest ciclu de mai multe ori, cu amplitudine din ce în ce mai mică.
De reținut: etapa de proiectare produce documente, nu cod. Diagramele bloc, tabelele de cerințe, tabelele de tranziții și harta pinilor rămân în urma proiectului și îi determină valoarea pe termen lung - un sistem funcțional despre care nu există niciun document este un sistem cu durată de viață egală cu memoria autorului lui.
3Exemplul de referință și caietul de sarcini13 min
Tot capitolul urmărește un singur exemplu: un sistem de udare automată pentru o seră mică. Măsoară umiditatea solului și, sub un prag, deschide o electrovalvă printr-un releu pentru un interval limitat; are un buton de udare manuală, un buton de oprire, două LED-uri de stare și transmite pe interfața serială un jurnal al evenimentelor. Din prima discuție cu beneficiarul rezultă întotdeauna un al doilea document, la fel de important ca descrierea: lista de întrebări - ce se întâmplă dacă senzorul se defectează și indică permanent sol uscat? Dacă cineva apasă simultan udarea manuală și oprirea? Dacă alimentarea cade în timpul udării? Un proiectant experimentat se recunoaște după calitatea acestei liste, pentru că fiecare întrebare pusă acum este o eroare care nu va mai fi descoperită în teren.
Ce înseamnă o cerință verificabilă
O cerință e verificabilă dacă, având sistemul în față și un aparat de măsură, două persoane independente ajung obligatoriu la aceeași concluzie despre îndeplinirea ei.
Verificabilă: „sistemul trebuie să comande releul în cel mult 50 ms de la momentul în care mărimea măsurată depășește pragul configurat”.
A doua fixează un moment de start, un moment de final, o valoare-limită și, implicit, o metodă de verificare.
O cerință bine scrisă are patru elemente: un subiect, o acțiune observabilă, o condiție de declanșare și o valoare numerică cu unitate de măsură și toleranță. Cuvintele care trebuie să dea de gândit când apar într-un caiet de sarcini: „repede”, „stabil”, „sigur”, „prietenos”, „aproximativ” - fiecare ascunde o negociere care nu a avut loc.
Cerințele funcționale descriu ce face sistemul; sunt relativ ușor de formulat, pentru că decurg direct din descrierea beneficiarului. Cerințele nefuncționale descriu în ce condiții trebuie să facă acele lucruri, și sunt sursa majorității surprizelor târzii: consumul mediu decide dacă sistemul poate fi alimentat de la panou solar; domeniul de temperatură decide dacă oscilatorul poate fi cel intern sau trebuie cuarț; costul unitar decide între 8 și 32 KB de memorie de program. Niciuna dintre aceste cerințe nu apare în descrierea beneficiarului, dar toate schimbă proiectul dacă sunt descoperite târziu.
| Cod | Cerință | Verificare |
|---|---|---|
| CF1 | Măsoară umiditatea o dată la 1±0,1 s | cronometrare cu osciloscop |
| CF2 | Sub prag, deschide valva în ≤200 ms | injectare tensiune sub prag |
| CF5 | La oprire, valva se închide în ≤50 ms | osciloscop pe ambele semnale |
| CN1 | Consum mediu <20 mA | ampermetru, 10 minute |
| CN4 | Praguri modificabile fără reprogramare | verificare EEPROM |
Cerința CF5 (închiderea valvei în ≤50 ms de la apăsarea butonului) trebuie verificată pentru o arhitectură în care butoanele sunt scrutate la fiecare 2 ms, o apăsare e confirmată după 20 ms de nivel stabil, iar releul are un timp de acționare de 10 ms conform catalogului. Este realizabilă?
Vezi rezolvarea
Bugetul se construiește adunând, în cel mai defavorabil caz, toate întârzierile de pe lanțul cauză-efect. Apăsarea poate cădea imediat după o citire: se pierde un interval de scrutare, 2 ms. Confirmarea antirebound: 20 ms. Decizia și scrierea în portul releului: sub 1 ms. Acționarea releului: 10 ms. Total: 2+20+1+10 = 33 ms, cu o rezervă de 17 ms față de limita de 50 ms - cerința e realizabilă. Dacă însă confirmarea antirebound ar fi fost aleasă la 50 ms, cum se întâmplă adesea fără justificare, cerința ar fi fost încălcată din proiectare, iar eroarea ar fi apărut abia la măsurătorile de recepție, nu acum, pe hârtie.
4Partiționarea sistemului și alegerea microcontrolerului13 min
Partiționarea înseamnă împărțirea sistemului în subsisteme, fiecare cu o responsabili- tate unică, și stabilirea modului în care comunică. Regula fundamentală: interfața unui subsistem se fixează înainte ca subsistemul să fie implementat. Motivul e practic - atât timp cât interfața nu e fixată, niciun alt subsistem nu poate fi scris, pentru că nu se știe ce va primi și ce va returna. Din momentul în care interfața e scrisă, fie și doar ca prototipuri de funcții cu comentarii, toate modulele pot fi dezvoltate în paralel.
uint16_t citesteSenzor(void); nu e o interfață completă. Trebuie precizat: ce
unități de măsură, ce domeniu de valori, cu ce eroare, ce se întâmplă la valori invalide, cât
durează operația, și dacă poate fi apelată dintr-o rutină de întrerupere.
Al doilea criteriu de partiționare bună: fiecare subsistem are un singur motiv de a se schimba. Dacă înlocuirea senzorului de umiditate cu altul obligă la modificarea codului care aprinde LED-urile, partiționarea e greșită. Sistemul de udare se împarte astfel în: achiziție umiditate, interfață operator (butoane + LED-uri), comandă valvă, bază de timp, jurnalizare serială, și, în mijloc, aplicația propriu-zisă, care ia decizii fără să atingă niciun registru.
Alegerea microcontrolerului
Alegerea nu se face după obișnuință sau după piesa din sertar, ci ca o consecință a caietului de sarcini și a partiționării: după ce se știe ce semnale intră și ies, numărul de pini e determinat; după ce se știu perioadele de lucru, se determină perifericele necesare. Abia atunci se deschide catalogul.
| Criteriu | Cerința aplicației | Consecința |
|---|---|---|
| Pini | 1 analogic, 3 intrări, 3 ieșiri digitale, serial | capsulă de 28 pini e suficientă |
| Periferice | ADC, timer, USART | fără SPI/I²C necesare |
| Memorie nevolatilă | praguri modificabile din teren (CN4) | e obligatorie o EEPROM |
| Consum | sub 20 mA mediu (CN1) | moduri de consum redus |
| Temperatură | −5...+55°C (CN2) | variantă industrială sau verificarea derivei oscilatorului |
Un ATmega328P satisface toate liniile tabelului cu rezervă. Important: concluzia a rezultat din tabel, nu l-a precedat - dacă aplicația ar fi cerut opt canale analogice și un consum sub 100 µA, aceeași metodă ar fi condus la altă piesă. Regula practică: se păstrează o rezervă de cel puțin 20% la pini, memorie și timp de execuție - un proiect care folosește tot ce are e un proiect fără viitor, pentru care prima cerință suplimentară impune schimbarea microcontrolerului și reproiectarea cablajului.
5Arhitectura programului și mașina cu stări finite13 min
Cu subsistemele definite, urmează descrierea a ceea ce face fiecare înăuntru, prin diagrame de activități și de stări (limbajul UML). Criteriul de calitate al unei diagrame de activități e simplu și sever: din ea trebuie să se poată scrie codul direct, fără nicio decizie de proiectare suplimentară.
Buclă de scrutare sau arhitectură condusă de evenimente
În bucla de scrutare (polling), programul parcurge la nesfârșit aceeași secvență: citește
toate intrările, calculează, actualizează toate ieșirile - simplă și previzibilă, dar timpul de
reacție e, în cel mai rău caz, egal cu durata întregii bucle. În arhitectura condusă de
evenimente, perifericele semnalează prin întreruperi momentul unui eveniment, iar bucla
principală consumă fanioanele ridicate - reacție rapidă, dar apar probleme noi: partajarea
variabilelor cu ISR-ul, volatile, secțiuni critice. În practică, aproape orice
aplicație reală e un hibrid: întreruperile captează evenimentele rapide, iar bucla principală,
ritmată de un tic al unui timer, ia deciziile.
Mașina cu stări finite
Forma naturală de organizare a logicii unei aplicații încorporate. Ideea: sistemul se află, în orice moment, într-una dintr-un număr mic de stări bine definite, iar comportamentul depinde nu doar de intrări, ci și de starea curentă - aceeași apăsare de buton înseamnă altceva când sistemul udă și altceva când așteaptă.
| Stare curentă | Eveniment | Acțiune | Stare următoare |
|---|---|---|---|
| AȘTEPTARE | tic 1 s | pornește conversia ADC | MĂSURARE |
| MĂSURARE | medie < prag | deschide valva, pornește cronometrul | UDARE |
| MĂSURARE | valoare invalidă | aprinde LED avarie | AVARIE |
| UDARE | durata maximă atinsă | închide valva, pornește temporizarea | PAUZĂ |
| PAUZĂ | buton udare manuală | ignoră, jurnalizează refuzul | PAUZĂ |
| AVARIE | resetare de la operator | șterge indicația de avarie | AȘTEPTARE |
Tabelul trebuie să fie complet - pentru fiecare stare, toate evenimentele posibile, inclusiv cele care nu produc nicio tranziție; altfel rămân colțuri nedefinite ale comportamentului, exact locurile unde apar defectele. Traducerea în cod e mecanică:
typedef enum { ST_ASTEPTARE=0, ST_MASURARE, ST_UDARE, ST_PAUZA, ST_AVARIE } stare_t;
static stare_t stareCurenta = ST_ASTEPTARE;
void ruleazaMasinaDeStari(eveniment_t ev) {
switch (stareCurenta) {
case ST_ASTEPTARE:
if (ev == EV_TIC_1S) { adcPornesteConversia(); stareCurenta = ST_MASURARE; }
else if (ev == EV_BUTON_MANUAL) { valvaDeschide(); stareCurenta = ST_UDARE; }
break;
case ST_MASURARE:
if (ev == EV_VALOARE_INVALIDA) { stareCurenta = ST_AVARIE; }
else if (ev == EV_SUB_PRAG) { valvaDeschide(); stareCurenta = ST_UDARE; }
else if (ev == EV_PESTE_PRAG) { stareCurenta = ST_ASTEPTARE; }
break;
/* ... celelalte stari, cate un caz per stare ... */
}
}
Dacă starea se schimbă din trei locuri diferite ale programului, avantajul mașinii cu stări dispare complet - devine iar un labirint de condiții imbricate, doar cu un nume mai frumos.
6Activitatea principală: inițializări, buclă, butoane11 min
Diagrama de activități a programului principal descrie ce se întâmplă de la alimentare până la oprire. Începe invariabil cu inițializări: variabile, porturi cu direcțiile lor, timerul care dă baza de timp, ADC, interfața serială, apoi validarea globală a întreruperilor. Ordinea nu e indiferentă.
Dacă direcția e configurată înainte de a scrie starea dorită în registrul de date, în intervalul dintre cele două instrucțiuni ieșirea preia valoarea reziduală din registru, iar valva se poate deschide pentru câteva microsecunde la fiecare pornire a sistemului. Regula generală: orice ieșire care comandă ceva periculos se aduce în starea sigură înainte de a fi declarată ieșire.
volatile uint8_t bTic = 0; /* ridicat de ISR, citit de bucla principala */
ISR(TIMER1_COMPA_vect) { bTic = 1; } /* ISR foarte scurt: doar marcheaza timpul */
int main(void) {
valvaInchide(); /* stare sigura INAINTE de a configura iesirea */
porturiInit();
timerInit(); /* intrerupere la fiecare 10 ms */
adcInit(); serialInit(9600);
parametriIncarcaDinEeprom();
sei();
for (;;) {
if (bTic) {
cli(); bTic = 0; sei(); /* stergere protejata: ISR poate scrie oricand */
eveniment_t ev = colecteazaEveniment();
ruleazaMasinaDeStari(ev);
cronometruTic();
jurnalTrimiteUnCaracter();
}
}
}
Se observă că bucla nu conține nicio întârziere activă și nicio operație lungă - tot ce durează mult, precum transmisia jurnalului, e fragmentat sau lăsat pe seama întreruperilor.
Citirea butoanelor prin tranziție, nu prin nivel
Un buton nu se citește ca nivel, ci ca tranziție: sistemul trebuie să reacționeze la momentul apăsării, nu la faptul că e ținut apăsat. Metoda standard: se păstrează valoarea citită anterior a portului și se compară cu cea curentă; dacă diferă, s-a produs o schimbare, iar biții modificați arată care buton a fost acționat. Aceeași metodă rezolvă apăsarea simultană a mai multor butoane, pentru că valoarea întregului port se interpretează ca un cod, iar codurile nepermise se pot trata explicit - dacă utilizatorul apasă simultan oprirea și udarea manuală, programul poate decide explicit că oprirea are prioritate, decizie care trebuie să figureze în caietul de sarcini, nu lăsată la voia ordinii instrucțiunilor.
7Scrierea și testarea primară a codului11 min
Cu diagramele finalizate, scrierea codului devine în bună măsură transcriere. Regula esențială a acestei etape: nu se scrie tot programul deodată. Codul se dezvoltă pe straturi, de jos în sus - mai întâi se verifică frecvența reală a microcontrolerului (comutând un pin într-o buclă, măsurat cu osciloscopul), apoi ieșirea cu un LED, apoi intrarea cu un buton, apoi ADC-ul cu un potențiometru, apoi timerul, și abia la sfârșit se aduc împreună.
De ce nu se testează direct pe instalația reală
Două motive. Primul: o eroare de software poate distruge hardware scump - o comandă simultană a două brațe ale unei punți în H scurtcircuitează sursa, o valvă lăsată deschisă inundă sera. Al doilea, mai puțin evident: instalația reală nu poate fi adusă la comandă în situațiile care contează - nu poți face solul uscat în cinci secunde ca să verifici pragul, nu poți repeta de o sută de ori o cădere de tensiune.
Soluția e un modul de simulare ieftin, care imită interfața electrică a instalației: intrări digitale de la butoane, intrări analogice de la potențiometre, ieșiri pe LED-uri - pentru sistemul de udare, un potențiometru în locul senzorului, trei butoane, trei LED-uri. Costul e de câțiva lei; câștigul e posibilitatea de a produce oricând orice combinație de intrări, inclusiv cele imposibile fizic, care sunt cele mai interesante pentru testare.
Simulatoarele software sunt de neînlocuit pentru verificarea logicii și numărarea ciclurilor, dar reboundul contactelor, zgomotul liniei analogice, căderea de tensiune la anclanșarea releului și cuplajul dintre cablul de forță și cel de semnal nu apar în simulator - și sunt, în practică, sursa majorității defectelor.
Cod ușor de testat
Se poate scrie cod care se lasă testat și cod care nu se lasă - diferența stă în separarea deciziei de accesul la periferice. O funcție care citește singură ADC-ul și scrie singură în portul releului nu poate fi verificată decât cu hardware. Aceeași logică, scrisă ca funcție pură care primește valoarea măsurată și returnează decizia, poate fi compilată și rulată pe calculator, cu mii de combinații, în câteva secunde.
/* histerezis: latimea zonei moarte, in aceleasi unitati ca umiditatea si pragul */
uint8_t decideUdarea(uint16_t umiditate, uint16_t prag,
uint16_t histerezis, uint8_t udaAcum) {
if (udaAcum) return (umiditate < prag + histerezis) ? 1 : 0;
return (umiditate < prag) ? 1 : 0;
}
8Planul de testare și nivelurile lui11 min
După ce modulul de încercare e gata, se scrie planul de testare - nu o listă de lucruri încercate „când vine în minte”, ci un document derivat din caietul de sarcini, în care fiecare cerință are cel puțin un test asociat și fiecare test are un criteriu de acceptare numeric. Legătura se face prin codurile cerințelor, astfel încât la sfârșit să se poată demonstra că nicio cerință n-a rămas neverificată.
Nivelurile de testare
Testele unitare se aplică funcțiilor pur logice, care nu ating periferice: conversia ADC în procente, medierea, decizia de udare. Se compilează pentru calculatorul gazdă și rulează automat, deci pot fi repetate la fiecare modificare a codului, cu efort nul. Un test unitar bun include valorile de la limitele domeniului.
assert(decideUdarea(300, 400, 50, 0) == 1); /* sub prag, inchisa: porneste */
assert(decideUdarea(500, 400, 50, 0) == 0); /* peste prag: nu porneste */
assert(decideUdarea(400, 400, 50, 0) == 0); /* exact pe prag: comparatia e stricta */
assert(decideUdarea(430, 400, 50, 1) == 1); /* in zona de histerezis: ramane deschisa */
Testele pe modul verifică un subsistem întreg, cu hardware simulat - se aplică o tensiune cunoscută și se compară valoarea afișată cu cea măsurată cu voltmetrul, verificând nu doar logica, ci și configurarea perifericului (referință, prescalare, aliniere). Testele de sistem verifică ansamblul prin scenarii de folosire reală, cronometrate și comparate cu limitele din caietul de sarcini. Testele la limite exercitează valorile extreme - un contor de milisecunde pe 16 biți se rotește după ~65 s, iar o comparație scrisă neatent dă un rezultat greșit exact atunci; testul trebuie să existe în plan, altfel defectul apare la beneficiar. Testele de robustețe verifică situații nedorite: deconectarea senzorului, alimentare instabilă, resetare în timpul udării, apăsare simultană a butoanelor. Criteriul nu e ca sistemul să facă ceva util, ci să nu facă ceva periculos.
| Test | Cerință | Mod de efectuare | Criteriu de acceptare |
|---|---|---|---|
| T1 | CF1 | comutare pin la fiecare măsurare | perioadă 0,9-1,1 s, 100 cicluri |
| T6 | CF6 | deconectare senzor, apoi scurtcircuit la masă | trece în AVARIE, valva închisă |
| T9 | CN3 | întrerupere alimentare în timpul udării | valva închisă la revenire |
| T11 | - | apăsare simultană a tuturor butoanelor, ×50 | stare definită, fără blocaj |
9Depanarea sistematică9 min
Oricât de bine organizat un proiect, vor exista defecte, iar modul în care sunt căutate face diferența între o oră și o săptămână. Depanarea nu e o activitate de inspirație, ci o procedură cu trei principii.
Primul: reducerea la un caz minim reproductibil. Un defect care apare „uneori, după vreo jumătate de oră” nu poate fi studiat. Trebuie găsită cea mai scurtă secvență de acțiuni care îl provoacă de fiecare dată, apoi eliminată, una câte una, orice parte a sistemului care nu e necesară pentru reproducere - deconectăm afișajul: mai apare? Oprim jurnalul serial: mai apare? De cele mai multe ori defectul se lămurește singur în timpul acestei reduceri, pentru că partea eliminată la care defectul dispare arată direct cauza.
Al doilea: măsoară, nu ghici. Majoritatea ipotezelor plauzibile sunt false, iar verificarea lor costă adesea mai puțin decât discuția despre ele. Dacă bănuiți că o întrerupere nu se declanșează, comutați un pin în ISR și priviți-l cu osciloscopul - în trei minute aveți un răspuns sigur.
#define PIN_TEST_SUS (PORTD |= (1<
Al treilea: izolează „e hardware sau e software?” cât mai devreme, pentru că răspunsul schimbă complet direcția căutării. Un LED care nu se aprinde poate însemna un bit scris greșit sau un rezistor lipsă; măsurarea tensiunii pe pin separă imediat cele două situații - dacă pinul are nivelul corect și LED-ul nu luminează, problema e în circuit; dacă pinul rămâne la zero, problema e în cod sau în configurarea direcției portului.
Ai modificat timpii, nu ai reparat nimic. Asemenea defecte, sensibile la temporizare, provin
aproape întotdeauna dintr-o variabilă partajată cu o rutină de întrerupere fără
volatile, sau dintr-un acces neprotejat la o variabilă pe mai mulți octeți.
Reparația corectă e protejarea accesului, nu păstrarea întârzierii care „face să meargă”.
10Documentarea6 min
O parte însemnată a documentației există deja dacă etapele anterioare au fost parcurse: descrierea sistemului, caietul de sarcini, diagramele bloc, tabelele de tranziții, planul de testare și rezultatele încercărilor. Documentarea finală înseamnă strângerea lor într-un ansamblu coerent.
| Pin | Direcție | Semnal | Observații |
|---|---|---|---|
| PC0 (ADC0) | Intrare | Senzor umiditate | 0...5V, filtru RC 10kΩ/100nF |
| PD5 | Ieșire | Comandă releu valvă | activ în 1, tranzistor + diodă |
| PD7 | Ieșire | Pin de test | doar pentru depanare, neconectat în producție |
| PD0, PD1 | Serial | Jurnal RXD, TXD | 9600 bd, 8N1 |
Documentarea codului: fiecare funcție însoțită de scopul ei, parametrii cu unități și domenii, valoarea returnată și condițiile de eroare; comentariile care repetă instrucțiunea n-au valoare, cele care explică de ce s-a ales o soluție sunt cele care salvează timp peste doi ani. La final, jurnalul deciziilor: lista scurtă a alegerilor importante și a motivelor lor - de ce histerezisul e 5% și nu 2%, de ce pauza e 30 de minute. Fiecare decizie pare evidentă în ziua în care e luată și devine de neînțeles peste un an; fără jurnal, primul lucru pe care îl face cel care preia proiectul e să „corecteze” o decizie corectă, iar defectul reapare exact în situația care a impus-o inițial.
11Erori frecvente4 min
- „Am scris tot programul dintr-o bucată, o să-l testez la final." Când integrarea eșuează, spațiul de căutare al defectului include toate modulele și toate interacțiunile dintre ele - depanarea devine practic nemărginită. Fiecare periferic se verifică izolat, cu un program minimal, înainte de a fi integrat cu restul.
- „Funcția care decide udarea citește singură ADC-ul și comandă singură releul, ca să fie mai puține funcții." Amestecarea deciziei cu accesul la periferice face funcția netestabilă fără hardware real. Separați logica pură (primește valori, returnează o decizie) de accesul la periferice - logica pură se testează pe calculator, în secunde.
- „Am adăugat un
delay()și defectul intermitent a dispărut - e reparat." Fals - de regulă ați modificat doar temporizarea unei curse la o variabilă partajată cu o întrerupere; defectul reapare la altă combinație de viteze. Căutați accesul neprotejat (lipsa luivolatilesau a unei secțiuni critice) și protejați-l explicit, în loc să păstrați întârzierea „care face să meargă”.
12Rezumat și glosar5 min
Un proiect cu microcontroler nu eșuează din lipsă de cunoștințe despre registre, ci din lipsă de metodă: cerințe neverificabile, subsisteme fără interfețe fixate, testare amânată până la final. Antidotul e un drum ordonat, cu bucla de reacție dintre testare și proiectare: descrierea sistemului devine caiet de sarcini prin cerințe verificabile (subiect, acțiune, condiție, valoare numerică); partiționarea fixează interfețele înainte de implementare și face testabilitatea un criteriu explicit; alegerea microcontrolerului decurge din tabelul de cerințe, cu o rezervă de minimum 20%; logica aplicației se organizează ca mașină cu stări finite, cu un tabel de tranziții complet, care face imposibile prin construcție combinațiile nedorite; codul se scrie pe straturi, testat pe un modul de simulare ieftin, cu logica pură separată de accesul la periferice; planul de testare leagă fiecare cerință de un test cu criteriu numeric, pe cinci niveluri (unitar, modul, sistem, limite, robustețe); depanarea urmează trei principii - caz minim reproductibil, măsoară nu ghici, separă hardware de software; iar documentația finală, inclusiv jurnalul deciziilor, face proiectul transferabil către oricine altcineva.
13Întrebări de verificare7 min
- Rescrieți cerința „sistemul trebuie să răspundă repede la apăsarea butonului” astfel încât să devină verificabilă, precizând și metoda de măsurare.
- De ce trebuie fixată interfața unui subsistem înainte de implementarea lui? Ce informații trebuie să conțină, dincolo de prototipul funcției?
- Un coleg propune ca funcția de decizie a udării să citească singură ADC-ul și să comande direct releul. Ce se pierde din punctul de vedere al testării?
- Comparați bucla de scrutare cu arhitectura condusă de evenimente din punctul de vedere al timpului maxim de reacție.
- Explicați de ce absența unei tranziții din tabel, nu o condiție în cod, este mecanismul prin care mașina cu stări previne udarea repetată.
- Un contor de milisecunde pe 16 biți măsoară o pauză de 30 de minute. De ce e greșită implementarea, și după cât timp apare eroarea?
14Direcții de aprofundare2 min
Ultimul curs al disciplinei pune în practică exact această metodă, pe un proiect complet și mai complex: un robot care urmărește o linie și evită obstacole, de la caietul de sarcini până la arhitectura finală a programului.
Aplicarea integrată a achiziției, deciziei și comenzii, fără delay(), se exersează
în Laboratorul 07.