Contacte

Temporizatoare Arduino mega 2560. AVR. Curs de pregatire. Cronometre. Temporizatoare pe Arduino

Recent, din ce în ce mai mulți începători se confruntă cu problema stăpânirii temporizatoarelor/contoarelor (denumite în continuare T/C) în stadiul studierii microcontrolerelor. În acest articol voi încerca să elimin temerile legate de aceste module și să explic clar cum și cu ce sunt folosite aceleași T/S.

Vom lua ca bază o carte care este foarte populară în rândul dezvoltatorilor de dispozitive MK, scrisă de A.V. Evstifeev. Folosind linkurile de la sfârșitul articolului, puteți găsi proiectul în și proiectul în. În acest articol vom analiza funcționarea T/S T2 pe 8 biți, care face parte din Atmega8 T/S MK.

Deci, ce este un cronometru/contor? T/S este unul dintre modulele AVR MK cu care puteți măsura anumite perioade de timp, organiza PWM și multe alte sarcini. În funcție de modelul MK, numărul de T/S poate fi de 4 sau mai mult. Un exemplu în acest sens este Atmega640x, 1280x/1281x, 2560x/2561x MK, care conțin 6 T/S la bord: două pe 8 biți și patru pe 16 biți. Atmega8 MK conține trei T/S: T0 și T2 cu 8 biți, T1 cu 16 biți.

Să aruncăm o privire mai atentă la microcontrolerul T/C T2 Atmega8.

Acest cronometru poate funcționa în mai multe moduri: Normal, PWM corect de fază, CTC (resetare la coincidență), PWM rapid. Puteți citi mai multe despre fiecare mod din carte.

Acest T/C constă dintr-un registru de control, un registru de numărare, un registru de comparație și un registru de stare a modului asincron. Schema bloc a lui T2 este prezentată în Fig. 1

Să vedem cum funcționează acest modul în teorie. Pentru a vă fi mai clar pentru început, nu vom lua în considerare toate gadgeturile inutile ale temporizatorului și vom lua în considerare modul său cel mai comun - NORMAL. Să stabilim singuri că MK este tactat de la un oscilator RC intern cu o frecvență de 1 MHz și temporizatorul este configurat să funcționeze în modul NORMAL.

Impulsurile de ceas ajung la intrarea clk i\o și intră în prescalerul cronometrului. Prescalerul poate fi configurat, în funcție de nevoile dvs., pentru a transmite impulsurile de ceas direct sau pentru a împărți impulsurile primite, trecând doar o anumită parte a acestora. Impulsurile primite pot fi împărțite în /8, /64, /256, /1024. Deoarece T\S-ul nostru poate funcționa în modul asincron, atunci când este pornit în acest mod, numărul de prescalere crește semnificativ, dar nu le vom lua în considerare deocamdată. Din prescaler, impulsurile de ceas intră în unitatea de comandă și din aceasta intră în registrul de numărare. Registrul de numărare, la rândul său, crește pentru fiecare impuls de intrare. Registrul de numărare T2 este de 8 biți, deci poate număra doar până la 255. Când registrul de numărare depășește, acesta este resetat la 0 și începe să conteze din nou în același ciclu. De asemenea, atunci când registrul de contor depășește, este setat indicatorul TOV2 (steagul de întrerupere a depășirii) al registrului TIFR.

Acum, din moment ce am atins cuvinte precum ÎNREGISTRARE, este timpul să facem cunoștință cu ele. Pentru început, vom atinge doar acele registre cu care vom lucra direct, pentru a nu umple creierul cu informații inutile.

TCNT2 este un registru de numărare, am vorbit deja despre funcționarea lui.

TCCR2 - registru de control al temporizatorului.

TIMSK - registru de masca de intrerupere (in Atmega8 acest registru este singurul pentru toate cronometrele).

TIFR - registru steag de întrerupere (în Atmega8 acest registru este singurul pentru toate cronometrele).

Și acum despre fiecare în detaliu:

Registrul de control TCCR2. Puteți vedea conținutul acestui registru în Fig. 2.


Fig.2

Biții 0-2 sunt responsabili pentru sincronizarea temporizatorului. Setarea anumitor combinații ale acestor biți configurează prescaler-ul pentru un anumit cronometru. Dacă toți cei trei biți sunt clari, temporizatorul este oprit.

Biții 3.6 sunt responsabili pentru modul de funcționare al temporizatorului.

Biții 4.5 sunt necesari pentru a configura comportamentul pinului OSn (cu alte cuvinte, aceștia sunt utilizați la configurarea PWM)

Iar ultimul bit al acestui registru este bitul 7. Cu ajutorul lui, putem schimba cu forță starea pinului OSn.

Întreruperea registrului măștii - TIMSK. O vedem în poza nr. 3

Din acest registru ne interesează doar ultimii doi biți, biții 6 și 7. Cu acești biți activăm întreruperi.

Bit 6, dacă este scris unul, activează întreruperea din cauza evenimentului „T\C T2 overflow”

Bit 7, dacă îi scriiNitsya, permite întreruperea evenimentului „Coincidența registrului de numărare cu registrul de comparație”

Întrerupe registrul steagului TIFR. O vedem în poza nr. 4

Fig.4

În acest registru ne interesează și ultimii doi biți: biții 6 și 7.

Bit 6 - flag, setat de evenimentul „T\C T2 overflow”
Bit 7 - steag, este instalat prin evenimentul „Coincidența registrului de numărare cu registrul de comparație”

Acești biți sunt resetati automat la ieșirea operatorului de întrerupere, dar pentru a fi în siguranță, îi puteți reseta singuri resetând acești biți la „0”.

Biții rămași ai registrelor TIMSK și TIFR sunt utilizați de T\C T0 și T1. După cum ați observat deja, biții acestor registre au chiar aceleași nume, cu excepția numărului de la sfârșitul numelui, care indică la ce temporizator se aplică acest bit.

Rămâne de luat în considerare două tabele simple, și anume: un tabel care descrie controlul semnalului ceasului (Fig. 6) și un tabel care descrie modul în care se configurează în general un temporizator (Fig. 5).

Am scris mai sus despre ceea ce este în aceste tabele, dar vi le aduc pentru claritate.

Acum am terminat cu teoria și este timpul să începem partea practică. Voi face o rezervare imediat.

Ceasurile pe care LE OBȚINEȚI DIN STUDIAREA ACESTUI ARTICOL NU SUNT FOARTE EXACTE. ACEST ARTICOL ESTE ORIENTAT PE PRINCIPII GENERALE DE LUCRU CU CRONOMETRICE.

Deschideți Studio 6, creați un proiect și selectați Atmega8.

La început, indicăm frecvența ceasului și conectăm bibliotecile de care avem nevoie pentru lucru

< avr/io.h >#include< avr/interrupt.h >

În prima linie indicăm frecvența. Acest lucru este necesar pentru ca compilatorul să ne înțeleagă mai bine dacă vrem brusc să folosim funcțiile _delay_().

A doua linie de cod include o bibliotecă cu o descriere generală a registrelor MK-ului nostru. De asemenea, atribuie nume care pot fi citite tuturor registrelor.

A treia linie include o bibliotecă pentru lucrul cu vectori de întrerupere.

TIMSK |= (1< < TOIE2); TCCR2 |= (1< < CS22)|(1< < CS20); SREG |= (1< < 7);

Aceasta completează configurarea cronometrului nostru. Să aruncăm o privire mai atentă la ultimele trei linii de cod.

În prima linie am activat întreruperi pentru evenimentul „Timer/counter T2 overflow”

Și în a treia linie am activat la nivel global întreruperile. Ar putea fi scris și așa:

Asm("sei");

Tot ce rămâne este să adăugăm handlerul de întrerupere și codul pentru ceasul nostru în timp real.

ISR (TIMER2_OVF_vect) ( takt++; if (takt>=4)(sek++; takt=0x00;) if (sek>=60) (min++; sek=0x00;) if (min>=60) (hour++; min=0x00) ;) dacă (ora>=24) (ora=0x00); )

Nu este nimic complicat sau nou pentru tine în codul care se află în gestionarea întreruperilor. Să fim atenți doar la variabila takt și la numărul magic „4”. De unde această cifră? Să aruncăm o privire mai atentă la acest punct.

Știm că MK-ul nostru funcționează de la un oscilator intern cu o frecvență de 1 MHz, cronometrul este tactat cu un prescaler de \1024, cronometrul nostru poate număra până la 255. Cunoscând acești parametri, putem calcula câte depășiri va face în 1 secunda

1 000 000 \ 1024 \ 256 = 3,814697.....

Ei bine, deoarece învățăm să lucrăm cu cronometre și nu ne-am stabilit un obiectiv pentru a obține un ceas foarte precis, ne rotunjim rezultatul și obținem „4”. Acestea. în 1 secundă cronometrul va depăși de ~4 ori.

De ce am împărțit la 256 dacă cronometrul numără doar până la 255? Pentru că „0” este și un număr. Cred că totul este clar aici.

Nu uitați că toate variabilele trebuie declarate ca fiind globale.

Iată lista completă a programului pe care l-am primit.

#define F_CPU 1000000UL #include< avr/io.h >#include< avr/interrupt.h >unsigned char takt = 0; unsigned char sek = 0; caracter nesemnat min=0; unsigned char hour=0; ISR (TIMER2_OVF_vect) ( takt++; if (takt>=4)(sek++; takt=0x00;) if (sek>=60) (min++; sek=0x00;) if (min>=60) (hour++; min=0x00) ;) if (ora>=24) (ora=0x00); ) int main(void) ( TIMSK |= (1)< < TOIE2); TCCR2 |= (1< < CS22)|(1< < CS20); SREG |= (1< < 7); while(1) { } }

Dar cum rămâne cu transmiterea de informații către utilizator? Și apoi cui îi place. Puteți utiliza indicatori cu șapte segmente, afișaje grafice sau care generează caractere etc.

În arhivă veți găsi un proiect cu informații de afișare de la nokia5110, un proiect în Proteus 7 și toate fișierele și bibliotecile necesare pentru lucru.

Vă rugăm să rețineți că biblioteca LCD_5110 pentru lucrul cu afișajul a fost scrisă de un participant la forum și furnizată cu permisiunea acestuia.

Ținând cont de tot ce s-a spus, să scriem un program care comută LED-ul. În acest caz, va face acest lucru pe baza evenimentului de depășire a temporizatorului-contor Temporizator 1(vectorul nostru este desemnat: TIM1_OVF). Deoarece contorul este de 16 biți, un eveniment de depășire va avea loc la fiecare 65.536-lea impuls al frecvenței de intrare. Dacă setăm factorul de divizare a frecvenței ceasului la intrare Temporizator 1 egal cu 64, apoi la frecvența generatorului de 4 MHz obținem aproximativ 1 Hz: 4.000.000/64/65.536 = 0,953674 Hz.

Nu este exact ceea ce avem nevoie și, în plus, frecvența nu este exact de un hertz. Pentru ca LED-ul să comute exact o dată la fiecare jumătate de secundă (adică, perioada sa a fost egală cu o secundă), programul va trebui să fie ușor complicat prin încărcarea unei anumite valori în registrele de numărare de fiecare dată, care se calculează simplu: dacă perioada unui impuls de ceas al temporizatorului este egală cu 16 μs (frecvența 4.000.000/64), atunci pentru a obține 500.000 de microsecunde este necesar să se numere 31.250 de astfel de impulsuri.Deoarece contorul este însumând și are loc o întrerupere când numărul 65.536 este atins, trebuie să încărcați mai întâi în el numărul necesar 65.536 – 31250 = 34.286.

Aceasta nu este singura metodă, ci cea mai universală, potrivită pentru toate cronometrele. Apropo, acesta este exact modul în care este implementată contorizarea timpului Arduino(cm. capitolul 21). O altă modalitate este de a folosi o întrerupere atunci când este atins un anumit număr încărcat în registrul de comparație A sau ÎN. Vom vedea cum se face acest lucru mai târziu în acest capitol. Pentru a efectua trecerea de la roșu la verde, va trebui să facem ca înainte, adică, pentru fiecare eveniment de depășire, întoarcem doi biți în registru PortD .

Programul complet va arăta astfel:

Nu voi comenta în detaliu fiecare afirmație, pentru că ar ocupa prea mult spațiu. După executarea tuturor comenzilor inițiale de instalare, MK intră într-o buclă, dar bucla nesfârșită va fi întreruptă de apariția unei întreruperi - aici totul este similar cu sistemul de operare Windows, care reprezintă și o buclă nesfârșită de așteptare a evenimentelor. După cum veți afla în capitolele următoare, Arduino o astfel de buclă este una dintre componentele principale ale oricărui program, tocmai pentru că acolo nu se folosesc aproape niciodată întreruperi. Într-o buclă nesfârșită, puteți pune o comandă familiară aici dormi, fără setări suplimentare pentru modul de consum de energie, va economisi aproximativ 30% energie. Dar nu veți putea economisi și mai mult, deoarece va trebui să opriți nucleul procesorului și temporizatorul nu va mai funcționa.

Note în margini

Apropo, cum poți opri un cronometru care rulează dacă este necesar? Este foarte simplu: dacă resetați registrul TCCR1B (cel în care este setat factorul de divizare a frecvenței ceasului), temporizatorul se va opri. Pentru a rula din nou cu un coeficient de 1/64, trebuie să scrieți din nou valoarea 0b00000011 în acest registru.

Vă rugăm să rețineți că operatorul reti(sfârșitul procesării întreruperii) apare de două ori la procesarea unei întreruperi de cronometru - aceasta este o tehnică complet normală atunci când o subrutină se ramifică. Puteți, desigur, să marcați ultima afirmație reti etichetă, iar apoi textul procedurii ar deveni nediferențiat de prima opțiune, dar aceasta ar fi mai corectă.

Vă rugăm să acordați atenție și formularului de înregistrare temperatura ldi, (1<< TOIE1) . Deoarece bitul desemnat ca TOIE1 în registrul TIMSK este numărul 7, această intrare este echivalentă cu intrarea ldi temp,0b10000000 - o puteți scrie în acest fel, în felul acesta și într-o grămadă de moduri diferite. De exemplu, pentru a porni un temporizator cu un coeficient de 1/64, este necesar, după cum se poate vedea din textul programului, să setați cei doi biți mai puțin semnificativi ai registrului TCCR1B. Aici le instalăm în temp direct, dar deoarece acești biți se numesc CS11 și CS10, îl puteți scrie astfel:

temperatura ldi, (1<< CS11) I (1 << CS10)

sau chiar asa:

temperatura ldi, (3<< CS10)

Această metodă de înregistrare este descrisă în detaliu în descrierea asamblatorului AVR de pe site Atmel .

Detalii

Există un punct subtil în acest program legat de încărcarea registrelor de numărare a temporizatorului. La citirea și scrierea registrelor Timer 1 pe 16 biți, conținutul acestora se poate modifica în intervalul dintre citirea sau scrierea „jumătăților” individuale de 8 biți (la urma urmei, de exemplu, în acest caz, temporizatorul continuă să conteze în timp ce întreruperea este procesată ). Prin urmare, temporizatoarele AVR pe 16 biți au un mecanism special pentru citirea și scrierea unor astfel de registre. La scriere, se încarcă mai întâi valoarea octetului mare, care este plasat automat într-un anumit registru tampon (inaccesibil pentru programator). Apoi, când se primește o comandă pentru a scrie octetul mic, ambele valori sunt combinate, iar scrierea este efectuată simultan în ambele „jumătăți” ale registrului de 16 biți. Dimpotrivă, la citire, mai întâi trebuie citit octetul mic, valoarea octetului mare este fixată automat prin plasarea acestuia în același registru tampon, iar data viitoare când este citit octetul mare, valoarea acestuia este preluată de acolo. Astfel, la citirea unei valori, ambii octeți corespund aceluiași moment în timp.

Când am decis să dezvolt pentru Arduino, am întâmpinat câteva probleme:
  • Selectarea unui model din lista celor disponibile
  • Încerc să înțeleg de ce voi avea nevoie în afară de platforma în sine
  • Instalarea si configurarea mediului de dezvoltare
  • Căutarea și analiza cazurilor de testare
  • „Confruntare” cu ecranul
  • „Confruntare” cu procesorul

Pentru a rezolva aceste probleme, am căutat și citit destul de multe surse diferite, iar în acest articol voi încerca să trec în revistă soluțiile pe care le-am găsit și metodele de a le găsi.

Selectarea platformei

Înainte de a începe să programați pentru o piesă hardware, trebuie mai întâi să o cumpărați. Și apoi am dat de prima problemă: s-a dovedit că erau destul de multe *duini diferite. Există o gamă largă de Arduino și aproximativ aceeași gamă largă de Freeduino și alți analogi. După cum sa dovedit, nu există o mare diferență în ceea ce anume să luați. Adică unele dintre aceste dispozitive sunt puțin mai rapide, altele puțin mai lente, unele sunt mai ieftine, altele sunt mai scumpe, dar principiile de bază de funcționare sunt practic aceleași. Diferențele apar aproape doar atunci când lucrați cu registrele procesorului, iar apoi voi explica în continuare cum să evitați problemele dacă este posibil.

Am ales platforma Arduino Leonardo ca fiind cea mai accesibilă și disponibilă la acel moment în magazinul online de unde am comandat totul. Diferă de restul liniei prin faptul că are un singur controler instalat la bord, care este responsabil atât pentru lucrul cu portul USB, cât și pentru îndeplinirea sarcinilor pe care le atribuim dispozitivului nostru. Acest lucru are avantaje și dezavantaje, dar nu le veți putea întâlni în timpul studiului inițial, așa că să uităm de ele pentru moment. S-a dovedit că se conectează la computer prin micro-USB, și nu USB-B, așa cum par să fie majoritatea celorlalți. Acest lucru m-a surprins oarecum, dar m-a si bucurat, pentru ca eu, ca posesor al unui dispozitiv modern Android, nu ies niciodata din casa fara acest cablu.
Da, aproape orice piesă hardware compatibilă cu *Duino este alimentată în mai multe moduri, inclusiv de la același cablu prin care este programată. De asemenea, aproape toate plăcile au un LED situat direct pe placa controlerului, ceea ce vă permite să începeți să lucrați cu dispozitivul imediat după cumpărare, chiar și fără a avea nimic în mână cu excepția unui cablu compatibil.

Gama de sarcini

Cred că înainte de a începe să scrieți ceva pe o bucată de hardware, este interesant să înțelegeți ce poate fi implementat pe ea. Cu Arduino poți implementa aproape orice. Sisteme de automatizare, idei pentru o „casă inteligentă”, controlere pentru a controla ceva util, „creiere” roboților... Sunt doar o mulțime de opțiuni. Iar o gamă destul de largă de module de expansiune, care sunt extrem de ușor de conectat la placa de control, ajută foarte mult în această direcție. Lista acestora este destul de lungă și promițătoare și sunt căutate pe Internet folosind cuvântul scut. Dintre toate aceste dispozitive, cel mai util mi s-a părut un ecran LCD cu un set de butoane de bază, fără de care, după umila mea părere, a face orice fel de proiecte de formare este complet neinteresant. Ecranul a fost preluat de aici, există și o descriere a acestuia și, de asemenea, din pagina de mai sus există link-uri către site-ul oficial al producătorului.

Formularea problemei

Sunt cumva obișnuit, când pun mâna pe un instrument nou, îmi propun imediat o sarcină moderat complexă și absolut inutilă, o rezolv cu curaj, apoi las codul sursă deoparte și abia apoi preiau ceva cu adevărat complex și util. Acum aveam la îndemână un ecran care semăna foarte mult cu o componentă a unei mine dintr-un film de la Hollywood, cu toate butoanele necesare, cu care trebuia să învăț să lucrez și, de asemenea, îmi doream foarte mult să stăpânesc lucrul cu întreruperi (altfel ce este scopul folosirii unui controler?) Deci mai întâi Ce mi-a venit în minte a fost să scriem un ceas. Și din moment ce dimensiunea ecranului a permis, a inclus și o dată.

Primii pasi

Au sosit în sfârșit toate componentele achiziționate și le-am asamblat. Conectorul ecranului s-a conectat la placa de control ca la una nativă, placa era conectată la computer... Și atunci acest articol m-a ajutat foarte mult. Nu voi repeta același lucru.

Text ascuns

Singurul lucru pe care îl voi spune este că amintindu-mi de tinerețe (sau mai degrabă primul „proiect”, asamblat în timp ce studia electronica radio la Palatul Pionierilor - un multivibrator cu două LED-uri), am găsit 2 LED-uri și am corectat exemplul dat în articol. si a inceput sa le clipeasca :).

„Pașii doi”

Următoarea întrebare logică pentru mine a fost „cum să lucrez cu un ecran LCD?” Pagina oficială a dispozitivului mi-a oferit cu amabilitate link-uri către arhivă, care conținea 2 biblioteci cu exemple minunate. Ea pur și simplu nu a spus ce să facă cu toate acestea. S-a dovedit că conținutul trebuie pur și simplu despachetat în folderul biblioteci al mediului de dezvoltare.

După aceasta, puteți deschide exemplul GuessTheNumber.pde și îl puteți încărca pe placă în același mod ca exemplul cu LED-ul care clipește. Cu toate acestea, personal, după ce a afișat firmware-ul intermitent, ecranul meu a rămas strălucitor uniform și fără o singură literă. După o scurtă căutare a problemei, s-a dovedit că a fost necesar să strângeți pur și simplu singurul potențiometru de pe placa ecranului cu o șurubelniță pentru a seta valoarea normală a contrastului.

Setul de comenzi folosit în exemplu este, în principiu, suficient pentru lucrul simplu cu ecranul, dar dacă vrei ceva mai mult, poți deschide codul sursă al bibliotecilor LCDKeypad și LiquidCrystal și vezi ce mai este acolo.

Arhitectura programului

Sarcina principală a unui ceas este să numere timpul. Și trebuie să facă acest lucru exact. Desigur, fără a utiliza mecanismul de întrerupere, nimeni nu poate garanta că timpul este calculat cu suficientă precizie. Prin urmare, calculul timpului trebuie lăsat cu siguranță în seama lor. Orice altceva poate fi plasat în corpul programului principal. Și avem destul de mult din această „odihnă” - toată munca cu interfața. Ar fi posibil să o faceți altfel, să creați un teanc de evenimente, create inclusiv prin mecanismul de gestionare a întreruperilor și procesate în interiorul aplicației principale, acest lucru ar permite, de exemplu, actualizarea ecranului nu mai des de o dată la jumătate de secundă ( sau la apăsarea unui buton), dar am calculat acest lucru de prisos pentru o sarcină atât de simplă, deoarece pe lângă redesenarea ecranului, procesorul încă nu are ce face. Prin urmare, în tot timpul liber programul recitește starea butoanelor și redesenează ecranul.
Probleme cu această abordare
Schimbări periodice ale ecranului
Mi-am dorit foarte mult să fac două puncte intermitente între ore, minute și secunde, astfel încât, ca într-un ceas clasic, să se aprindă jumătate de secundă, dar podeaua nu. Dar, deoarece ecranul este redesenat tot timpul, a fost necesar să se determine cumva în ce jumătate de secundă să le deseneze și în ce jumătate de secundă nu. Cea mai ușoară modalitate a fost să faci 120 de secunde într-un minut și să atragi două puncte la fiecare secundă impară.
Intermitent
Când ecranul este redesenat constant, pâlpâirea devine vizibilă. Pentru a preveni acest lucru, este logic să nu ștergeți ecranul, ci să desenați text nou peste cel vechi. Dacă textul în sine nu se schimbă, atunci nu va exista nicio pâlpâire pe ecran. Apoi, funcția de retragere a timpului va arăta astfel:
LCD Tastatură LCD; void showTime())(lcd.home(); if (ora<10) lcd.print("0"); // Случай разной длины приходится обрабатывать руками lcd.print(hour,DEC); // английские буквы и цифры ОНО пишет само, русские буквы нужно определять программисту if (second %2) lcd.print(" "); else lcd.print(":"); // вот они где используются, мои 120 секунд в минуте if (minute<10) lcd.print("0"); lcd.print(minute,DEC); if (second %2) lcd.print(" "); else lcd.print(":"); if (second<20) lcd.print("0"); lcd.print(second / 2,DEC); lcd.print(" "); lcd.setCursor(0,1); // переходим в координаты x=0, y=1 то есть в начало второй строки lcd.print(" "); lcd.print(day,DEC); lcd.print(months); // месяцы мне было приятнее нумеровать от 1 до 12, а массив с названиями от 0 до 11 lcd.print(year,DEC); }
Lucrul cu butoanele
Situația este similară cu butoanele. Butonul apăsat este listat ca apăsat în timpul fiecărei rulări a programului, astfel încât o apăsare poate fi procesată de orice număr de ori. Trebuie să forțăm programul să aștepte „push-up-urile” separat. Să începem programul principal astfel:
int lb=0; // variabila stochează valoarea veche a butonului void loop())( // programul principal int cb,rb; // definesc 2 variabile, pentru butonul efectiv apăsat și pentru cel pe care programul îl va considera apăsat cb=rb =lcd.button(); // la început putem presupune că acestea sunt același buton dacă (rb!=KEYPAD_NONE) showval=1; // variabila indică faptul că în timp ce butonul este apăsat, ceea ce este configurat nu ar trebui clipește dacă (cb!=lb) lb= cb; // dacă starea butonului s-a schimbat, reține-l pe cel nou, altfel cb=KEYPAD_NONE; // altfel spunem programului că toate butoanele au fost eliberate de mult timp .

Lucrul cu cronometrul

De fapt, toate lucrările cu un temporizator constă din două componente importante:
  • Inițializarea mecanismului de întrerupere a temporizatorului într-un mod convenabil pentru noi
  • De fapt, întrerupeți manipularea
Inițializarea temporizatorului
Pentru a începe să primim întreruperile de care avem nevoie, trebuie să configuram procesorul astfel încât să înceapă să le genereze. Pentru a face acest lucru, trebuie să setăm registrele de care avem nevoie la valorile necesare. Ce registre și la ce valori ar trebui setate, trebuie să te uiți în... fișa de date pentru procesor :(. Sincer să fiu, am sperat foarte mult că această informație poate fi găsită în documentația pentru Arduino în sine, dar nu, ar fi prea simplu. Mai mult, pentru diferite procesoare numerele de biți de serie pot diferi. Și eu personal am dat peste faptul că o încercare de a seta biții în conformitate cu fișa de date pe un procesor vecin a dus la rezultate dezastruoase.. Dar, cu toate acestea, totul nu este atât de trist pe cât ar părea, deoarece pentru Acești biți au și nume, sunt mai mult sau mai puțin obișnuiți pentru diferite procesoare. Prin urmare, nu vom folosi valori digitale, ci doar nume.

Pentru început, să ne amintim că microcontrolerele AVR au mai multe temporizatoare. Null este folosit pentru a calcula valorile delay() și lucruri de genul acesta, așa că nu îl vom folosi. În consecință, îl folosim pe primul. Prin urmare, mai târziu în desemnarea registrelor va exista adesea un 1; pentru a configura, de exemplu, un al doilea cronometru, trebuie să puneți un 2 acolo.

Toată inițializarea temporizatorului trebuie să aibă loc în rutina setup(). Constă în plasarea valorilor în 4 registre, TCCR1A, TCCR1B, TIMSK1, OCR1A. Primele 2 dintre ele se numesc „Registrele de control Timer-Counter 1 A și B”. Al treilea este „Registrul masca de întrerupere Timer/Counter 1” iar ultimul este „Registrul A de comparare contor 1”.

Comenzile pentru setarea biților sunt utilizate de obicei după cum urmează (desigur, există multe opțiuni, dar acestea sunt cele mai des folosite):
MUSCA |= (1<< POSITION)
adică împingem „1” pe bitul POZIȚIE de la dreapta la stânga și efectuăm un „sau” logic între octeții țintă și primiți. Când controlerul este pornit, valorile tuturor acestor registre conțin 0, așa că pur și simplu uităm de zerouri. Deci, după executarea următorului cod

A=0; A |= (1<< 3)

Valoarea lui A va deveni 8.

Există o mulțime de opțiuni pentru configurarea cronometrului, dar trebuie să realizăm următoarele de la cronometru:

  • Pentru ca temporizatorul să treacă la modul de operare CTC (adică la modul de numărare cu resetare după o potrivire, „Clear Timer on Compare match”), judecând după fișa de date, acest lucru se realizează prin setarea biților WGM12:0 = 2, care în sine înseamnă setarea biților de la secundă la zero la valoarea „2”, adică „010”, comanda TCCR1B |= (1<< WGM12) ;
  • Deoarece 16 MHz (și anume, aceasta este frecvența rezonatorului de cuarț de pe placa mea) este mult, alegeți divizorul maxim posibil, 1024 (adică numai fiecare ciclu de ceas 1024 va ajunge la contorul nostru), CS12: 0 = 5
  • Asigurați-vă că întreruperea are loc atunci când se potrivește registrul A, pentru acest contor TIMSK1 |= (1<< OCIE1A)
  • Specificați când se atinge o anumită valoare pentru a declanșa procesarea întreruperii; această valoare este plasată în același registru A al contorului 1 (întregul său nume este OCR1A), întrerupere coincidentă cu care am activat-o în paragraful anterior.

Cum să calculăm cât timp trebuie să efectuăm calculele? - Este ușor, dacă frecvența de ceas a rezonatorului de cuarț este de 16 MHz, atunci când contorul ajunge la valoarea de 16000, ar trece o secundă dacă coeficientul de împărțire ar fi 1. Deoarece este 1024, atunci obținem 16000000/1024 = 15625 pe secunda. Și totul ar fi bine, dar trebuie să obținem valori la fiecare jumătate de secundă, iar 15625 nu este divizibil cu 2. Aceasta înseamnă că am făcut o greșeală înainte și va trebui să luăm un factor de divizare mai mic. Iar următorul mai mic pe care îl avem este 256, ceea ce dă 62500 de bifături pe secundă sau 31250 într-o jumătate de secundă. Contorul nostru este de 16 biți, deci poate număra până la 65536. Cu alte cuvinte, este suficient atât pentru o jumătate de secundă, cât și pentru o secundă. Intrăm în fișa de date, apoi în codul sursă și îl corectăm la CS12:0=4, iar după aceea OCR1A = 31249; (după cum am înțeles, un ciclu este cheltuit fie la resetare, fie în altă parte, așa că există sfaturi pentru a reseta un altul de la numărul primit).

Manevrarea întreruperii
Sintaxa funcției de serviciu de întrerupere s-a schimbat ușor; acum arată ca exemplul de mai jos. Deci, nu fi surprins dacă vezi undeva o descriere ușor diferită a numelui funcției.

De fapt, acum constă din cuvântul rezervat ISR și o indicație a întreruperii specifice pe care această funcție o procesează între paranteze. Dar după cum puteți vedea, această funcție nu are nimic fantastic în interior. Chiar și RETI obligatoriu, după cum puteți vedea, este inserat automat de compilator pentru noi.

ISR(TIMER1_COMPA_vect) ( digitalWrite(LEDPIN, !digitalRead(LEDPIN)); // LEDPIN=13. Această linie clipește LED-ul de pe placă. Convenabil și cool :) secunda++; if ((a doua %2) && lastshowval) ( // aceasta și următoarele 7 linii sunt necesare numai pentru lastshowval = 0; // astfel încât să puteți obține acest efect amuzant, ca pe un ceas hardware, showval = 0; // când sunteți în modul de setare, să spunem minute, valoarea parametrului configurat clipește ) dacă (!(a doua %2) && !lastshowval)( // numai când butoanele sunt eliberate, iar în timp ce butoanele sunt apăsate, pur și simplu se aprinde. lastshowval = 1; showval = 1; ) if ( secunda>=120) ( // din nou cele 120 de secunde ale mele într-un minut. Ei bine, cine este ușor acum? secunda-=120; minute++; dacă (minut>=60)( minute-=60; ora++; if (ora>=24) ( ora-=24; zi++; if (daylarge(zi,lună,an) // returnează adevărat dacă valoarea zilei // este mai mare decât maximul posibil pentru această lună a acestui an.) ( zi=1; lună++; dacă (lună>12) (lună = 1; an++; ) ) ) ) ) )

Sper că acest articol va fi util cuiva, deoarece există destul de multe instrucțiuni detaliate despre lucrul cu întreruperi ale temporizatorului în rusă.

Am descoperit contorul de iterații al buclei principale și am descoperit că este complet nepotrivit pentru citiri precise ale timpului - viteza obturatorului plutește și este dificil să o numărăm. Ce să fac?

Evident, avem nevoie de un fel de contor extern care să bifeze indiferent de funcționarea procesorului, iar procesorul ar putea oricând să vadă ce bifează în el. Sau pentru ca contorul să genereze evenimente de overflow sau underflow - ridicați steag sau generați o întrerupere. Și procentul îl va mirosi și îl va procesa.

Și există un astfel de contor, nici măcar unul - acestea sunt temporizatoare periferice. Pot fi mai multe dintre ele într-un AVR și chiar cu adâncimi de biți diferite. ATmega16 are trei, ATmega128 are patru. Și în noile MK-uri ale seriei AVR pot fi și mai multe, nu l-am recunoscut.

Mai mult, un cronometru poate fi mai mult decât un numărător stupid; un cronometru este unul dintre cele mai sofisticate (în ceea ce privește funcțiile alternative) dispozitive periferice.

Ce pot face cronometrele?

  • Bifați cu viteze diferite, numărând timpul
  • Numărați impulsurile primite din exterior (mod contor)
  • Bifați din cuarț extern la 32768Hz
  • Generați mai multe tipuri de semnal PWM
  • Emite întreruperi (o jumătate de duzină de evenimente diferite) și setează steaguri

Temporizatoarele diferite au funcționalități diferite și adâncimi de biți diferite. Consultați fișa tehnică pentru mai multe detalii.

Sursa de bifare a temporizatorului
Timer/Counter (în continuare îl voi numi T/C) numără fie impulsurile de ceas de la generatorul de ceas încorporat, fie de la intrarea de numărare.

Uită-te cu atenție la pinout-ul picioarelor ATmega16, vezi picioarele T1 și T0 acolo?

Deci, acestea sunt intrările de numărare Timer 0 și Timer 1. Cu setări adecvate, T/S va număra fie muchia anterioară (marginea de la 0-1), fie muchia posterior (marginea de 1-0) a impulsurile care ajung la aceste intrări.

Principalul lucru este că frecvența impulsurilor de intrare nu depășește frecvența de ceas a procesorului, altfel nu va avea timp să proceseze impulsurile.

În plus, T/C2 este capabil să funcționeze în modul asincron. Adică, T/S nu numără impulsurile de ceas ale procesorului, nu impulsurile primite către picioare, ci impulsurile propriului oscilator, alimentat de un cuarț separat. Pentru a face acest lucru, T/C2 are intrări TOSC1 și TOSC2, pe care puteți atașa un rezonator de cuarț.

De ce este chiar necesar acest lucru? Da, cel puțin organizează un ceas în timp real. Am agățat un ceas de cuarț pe ele la 32768 Hz și am numărat timpul - vor avea loc 128 de depășiri pe secundă (deoarece T/C2 este de opt biți). Deci un debordare este 1/128 de secundă. În plus, cronometrul nu se oprește în timp ce întreruperea de depășire este procesată; de asemenea, continuă să conteze. Deci ceasul este o briză!

Predivider
Dacă temporizatorul numără impulsurile de la un generator de ceas sau de la cel intern, atunci acestea pot fi încă trecute printr-un prescaler.

Adică, chiar înainte de a intra în registrul de numărare, frecvența pulsului va fi împărțită. Puteți împărți la 8, 32, 64, 128, 256, 1024. Deci, dacă puneți un ceas cuarț pe T/C2 și îl treceți printr-un prescaler la 128, atunci cronometrul va bifa cu o viteză de un tic pe secundă.

Confortabil! De asemenea, este convenabil să folosești un prescaler atunci când trebuie doar să obții un interval mare, iar singura sursă de bifături este generatorul de ceas al procesorului la 8 MHz, te vei sătura să numeri acești megaherți, dar dacă îl treci prin prescaler , la 1024, atunci totul este mult mai fericit.

Dar există o particularitate aici, faptul este că dacă lansăm T/S cu un fel de prescaler brutal, de exemplu la 1024, atunci prima bifă în registrul de numărare nu va veni neapărat după 1024 de impulsuri.

Depinde de starea în care se afla prescalerul, și dacă până la pornire ar fi numărat deja până la aproape 1024? Aceasta înseamnă că va apărea imediat o bifă. Prescalerul funcționează tot timpul, indiferent dacă temporizatorul este pornit sau nu.

Prin urmare, prescalerele pot și ar trebui să fie resetate. De asemenea, trebuie să țineți cont de faptul că prescaler-ul este același pentru toate contoarele, așa că atunci când îl resetați, trebuie să țineți cont de faptul că un alt cronometru va pierde timp până la următoarea bifă și poate merge prost în exact. Pe aici.

De exemplu, primul cronometru funcționează pe pinul 1:64, iar al doilea pe pinul 1:1024 al prescalerului. Cel de-al doilea aproape a ajuns la 1024 în prescaler și acum ar trebui să fie o bifare a cronometrului, dar apoi ai mers și ai resetat prescaler-ul pentru a porni primul timer exact de la zero. Ce se va intampla? Așa este, al doilea divizor se va reseta imediat la 0 (prescaler-ul este același, are un singur registru) iar al doilea timer va trebui să aștepte încă 1024 de cicluri de ceas pentru a obține impulsul dorit!

Și dacă resetați prescaler-ul în buclă, în beneficiul primului temporizator, mai des decât o dată la fiecare 1024 de cicluri de ceas, atunci al doilea cronometru nu va bifa niciodată și vă veți lovi cu capul de masă, încercând să înțelegeți de ce. Al doilea cronometru nu funcționează, deși trebuie.

Pentru a reseta prescalerele, pur și simplu scrieți bitul PSR10 în registrul SFIOR. Bitul PSR10 va fi resetat automat la următorul ciclu de ceas.

Registrul contului
Întregul rezultat al chinului descris mai sus este acumulat în registrul de numărare TCNTx, unde x este numărul cronometrului. poate fi de opt biți, fie de șaisprezece biți, caz în care constă din două registre TCNTxH și TCNTxL - octeții înalți și, respectiv, inferiori.

Și există o captură aici, dacă trebuie să puneți un număr într-un registru de opt biți, atunci nu există probleme OUT TCNT0, Rx și fără cuie, atunci cu registre de doi octeți va trebui să jucați.

Și ideea este că temporizatorul contează independent de procesor, așa că putem pune un octet mai întâi, va începe să conteze, apoi al doilea, iar recalcularea va începe ținând cont de al doilea octet.

Simți că mă înțeleg? Aici! Cronometrul este un dispozitiv precis, astfel încât registrele sale de numărare trebuie încărcate în același timp! Dar cum? Iar inginerii de la Atmel au rezolvat problema simplu:
Registrul înalt (TCNTxH) este scris mai întâi în registrul TEMP. Acest registru este pur oficial și nu ne este în niciun caz accesibil.

Cu ce ​​ajungem: scriem octetul mare în registrul TEMP (pentru noi acesta este un TCNTxH al naibii), apoi scriem octetul mic. În acest moment, valoarea pe care am înregistrat-o anterior este introdusă în TCNTxH real. Adică doi octeți, mare și mic, sunt scrisi simultan! Nu poți schimba ordinea! Singura cale

Arata cam asa:

CLI ; Interzicem întreruperile, fără greș! OUT TCNT1H,R16; Cel mai semnificativ octet a fost scris mai întâi în TEMP OUT TCNT1L,R17; Și acum m-am înscris atât la clasele pentru seniori, cât și pentru juniori! SEI ; Activați întreruperile

De ce dezactivați întreruperile? Da, astfel încât, după ce ați scris primul octet, programul să nu se grăbească accidental fără întrerupere, iar apoi cineva ne violează cronometrul. Atunci în registrele sale nu va fi ceea ce am trimis aici (sau în întrerupere), ci ce naiba. Așa că încercați să prindeți un astfel de bug mai târziu! Dar poate ieși în cel mai inoportun moment, dar nu o veți prinde, pentru că o întrerupere este aproape o variabilă aleatorie. Deci, astfel de momente trebuie abordate imediat.

Totul se citește la fel, doar în ordine inversă. Mai întâi, octetul scăzut (în timp ce cel înalt este împins în TEMP), apoi cel înalt. Acest lucru garantează că numărăm exact octetul care se afla în prezent în registrul de numărare și nu cel care rula în timp ce îl alegem octetul octet din registrul de numărare.

Registre de control
Nu voi descrie toate funcțiile temporizatoarelor, altfel se va dovedi a fi un tratat copleșitor. Este mai bine să vorbim despre cel principal - cel de numărare, iar tot felul de PWM și alte generatoare vor fi într-un alt articol. Așa că aveți răbdare sau mestecați fișa de date, este și util.

Deci registrul principal este TCCRx
Pentru T/C0 și T/C2 acestea sunt TCCR0 și, respectiv, TCCR2, iar pentru T/C1 aceasta este TCCR1B

Deocamdată, ne interesează doar primii trei biți ai acestui registru:
CSx2.. CSx0, înlocuiți x cu numărul cronometrului.
Ei sunt responsabili pentru setarea prescalerului și a sursei ceasului.

Temporizatoarele diferite sunt ușor diferite, așa că voi descrie biții CS02..CS00 numai pentru temporizatorul 0

  • 000 - temporizator oprit
  • 001 — prescaler-ul este egal cu 1, adică dezactivat. temporizatorul numără impulsurile ceasului
  • 010 - prescaler este 8, frecvența ceasului este împărțită la 8
  • 011 - prescaler este 64, frecvența ceasului este împărțită la 64
  • 100 - prescaler este 256, frecvența ceasului este împărțită la 256
  • 101 - prescaler este 1024, frecvența ceasului este împărțită la 1024
  • 110 - impulsurile de ceas provin de la pinul T0 la trecerea de la 1 la 0
  • 111 - impulsurile de ceas provin de la pinul T0 la trecerea de la 0 la 1

întreruperi
Fiecare eveniment hardware are o întrerupere, iar cronometrul nu face excepție. De îndată ce are loc un debordare sau un alt eveniment curios, apare imediat o întrerupere.

Registrele TIMSK și TIFR sunt responsabile pentru întreruperile de la temporizatoare. Și AVR-urile mai cool, precum ATMega128, au și ETIFR și ETIMSK - un fel de continuare, deoarece vor exista mai multe cronometre acolo.

TIMSK este un registru de mască. Adică, biții conținuți în el activează local întreruperi. Dacă bitul este setat, întreruperea specifică este activată. Dacă bitul este zero, atunci această întrerupere este acoperită cu un bazin. În mod implicit, toți biții sunt zero.

Momentan ne interesează doar întreruperile de overflow. Biții sunt responsabili pentru ele

  • TOIE0 - permisiunea de a întrerupe la depășirea temporizatorului 0
  • TOIE1 - permisiunea de a întrerupe la depășirea temporizatorului 1
  • TOIE2 - permisiunea de a întrerupe la depășirea temporizatorului 2

Vom vorbi despre alte funcții și întreruperi ale temporizatorului mai târziu, când ne uităm la PWM.

Registrul TIFR este direct un registru steag. Când se declanșează o întrerupere, apare un steag care indică faptul că avem o întrerupere. Acest flag este resetat de hardware atunci când programul părăsește vectorul. Dacă întreruperile sunt dezactivate, atunci indicatorul va rămâne acolo până când întreruperile sunt activate și programul trece la întrerupere.

Pentru a preveni acest lucru, indicatorul poate fi resetat manual. Pentru a face acest lucru, trebuie să scrieți 1 în registrul TIFR!

Acum hai să dracului
Ei bine, să reproiectăm programul pentru a funcționa cu un cronometru. Să introducem un temporizator de program. Orga de butoi va rămâne așa, lasă-l să bifeze. Și vom adăuga o a doua variabilă, tot patru octeți:

ORG $010 RETI ; (TIMER1 OVF) Timer/Counter1 Overflow .ORG $012 RJMP Timer0_OV ; (TIMER0 OVF) Timer/Counter0 Overflow .ORG $014 RETI ; (SPI,STC) Transfer serial finalizat

Să adăugăm un handler de întrerupere pentru depășirea temporizatorului 0 la secțiunea de întrerupere. Deoarece macrocomanda noastră de bifare funcționează în mod activ cu registre și corupe steaguri, trebuie să salvăm mai întâi toată această chestiune în stivă:

Apropo, să creăm o altă macrocomandă care împinge registrul de steag SREG în stivă și o a doua care îl preia de acolo.

1 2 3 4 5 6 7 8 9 10 11 12 .MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

Ca efect secundar, reține și R16, rețineți că :)

1 2 3 4 5 6 7 8 9 10 11 12 13 Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Acum inițializați cronometrul. Adăugați-l la secțiunea Internal Hardware Init.

; Internal Hardware Init ======================================= SETB DDRD,4,R16; DDRD.4 = 1 SETB DDRD,5,R16 ; DDRD.5 = 1 SETB DDRD,7,R16 ; DDRD.7 = 1 SETB PORTD,6,R16 ; Ieșire PD6 la intrare pull-up CLRB DDRD,6,R16 ; Pentru a citi butonul SETB TIMSK,TOIE0,R16 ; Activați întreruperea temporizatorului OUTI TCCR0,1<

Tot ce rămâne este să rescriem blocul nostru de comparație și să recalculăm numărul. Acum totul este simplu, o bifă o bară. Fără probleme cu diferite lungimi de cod. Pentru o secundă la 8 MHz, trebuie făcute 8 milioane de bifături. În hexuri, acesta este 7A 12 00, ținând cont de faptul că octetul mic este TCNT0, apoi 7A 12 este lăsat pentru contorul nostru și, de asemenea, cei mai mari doi octeți 00 00, nu trebuie să fie verificați. Nu este nevoie de masca; oricum vom reseta cronometrul mai târziu.

Există o singură problemă - octetul mic, cel din temporizator. Bifează fiecare bifă și va fi aproape imposibil să verifici conformitatea. Deoarece cea mai mică discrepanță și condiția de comparație vor apărea în NoMatch, dar pentru a o ghici astfel încât verificarea valorii sale să coincidă cu acest pas anume... Este mai ușor să scoți un ac dintr-un car de fân la prima încercare la întâmplare.

Deci, precizia în acest caz este limitată - trebuie să aveți timp să verificați valoarea înainte de a părăsi intervalul. În acest caz, intervalul va fi, pentru simplitate, 255 - valoarea octetului scăzut, cea din timer.

Apoi, al doilea nostru este furnizat cu o precizie de 8.000.000 plus sau minus 256 de cicluri. Eroarea nu este mare, doar 0,003%.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ; Principal =================================================== ================ ======== Principal: SBIS PIND,6 ; Dacă butonul este apăsat - tranziție RJMP BT_Push SETB PORTD,5 ; Să aprindem LED2 CLRB PORTD,4 ; Opriți LED1 Următorul: LDS R16,TCNT ; Încărcați numere în registre LDS R17,TCNT+1 CPI R16.0x12; Să comparăm octet cu octet. Primul octet BRCS NoMatch; Dacă este mai puțin, înseamnă că nu a lovit. CPI R17.0x7A; Al doilea octet BRCS NoMatch ; Dacă este mai puțin, înseamnă că nu a lovit. ; Dacă se potrivește, atunci facem acțiunea Potrivire: INVB PORTD,7,R16,R17 ; LED3 inversat; Acum trebuie să resetam contorul, altfel în timpul aceleiași iterații a buclei principale; vom ajunge aici de mai multe ori - cronometrul nu va avea timp să atingă 255 de valori; astfel încât numărul din primii doi octeți ai contorului se modifică și condiția este declanșată. ; Desigur, puteți ocoli acest lucru cu un semnal suplimentar, dar este mai ușor să resetați contorul :) CLR R16 ; Avem nevoie de zero CLI; Acces la o variabilă multiocteți; simultan de la întrerupere și fundal; Este necesar accesul atomic. Dezactivare întreruperi OUTU TCNT0,R16 ; Zero la registrul contorului temporizatorului STS TCNT, R16 ; Zero în primul octet al contorului din RAM STS TCNT+1,R16 ; Zero în al doilea octet al contorului din RAM STS TCNT+2,R16 ; Zero în al treilea octet al contorului din RAM STS TCNT+3,R16; Zero în primul octet al contorului din RAM SEI; Să activăm din nou întreruperile. ; Dacă nu se potrivește, nu o facem :) NoMatch: NOP INCM CCNT ; Contorul de cicluri bifează; Chiar dacă nu este folosit. JMP Main BT_Push: SETB PORTD,4 ; Să aprindem LED1 CLRB PORTD,5; Opriți LED2 RJMP Next; Sfârșit principal ================================================== ================ =====

; Principal =================================================== ================ ======== Principal: SBIS PIND,6 ; Dacă butonul este apăsat - tranziție RJMP BT_Push SETB PORTD,5 ; Să aprindem LED2 CLRB PORTD,4 ; Opriți LED1 Următorul: LDS R16,TCNT ; Încărcați numere în registre LDS R17,TCNT+1 CPI R16.0x12; Să comparăm octet cu octet. Primul octet BRCS NoMatch; Dacă este mai puțin, înseamnă că nu a lovit. CPI R17.0x7A; Al doilea octet BRCS NoMatch ; Dacă este mai puțin, înseamnă că nu a lovit. ; Dacă se potrivește, atunci facem acțiunea Potrivire: INVB PORTD,7,R16,R17 ; LED3 inversat; Acum trebuie să resetam contorul, altfel în timpul aceleiași iterații a buclei principale; vom ajunge aici de mai multe ori - cronometrul nu va avea timp să atingă 255 de valori; astfel încât numărul din primii doi octeți ai contorului se modifică și condiția este declanșată. ; Desigur, puteți ocoli acest lucru cu un semnal suplimentar, dar este mai ușor să resetați contorul :) CLR R16 ; Avem nevoie de zero CLI; Acces la o variabilă multiocteți; simultan de la întrerupere și fundal; Este necesar accesul atomic. Dezactivare întreruperi OUTU TCNT0,R16 ; Zero la registrul contorului temporizatorului STS TCNT, R16 ; Zero în primul octet al contorului din RAM STS TCNT+1,R16 ; Zero în al doilea octet al contorului din RAM STS TCNT+2,R16 ; Zero în al treilea octet al contorului din RAM STS TCNT+3,R16; Zero în primul octet al contorului din RAM SEI; Să activăm din nou întreruperile. ; Dacă nu se potrivește, nu o facem :) NoMatch: NOP INCM CCNT ; Contorul de cicluri bifează; Chiar dacă nu este folosit. JMP Main BT_Push: SETB PORTD,4 ; Să aprindem LED1 CLRB PORTD,5; Opriți LED2 RJMP Next; Sfârșit principal ================================================== ================ =====

Așa arată în acțiune

Și dacă trebuie să clipim o a doua diodă cu o perioadă diferită, atunci putem pune în siguranță o altă variabilă în program, iar în handlerul de întrerupere a temporizatorului putem incrementa două variabile simultan. Verificându-le unul câte unul în bucla principală a programului.

Puteți optimiza puțin mai mult procesul de verificare. Fă-o mai repede.

Trebuie doar să faceți contul nu în sus, ci în jos. Acestea. Încărcăm un număr într-o variabilă și începem să-l decrementăm în întrerupere. Și acolo, în handler, îl verificăm pentru zero. Dacă este zero, atunci setați un steag în memorie. Și programul nostru de fundal prinde acest steag și lansează acțiunea, resetând simultan viteza obturatorului.

Dar dacă trebuie să fii mai precis? Ei bine, există o singură opțiune - să utilizați procesarea evenimentelor direct în gestionarea întreruperilor și să ajustați valoarea în TCNT:TCNT0 de fiecare dată, astfel încât întreruperea să aibă loc exact la momentul potrivit.

Întreruperile permit microcontrolerelor să răspundă la evenimente fără a fi nevoie să verifice în mod constant orice condiții pentru a determina când au avut loc schimbări importante. Pe lângă capacitatea de a conecta surse de întrerupere la unii pini, puteți utiliza și întreruperi generate de un cronometru.

Întreruperi hardware

Pentru a demonstra utilizarea întreruperilor, să revenim la intrările digitale. Adesea, pentru a determina momentul unui eveniment de intrare (de exemplu, apăsarea unui buton), este utilizat următorul cod:

dacă (digitalRead(inputPin) == LOW)

// Efectuați unele acțiuni

Acest cod verifică în mod constant nivelul de tensiune la inputPin, iar când digitalRead revine LOW, face ceva, indicat de comentariul // Fă ceva. Aceasta este o soluție complet viabilă, dar ce se întâmplă dacă există o mulțime de alte operațiuni care trebuie făcute în cadrul funcției de buclă? Toate aceste operațiuni necesită timp, așa că este posibil să ratezi o apăsare scurtă de buton în timp ce procesorul este ocupat cu altceva. De fapt, este aproape imposibil să ratezi faptul că butonul a fost apăsat, deoarece după standardele microcontrolerului acesta rămâne apăsat foarte mult timp.

Dar ce zici de impulsurile scurte de la senzor, care pot dura milioane de secundă? Pentru a primi astfel de evenimente, ar trebui să utilizați întreruperi, definind funcții care vor fi apelate pe aceste evenimente, indiferent de ceea ce face microcontrolerul. Se numesc astfel de întreruperi întreruperi hardware(întreruperi hardware).

În Arduino Uno, doar doi pini sunt asociați cu întreruperi hardware, motiv pentru care sunt utilizați foarte puțin. Leonardo are cinci dintre acești pini, plăcile mai mari precum Mega2560 au mult mai multe, iar pinii lui Due sunt toți capabili de întrerupere.

Următoarele explică cum funcționează întreruperile hardware. Pentru a încerca exemplul prezentat, veți avea nevoie de o placă suplimentară, un buton, un rezistor de 1k ohm și câteva fire jumper.

În fig. Figura 3.1 prezintă circuitul asamblat. Prin rezistență, tensiunea ÎNALTĂ este aplicată pinului D2 până când butonul este apăsat, moment în care pinul D2 va fi împământat și nivelul de tensiune de pe acesta va scădea la LOW.

Încărcați următoarea schiță pe placa dvs. Arduino:

// schiță 03_01_întreruperi

int ledPin = 13;

pinMode(ledPin, OUTPUT);

void stuffHapenned()

digitalWrite(ledPin, HIGH);

Orez. 3.1. Schema circuitului pentru testarea întreruperii

Pe lângă setarea pinului LED-ului pentru a funcționa ca ieșire digitală, funcția de configurare folosește o altă linie pentru a asocia funcția cu o întrerupere. Acum această funcție va fi apelată automat ca răspuns la fiecare întrerupere. Să aruncăm o privire mai atentă la această linie, deoarece argumentele funcției numite aici par puțin neobișnuite:

attachInterrupt(0, stuffHapenned, FALLING);

Primul argument - 0 - este numărul de întrerupere. Ar fi mai clar dacă numărul de întrerupere ar coincide cu numărul pin, dar nu este cazul. În Arduino Uno, întreruperea 0 este asociată cu pinul D2, iar întreruperea 1 este asociată cu pinul D3. Situația este făcută și mai confuză de faptul că în alte modele Arduino aceste întreruperi sunt asociate cu diferiți pini, iar în plus, în Arduino Due trebuie să specificați numărul de pin. Pe placa Arduino Due, toți pinii sunt asociați cu întreruperi.

Voi reveni la această problemă mai târziu, dar deocamdată să trecem la al doilea argument. Acest argument, stuffHappened, reprezintă numele funcției care ar trebui apelată pentru a gestiona întrerupere. Această funcție este definită în continuare în schiță. Astfel de funcții sunt numite rutine de întrerupere(Rutina de întrerupere a serviciului, ISR), există cerințe speciale. Nu pot avea parametri și nu trebuie să returneze nimic. Acest lucru are sens: chiar dacă sunt numite în locuri diferite în schiță, nu există o singură linie de cod care să apeleze direct ISR, așa că nu există nicio modalitate de a le transmite parametri sau de a obține o valoare returnată.

Ultimul parametru al funcției, attachInterrupt, este o constantă, în acest caz FALLING. Înseamnă că rutina de întrerupere va fi apelată numai atunci când tensiunea de pe pinul D2 se schimbă de la nivelul HIGH la nivelul LOW (adică la scădere), ceea ce are loc la apăsarea butonului.

Observați că nu există cod în funcția de buclă. În general, această funcție poate conține cod care se execută până când apare o întrerupere. Rutina de întrerupere în sine aprinde pur și simplu LED-ul L.

Când experimentați, după resetarea Arduino, LED-ul L ar trebui să se stingă. Și după apăsarea butonului, acesta se va aprinde imediat și rămâne aprins până la următoarea resetare.

După experimentare, încercați să schimbați ultimul argument din apelul attachInterrupt la RISING și încărcați schița modificată. După repornirea Arduino, LED-ul ar trebui să rămână stins, deoarece tensiunea de pe pin, deși la nivelul HIGH, a rămas la acest nivel de la repornire. Pana in acest moment, tensiunea la contact nu a scazut la nivelul LOW si apoi a urcat (in crestere) la nivelul HIGH.

Odată ce apăsați și mențineți apăsat butonul, LED-ul ar trebui să rămână stins până când îl eliberați. Eliberarea butonului va provoca o întrerupere asociată cu pinul D2, deoarece, în timp ce butonul a fost ținut apăsat, nivelul de tensiune de pe pin a fost JOS, iar atunci când este eliberat, a crescut la HIGH.

Dacă în timpul testării se dovedește că ceea ce ți se întâmplă nu corespunde cu descrierea dată mai devreme, acest lucru se datorează cel mai probabil efectului de respingere a contactelor din buton. Acest efect este cauzat de faptul că butonul nu asigură o tranziție clară între stările „pornit” și „oprit”; în schimb, în ​​momentul apăsării, are loc o tranziție repetată între aceste stări până când starea „pornit” este fix. Încercați să apăsați butonul mai puternic, acest lucru ar trebui să vă ajute să obțineți o tranziție clară între stări, fără efectul de respingere.

O altă modalitate de a încerca această schiță este să apăsați și să țineți apăsat butonul în timp ce apăsați și eliberați butonul Reset de pe placa Arduino. Apoi, când începe schița, eliberați butonul de pe placa și LED-ul L se va aprinde.

Pini activați pentru întrerupere

Să revenim acum la problema denumirii întreruperilor. În tabel 3.1 enumeră cele mai comune modele de plăci Arduino și arată corespondența numerelor de întreruperi și contacte din ele.

Tabelul 3.1. Pini activați pentru întrerupere în diferite modele Arduino

Model Numărul de întrerupere Note
0 1 2 3 4 5
O.N.U D2 D3 - - - -
Leonardo D3 D2 D0 D1 D7 - Într-adevăr, în comparație cu Uno, primele două întreruperi sunt alocate unor pini diferiți
Mega2560 D2 D3 D21 D20 D19 D18
Datorită - - - - - - În loc de numere de întrerupere, funcției attachInterrupt ar trebui să i se transmită numere PIN

Schimbarea știfturilor primelor două întreruperi în Uno și Leonardo creează o capcană în care este ușor de căzut. În modelul Due, în loc de numere de întrerupere, funcției attachInterrupt ar trebui să i se transmită numere de pin, ceea ce pare mai logic.

Moduri de întrerupere

Modurile de întrerupere RISING (margine de creștere) și FALLING (margine de cădere) utilizate în exemplul anterior sunt cele mai des folosite în practică. Cu toate acestea, există câteva alte moduri. Aceste moduri sunt listate și descrise în tabel. 3.2.

Tabelul 3.2. Moduri de întrerupere

Modul Acțiune Descriere
SCĂZUT Întreruperea este generată la nivelul de tensiune LOW În acest mod, rutina de întrerupere va fi apelată continuu atâta timp cât pinul rămâne scăzut.
ÎN CREȘTERE O întrerupere este generată atunci când tensiunea scade pozitiv, de la LOW la HIGH. -
CĂDERE Se generează o întrerupere la o cădere de tensiune negativă, de la HIGH la LOW. -
ÎNALT Întreruperea este generată la nivelul de tensiune HIGH Acest mod este acceptat doar în modelul Arduino Due și, ca și modul LOW, este rar folosit în practică.

Activați impedanța internă

Circuitul din exemplul anterior a folosit un rezistor extern de tragere. Cu toate acestea, în practică, semnalele care cauzează întreruperi sunt adesea conduse de la ieșirile digitale ale senzorilor, caz în care nu este nevoie să folosiți o rezistență de tragere.

Dar dacă rolul senzorului este jucat de un buton conectat exact în același mod ca placa de breadboard din Fig. 3.1, este posibil să scăpați de rezistență prin pornirea rezistenței interne „pull-up” cu o valoare nominală de aproximativ 40 kOhm. Pentru a face acest lucru, trebuie să setați în mod explicit modul INPUT_PULLUP pentru pinul legat de întrerupere, așa cum se arată în rândul îngroșat:

pinMode(ledPin, OUTPUT);

pinMode(2, INPUT_PULLUP);

attachInterrupt(0, stuffHapenned, FALLING);

Întrerupe rutine

Uneori poate părea că abilitatea de a gestiona întreruperile în timp ce funcția de buclă rulează oferă o modalitate ușoară de a gestiona evenimente precum apăsările de taste. Dar, în realitate, există restricții foarte stricte cu privire la ceea ce rutinele de întrerupere pot și nu pot face.

Rutinele de întrerupere ar trebui să fie cât mai scurte și rapide posibil. Dacă apare o altă întrerupere în timp ce rutina de întrerupere rulează, rutina nu va fi întreruptă și semnalul rezultat va fi pur și simplu ignorat. Aceasta înseamnă, de exemplu, că dacă sunt folosite întreruperi pentru a măsura frecvența, este posibil să obțineți o valoare incorectă.

În plus, în timp ce rutina de întrerupere rulează, codul din funcția buclă este inactiv.

Întreruperile sunt dezactivate automat în timpul procesării. Această soluție previne confuzia între subrutinele care se întrerup reciproc, dar are efecte secundare nedorite. Funcția de întârziere folosește temporizatoare și întreruperi, deci nu va funcționa în rutinele de întrerupere. Același lucru este valabil și pentru funcția millis. Încercarea de a utiliza milisecunde pentru a obține numărul de milisecunde care au trecut de la ultima resetare a plăcii pentru a efectua o întârziere în acest mod nu va reuși, deoarece va returna aceeași valoare până la finalizarea rutinei de întrerupere. Cu toate acestea, puteți utiliza funcția delayMicroseconds, care nu utilizează întreruperi.

Întreruperile sunt, de asemenea, folosite în comunicațiile seriale, așa că nu încercați să utilizați funcțiile Serial.print sau citirea portului serial. Cu toate acestea, puteți încerca și uneori chiar vor funcționa, dar nu vă așteptați la o fiabilitate ridicată de la o astfel de conexiune.

Variabile operaționale

Deoarece rutina de întrerupere nu poate avea parametri și nu poate returna nimic, este nevoie de o modalitate de a transmite informații între ea și restul programului. Acest lucru se face de obicei folosind variabile globale, așa cum se arată în exemplul următor:

// schiță 03_02_interrupt_flash

int ledPin = 13;

volatil boolean flashFast = fals;

pinMode(ledPin, OUTPUT);

attachInterrupt(0, stuffHapenned, FALLING);

int perioada = 1000;

if (flashFast) perioada = 100;

digitalWrite(ledPin, HIGH);

digitalWrite(ledPin, LOW);

void stuffHapenned()

flashFast = ! flashFast;

În această schiță, funcția buclă folosește variabila globală flashFast pentru a determina perioada de întârziere. Rutina de procesare schimbă valoarea acestei variabile între adevărat și fals.

Rețineți că declarația variabilei flashFast include cuvântul volatil. Puteți dezvolta cu succes o schiță fără specificatorul volatil, dar este absolut necesar deoarece fără specificatorul volatil, compilatorul C poate genera cod mașină care memorează în cache valoarea variabilei într-un registru pentru a îmbunătăți performanța. Dacă, ca în acest caz, codul de cache este întrerupt, este posibil să nu observe modificarea valorii variabilei.

În concluzie despre rutinele de întrerupere

Când scrieți rutine de întrerupere, amintiți-vă următoarele reguli.

Subrutinele trebuie să acționeze rapid.

Pentru a transfera date între rutina de întrerupere și restul programului, trebuie utilizate variabilele declarate cu specificatorul volatil.

Nu utilizați întârziere, dar puteți utiliza delayMicrosecunde.

Nu vă așteptați la comunicații extrem de fiabile prin porturi seriale.

Nu vă așteptați ca valoarea returnată de funcția millis să se schimbe.

Activați și dezactivați întreruperile

În mod implicit, întreruperile sunt activate în schițe și, așa cum am menționat mai devreme, sunt dezactivate automat în timp ce rutina de întrerupere rulează. Cu toate acestea, este posibil să dezactivați și să activați în mod explicit întreruperile în codul programului apelând funcțiile noInterrupts și interrupts. Aceste funcții nu au parametri, iar prima dintre ele dezactivează întreruperile, iar a doua le activează.

Poate fi necesar un control explicit pentru a se asigura că o bucată de cod, cum ar fi una care emite o secvență de date sau generează o secvență de impulsuri și este cronometrată precis folosind funcția delayMicroseconds, nu poate fi întreruptă.

Cronometrul se întrerupe

Apelul rutinelor de gestionare a întreruperilor poate fi organizat nu numai de evenimente externe, ci și de evenimente interne de schimbare a orei. Această caracteristică este utilă în special atunci când trebuie să efectuați anumite operații la anumite intervale.

Biblioteca TimerOne facilitează configurarea întreruperilor temporizatorului. Poate fi găsit și descărcat de la http://playground.arduino.cc/Code/Timer1.

Următorul exemplu arată cum să utilizați TimerOne pentru a genera un tren de impulsuri cu undă pătrată de 1 kHz. Dacă aveți un osciloscop sau un multimetru cu capacitatea de a măsura frecvența, conectați-l la pinul 12 pentru a vedea semnalul (Fig. 3.2).

Orez. 3.2. Secvență de impulsuri dreptunghiulare generată folosind un temporizator

// schiță_03_03_1kHz

#include

int outputPin = 12;

volatile int output = LOW;

pinMode(12, OUTPUT);

Timer1.initialize(500);

Timer1.attachInterrupt(toggleOutput);

void toggleOutput()

digitalWrite(outputPin, output);

ieșire = ! ieșire;

Același lucru ar putea fi implementat folosind întârziere, dar utilizarea întreruperilor temporizatorului vă permite să organizați execuția oricăror alte operațiuni în interiorul buclei. În plus, utilizarea funcției de întârziere nu va obține o precizie ridicată, deoarece timpul necesar pentru modificarea nivelului de tensiune pe contact nu va fi luat în considerare în valoarea întârzierii.

NOTĂ

Toate restricțiile pentru rutinele de întrerupere externe care au fost discutate mai devreme se aplică și rutinelor de întrerupere a temporizatorului.

Folosind metoda prezentată, puteți seta orice interval între întreruperi în intervalul de la 1 la 8.388.480 μs, adică până la aproximativ 8,4 s. Valoarea intervalului este transmisă funcției de inițializare în microsecunde.

Biblioteca TimerOne face posibilă, de asemenea, utilizarea unui temporizator pentru a genera semnale de modulare în lățime a impulsurilor (PWM) pe pinii 9 și 10 ai plăcii. Acest lucru poate părea exagerat, deoarece analogWrite face același lucru, dar utilizarea întreruperilor permite un control mai precis al semnalului PWM. În special, folosind această abordare, este posibil să se organizeze măsurarea lungimii unui impuls pozitiv în intervalul 0...1023 în loc de 0...255 în funcția analogWrite. În plus, atunci când utilizați analogWrite, rata de repetiție a pulsului în semnalul PWM este de 500 Hz, iar folosind TimerOne puteți crește sau micșora această frecvență.

Pentru a genera un semnal PWM folosind biblioteca TimerOne, utilizați funcția Timer1.pwm, așa cum se arată în exemplul următor:

// schiță_03_04_pwm

#include

pinMode(9, OUTPUT);

pinMode(10, OUTPUT);

Timer1.initialize(1000);

Timer1.pwm(9, 512);

Timer1.pwm(10, 255);

Aici, perioada de repetare a impulsului este aleasă să fie de 1000 μs, adică frecvența semnalului PWM este de 1 kHz. În fig. 3.3 arată forma de undă pe pinii 10 ( sus) și 9 ( în partea de jos).

Orez. 3.3. Semnal cu lățimea impulsului de 1 kHz generat folosind TimerOne

Doar pentru distracție, să vedem în ce măsură se poate crește frecvența semnalului PWM. Dacă durata perioadei este redusă la 10, frecvența semnalului PWM ar trebui să crească la 100 kHz. Formele de undă obținute cu acești parametri sunt prezentate în Fig. 3.4.

În ciuda prezenței unor distorsiuni tranzitorii semnificative, ceea ce este destul de așteptat, lungimea impulsurilor pozitive rămâne încă destul de apropiată de 25 și, respectiv, 50%.

Orez. 3.4. Semnal cu lățimea impulsului de 100 kHz generat folosind TimerOne

In cele din urma

Întreruperile, care uneori par a fi soluția ideală pentru proiecte dificile, pot face dificilă depanarea codului și nu sunt întotdeauna cea mai bună modalitate de a rezolva probleme dificile. Luați în considerare cu atenție posibilele soluții înainte de a le angaja. În capitolul 14, ne vom uita la un alt truc pentru a depăși dificultatea incapacității Arduino de a gestiona mai multe sarcini la un moment dat.

Vom reveni la întreruperi în Capitolul 5, unde vom analiza cum pot fi utilizate pentru a reduce consumul de energie al plăcii Arduino, punând-o periodic în modul de economisire a energiei, și în Capitolul 13, unde vom folosi întreruperi pentru a crește acuratețea procesării semnalului digital.

În capitolul următor, vom explora tehnici pentru a maximiza performanța Arduino.



Ți-a plăcut articolul? Împărtășește-l