CURSUL 10

Proiectarea și Testarea Aplicațiilor cu Microcontrolere

Durată: 122 min de predare Nivel: licență, anul II - recomandat după Cursul 09 Disciplină: Microcontrollere și Microprocesoare PDF: descarcă suportul EN English version

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.

Recapitulare din Cursul 09
  • 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.

Costul unei erori crește puternic cu momentul descoperirii O cerință scrisă greșit și corectată pe hârtie costă o oră. Aceeași cerință descoperită după ce a fost implementată costă rescrierea codului. Descoperită după ce s-a proiectat cablajul, costă un nou cablaj. Descoperită după ce o sută de plăci sunt montate în teren, costă deplasarea la o sută de locații. Nimic din ceea ce face un proiectant nu are un randament mai bun decât timpul petrecut clarificând cerințele înainte de a începe.
Cat costa corectarea unei erori, in functie de cand e descoperita

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ă.

Metoda de proiectare: puneti etapele in ordinea in care se parcurg

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.

Bugetul de timp: incape cerinta de raspuns in lantul cauza-efect?

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.

Cerință vagă vs. cerință verificabilă
Vagă: „sistemul trebuie să răspundă repede la depășirea pragului”.
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.

CodCerințăVerificare
CF1Măsoară umiditatea o dată la 1±0,1 scronometrare cu osciloscop
CF2Sub prag, deschide valva în ≤200 msinjectare tensiune sub prag
CF5La oprire, valva se închide în ≤50 msosciloscop pe ambele semnale
CN1Consum mediu <20 mAampermetru, 10 minute
CN4Praguri modificabile fără reprogramareverificare EEPROM
Exercițiu rezolvat - bugetul de timp pentru CF5

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.

O interfață completă spune mai mult decât o semnătură de funcție

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.

Testabilitatea este criteriul partiționării, nu o consecință a ei Un bloc funcțional bine delimitat se recunoaște după faptul că poate fi testat singur. Dacă, pentru a verifica funcția de decizie a udării, trebuie conectate senzorul real, sursa de 12 V și electrovalva, atunci funcția de decizie nu este un bloc separat - s-a amestecat cu accesul la periferice.

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.

CriteriuCerința aplicațieiConsecința
Pini1 analogic, 3 intrări, 3 ieșiri digitale, serialcapsulă de 28 pini e suficientă
PerifericeADC, timer, USARTfără SPI/I²C necesare
Memorie nevolatilăpraguri modificabile din teren (CN4)e obligatorie o EEPROM
Consumsub 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ă.

Avantajul: combinațiile nedorite devin imposibile prin construcție Dacă tranziția din PAUZĂ către UDARE nu există în tabel, atunci nu există nici în cod - sistemul nu poate uda de două ori la rând, indiferent ce apasă utilizatorul. Cerințele de siguranță se traduc astfel în absența unor tranziții, formă mult mai ușor de verificat decât o condiție complicată.
Stare curentăEvenimentAcțiuneStare următoare
AȘTEPTAREtic 1 spornește conversia ADCMĂSURARE
MĂSURAREmedie < pragdeschide valva, pornește cronometrulUDARE
MĂSURAREvaloare invalidăaprinde LED avarieAVARIE
UDAREdurata maximă atinsăînchide valva, pornește temporizareaPAUZĂ
PAUZĂbuton udare manualăignoră, jurnalizează refuzulPAUZĂ
AVARIEresetare de la operatorșterge indicația de avarieAȘ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 ... */
    }
}
Regula de aur: un singur loc modifică variabila de 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ă.

Pinul releului trebuie adus inactiv înainte de a fi configurat ca ieșire

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.

Un simulator software modelează microcontrolerul, nu lumea din jurul lui

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;
}
De ce e nevoie de histerezis Fără cele două praguri diferite pentru pornire și oprire, o valoare care oscilează din cauza zgomotului în jurul pragului ar produce deschideri și închideri repetate ale releului, cu uzura rapidă a contactelor. Zona moartă introdusă face ca, odată pornită udarea, oprirea să se producă numai după ce umiditatea a urcat vizibil peste prag - aceeași problemă și aceeași soluție apar la orice termostat.

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.

Potriviti nivelul de testare cu ce verifica
TestCerințăMod de efectuareCriteriu de acceptare
T1CF1comutare pin la fiecare măsurareperioadă 0,9-1,1 s, 100 cicluri
T6CF6deconectare senzor, apoi scurtcircuit la masătrece în AVARIE, valva închisă
T9CN3întrerupere alimentare în timpul udăriivalva închisă la revenire
T11-apăsare simultană a tuturor butoanelor, ×50stare definită, fără blocaj
Testele fără cerință asociată sunt un semnal util T11 și T12 nu au o cerință - provin din testarea atipică, iar dacă descoperă un defect, înseamnă că în caietul de sarcini lipsea o cerință. Corectarea nu se face doar în cod, ci și în caietul de sarcini, completat cu cerința nou descoperită; apoi planul de testare se rulează din nou, integral, pentru că o corecție e exact momentul în care se introduc alte defecte.

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<
„Depanarea cu un fir” nu oprește programul Comutarea unui pin costă două instrucțiuni și nu perturbă practic nimic - spre deosebire de un depanator cu puncte de oprire, care, într-un sistem ce comandă un motor sau o valvă, schimbă complet comportamentul fizic (uneori periculos) și face să dispară exact defectele legate de timp, pentru că timpul a fost modificat de instrumentul de măsură.

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.

Un defect care dispare când adaugi o întârziere sau o afișare nu a fost reparat

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.

Criteriul practic al unei documentații suficiente Nu numărul de pagini, ci întrebarea dacă un coleg care nu a participat la proiect poate, folosind numai documentele, să repare un exemplar defect, să compileze programul, să îl încarce și să verifice că funcționează. Documentația trebuie să conțină: schema electrică cu valorile componentelor; harta pinilor (ce e conectat la fiecare pin, direcția, nivelul activ); protocolul de comunicație; valorile de configurare; procedura de programare.
PinDirecțieSemnalObservații
PC0 (ADC0)IntrareSenzor umiditate0...5V, filtru RC 10kΩ/100nF
PD5IeșireComandă releu valvăactiv în 1, tranzistor + diodă
PD7IeșirePin de testdoar pentru depanare, neconectat în producție
PD0, PD1SerialJurnal RXD, TXD9600 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 lui volatile sau 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.

Cerință verificabilă
afirmație cu subiect, acțiune, condiție și valoare numerică, testabilă printr-o măsurătoare.
Partiționare
împărțirea sistemului în subsisteme cu o responsabilitate unică și interfețe fixate dinainte.
Mașină cu stări finite
model în care comportamentul depinde de starea curentă și de eveniment, descris printr-un tabel de tranziții.
Histerezis
zonă moartă între pragul de pornire și cel de oprire, care previne comutații repetate din cauza zgomotului.
Test unitar
test al unei funcții pur logice, fără acces la periferice, rulat pe calculatorul gazdă.
Depanare cu un fir
tehnica de a comuta un pin de test pentru a măsura durate cu osciloscopul, fără a opri programul.

13Întrebări de verificare7 min

  1. 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.
  2. De ce trebuie fixată interfața unui subsistem înainte de implementarea lui? Ce informații trebuie să conțină, dincolo de prototipul funcției?
  3. 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?
  4. Comparați bucla de scrutare cu arhitectura condusă de evenimente din punctul de vedere al timpului maxim de reacție.
  5. 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ă.
  6. 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.