Arduino se întrerupe în fiecare secundă. Procesarea contactului

Și iată un exemplu de utilizare a funcției Arduino attachInterrupt().

O întrerupere este un semnal care anunță procesorul că a avut loc un eveniment care necesită atenție imediată. Procesorul trebuie să răspundă la acest semnal întrerupând execuția instrucțiunilor curente și transferând controlul către manipulatorul de întreruperi (ISR, Interrupt Service Routine). Un handler este o funcție obișnuită pe care o scriem noi înșine și plasăm acolo codul care ar trebui să răspundă la eveniment.

După deservirea întreruperii ISR, funcția își finalizează activitatea și procesorul revine cu bucurie la activitățile întrerupte - continuă să execute codul de unde a rămas. Toate acestea se întâmplă automat, așa că sarcina noastră este doar să scriem un handler de întrerupere fără a sparge nimic sau a face ca procesorul să fie distras prea des de noi. Veți avea nevoie de o înțelegere a circuitului, a principiilor de funcționare a dispozitivelor conectate și de o idee despre cât de des poate fi declanșată o întrerupere și care sunt caracteristicile apariției acesteia. Toate acestea constituie principala dificultate a lucrului cu întreruperi.

Întreruperi hardware și software

Întreruperile din Arduino pot fi împărțite în mai multe tipuri:

  • Întreruperi hardware. Întreruperea la nivelul arhitecturii microprocesorului. Evenimentul în sine poate avea loc într-un moment productiv de pe un dispozitiv extern - de exemplu, apăsarea unui buton de pe tastatură, mutarea mouse-ului computerului etc.
  • Software-ul se întrerupe. Acestea sunt lansate în interiorul programului folosind instrucțiuni speciale. Folosit pentru a apela operatorul de întrerupere.
  • Întreruperi interne (sincrone).. O întrerupere internă apare ca urmare a unei modificări sau încălcări în execuția programului (de exemplu, la accesarea unei adrese nevalide, a unui cod de operare nevalid etc.).

De ce sunt necesare întreruperi hardware?

Întreruperile hardware apar ca răspuns la un eveniment extern și provin de la un dispozitiv hardware extern. Arduino oferă 4 tipuri de întreruperi hardware. Toate diferă în ceea ce privește semnalul de la pinul de întrerupere:

  • Contactul este tras la sol. Operatorul de întrerupere este executat atâta timp cât există un semnal LOW pe pinul de întrerupere.
  • Schimbarea semnalului de pe contact. În acest caz, Arduino execută un handler de întrerupere atunci când are loc o schimbare a semnalului pe pinul de întrerupere.
  • Schimbarea semnalului de la LOW la HIGH pe un pin - la schimbarea de la un semnal scăzut la unul ridicat, se va executa gestionarea întreruperilor.
  • Schimbarea semnalului de la HIGH la LOW pe un pin - la trecerea de la un semnal ridicat la un semnal scăzut, va fi executat gestionarea întreruperilor.

Întreruperile sunt utile în programele Arduino, deoarece ajută la rezolvarea problemelor de sincronizare. De exemplu, atunci când lucrați cu UART, întreruperile vă permit să evitați nevoia de a urmări sosirea fiecărui personaj. Un dispozitiv hardware extern emite un semnal de întrerupere, procesorul apelează imediat un handler de întrerupere, care captează caracterul în timp. Acest lucru economisește timp CPU care altfel ar fi cheltuit verificând starea UART fără întreruperi, în schimb, toate acțiunile necesare sunt efectuate de gestionarul de întreruperi fără a afecta programul principal; Nu sunt necesare capacități speciale de la dispozitivul hardware.

Principalele motive pentru care trebuie apelată o întrerupere sunt:

  • Detectarea unei schimbări în starea de ieșire;
  • Întreruperea temporizatorului;
  • Întreruperea datelor prin SPI, I2C, USART;
  • Conversie analog-digitală;
  • Dorința de a folosi EEPROM, memorie flash.

Cum sunt implementate întreruperile în Arduino

Când se primește un semnal de întrerupere, funcționarea este suspendată. Începe execuția funcției care este declarată a fi executată atunci când este întreruptă. O funcție declarată nu poate accepta valori de intrare și returnează valori atunci când iese. Întreruperea nu afectează codul în sine în bucla principală a programului. Pentru a lucra cu întreruperi în Arduino, se folosește o funcție standard attachInterrupt().

Diferențe în implementarea întreruperilor în diferite plăci Arduino

În funcție de implementarea hardware a unui anumit model de microcontroler, există mai multe întreruperi. Placa Arduino Uno are 2 întreruperi pe al doilea și al treilea pin, dar dacă sunt necesare mai mult de două ieșiri, placa acceptă un mod special de „schimbare de pin”. Acest mod funcționează prin schimbarea intrării pentru toți pinii. Diferența dintre modul de întrerupere a modificării intrării este că întreruperile pot fi generate pe oricare dintre cei opt pini. În acest caz, procesarea va fi mai dificilă și mai lungă, deoarece va trebui să urmăriți cea mai recentă stare pentru fiecare dintre contacte.

Pe alte plăci numărul de întreruperi este mai mare. De exemplu, placa are 6 pini care pot gestiona întreruperi externe. Pentru toate plăcile Arduino, atunci când lucrați cu funcția attachInterrupt (întrerupere, funcție, mod), argumentul Inerrupt 0 este asociat cu pinul digital 2.

Întreruperi în limbajul Arduino

Acum să fim practici și să vorbim despre cum să folosiți întreruperile în proiectele dvs.

Sintaxă attachInterrupt()

Funcția attachInterrupt este utilizată pentru a lucra cu întreruperi. Servește pentru a conecta o întrerupere externă la un handler.

Sintaxa apelului: attachInterrupt(întrerupere, funcție, mod)

Argumente ale funcției:

  • întrerupere – numărul întreruperii de apelat (standard 0 – pentru al 2-lea pin, pentru placa Arduino Uno 1 – pentru al treilea pin),
  • function – numele functiei care va fi apelata atunci cand este intrerupta (important - functia nu trebuie nici sa accepte si nici sa returneze valori),
  • mod – condiție pentru declanșarea întreruperii.

Pot fi setate următoarele condiții de declanșare:

  • LOW – se efectuează când nivelul semnalului este scăzut când contactul are o valoare zero. Întreruperea poate fi repetată ciclic - de exemplu, atunci când este apăsat un buton.
  • SCHIMBARE – pe margine, întreruperea are loc atunci când semnalul trece de la mare la scăzut sau invers. Se execută o dată pentru orice schimbare de semnal.
  • RISING – executați o întrerupere o dată când semnalul se schimbă de la LOW la HIGH.
  • FALLING – executați o întrerupere o dată când semnalul trece de la HIGH la LOW.4

Notite importante

Când lucrați cu întreruperi, trebuie luate în considerare următoarele limitări importante:

  • Funcția de gestionare nu ar trebui să ruleze prea mult timp. Chestia este că Arduino nu poate gestiona mai multe întreruperi în același timp. În timp ce funcția de gestionare rulează, toate celelalte întreruperi vor fi ignorate și este posibil să pierdeți evenimente importante. Dacă trebuie să faceți ceva mare, transferați procesarea evenimentelor în bucla principală () . În handler poți seta doar steag-ul evenimentului, iar în buclă poți să verifici steag-ul și să-l procesezi.
  • Trebuie să fii foarte atent la variabile. Un compilator inteligent C++ vă poate „re-optimiza” programul - eliminați variabilele care, în opinia sa, nu sunt necesare. Compilatorul pur și simplu nu va vedea că setați unele variabile într-o parte și le utilizați în alta. Pentru a elimina această posibilitate în cazul tipurilor de date de bază, puteți folosi cuvântul cheie volatil, de exemplu: stare booleană volatilă = 0. Dar această metodă nu va funcționa cu structuri de date complexe. Deci trebuie să fii mereu în alertă.
  • Nu este recomandat să folosiți un număr mare de întreruperi (încercați să nu folosiți mai mult de 6-8). Un număr mare de evenimente diferite necesită o complicare serioasă a codului și, prin urmare, duce la erori. În plus, trebuie să înțelegeți că nu se poate vorbi despre nicio acuratețe temporală a execuției în sistemele cu un număr mare de întreruperi - nu veți înțelege niciodată exact care este intervalul dintre apelurile de comenzi care sunt importante pentru dvs.
  • Este strict interzisă utilizarea delay() în handlere. Mecanismul de determinare a intervalului de întârziere folosește cronometre și aceștia funcționează, de asemenea, pe întreruperi pe care managerul dumneavoastră le va bloca. Drept urmare, toată lumea va aștepta pe toată lumea și programul se va îngheța. Din același motiv, protocoalele de comunicare bazate pe întreruperi (de exemplu, i2c) nu pot fi utilizate.

Exemple de utilizare a attachInterrupt

Să trecem la practică și să privim un exemplu simplu de utilizare a întreruperilor. În exemplu, definim o funcție de gestionare care, atunci când semnalul de pe pinul 2 al Arduino Uno se schimbă, va comuta starea pinului 13, la care conectăm în mod tradițional LED-ul.

#define PIN_LED 13 volatile boolean actionState = LOW; void setup() ( pinMode(PIN_LED, OUTPUT); // Setați întreruperea // Funcția myEventListener va fi apelată când // pe pinul 2 (întreruperea 0 este conectată la pinul 2) // semnalul se schimbă (indiferent în care direction) attachInterrupt (0, myEventListener, CHANGE void loop() ( // Nu facem nimic în funcția de buclă, deoarece tot codul de gestionare a evenimentelor va fi în funcția myEventListener) void myEventListener() ( actionState != actionState); // // Efectuați alte acțiuni, de exemplu, porniți sau opriți LED-ul digitalWrite(PIN_LED, actionState )

Să ne uităm la câteva exemple de întreruperi mai complexe și de gestionare a acestora: pentru temporizatoare și butoane.

Se întrerupe prin apăsarea unui buton cu anti-săritură

Când este întrerupt, apare - înainte ca contactele să intre în contact strâns la apăsarea butonului, acestea vor oscila, generând mai multe operații. Există două moduri de a face față sărituri: hardware, adică lipirea unui condensator la buton și software.

Puteți scăpa de chatter folosind funcția - vă permite să măsurați timpul care a trecut de la prima acționare a butonului.

If(digitalRead(2)==HIGH) ( //când butonul este apăsat //Dacă au trecut mai mult de 100 de milisecunde de la apăsarea anterioară if (millis() - previousMillis >= 100) ( //Timpul primei operația este reținută anteriorMillis = millis( if (led==vechi) ( //verifică dacă starea butonului nu s-a schimbat led=!led; )

Acest cod vă permite să eliminați debounce și nu blochează execuția programului, așa cum este cazul funcției de întârziere, care nu este permisă în întreruperi.

Cronometrul se întrerupe

Un cronometru este un numărător care numără la o anumită frecvență, obținută de la procesorul 16 MHz. Divizorul de frecvență poate fi configurat pentru a obține modul de numărare dorit. De asemenea, puteți configura contorul pentru a genera întreruperi atunci când este atinsă o valoare setată.

Și o întrerupere a temporizatorului vă permite să întrerupeți o dată la fiecare milisecundă. Arduino are 3 temporizatoare - Timer0, Timer1 și Timer2. Timer0 este folosit pentru a genera întreruperi o dată la fiecare milisecundă, care actualizează contorul și îl transmite funcției millis(). Acest cronometru este de opt biți și numără de la 0 la 255. O întrerupere este generată când valoarea ajunge la 255. În mod implicit, un divizor de ceas de 65 este utilizat pentru a obține o frecvență apropiată de 1 kHz.

Registrele de comparație sunt folosite pentru a compara starea temporizatorului și datele stocate. În acest exemplu, codul va genera o întrerupere când contorul ajunge la 0xAF.

TIMSK0 |= _BV(OCIE0A);

Trebuie să definiți un handler de întrerupere pentru un vector de întrerupere a temporizatorului. Un vector de întrerupere este un pointer către adresa locației comenzii care va fi executată atunci când întreruperea este apelată. Mai mulți vectori de întrerupere sunt combinați într-un tabel de vectori de întrerupere. Cronometrul în acest caz va fi numit TIMER0_COMPA_vect. Acest handler va efectua aceleași acțiuni ca în loop().

SIGNAL(TIMER0_COMPA_vect) (unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); if(digitalRead(2) == HIGH) ( sweeper2.Update(currentMillis); led1.Update(currentMillis); ) led2.Update( currentMillis); led3.Update(currentMillis) //Funcția loop() va rămâne goală. void loop() ( )

Rezumând

Întreruperea în Arduino este un subiect destul de complex, deoarece trebuie să vă gândiți la întreaga arhitectură a proiectului dintr-o dată, să vă imaginați cum este executat codul, ce evenimente sunt posibile, ce se întâmplă atunci când codul principal este întrerupt. Nu ne-am propus să dezvăluim toate caracteristicile lucrului cu acest construct de limbaj, scopul principal a fost introducerea principalelor cazuri de utilizare. În articolele viitoare vom continua conversația despre întreruperi mai detaliat.

Pe parcursul unui proiect, pot fi necesare mai multe întreruperi, dar dacă fiecare dintre ele are prioritate maximă, atunci de fapt nici una dintre funcții nu o va avea. Din același motiv, nu este recomandat să folosiți mai mult de o duzină de întreruperi.

Manipulatorii ar trebui aplicați numai proceselor care au cea mai mare sensibilitate la intervalele de timp. Nu uitați că în timp ce programul se află în gestionarea întreruperilor, toate celelalte întreruperi sunt dezactivate. Un număr mare de întreruperi duce la o deteriorare a răspunsului lor.

În momentul în care o întrerupere este activă, iar celelalte sunt dezactivate, apar două nuanțe importante de care proiectantul de circuite trebuie să țină cont. În primul rând, timpul de întrerupere ar trebui să fie cât mai scurt posibil.

Acest lucru vă va asigura că nu pierdeți alte întreruperi programate. În al doilea rând, atunci când se gestionează o întrerupere, codul programului nu ar trebui să necesite activitate de la alte întreruperi. Dacă acest lucru nu este împiedicat, programul se va îngheța pur și simplu.

Nu utilizați procesare pe termen lung buclă() , este mai bine să dezvoltați codul pentru manipulatorul de întreruperi cu variabila setată la volatilă. Acesta va spune programului că nu este nevoie de procesare ulterioară.

Dacă funcția apel Actualizați() este încă necesar, mai întâi va trebui să verificați variabila de stare. Acest lucru vă va permite să determinați dacă este necesară o prelucrare ulterioară.

Înainte de a configura cronometrul, ar trebui să verificați codul. Temporizatoarele Anduino ar trebui clasificate ca resurse limitate, deoarece există doar trei dintre ele și sunt folosite pentru a îndeplini o varietate de funcții. Dacă vă confundați cu utilizarea temporizatoarelor, atunci o serie de operațiuni pot pur și simplu să nu mai funcționeze.

Ce funcții operează acesta sau acel temporizator?

Pentru microcontrolerul Arduino Uno, fiecare dintre cele trei temporizatoare are propriile operațiuni.

Asa de Timer0 responsabil pentru PWM pe al cincilea și al șaselea pin, funcții milis() , micros() , întârziere() .

Un alt cronometru - Timer1, folosit cu PWM pe al nouălea și al zecelea pini, cu biblioteci WaveHC și Servo.

Timer2 funcționează cu PWM pe pinii 11 și 13, precum și Ton.

Proiectantul circuitului trebuie să aibă grijă să asigure utilizarea în siguranță a datelor partajate. La urma urmei, o întrerupere oprește toate operațiunile procesorului pentru o milisecundă și schimbul de date între ele buclă() iar manipulatorii de întreruperi trebuie să fie persistenti. Poate apărea o situație când compilatorul începe să optimizeze codul pentru a-și atinge performanța maximă.

Rezultatul acestui proces va fi stocarea unei copii a principalelor variabile de cod în registru, ceea ce va asigura viteza maximă de acces la acestea.

Dezavantajul acestui proces poate fi că valorile reale sunt înlocuite cu copii stocate, ceea ce poate duce la pierderea funcționalității.

Pentru a preveni acest lucru, trebuie să utilizați o variabilă voltatil , ceea ce va ajuta la prevenirea optimizărilor inutile. Când utilizați matrice mari care necesită cicluri pentru actualizări, trebuie să dezactivați întreruperile în timpul acestor actualizări.

Când instalați acest program, veți fi surprins cât de asemănător este cu IDE-ul Arduino. Nu fi surprins, ambele programe sunt realizate pe acelasi motor.

Aplicația are o mulțime de caracteristici, inclusiv o bibliotecă Serial, astfel încât să putem lega transferul de date între placă și .

Să lansăm Arduino IDE și să selectăm cel mai simplu exemplu de ieșire a datelor către Port serial:

Void setup() ( Serial.begin(9600); ) void loop() ( Serial.println("Hello Kitty!"); // așteptați 500 de milisecunde înainte de a trimite următoarea întârziere (500); )

Să rulăm exemplul și să ne asigurăm că codul funcționează.

Primirea datelor

Acum vrem să obținem același text în . Să începem un nou proiect și să scriem ceva cod.

Primul pas este să importați biblioteca. Să mergem la Schiță | Import Biblioteca | Serial. Linia va apărea în schiță:

Procesare import.serial.*; Serial serial; // creează un obiect de port serial String primit; // date primite de la portul serial void setup() ( String port = Serial.list(); serial = new Serial(this, port, 9600); ) void draw() (dacă (serial.available() > 0) ( // dacă există date, primite = serial.readStringUntil("\n"); // citește datele ) println(received // afișează datele în consolă);

Pentru a ne asigura că datele sunt primite de la portul serial, avem nevoie de un obiect al clasei Serial. Deoarece trimitem date de tip String cu Arduino, trebuie să primim șirul în Procesare.

In metoda înființat() trebuie să obțineți un port serial disponibil. De obicei, acesta este primul port disponibil din listă. După aceasta putem configura obiectul Serial, indicând portul și viteza de transfer de date (este de dorit ca vitezele să se potrivească).

Tot ce rămâne este să conectați din nou placa, să rulați schița din Procesare și să observați datele primite în consola aplicației.

Procesarea vă permite să lucrați nu numai cu consola, ci și să creați ferestre standard. Să rescriem codul.

Procesare import.serial.*; Serial serial; // creează un obiect de port serial String primit; // date primite de la portul serial void setup() ( size(320, 120); String port = Serial.list(); serial = new Serial(this, port, 9600); ) void draw() ( if (serial) .available() > 0) ( // dacă există date, // le citiți și scrieți-le în variabila primită = serial.readStringUntil("\n"); ) // Setări pentru textSize(24); (); dacă (primit != nul) ( text(primit, 10, 30); ) )

Să rulăm din nou exemplul și să vedem o fereastră cu o inscripție care este redesenată într-un singur loc.

În acest fel am învățat cum să primim date de la Arduino. Acest lucru ne va permite să desenăm grafice frumoase sau să creăm programe pentru monitorizarea citirilor senzorilor.

Trimiterea datelor

Nu numai că putem primi date de pe placă, dar și să trimitem date către placă, forțând-o să execute comenzi de la computer.

Să presupunem că trimitem caracterul „1” de la Procesare. Când placa detectează simbolul trimis, aprindeți LED-ul de pe portul 13 (încorporat).

Schița va fi similară cu cea anterioară. De exemplu, să creăm o fereastră mică. Când faceți clic în zona ferestrei, vom trimite „1” și îl vom duplica în consolă pentru verificare. Dacă nu există clicuri, atunci este trimisă comanda „0”.

Procesare import.serial.*; Serial serial; // creează un obiect de port serial String primit; // date primite de la portul serial void setup() ( size(320, 120); String port = Serial.list(); serial = new Serial(this, port, 9600); ) void draw() ( if (mousePressed) == true) ( ​​​​//dacă am făcut clic cu mouse-ul în fereastra serial.write("1"); //trimite 1 println("1"); ) else ( //dacă nu a existat un clic serial.write ("0" ); //trimite 0 ) )

Acum să scriem o schiță pentru Arduino.

Char commandValue; // date care provin de la portul serial int ledPin = 13; // LED-ul încorporat void setup() ( pinMode(ledPin, OUTPUT); // modul de ieșire a datelor Serial.begin(9600); ) void loop() ( if (Serial.available()) ( commandValue = Serial.read ( ); ) if (commandValue == "1") ( digitalWrite(ledPin, HIGH); // aprinde LED-ul ) else ( digitalWrite(ledPin, LOW); // în caz contrar, întârziere (10); citind)

Să rulăm ambele schițe. Facem clic în interiorul ferestrei și observăm că LED-ul se aprinde. Nici măcar nu trebuie să faceți clic, dar țineți apăsat butonul mouse-ului - LED-ul se va aprinde constant.

Schimb de date

Acum să încercăm să combinăm ambele abordări și să schimbăm mesaje între bord și aplicație în două direcții.

Pentru o eficiență maximă, să adăugăm o variabilă booleană. Ca urmare, nu mai trebuie să trimitem constant 1 sau 0 de la Procesare și portul serial este descărcat și nu transmite informații inutile.

Când placa detectează unitatea trimisă, schimbăm valoarea booleană cu una opusă față de starea curentă ( SCĂZUT pe ÎNALT si invers). ÎN altfel Folosim șirul „Hello Kity”, pe care îl vom trimite doar dacă nu detectăm „1”.

Funcţie stabilContact() trimite șirul pe care ne așteptăm să îl primim în Procesare. Dacă vine răspunsul, Procesarea poate primi datele.

Char commandValue; // date care provin de la portul serial int ledPin = 13; boolean ledState = LOW; //controlează starea LED-ului void setup() ( pinMode(ledPin, OUTPUT); Serial.begin(9600); establishContact(); // trimite un octet pentru contact în timp ce receptorul răspunde) void loop() ( / / dacă datele pot fi citite dacă (Serial.available() > 0) ( // citește datele commandValue = Serial.read(); dacă (commandValue == "1") ( ledState = !ledState; digitalWrite(ledPin, ledState) ); ) delay(100) ) else ( // Trimite înapoi Serial.println("Hello Kitty"); ) delay(50 ) void establishContact() ( while (Serial.available());<= 0) { Serial.println("A"); // отсылает заглавную A delay(300); } }

Să trecem la schița de procesare. Vom folosi metoda serialEvent(), care va fi apelat de fiecare dată când un anumit caracter este întâlnit în buffer.

Să adăugăm o nouă variabilă booleană primul contact, care vă permite să determinați dacă există o conexiune la Arduino.

In metoda înființat() adăugați o linie serial.bufferUntil("\n");. Acest lucru ne permite să stocăm datele primite într-un buffer până când întâlnim un anumit personaj. În acest caz revenim (\n) din moment ce trimitem Serial.println() de la Arduino. "\n" la final înseamnă că vom activa un nou rând, adică acestea vor fi ultimele date pe care le vom vedea.

Deoarece trimitem în mod constant date, metoda serialEvent() efectuează sarcini de buclă a desena(), apoi îl puteți lăsa gol.

Acum să ne uităm la metoda principală serialEvent(). De fiecare dată când introducem o nouă linie (\n), această metodă este apelată. Și de fiecare dată când se efectuează următoarea secvență de acțiuni:

  • Datele primite sunt citite;
  • Se verifică dacă acestea conțin vreo valoare (adică dacă ne-a fost transmisă o matrice de date goală sau „null”);
  • Eliminați spațiile;
  • Dacă am primit datele necesare pentru prima dată, schimbăm valoarea variabilei booleene primul contactși spune-i lui Arduino că suntem gata să acceptăm date noi;
  • Dacă aceasta nu este prima recepție a tipului de date cerut, o afișam în consolă și trimitem microcontrolerului datele despre clicul care a fost făcut;
  • Îi informăm pe Arduino că suntem pregătiți să primim un nou pachet de date.
procesare import.serial.*; Serial serial; // creează un obiect de port serial String primit; // date primite de la portul serial // Verificarea datelor primite de la Arduino boolean firstContact = false; void setup() ( size(320, 120); String port = Serial.list(); serial = new Serial(this, port, 9600); serial.bufferUntil("\n"); ) void draw() ( ) void serialEvent(Serial myPort) ( //formează un șir din datele care sosesc // "\n" - delimitator - sfârșitul pachetului de date primit = myPort.readStringUntil("\n"); //asigură-te că datele noastre nu este gol înainte de cum să continuați dacă (primit != nul) ( //eliminați spațiile primite = trim(primit); println(primit); //căutați șirul nostru „A” pentru a începe strângerea de mână //dacă este găsit, ștergeți tamponul și trimiteți cererea de date dacă (firstContact == false) ( if (received.equals("A")) ( serial.clear(); firstContact = true; myPort.write("A"); println("contact" "); ) ) else ( //dacă contactul este stabilit, primim și analizam datele println(received); if (mousePressed == true) ( ​​​​//dacă am dat clic pe fereastra serial.write("1 "); //trimite 1 println("1"); ) // când aveți toate datele, faceți o cerere pentru un nou pachet serial.write("A"); ) ) )

Când este conectat și lansat, expresia „Hello Kitty” ar trebui să apară în consolă. Când faceți clic cu mouse-ul în fereastra Procesare, LED-ul de pe pinul 13 se va aprinde și se va stinge.

Pe lângă Procesare, puteți folosi programe PuTTy sau puteți scrie propriul program în C# folosind clase gata făcute pentru lucrul cu porturile.

04.Comunicare: Dimmer

Exemplul demonstrează modul în care datele pot fi trimise de la un computer la placă pentru a controla luminozitatea unui LED. Datele vin sub formă de octeți individuali de la 0 la 255. Datele pot veni din orice program de pe computer care are acces la portul serial, inclusiv Procesare.

De exemplu, veți avea nevoie de un circuit standard cu o rezistență și un LED la pinul 9.

Schiță pentru Arduino.

Const int ledPin = 9; // LED pe pinul 9 void setup() ( Serial.begin(9600); // setați modul la pinMode(ledPin, OUTPUT); ) void loop() (luminozitate octet; // verificați dacă există date de la computerul dacă (Serial.available()) ( // citește ultimii octeți primiți de la 0 la 255 luminozitate = Serial.read(); // setează luminozitatea LED-ului analogWrite(ledPin, luminozitate); ) )

Cod pentru procesare

Procesare import.serial.*; Port serial; void setup() ( size(256, 150); println("Porturi seriale disponibile:"); println(Serial.list()); // Folosește primul port din această listă (numărul 0). Modificați acest lucru pentru a selecta port // care corespunde plăcii dvs. Arduino Ultimul parametru (de exemplu, 9600) este // viteza de comunicare. Trebuie să corespundă valorii transmise către // Serial.begin() în portul dvs. de schiță Arduino = new Serial. (this, Serial.list(), 9600 // Dacă cunoașteți numele portului folosit de placa Arduino, atunci specificați în mod explicit //port = new Serial(this, "COM1", 9600 ) void draw()); (); // desenează un gradient de la negru la alb pentru (int i = 0; i

Lansați-l și mutați mouse-ul peste fereastra creată în orice direcție. Când vă deplasați spre stânga, luminozitatea LED-ului va scădea, când vă deplasați spre dreapta, va crește.

04. Comunicare: PhysicalPixel (Aprinde LED-ul cu mouse-ul)

Să schimbăm puțin problema. Vom muta mouse-ul peste pătrat și vom trimite simbolul „H” (Înalt) pentru a aprinde LED-ul de pe tablă. Când mouse-ul părăsește zona pătrată, vom trimite simbolul „L” (Low) pentru a stinge LED-ul.

Cod pentru Arduino.

Const int ledPin = 13; // pinul 13 pentru LED int incomingByte; // variabilă pentru primirea datelor void setup() ( Serial.begin(9600); pinMode(ledPin, OUTPUT); ) void loop() ( // dacă există date dacă (Serial.available() > 0) ( // citește octetul în buffer incomingByte = Serial.read( // dacă acesta este caracterul H (ASCII 72), atunci pornește LED-ul dacă (incomingByte == "H") ( digitalWrite(ledPin, HIGH); ) / / dacă acesta este caracterul L ( ASCII 76), atunci stingeți LED-ul dacă (incomingByte == "L") ( digitalWrite(ledPin, LOW); ) ) )

Cod pentru procesare.

Procesare import.serial.*; float boxX; float boxY; int boxSize = 20; boolean mouseOverBox = false; Port serial; void setup() ( dimensiune (200, 200); boxX = lățime / 2,0; boxY = înălțime / 2,0; rectMode(RADIUS); println(Serial.list()); // Deschide portul la care este conectată placa Arduino (în acest caz #0) // Asigurați-vă că deschideți portul la aceeași viteză pe care Arduino folosește (9600bps) port = new Serial(this, Serial.list(), 9600 ) void draw() ( background(0); ); // Dacă cursorul este peste pătrat if (mouseX > boxX - boxSize && mouseX boxY - boxSize && mouseY);

04.Comunicare: Grafic (Desenarea unui grafic)

Dacă în exemplul precedent am trimis date de la computer la placă, acum vom îndeplini sarcina opusă - vom primi date de la potențiometru și le vom afișa sub forma unui grafic.


Întreruperi hardware

Nu am putut găsi o imagine amuzantă pentru această lecție, am găsit doar o prelegere despre programare și chiar începutul acestei prelegeri ne explică perfect ce întrerupe. O întrerupere în Arduino poate fi descrisă exact în același mod: microcontrolerul „aruncă totul”, trece la executarea unui bloc de funcții în gestionarea întreruperilor, le execută și apoi revine exact la locul din codul principal unde s-a oprit.

Există diferite tipuri de întreruperi, adică nu întreruperile în sine, ci cauzele lor: o întrerupere poate fi cauzată de un convertor analog-digital, un temporizator-contor sau literalmente un pin de microcontroler. Aceste întreruperi se numesc întreruperi externe. hardware, și despre asta vom vorbi astăzi.

Întreruperea hardware externă este o întrerupere cauzată de o schimbare a tensiunii pe un pin al microcontrolerului. Principalul punct este că microcontrolerul (nucleul de calcul) nu sonda pinulȘi nu pierde timpul cu el, pinul este manevrat de o altă piesă hardware. De îndată ce tensiunea de pe pin se schimbă (adică un semnal digital, +5 aplicat/+5 eliminat), microcontrolerul primește semnalul, elimină totul, procesează întreruperea și revine la lucru. De ce este necesar acest lucru? Cel mai adesea, întreruperile sunt folosite pentru a detecta evenimente scurte - impulsuri, sau chiar pentru a număra numărul acestora, fără a încărca codul principal. O întrerupere hardware poate prinde o apăsare scurtă de buton sau declanșarea senzorului în timpul calculelor lungi și complexe sau întârzierilor în cod, de exemplu. aproximativ vorbind, pinul este sondat paralel cu codul principal. Întreruperile pot, de asemenea, trezi microcontrolerul din modurile de economisire a energiei, când aproape toate perifericele sunt oprite. Să vedem cum să lucrăm cu întreruperi hardware în IDE-ul Arduino.

Întreruperi în Arduino

Să începem cu faptul că nu toți pinii „pot” întrerupe. Da, există așa ceva ca pinChangeInterrupts, dar despre asta vom vorbi în lecțiile avansate. Acum trebuie să înțelegem că întreruperile hardware pot genera doar anumiți pini:

MK / număr de întrerupere INT 0 INT 1 INT 2 INT 3 INT 4 INT 5
ATmega 328/168 (Nano, UNO, Mini) D2 D3
ATmega 32U4 (Leonardo, Micro) D3 D2 D0 D1 D7
ATmega 2560 (Mega) D2 D3 D21 D20 D19 D18

După cum înțelegeți din tabel, întreruperile au propriul număr, care diferă de numărul PIN. Apropo, există o funcție convenabilă digitalPinToInterrupt(pin), care ia numărul PIN și returnează numărul de întrerupere. Prin alimentarea acestei funcții cu numărul 3 la Arduino nano, obținem 1. Totul este conform tabelului de mai sus, o funcție pentru leneși.

O întrerupere este conectată folosind funcția attachInterrupt(pin, handler, mod):

  • pin– numărul de întrerupere
  • manipulator– numele funcției de gestionare a întreruperii (trebuie să o creați singur)
  • modul– „modul” de întrerupere:
    • SCĂZUT(scăzut) – declanșat de un semnal SCĂZUT pe pin
    • ÎN CREȘTERE(creștere) – declanșat atunci când semnalul de pe pinul c se schimbă SCĂZUT pe ÎNALT
    • CĂDERE(drop) – declanșat atunci când semnalul de pe pin se schimbă ÎNALT pe SCĂZUT
    • SCHIMBARE(schimbare) – declanșat atunci când semnalul se schimbă (cu SCĂZUT pe ÎNALT si invers)

Întreruperea poate fi, de asemenea, dezactivată folosind funcția detachInterrupt(pin), unde pin – din nou numărul de întrerupere.

De asemenea, puteți dezactiva global întreruperile folosind funcția fara intreruperi() si rezolva-le din nou cu întrerupe (). Fii atent cu ele! fara intreruperi() va opri, de asemenea, întreruperile temporizatorului și toate funcțiile de timp și generarea PWM se vor întrerupe.

Să ne uităm la un exemplu în care apăsările de buton sunt numărate în întrerupere, dar în bucla principală sunt scoase cu o întârziere de 1 secundă. Lucrând cu butonul în modul normal, este imposibil să combinați o astfel de ieșire brută cu o întârziere:

Contor int volatil = 0; // counter variable void setup() ( Serial.begin(9600); // a deschis un port pentru comunicare // a conectat un buton pe D2 și GND pinMode(2, INPUT_PULLUP); \ // D2 este întrerupere 0 // handler - function buttonTick // FALLING - cand butonul este apasat, va fi un semnal 0, il prindem attachInterrupt(0, buttonTick, FALLING) void buttonTick() ( counter++; // + apasare ) void loop() ( Serial.println (contor); // întârziere de ieșire (1000);

Deci, codul nostru numără apăsările de taste chiar și în timpul întârzierii! Grozav. Dar ce este volatil? Am declarat o variabilă globală tejghea, care va stoca numărul de clicuri pe buton. Dacă valoarea variabilei se modifică în întrerupere, trebuie să informați microcontrolerul despre aceasta folosind un specificator volatil, care este scris înainte de a indica tipul de date al variabilei, altfel lucrul va fi incorect. Acesta este doar ceva de reținut: dacă o variabilă se modifică într-o întrerupere, fă-o volatil.

Câteva puncte mai importante:

  • Variabilele modificate în întrerupere trebuie declarate ca volatil
  • Întârzierile de acest fel nu funcționează în întreruperi întârziere()
  • Nu își schimbă sensul într-o întrerupere milis()Și micros()
  • În întrerupere, ieșirea către port nu funcționează corect ( Serial.print()), de asemenea, nu ar trebui să-l folosiți acolo - încarcă nucleul
  • Când întrerupeți, ar trebui să încercați să faceți cât mai puține calcule și, în general, acțiuni „lungi” posibil - acest lucru va încetini funcționarea MC cu întreruperi frecvente! Ce să fac? Cititi mai jos.

Dacă întreruperea prinde un eveniment care nu trebuie procesat imediat, atunci este mai bine să utilizați următorul algoritm pentru a lucra cu întrerupere:

  • În gestionarea întreruperilor ridicăm pur și simplu steagul
  • În bucla principală a programului verificăm steagul, dacă este ridicat, îl resetam și efectuăm acțiunile necesare
boolean volatil intFlag = fals; // flag void setup() ( Serial.begin(9600); // a deschis un port pentru comunicare // a conectat un buton la D2 și GND pinMode(2, INPUT_PULLUP); // D2 este întrerupere 0 // handler - function buttonTick // FALLING - când butonul este apăsat, va fi semnalul 0, îl prindem attachInterrupt(0, buttonTick, FALLING) void buttonTick() ( intFlag = true; // a ridicat semnalul de întrerupere ) void loop() ( dacă (intFlag) ( intFlag = false; // reset // efectuează unele acțiuni Serial.println("Întrerupere!");

Acesta este practic tot ce trebuie să știți despre întreruperi, vom analiza cazuri mai specifice în lecțiile avansate.

Video

Să învățăm cum să lucrăm cu întreruperi ale temporizatorului. Să scriem un program simplu cu procese paralele.

Într-un program real, multe acțiuni trebuie efectuate simultan. În introducere am dat un exemplu. Voi enumera ce acțiuni efectuează ea:

Operațiune

Durata ciclului
Interogează 3 butoane, procesează semnalele de la acestea pentru a elimina saritura 2 ms
Regenerează datele de la indicatoarele LED cu șapte segmente 2 ms
Generează semnale de control pentru 2 senzori de temperatură DS18B20 și citește datele de la aceștia. Senzorii au o interfață serială cu 1 fir. 100 µs pentru fiecare bit,
Ciclu total de citire de 1 secundă
Citirea valorilor analogice ale curentului și tensiunii pe elementul Peltier, tensiune de alimentare 100 µs
Filtrarea digitală a valorilor analogice de curent și tensiune 10 ms
Calculul puterii pe un element Peltier 10 ms
Controler de stabilizare a curentului și tensiunii PID (diferențial integral proporțional). 100 µs
Regulator de putere 10 ms
Regulator de temperatura 1 sec
Funcții de securitate, monitorizare a integrității datelor 1 sec
Management, logica generală a funcționării sistemului 10 ms

Toate aceste operații sunt efectuate ciclic, fiecare cu perioade de ciclu diferite. Niciunul dintre ei nu poate fi suspendat. Orice modificare, chiar și pe termen scurt, a perioadei de funcționare va duce la probleme: o eroare semnificativă de măsurare, funcționare incorectă a stabilizatorilor, indicatoare care pâlpâie, răspuns instabil la apăsarea butoanelor etc.

În programul de control al frigiderului, există mai multe procese paralele care efectuează toate aceste acțiuni, fiecare într-un ciclu cu propria sa perioadă de timp. Procesele paralele sunt procese ale căror acțiuni sunt efectuate simultan.

În lecțiile anterioare am creat o clasă pentru un obiect buton. Am spus că aceasta este o clasă pentru procesarea unui semnal într-un proces paralel. Că pentru funcționarea sa normală este necesar să apelăm funcția (metoda) de procesare a semnalului într-un ciclu cu perioadă obișnuită (am ales un timp de 2 ms). Și apoi, oriunde în program, sunt disponibile semne care arată starea curentă a butonului sau a semnalului.

Într-o buclă am plasat codul pentru procesarea stării butoanelor și controlul LED-urilor. Și la sfârșitul buclei setăm funcția de întârziere delay(2). Dar, timpul necesar pentru a executa un program într-o buclă modifică timpul total al buclei. Și perioada ciclului în mod clar nu este egală cu 2 ms. În plus, în timpul execuției funcției delay(), programul se blochează și nu poate efectua alte acțiuni. Un program complex va duce la un haos complet.

Ieșire - apelați funcția de procesare a stării butonului la întreruperea cronometrului hardware. La fiecare 2 ms bucla principală a programului trebuie întreruptă, semnalul butonului este procesat și controlul revine la bucla principală la codul în care a fost întreruptă. Un timp scurt pentru procesarea semnalului butonului nu va afecta semnificativ execuția buclei principale. Acestea. Procesarea butoanelor va avea loc în paralel, neobservată de programul principal.

Întreruperea temporizatorului hardware.

O întrerupere hardware este un semnal care raportează un eveniment. La sosirea acestuia, execuția programului este suspendată și controlul trece la manipulatorul de întreruperi. După procesare, controlul revine la codul programului întrerupt.

Din punctul de vedere al unui program, o întrerupere este un apel de funcție datorat unui eveniment extern care nu are legătură directă cu codul programului.

Semnalul de întrerupere a temporizatorului este generat ciclic, cu o perioadă de timp specificată. Este generat de un cronometru hardware - un contor cu logica care isi reseta codul atunci cand este atinsa o anumita valoare. Prin setarea programatică a codului pentru logica de resetare, putem seta timpul perioadei de întrerupere a temporizatorului.

Setarea modului și timpului perioadei de cronometru Arduino se face prin registrele hardware ale microcontrolerului. Dacă doriți, vă puteți da seama cum se face acest lucru. Dar sugerez o opțiune mai simplă - folosind biblioteca MsTimer2. Mai mult, setarea modului temporizatorului apare rar, ceea ce înseamnă că utilizarea funcțiilor de bibliotecă nu va încetini programul.

Biblioteca MsTimer2.

Biblioteca este destinată configurarii unei întreruperi hardware de la Timer 2 al microcontrolerului. Are doar trei funcții:

  • MsTimer2::set(unsigned long ms, void (*f)())

Această funcție setează durata perioadei de întrerupere în ms. Managerul de întreruperi f va fi apelat cu această perioadă. Trebuie să fie declarat nul (nu returnează nimic) și să nu aibă argumente. * f este un indicator de funcție. În schimb, trebuie să scrieți numele funcției.

  • MsTimer2::start()

Funcția permite întreruperi ale temporizatorului.

  • MsTimer2::stop()

Funcția dezactivează întreruperile temporizatorului.

Înainte de numele funcțiilor trebuie să scrieți MsTimer2::, deoarece Biblioteca este scrisă folosind directiva namespace.

Pentru a instala biblioteca, copiați directorul MsTimer2 în folderul biblioteci din folderul de lucru Arduino IDE. Apoi lansați programul Arduino IDE, deschideți Schiță -> Conectați bibliotecași vezi că biblioteca MsTimer2 este prezentă în lista de biblioteci.

Puteți descărca biblioteca MsTimer2 într-o arhivă zip. Pentru a-l instala trebuie să-l despachetezi.

Un program simplu cu procesare paralelă a semnalului unui buton.

Acum să scriem un program simplu cu un buton și un LED din lecția 6. Un buton este conectat la placa Arduino conform diagramei:

Arata cam asa:

Pentru fiecare apăsare de buton, LED-ul de pe placa Arduino își schimbă starea. Este necesar ca bibliotecile MsTimer2 și Button să fie instalate:

MsTimer2

Și plătește. Doar 40 de ruble. pe lună pentru acces la toate resursele site-ului!

// schiță_10_1 lecția 10
// Apăsarea butonului schimbă starea LED-ului

#include
#include

#define LED_1_PIN 13 //
#define BUTTON_1_PIN 12 // butonul este conectat la pinul 12

Buton button1(BUTTON_1_PIN, 15); // crearea obiectului - buton

void setup() (

MsTimer2::set(2, timerInterupt); // setați perioada de întrerupere a temporizatorului la 2 ms
MsTimer2::start(); //
}

void loop() (

// Control LED
dacă (button1.flagClick == adevărat) (
// a fost apăsat butonul



}
}

// handler de întrerupere
void timerInterrupt() (
button1.scanState(); // apelarea unei metode de a aștepta o stare stabilă pentru un buton
}

În funcția setup(), setăm timpul ciclului de întrerupere a temporizatorului la 2 ms și specificăm numele gestionarului de întreruperi timerInterrupt . Funcția de procesare a semnalului butonului button1.scanState() este apelată în handlerul de întrerupere a temporizatorului la fiecare 2 ms.

Astfel, procesăm starea butonului într-un proces paralel. Și în bucla principală a programului verificăm semnul unui clic de buton și schimbăm starea LED-ului.

Calificativul volatil.

Să modificăm bucla () din programul anterior.

void loop() (

în timp ce (adevărat) (
if (button1.flagClick == true) break;
}

// a fost apăsat butonul
button1.flagClick= false; // resetați semnul
digitalWrite(LED_1_PIN, ! digitalRead(LED_1_PIN)); // inversare LED
}

Logic nu s-a schimbat nimic.

  • În prima versiune, programul a trecut prin buclă până la sfârșit și a analizat steag-ul button1.flagClick din acesta.
  • În a doua opțiune, programul analizează steag-ul button1.flagClick într-o buclă while fără sfârșit. Când steagul devine activ, iese din bucla while prin pauză și inversează starea LED-ului.

Singura diferență este în ce ciclu programul rulează în buclă sau while.

Dar dacă rulăm cea mai recentă versiune a programului, vom vedea că LED-ul nu răspunde la apăsarea butoanelor. Să eliminăm clasa și să simplificăm programul.

#include
#define LED_1_PIN 13 // LED-ul conectat la pinul 13
int count=0;

void setup() (
pinMode(LED_1_PIN, OUTPUT); // definiți pinul LED-ului ca ieșire
MsTimer2::set(500, timerInterupt); // setați perioada de întrerupere a temporizatorului la 500 ms
MsTimer2::start(); // activați întreruperea temporizatorului
}

void loop() (

în timp ce (adevărat) (
dacă (număr != 0) pauză;
}

număr = 0;
digitalWrite(LED_1_PIN, ! digitalRead(LED_1_PIN)); // inversarea stării LED-ului
}

// handler de întrerupere
void timerInterrupt() (
numără++;
}

În acest program, contorul de numărare este incrementat cu 1 în manipulatorul de întreruperi la fiecare 500 ms. În bucla while este analizată, cu break ieșim din buclă și inversăm starea LED-ului. Nu ți-ai putea imagina un program mai simplu, dar nici nu funcționează.

Cert este că compilatorul limbajului C++ optimizează programul în funcție de inteligența sa. Uneori acest lucru nu merge bine. Compilatorul vede că nu sunt efectuate operații asupra variabilei count în bucla while. Prin urmare, el crede că este suficient să verifici starea numărării o singură dată. De ce să verificați într-o buclă ceva care nu se poate schimba niciodată. Compilatorul corectează codul, optimizându-l pentru timpul de execuție. Mai simplu spus, elimină codul de verificare a variabilei din buclă. Compilatorul nu poate înțelege că variabila count își schimbă starea în handler-ul de întreruperi. Drept urmare, rămânem blocați în bucla while.

În versiunile programului care execută o buclă până la sfârșit, compilatorul presupune că toate variabilele se pot schimba și lasă codul de verificare. Dacă inserați un apel la orice funcție de sistem în bucla while, compilatorul va decide, de asemenea, că variabilele se pot schimba.

Dacă, de exemplu, adăugați un apel la funcția delay() buclei while, programul va funcționa.

în timp ce (adevărat) (
dacă (număr != 0) pauză;
întârziere(1);
}

Un stil bun este acela de a dezvolta programe în care bucla este executată până la sfârșit și programul nu se blochează nicăieri. Următoarea lecție va conține singurul cod care analizează steaguri în bucle while nesfârșite. În continuare, plănuiesc să execut bucla până la sfârșit în toate programele.

Uneori, acest lucru nu este ușor sau eficient. Apoi trebuie să utilizați calificativul volatil. Este specificat când o variabilă este declarată și îi spune compilatorului să nu încerce să-și optimizeze utilizarea. Împiedică compilatorul să facă ipoteze despre valoarea unei variabile, deoarece variabila ar putea fi schimbată într-o altă unitate de program, cum ar fi într-un proces paralel. De asemenea, compilatorul plasează variabila în RAM mai degrabă decât în ​​registre de uz general.

La declararea numărului într-un program, este suficient să scrieți

număr de int volatile=0;

și toate opțiunile vor funcționa.

Pentru un program care controlează un buton, trebuie să declarați că proprietățile unei instanțe a clasei Button se pot schimba.

volatil Buton button1(BUTTON_1_PIN, 15); // crearea obiectului - buton

Conform observațiilor mele, utilizarea calificatorului volatil nu mărește în niciun fel lungimea codului programului.

Comparație a metodei de procesare a semnalului butonului cu biblioteca Bounce.

Există o bibliotecă gata făcută pentru deblocarea butoanelor Bounce. Starea butonului este verificată când este apelată funcția update(). În această funcție:

  • se citește semnalul butonului;
  • comparativ cu starea din timpul apelului anterior la update();
  • verifică cât timp a trecut de la apelul anterior folosind funcția millis();
  • se ia o decizie dacă starea butonului s-a schimbat.
  • Dar aceasta nu este procesare paralelă a semnalului. Funcția update() este de obicei apelată în bucla principală a programului asincron. Dacă nu este apelat mai mult de un anumit timp, informațiile semnalului butonului se vor pierde. Apelurile neregulate duc la funcționarea incorectă a algoritmului.
  • Funcția în sine are destul de mult cod și durează mult mai mult pentru a fi executată decât funcțiile din bibliotecă Button().
  • Nu există deloc filtrarea digitală a semnalelor pe baza valorii medii.

Este mai bine să nu utilizați această bibliotecă în programe complexe.

În lecția următoare vom scrie un program mai complex cu procese paralele. Să învățăm cum să implementăm execuția blocurilor de program în bucle cu diferite intervale de timp dintr-o singură întrerupere a temporizatorului.

Categorie: . Puteți să-l marcați.