Delay() versus millis()

Detalii proiect:
– la apasarea butonului starea LED-ului trebuie sa se modifice;
– cat timp butonul este apasat, LED-ul nu trebuie sa schimbe starea.
Prima cerinta se implementeaza astfel:
void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(butonPin, INPUT_PULLUP);
}

int stareLed = 0;

void loop() {

  if (digitalRead(butonPin) == LOW) {
    if (stareLed == 0) {
      stareLed = 1;
    }

    else {
      stareLed = 0;
    }

    digitalWrite(ledPin, stareLed);
    delay(100);
  }
}
Delay-ul de 100 de ms se introduce pentru a oferi timp utilizatorului sa apese pe buton si sa ia mana de pe buton, altfel pe perioada cat butonul este apasat, LED-ul se va stinge si aprinde in mod repetat, pana cand butonul este lasat liber.
Pentru a implementa cea de-a doua cerinta, vom adauga o noua variabila care sa retina starea butonului.
void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}

int stareLed = 0;
int stareButon = 0;

void loop() {
  if (digitalRead(7) == LOW && stareButon == 0) {
    stareButon = 1;
    stareLed = stareLed^1;//XOR
    digitalWrite(8, stareLed);
    delay(50);
  }

  if (digitalRead(7) == HIGH && stareButon == 1) {
    stareButon = 0;
    delay(50);
  }
}
Cele doua instructiuni delay sunt introduse pentru a rezolva problema debouncing-ului butonului, respectiv fluctuatia semnalului (primele cateva zeci de ms) citit pe pinul Arduino la trecerea butonului dintr-o stare in alta.
Pentru a evita folosirea instructiundea delay(), vom folosi instructiunea millis().
Millis() returneaza numarul de milisecunde de la pornirea Arduino. Astfel, la fiecare milisecunda contorul se incrementeaza. Pentru ca, daca Arduino functioneaza suficient de mult numarul de milisecunde va fi foarte mare, vom folosi o variabila de tip long pentru a retine numarul returnat de functia millis(). Astfel, putem stoca o valoare pana la 4,294,967,295 (care este 2^32 – 1).
void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(btnPin, INPUT_PULLUP);
}

int stareLed = 0;
int debouncingTime = 20; //20ms for debouncing purposes
unsigned long buttonTime = 0;
enum btn_state {INIT, START_PRESSED, PRESSED, START_DEPRESSED, LAST_STATE};
enum btn_state stareButon = INIT;

void loop() {

  //debouncing PRESSED_BUTTON state
  if (digitalRead(btnPin) == LOW && stareButon == INIT) {
    buttonTime = millis();
    stareButon = START_PRESSED;
  }

  if (digitalRead(btnPin) == LOW && stareButon == START_PRESSED && millis() - buttonTime > debouncingTime) {
    stareButon = PRESSED;
    stareLed = stareLed^1;
    digitalWrite(8, stareLed);
  }

  //debouncing RELEASED_BUTTON state
  if (digitalRead(btnPin) == HIGH && stareButon == PRESSED) {
    buttonTime = millis();
    stareButon = START_DEPRESSED;
  }

 if (digitalRead(btnPin) == HIGH && stareButon == START_DEPRESSED && millis() - buttonTime > debouncingTime) {
    stareButon = INIT;
  }
}


Electronica si Tinkercad.com

Folosind www.tinkercad.com poti proiecta si testa rapid circuite electronice cu/fara microcontrollere (Arduino).

De la circuite simple:

la circuite cu microcontroller:

la care poti chiar sa simulezi executia unui program:

Start tinkering!

Cum controlez un LED cu Arduino

Care sunt componentele necesare?

Un LED are doua terminale: anod (terminalul pozitiv) si catod (terminalul negativ). Ca sa identifici termialele uita-te cu atentie la LED:

  • anodul este terminalul mai lung, iar catodul este cel mai scurt;
  • in interiorul LEDului, anodul este mai mic, iar catodul are suprafata mai mare.

Intotdeauna cand folosesti un LED, trebuie sa adaugi mereu un rezistor in serie cu LED-ul. Foloseste un rezistor de 220 ohmi (verifica valoarea cu un multimetru). 

Evident, avem nevoie si de un Arduino. Nu este obligatoriu sa fie Arduino Uno.

Cum construim circuitul? 

Hai sa folosim pinul digital 3 pentru a controla LED-ul (puteam, la fel de bine, sa folosim si alti pini digitali in afara de 0 si 1).

La pinul 3 se leaga rezistorul, iar la rezistor se leaga anodul LED-ului. Catodul lui se leaga la GND (vezi ca exista pini de GND si pe partea opusa a placii Arduino – le poti folosi pe oricare).

Cum scriem codul?

In Arduino IDE, scriem urmatorul program care va aprinde LED-ul si il va stinge la interval de o secunda (se mai numeste blink.ino):

void setup() {
 pinMode(3, OUTPUT);
}
void loop() {
 digitalWrite(3, HIGH);
 delay(1000);
 digitalWrite(3, LOW);
 delay(1000);
}

Acesta este unul din primele programe pe care le putem face cu Arduino. Desi este simplu, invatam o multime de lucruri:

  • cele doua functii absolut necesare in orice program Arduino (setup() si loop()). In programe mai complexe este foarte posibil sa apara si alte functii;
  • cum sa declaram un anumit pin ca fiind INTRARE (INPUT) sau IESIRE (OUTPUT). In cazul nostru, pinul care controleaza LED-ul este OUTPUT, deoarece el va controla starea LED-ului;
  • cum modificam starea unui pin: folosind functia digitalWrite(), care are ca parametrii numarul pinului si starea lui (HIGH sau LOW);
  • cum introducem in program o pauza: delay() care primeste ca parametru valoarea pauzei in milisecunde (de ex: 1000 milisecunde = 1 secunda).
Cum modificam programul/circuitul?
  1. Cum modificam frecventa cu care se stinge si se aprinde LED-ul? Simplu, nu?
  2. Cum adaugam inca un LED? Realizati circuitul mai intai, apoi modificati si programul. Aprindeti LEDurile pe rand, apoi amandoua in acelasi timp.

 

Cum optimizez un program Arduino

Care este punctul de plecare?

Hai sa luam un program simplu: blink.ino.

void setup() {
    pinMode(13, OUTPUT);
}

void loop() {
  digitalWrite(13, HIGH);  
  delay(1000);             
  digitalWrite(13, LOW);    
  delay(1000);             
}

Sketch uses 928 bytes (2%) of program storage space. Max is 32,256 bytes.
Cum optimizez programul? 

Vrem sa eliminam folosirea instructiunii digitalWrite() (si pinMode()). 

Exista avantaje reale pentru folosirea lor: sunt usor de folosit, sunt portabile la alte microcontrollere din aceiasi familiei (daca vrem sa mutam programul de pe un Arduino Uno pe un Mega, de exemplu) si au implementata o metoda de a ne asigura ca nu scriem valori gresite. Dar, ca efect secundar, folosirea instructiunii digitalWrite() face codul mai lung si il incetineste. Cu cat? Vedem imediat!

Microcontrollerul pentru platforma Arduino este ATmega328p (acelasi pinout ca si ATmega168).

Pinul 13 apartine portului B al microcontrollerului – PB5. 

Fiecare port (B, C, D) are asociati cate trei registrii:

  • DDRx (DDRB, DDRC etc);
  • PORTx (PORTx etc);
  • PINx (PINB etc).

DDRx este registrul in care se defineste directia portului/pinului (Data Direction):

  • 0 inseamna INPUT;
  • 1 inseamna OUTPUT.

PORTx este registrul in care se scrie valoarea pinului (cand este declarat ca OUTPUT):

  • 1 = HIGH;
  • 0 = LOW.

PINx – contine valoarea pinilor portului (atunci cand pinul este declarat ca INPUT):

  • 1 = HIGH;
  • 0 = LOW.

Astfel, pentru a declara pinul 13 ca OUTPUT, putem scrie:

DDRB = DDRB | (0x01 << 5); //sau DDRB |= (0x01 << 5)

 

  1. Cateva explicatii:
  • dorim sa afectam doar pinul 5 al portului B, iar celeilalti pini sa ramana nemodificati;
  • tabela de adevar pentru operatorul logic OR este asa: daca ambii operanzi sunt egali cu 0, atunci rezultatul este 0; in rest, pentru orice alta combinatie (0 OR 1, 1 OR 1) rezultatul este 1. Sau, altfel spus, daca un operand este 0, atunci facand operatia cu OR ne va da valoarea celui de-al doilea operand (adica va lasa nemodificata valoarea celui de-al doilea operand). Daca, insa, operandul este 1, atunci operatia cu OR va da valoarea 1 indiferent de valoarea celui de-al doilea operand;
  • operatorul “<<” reprezinta shiftare bit cu un numar de pozitii la stanga;
  • astfel DDRB = DDRB | (0x01 << 5) este echivalenta cu DDRB = DDRB | 00100000. Aceasta inseamna ca doar bitul de pe pozitia 5 (unde bitul cel mai din dreapta este bitul de pe pozitia 0, iar bitul cel mai din stanga este bitul de pe pozitia 7) va primi valoarea 1, iar restul bitilor vor avea valoarea nemodificata.

 

In acelasi mod, ca sa inlocuim digitalWrite() cu o varianta mai “ieftina”:

PORTB |= (0x01 << 5); //pentru a aprinde LED
PORTB &= ~(0x01 << 5); //pentru a stinge LED

Observatii:

  • prima instructiune este la fel ca cea explicata mai sus. Doar registrul difera – PORTB in loc de DDRB. PORTB = PORTB | 00100000. Aceasta inseamna ca doar bitul de pe pozitia 5 (unde bitul cel mai din dreapta este bitul de pe pozitia 0, iar bitul cel mai din stanga este bitul de pe pozitia 7) va primi valoarea 1, iar restul bitilor vor avea valoarea nemodificata;
  • tabela de adevar pentru operatorul logic AND este asa: daca unul din operanzi este egal cu 0, atunci rezultatul este 0; daca ambii operanzi sunt 1, rezultatul este 1. Sau, altfel spus, daca un operand este 1, atunci facand operatia cu AND ne va da valoarea celui de-al doilea operand (adica va lasa nemodificata valoarea celui de-al doilea operand). Daca, insa, operandul este 0, atunci operatia cu AND va da valoarea 0 indiferent de valoarea celui de-al doilea operand;
  • operatorul “~” inseamna negare bit (0 trece in 1, iar 1 trece in 0);
  • astfel PORTB &= ~(0x01 << 5) este echivalenta cu PORTB = PORTB & ~(00100000) sau PORTB = PORTB & 11011111. Aceasta inseamna ca doar bitul de pe pozitia 5 (unde bitul cel mai din dreapta este bitul de pe pozitia 0, iar bitul cel mai din stanga este bitul de pe pozitia 7) va primi valoarea 0, iar restul bitilor vor avea valoarea nemodificata.

Daca rescriem programul cu instructiunile de mai sus, avem urmatoarul program:

void setup(){
     DDRB |= (0x01 << 5) ;//output
}

void loop(){
     PORTB |= (0x01 << 5);
     delay(1000);
     PORTB &= ~(0x01 << 5);
     delay(1000);
}
Sketch uses 644 bytes (1%) of program storage space. Max is 32,256 bytes.

Vedem o reducere a dimensiunii programului cu aprox 30% (de la 928 bytes la 644 bytes).

Exista imbunatatiri din punct de vedere al vitezei?

void setup() {
    pinMode(13, OUTPUT);
    Serial.begin(115200);
}

void loop() {
    Serial.println(micros());
    for(long i = 0; i < 500000; i++){
       digitalWrite(13, HIGH);
       digitalWrite(13, LOW);
       }
    Serial.println(micros());

}

Timpul intre doua treceri prin loop() este de 3.553.196 microsecunde.

void setup() {
    pinMode(13, OUTPUT);
    Serial.begin(115200);
}

void loop() {
    Serial.println(micros());
    for(long i = 0; i < 500000; i++){
       PORTB |= (1 << 5);
       PORTB &= ~(1 << 5);
      }
    Serial.println(micros());
}

Timpul intre doua treceri prin loop() este de 314.808 microsecunde.

Obtinem o imbunatatire a vitezei de mai mult de 10 ori!

Cum citesc un switch cu Arduino

Care sunt componentele necesare?

Un switch (un buton cu revenire, adica un buton care are o lamela elastica ce il aduce la pozitia initiala dupa ce a fost apasat).

Un rezistor de 10kOhm (verifica valoarea cu un multimetru). 

Evident, avem nevoie si de un Arduino. Nu este obligatoriu sa fie Arduino Uno.