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;
  }
}


Situri web de interes

Scratch – www.scratch.mit.edu

Descriere: platforma de dezvoltare programe, jocuri povesti interactive etc. Contul este necesar pentru a salva programele realizate pe serverele Scratch. Exista si aplicatie pentru Windows.

Code.org – www.code.org

Descriere: platforma pentru sute de lectii de programare. Aici (https://studio.code.org/s/courseb-2020/stage/3/puzzle/1) este un bun loc de inceput pentru cei in clasele primare. Contul nu este neaparat necesar.

Tinkercad – www.tinkercad.com

Descriere: platforma pentru proiectare si testare. Exista un modul pentru desen 3D si unul pentru circuite electronice. Aici putem testa circuite simple sau mai complexe, cu si fara controllere (Arduino sau micro:bit). Contul este necesar.

Microbit – https://microbit.org/code/

Descriere: dezvoltare programe pentru micro:bit (MakeCode sau Python). Nu este necesar un cont.

Codeguppy- https://codeguppy.com/

Descriere: site excelent cu o multime de tutoriale de programare. Limbajul de programare se numeste JavaScript si este principalul limbaj de programare penturu a construi jocuri web. Este recomandat sa faceti un cont.

Aplicatii avansate

App Inventor – https://appinventor.mit.edu/

Descriere: dezvoltare aplicatii care ruleaza pe Android. Limabajul folosit este asemanator cu Scratch, dar mult mai complex (vezi Blockly mai jos). Vom folosi platforma pentru a dezvolta aplicatii care lucreaza impreuna cu Arduino. Nu exista cont.

Github – https://github.com/

Descriere: un sistem de salvare si management al programelor dezvoltate. Contul este necesar.

OnlineGDB – https://www.onlinegdb.com/

Descriere: compilator online pentru zeci de limbaje de programare (inclusiv C, C++ si Python). Daca doriti sa testati rapid un program scris in C++ si nu doriti sa mai instalati un intreg compilator cu toate programele adiacente (de ex Codeblocks), puteti folosi onlinegdb: selectati limbajul de programare, scrieti codul si testati . Contul este necesar daca vreti sa va salvati programele online.

Mentiuni

Edublocks https://app.edublocks.org/ – dezvolta programe pentru micro:bit intr-un mod care combina Python si blocuri.

Blockly https://developers.google.com/blockly – limbaj de programare grafic dezvoltat de Google. Este folosit mult si in manualele de informatica de clasele 5 – 8.

Snap!https://snap.berkeley.edu/snap/snap.html – alternativa la Scratch. Mai puternic, dar putin mai dificil de folosit.

Mu https://codewith.mu/en/ – editor de Python pentru Windows.

Aici aveti documentul pe care-l puteti printa pentru a completa datele de logare.

Programare Scratch – control miscare personaje

Pentru cei mai mici, dar nu numai, care doresc sa invete programare, Scratch este o excelenta optiune. Pe langa faptul ca poti face jocuri complexe in Scratch, exista module ce interfateaza cu diferite platforme de robotica: Arduino, micro:bit, LEGO.

Pentru a va ajuta in aceasta calatorie in lumea programarii, am pregatit cateva tutoriale video. Aceasta lista va continua sa creasca, asa ca va puteti intoarce aici ca sa le vedeti pe celelalte.

Primele trei tutoriale sunt despre modul in care cotrolam miscarea unui personaj in Scratch.

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!

Multi Function Arduino Shield

Elemente programabile:

  • 4 LEDuri 
  • 3 butoane 
  • potentiometru 10KOhmi
  • un display cu 7-segmente (4 digiti) controlat de doua circuite interate 74HC595 inseriate
  • buzzer
  • interfete pentru senzori si alte module
  • 4 pini liberi – D5, D6, D9, A5

 

Leduri

  • D1 – conectat la pinul 13 Arduino
  • D2 – conectat la pinul 12 Arduino 
  • D3 – conectat la pinul 11 Arduino
  • D4 – conectat la pinul 10 Arduino

 

Aceste LED-uri sunt conectate in asa fel incat, pentru a le aprinde, trebuie sa scriem “0” logic (sau LOW sau 0V) la pinul respectiv.

De exemplu, pentru a aprinde LED D3:

int led = 11;

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

void loop(){
    digitalWrite(led, LOW);
}

Pentru a-l stinge: digitalWrite(led, HIGH);

 

Switch-uri

  • Switch 1 (S1) – pinul A1 Arduino
  • Switch 2 (S2) – pinul A2 Arduino
  • Switch 3 (S3) – pinul A3 Arduino

Aceste butoane (switch-uri) sunt in asa fel conectate incat, daca apas pe unul din ele, tensiunea care se va citi pe pinul respectiv va fi de 0V (sau LOW). Daca este neapasat, atunci la pinul respectiv se va citi valoare de 5V (sau HIGH).

int led = 12;

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

void loop(){
   if(digitalRead(A1) == LOW){
       digitalWrite(led, LOW);
   }
}

Potentiometrul

Potentiometrul este legat la pinul A0 al Arduino. Programul de mai jos citeste valoarea de pe pinul A0 (o valoare intre 0 si 1023) si o afiseaza pe monitorul serial.

void setup(){
     Serial.begin(9600);
}

void loop()
     Serial.println(analogRead(A0));
    delay(200);
}
Display

Display-ul este mai complicat (o prezentare pe larg se face in cadrul cursului). Ceea ce comanda display-ul (adica LED-urile din display) sunt niste circuite integrate situate chiar sub display (daca va uitati cu atentie veti vedea scris pe ele “74HC595”).

Arduino comunica cu integratele folosind trei pini:

  • pinul 8 – prin care transmite bitii care aprind LED-urile (DATA)
  • pinul 7 – prin care trimite un semnal de ceas (pentru fiecare bit, este un semnal de ceas) (CLOCK)
  • pinul 4 – pinul care marcheaza inceperea si finalizarea transmisiei datelor catre integrat. (LATCH)
int latchPin = 4;
int dataPin = 8;
int clockPin = 7;

void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
}

void loop() {
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, 0b11111001);
  shiftOut(dataPin, clockPin, MSBFIRST, 0b10000000);
  digitalWrite(latchPin, HIGH);
}

Buzzer

Buzzer-ul (sau speaker) este conectat la pinul digital 3. Programul de mai jos porneste si opreste buzzer-ul. Atentie: zgomotul produs este puternic (si enervant!).

int buzzerPin = 3;

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

void loop() {
   digitalWrite(buzzerPin, HIGH);
   delay(1000);
   digitalWrite(buzzerPin, LOW);
   delay(1000);
}
Proiecte pentru Multifunction Shield
  • un cronometru cu functiile START/STOP (cu switch S1), PAUSE (cu switch S2), RESET (cu switch S3). Se poate modifica astfel incat sa se afiseze si zecimile de secunde.
  • un cronometru pentru antrenament HIIT (High Intensity Interval Training). Ce inseamna asta? Sa poti seta un numar de serii, cu o perioada de lucru si o perioda de pauza. De exemplu: setez ca trebuie sa lucrez 10 serii, fiecare serie are 30 secunde urmata de 10 secunde de pauza, si tot asa pentru 10 serii.
  • un ceas digital cu ora, data, alarma. Ceasul are posibilitatea de a seta ora, data si alarma.
  • o statie meteo cu un senzor DHT-11. Pe durata unei secunde se afiseaza temperatura, iar urmatoarea secunda se afiseaa umiditatea. Si apoi se repeta.
  • joc „Simon says” (se pot folosi atat LED-urile de pe placa, cat si trei LED-uri externe). La fiecare repriza se afiseaza o secventa de n LED-uri. Apoi butoanele trebuie apasate in ordinea succesiunii LED-urilor. Daca este corect, se trece la etapa urmatoare, unde se aprind n+1 LED-uri. De exemplu, daca la etapa curenta se aprind LED-urile astfel: LED1, LED3, LED3, LED2, LED1, atunci trebuie apasate butoanele in urmatoarea secventa: S1, S3, S3, S2, S1.
  • un cod secret: daca se apasa butoanele intr-o anumita ordine, atunci se afiseaza un mesaj de succes.
  • de programat o functionalitate pentru apasare scurta a butonului si una pentru apasare lunga.

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 citesc un switch cu micro:bit si Micropython

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 sau 10000 de ohmi (verifica valoarea cu un multimetru sau verifica codul de culori ca in imagine). 

Evident, avem nevoie si de un micro:bit.

Cum construim circuitul? 

Hai sa folosim pinul 0 pentru a citi switch-ul.

La pinul 0 se leaga rezistorul si un terminal al switch-ului, iar rezistorul se leaga la 3V3. Celalalt terminal al switch-ului se leaga la GND.

Observatie: ca sa fim siguri ca selectam terminalele corecte la switch, atunci alegeti terminalele din doua colturi opuse (in diagonala).

Cum scriem codul?

In editorul de Micropython pentru micro:bit (https://python.microbit.org/v/1.1) sau in editorul offline Mu (https://codewith.mu/), scriem urmatorul program care va aprinde LED-ul din mijlocul display-ului daca buton este apasat si il stinge daca nu este apasat:

from microbit import *

while True:
   if p0.read_digital() == 0:
       display.set_pixel(2, 2, 9)
   else:
       display.set_pixel(2, 2, 0)

Desi este simplu, invatam o multime de lucruri:

  • prima linie (from microbit import *) este necesara pentru toate programele Micropython pentru micro:bit;
  • cum citim starea unui pin: p0.read_digital(). Aceasta returneaza doua valori: 1 (3.3V) sau 0 (0V);
  • daca butonul nu este apasat, la pinul 0 se citeste valoarea 1. Daca butonul este apasat, atunci va citi valoarea 0;
  • fiti atenti la spatiile goale din fata liniilor de cod! Daca modificam spatiile goale in alt mod decat le aseaza editorul, atunci vom avea eroare;
  • pentru a citi starea butoanelor de pe micro:bit (butoanele A si B) se foloseste instructiunea button_a.is_pressed().

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.