LABORATORUL 07

Aplicații practice integrate

Durată: 4 ore (2 ședințe) Suport: Capitolele 10–11 Integrează: Lab. 1–6 PDF îndrumar EN English version

Ultima lucrare combină toate noțiunile studiate în aplicații complete: achiziție de la senzori, comandă de actuatoare de putere, comunicație wireless și măsurarea distanței. Fiecare aplicație folosește cel puțin trei dintre modulele studiate anterior.

1Obiectivele lucrării

  • Integrarea perifericelor studiate într-o aplicație funcțională
  • Realizarea unei stații meteo cu afișarea datelor
  • Comanda unui motor de curent continuu cu reglaj de turație și sens
  • Transmiterea datelor între două plăci prin modul radio
  • Măsurarea distanței cu senzor ultrasonic și compensarea cu temperatura
  • Structurarea unui program complex ca automat de stări

2Materiale necesare

  • 2 Arduino Uno
  • 1 Senzor DHT11 / DHT22
  • 1 Motor DC + punte H L298N
  • 2 Module nRF24L01
  • 1 Senzor HC-SR04
  • 1 Potențiometru 10 kΩ
  • 1 Sursă externă 7–12 V
  • 2 Condensatoare 10 µF

3Aplicația 1 - Stație meteo

Senzorul DHT măsoară temperatura și umiditatea, transmițând datele printr-un protocol propriu, pe un singur fir. Temporizările sunt foarte stricte (de ordinul microsecundelor), motiv pentru care se folosește o bibliotecă dedicată.

ParametruDHT11DHT22
Domeniu temperatură0…50 °C−40…80 °C
Precizie temperatură±2 °C±0,5 °C
Domeniu umiditate20…90 %0…100 %
Rezoluție1 °C / 1 %0,1 °C / 0,1 %
Interval minim între citiri1 s2 s
Alimentare3,3–5,5 V3,3–6 V
Conectarea senzorului DHT22 la placa Arduino Uno
Fig. 2 - Conectarea senzorului DHT22 la placa Arduino Uno. Fig. 7.2 din îndrumar
Intervalul minim nu este opționalCitirea mai deasă decât intervalul specificat returnează NaN sau valoarea anterioară, memorată intern. Verificați întotdeauna cu isnan() înainte de a folosi rezultatul.
statie_meteo.ino
#include <DHT.h>

#define PIN_DHT  7
#define TIP_DHT  DHT11

DHT senzor(PIN_DHT, TIP_DHT);

unsigned long ultimaCitire = 0;
const unsigned long INTERVAL = 2000;   // respecta intervalul minim

float tMin = 999, tMax = -999;         // valori extreme de la pornire

void setup() {
  Serial.begin(9600);
  senzor.begin();
  Serial.println("Statie meteo pornita");
}

void loop() {
  if (millis() - ultimaCitire < INTERVAL) return;   // temporizare neblocanta
  ultimaCitire = millis();

  float umiditate   = senzor.readHumidity();
  float temperatura = senzor.readTemperature();

  if (isnan(umiditate) || isnan(temperatura)) {     // VERIFICARE obligatorie
    Serial.println("Eroare la citirea senzorului!");
    return;
  }

  if (temperatura < tMin) tMin = temperatura;
  if (temperatura > tMax) tMax = temperatura;

  float indiceCaldura = senzor.computeHeatIndex(temperatura, umiditate, false);

  Serial.print("T = ");        Serial.print(temperatura, 1);
  Serial.print(" C   RH = ");  Serial.print(umiditate, 1);
  Serial.print(" %   resimtit = "); Serial.print(indiceCaldura, 1);
  Serial.print(" C   [min ");  Serial.print(tMin, 1);
  Serial.print(" / max ");     Serial.print(tMax, 1);
  Serial.println("]");
}

4Aplicația 2 - Control motor de curent continuu

Un motor nu poate fi alimentat direct de la un pin al microcontrolerului: consumă sute de miliamperi, în timp ce un pin livrează maximum 40 mA. Se folosește o punte H, care permite atât reglarea turației prin PWM, cât și inversarea sensului de rotație.

Arduino Punte H L298N D9 (PWM) → ENA D4 → IN1 D5 → IN2 GND comun M sursă externă 7–12 V motorul primește curent de la sursa externă, NU de la Arduino
Fig. 4 - Comanda motorului prin punte H. Arduino furnizează doar semnalele logice; curentul de putere vine de la sursa externă.
IN1IN2ENA (PWM)Efect
00oriceoprit - rotire liberă (roată liberă)
100…255rotire înainte, cu turația dată de PWM
010…255rotire înapoi
11oricefrânare activă (scurtcircuitarea bornelor)
ObligatoriuAlimentați motorul de la o sursă externă, nu de la portul USB. Masa sursei externe trebuie legată la masa plăcii Arduino, altfel semnalele de comandă nu au referință comună și puntea nu comută corect.
De ce apare o valoare minimă de pornireSub un anumit factor de umplere, cuplul dezvoltat nu învinge frecarea statică: motorul doar zumzăie și se încălzește, fără să se rotească. Această valoare se determină experimental și este de regulă între 40 și 80 din 255.

5Simulator de turație

Reglați factorul de umplere și urmăriți tensiunea medie aplicată motorului, precum și forma de undă rezultată. Comparați valoarea afișată cu pragul minim de pornire pe care îl veți determina experimental.

PWM - factor de umplere, tensiune medie și formă de undă
control_motor_dc.ino
const uint8_t IN1 = 4, IN2 = 5, ENA = 9;   // ENA trebuie sa fie pin PWM
const uint8_t PIN_POT = A0;
const uint8_t DUTY_MINIM = 60;             // sub aceasta valoare motorul nu porneste

void setSens(bool inainte) {
  digitalWrite(IN1, inainte ? HIGH : LOW);
  digitalWrite(IN2, inainte ? LOW  : HIGH);
}

void opreste() {
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  analogWrite(ENA, 0);
}

void setup() {
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(ENA, OUTPUT);
  Serial.begin(9600);
  opreste();
}

void loop() {
  uint16_t brut = analogRead(PIN_POT);

  // zona moarta la mijloc: sub 490 inapoi, peste 530 inainte, intre ele oprit
  if (brut < 490) {
    setSens(false);
    uint8_t duty = map(brut, 489, 0, DUTY_MINIM, 255);
    analogWrite(ENA, duty);
    Serial.print("INAPOI  duty="); Serial.println(duty);
  }
  else if (brut > 530) {
    setSens(true);
    uint8_t duty = map(brut, 531, 1023, DUTY_MINIM, 255);
    analogWrite(ENA, duty);
    Serial.print("INAINTE duty="); Serial.println(duty);
  }
  else {
    opreste();
    Serial.println("OPRIT");
  }

  delay(100);
}

6Aplicația 3 - Comunicație radio

Modulul nRF24L01 lucrează în banda de 2,4 GHz și comunică cu microcontrolerul prin magistrala SPI. Permite legături bidirecționale pe distanțe de zeci de metri și confirmă automat recepția pachetelor.

Pin nRF24L01Arduino UnoRol
VCC3,3 Valimentare - nu 5 V!
GNDGNDmasă
CED9activare emisie/recepție
CSND10selecție SPI (Slave Select)
SCKD13ceas SPI
MOSID11date către modul
MISOD12date de la modul
Alimentarea este cea mai frecventă cauză de nefuncționareModulul are vârfuri de curent de peste 100 mA la emisie, iar regulatorul de 3,3 V al plăcii Arduino livrează doar 50 mA. Adăugați obligatoriu un condensator de 10 µF direct pe pinii de alimentare ai modulului. Fără el, comunicația funcționează intermitent sau deloc.
Schema de comunicație wireless între două module Arduino
Fig. 3 - Montajul complet pentru comunicația radio între două plăci: emițător (stânga) și receptor (dreapta). Fig. 7.10 din îndrumar
Pinii de date tolerează 5 VDeși alimentarea trebuie să fie 3,3 V, intrările SPI ale modulului acceptă niveluri de 5 V, deci nu este nevoie de convertor de nivel pentru SCK, MOSI, CE și CSN.
radio_emitator.ino
#include <SPI.h>
#include <RF24.h>

RF24 radio(9, 10);                        // CE, CSN
const byte adresa[6] = "NOD01";

struct Pachet {                           // structura trebuie IDENTICA pe ambele placi
  uint16_t contor;
  int16_t  temperatura;                   // in zecimi de grad, ca intreg
  bool     buton;
};

Pachet date = {0, 0, false};

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT_PULLUP);

  radio.begin();
  radio.openWritingPipe(adresa);
  radio.setPALevel(RF24_PA_LOW);          // putere redusa pentru teste pe masa
  radio.setDataRate(RF24_250KBPS);        // viteza mica -> raza mai mare
  radio.stopListening();                  // mod emisie
}

void loop() {
  date.contor++;
  date.temperatura = (int16_t)(analogRead(A0) * 0.489);   // exemplu
  date.buton = !digitalRead(2);

  bool trimis = radio.write(&date, sizeof(date));

  Serial.print("Pachet ");  Serial.print(date.contor);
  Serial.println(trimis ? " - confirmat" : " - ESUAT");

  delay(500);
}
radio_receptor.ino
#include <SPI.h>
#include <RF24.h>

RF24 radio(9, 10);
const byte adresa[6] = "NOD01";

struct Pachet {
  uint16_t contor;
  int16_t  temperatura;
  bool     buton;
};

Pachet date;
uint16_t ultimulContor = 0;

void setup() {
  Serial.begin(9600);
  pinMode(3, OUTPUT);                     // LED pe alt pin: D9-D13 sunt ocupati de SPI

  radio.begin();
  radio.openReadingPipe(0, adresa);
  radio.setPALevel(RF24_PA_LOW);
  radio.setDataRate(RF24_250KBPS);
  radio.startListening();                 // mod receptie
}

void loop() {
  if (radio.available()) {
    radio.read(&date, sizeof(date));

    digitalWrite(3, date.buton);

    // detectarea pachetelor pierdute
    if (date.contor != ultimulContor + 1 && ultimulContor != 0) {
      Serial.print("!! pierdute ");
      Serial.print(date.contor - ultimulContor - 1);
      Serial.println(" pachete");
    }
    ultimulContor = date.contor;

    Serial.print("Pachet #"); Serial.print(date.contor);
    Serial.print("  T = ");   Serial.print(date.temperatura / 10.0, 1);
    Serial.print(" C  buton = "); Serial.println(date.buton ? "apasat" : "liber");
  }
}

7Aplicația 4 - Măsurarea distanței

Senzorul HC-SR04 emite un impuls ultrasonic de 40 kHz și măsoară timpul până la întoarcerea ecoului. Cunoscând viteza sunetului, se calculează distanța până la obstacol.

Arduino HC-SR04 5V → VCC D9 → TRIG D10 ← ECHO GND obstacol
Fig. 7 - Un impuls de 10 µs pe TRIG declanșează măsurarea; durata impulsului de pe ECHO este proporțională cu distanța dus-întors.
Cronograma semnalelor Trig și Echo la senzorul cu ultrasunete
Fig. 5 - Principiul de funcționare: impuls Trig de 10 µs, urmat de un tren de 8 cicluri ultrasonice, iar durata semnalului Echo este proporțională cu distanța. Fig. 7.13 din îndrumar
Montaj practic pentru modulul de măsurare a distanței
Fig. 6 - Montajul practic pentru modulul de măsurare a distanței. Fig. 7.14 din îndrumar
ParametruValoareObservații
Domeniu de măsurare2 cm … 4 msub 2 cm ecoul se suprapune peste emisie
Precizie±3 mmîn condiții ideale, suprafață plană perpendiculară
Unghi de măsurare~15°obiectele oblice pot reflecta ecoul în altă direcție
Durată impuls TRIG10 µsminim necesar pentru declanșare
Frecvență maximă de citire~20 Hzse așteaptă stingerea ecourilor reziduale

8Calculator de distanță

Modificați durata ecoului și temperatura aerului. Observați diferența dintre formula exactă și cea simplificată (t/58) - la distanțe mari și temperaturi extreme, eroarea devine semnificativă.

Durata ecoului → distanță, cu compensare de temperatură
masurare_distanta.ino
const uint8_t PIN_TRIG = 9;
const uint8_t PIN_ECHO = 10;

float vitezaSunet(float tempC) {
  return (331.3 + 0.606 * tempC) / 10000.0;   // cm/us
}

float masoaraDistanta(float tempC) {
  digitalWrite(PIN_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH);
  delayMicroseconds(10);                       // impulsul de declansare
  digitalWrite(PIN_TRIG, LOW);

  unsigned long durata = pulseIn(PIN_ECHO, HIGH, 30000UL);   // limita 30 ms

  if (durata == 0) return -1.0;                // fara ecou

  return (durata * vitezaSunet(tempC)) / 2.0;  // dus-intors -> impartit la 2
}

void setup() {
  Serial.begin(9600);
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);
  pinMode(3, OUTPUT);
}

void loop() {
  float d = masoaraDistanta(20.0);

  if (d < 0) {
    Serial.println("Fara ecou - obiect prea departe");
    digitalWrite(3, LOW);
  } else {
    Serial.print("Distanta = "); Serial.print(d, 1); Serial.println(" cm");
    digitalWrite(3, d < 20.0);                 // avertizare sub 20 cm
  }

  delay(100);                                   // evita interferenta intre masuratori
}
De ce contează temperaturaViteza sunetului variază cu aproximativ 0,6 m/s pe grad Celsius. Între 0 °C și 30 °C diferența ajunge la aproape 6%, deci o măsurătoare la 2 m poate greși cu peste 10 cm dacă se folosește o valoare fixă. Combinând senzorul HC-SR04 cu DHT-ul din prima aplicație, compensarea se face automat.

9Integrarea aplicațiilor

Un program care face mai multe lucruri simultan nu poate folosi delay(). Structura recomandată combină temporizarea neblocantă cu un automat de stări:

ModulPerioadăMetodă
Citire senzor DHT2000 msmillis() neblocant
Măsurare distanță100 msmillis() neblocant
Comandă motorla fiecare iterațiedirect în loop()
Buton de oprireimediatîntrerupere (Lab. 3)
Transmisie radio500 msmillis() neblocant
schelet_multitasking.ino
/* Cele patru sarcini sunt functiile scrise la aplicatiile de mai sus.
   Aici sunt lasate goale, ca sa se vada limpede doar scheletul: */
void citesteDistanta()    { /* aplicatia 4 */ }
void citesteTemperatura() { /* aplicatia 1 */ }
void trimiteTelemetrie()  { /* aplicatia 3 */ }
void actualizeazaMotorul(){ /* aplicatia 2 */ }
void opresteMotorul()     { /* opreste PWM-ul motorului */ }

unsigned long tDHT = 0, tDist = 0, tRadio = 0;
const unsigned long P_DHT = 2000, P_DIST = 100, P_RADIO = 500;

volatile bool oprireUrgenta = false;

void butonOprire() { oprireUrgenta = true; }     // ISR foarte scurt

void setup() {
  attachInterrupt(digitalPinToInterrupt(2), butonOprire, FALLING);
}

void loop() {
  unsigned long acum = millis();

  if (oprireUrgenta) {                            // are prioritate maxima
    opresteMotorul();
    return;
  }

  if (acum - tDist >= P_DIST) {
    tDist = acum;
    citesteDistanta();
  }

  if (acum - tDHT >= P_DHT) {
    tDHT = acum;
    citesteTemperatura();
  }

  if (acum - tRadio >= P_RADIO) {
    tRadio = acum;
    trimiteTelemetrie();
  }

  actualizeazaMotorul();                          // la fiecare iteratie
}

10Simulator: automat de stări

Un robot care evită obstacole nu poate fi scris cu delay(): ar rămâne orb exact când are nevoie de senzor. Urmăriți cum automatul de stări rezolvă problema, comutând între stări în funcție de distanță și de timp.

Robot cu evitarea obstacolelor - automat de stări
Avantajul automatului de stăriÎn fiecare iterație a lui loop() senzorul este citit și starea este reevaluată. Nu există niciun moment în care robotul „nu vede". Cu delay(600) în locul stării INAPOI, robotul ar fi complet orb timp de 0,6 secunde.

11Montaj interactiv

Ultimele două montaje pun laolaltă tot ce ați învățat: citirea unui senzor, decizia, comanda unei ieșiri și execuția mai multor sarcini în paralel, fără să blocați programul.

Măsurarea distanței cu senzor cu ultrasunete
const int PIN_TRIG = 11;
const int PIN_ECHO = 12;
const int PIN_LED  = 13;

void setup() {
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);
  pinMode(PIN_LED,  OUTPUT);
  Serial.begin(9600);
}

void loop() {
  /* impulsul de declansare: 10 microsecunde */
  digitalWrite(PIN_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(PIN_TRIG, LOW);

  /* durata ecoului, in microsecunde */
  unsigned long durata = pulseIn(PIN_ECHO, HIGH);

  /* sunetul merge cu 343 m/s = 0,0343 cm/us, dus-intors */
  float distanta = durata * 0.0343 / 2.0;

  Serial.print("ecou = ");
  Serial.print(durata);
  Serial.print(" us   ->   ");
  Serial.print(distanta);
  Serial.println(" cm");

  digitalWrite(PIN_LED, distanta < 20 ? HIGH : LOW);
  delay(400);
}
De încercatMișcați cursorul „obstacol”: cardul vă arată ce durată de ecou produce distanța aleasă, iar programul o transformă înapoi în centimetri. Împărțirea la 2 este esențială - sunetul parcurge drumul de două ori. Scoateți-o și veți vedea imediat că distanțele se dublează.
Exercițiu - trei sarcini în paralel, fără delay
unsigned long tRapid = 0, tLent = 0, tSenzor = 0;

const unsigned long P_RAPID  = 200;
const unsigned long P_LENT   = 1000;
const unsigned long P_SENZOR = 1500;

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

void loop() {
  unsigned long acum = millis();

  if (acum - tRapid >= P_RAPID) {
    tRapid = acum;
    digitalWrite(13, !digitalRead(13));
  }

  /* Completati dupa acelasi tipar:
       - la fiecare P_LENT comutati LED-ul de pe pinul 12
       - la fiecare P_SENZOR cititi A0 si afisati valoarea pe serial
     Nu folositi delay() nicaieri: cele trei sarcini trebuie sa
     mearga independent una de alta. */
}
De încercatComparați cele două trasee de pe osciloscop: ele trebuie să meargă complet independent. Cu delay() așa ceva este imposibil - o pauză de o secundă ar opri și clipirea rapidă. Tiparul millis() este baza oricărui program serios de microcontroler.

12Sarcini de lucru

  • Realizați stația meteo și afișați temperatura, umiditatea și indicele de căldură pe serial.
  • Adăugați înregistrarea valorilor minime și maxime de la pornire.
  • Montați puntea H și comandați motorul cu turație și sens variabile de la potențiometru.
  • Determinați experimental factorul de umplere minim la care motorul începe să se rotească.
  • Măsurați curentul absorbit de motor la diferite valori PWM și corelați-l cu turația.
  • Realizați legătura radio între două plăci și transmiteți datele stației meteo.
  • Detectați și afișați pachetele pierdute, folosind contorul din structură.
  • Implementați măsurarea distanței cu compensare de temperatură de la senzorul DHT.
  • Combinați două aplicații: senzorul de distanță oprește motorul la detectarea unui obstacol.
  • Rescrieți un program cu delay() sub formă de automat de stări și comparați reactivitatea.

13Proiect final

Robot mobil cu evitarea obstacolelorRealizați un sistem complet care combină toate noțiunile studiate în semestru:
  • două motoare comandate prin punte H, cu turație reglată prin PWM (Lab. 4)
  • senzor HC-SR04 pentru detectarea obstacolelor (Lab. 5, Lab. 7)
  • buton de pornire/oprire de urgență tratat prin întrerupere (Lab. 3)
  • LED-uri de stare comandate prin acces direct la regiștri (Lab. 2)
  • telemetrie transmisă pe serial sau radio (Lab. 6, Lab. 7)
  • structură de automat de stări, fără niciun delay() în bucla principală

Documentați proiectul cu schema electrică, codul comentat și un tabel cu rezultatele testelor: distanța de oprire în funcție de viteză, pentru cel puțin trei valori PWM diferite.

14Întrebări de verificare

15Resurse