This shows you the differences between two versions of the page.
|
pm:prj2026:cezar.zlatea:ana_maria.focsa [2026/05/16 22:17] ana_maria.focsa [Descriere componente] |
pm:prj2026:cezar.zlatea:ana_maria.focsa [2026/05/23 02:03] (current) ana_maria.focsa [Configurarea senzorului de umiditate] |
||
|---|---|---|---|
| Line 226: | Line 226: | ||
| ===== Software Design ===== | ===== Software Design ===== | ||
| + | |||
| ==== Mediu de dezvoltare ==== | ==== Mediu de dezvoltare ==== | ||
| - | Firmware-ul proiectului va fi dezvoltat în: | + | Firmware-ul proiectului este dezvoltat în: |
| * PlatformIO în Visual Studio Code | * PlatformIO în Visual Studio Code | ||
| - | Codul va fi scris în C/C++ pentru Arduino. | + | Codul este scris în C/C++ pentru Arduino UNO R3. |
| + | |||
| + | Proiectul este organizat pe mai multe fișiere, pentru a separa logica aplicației de partea de configurare hardware și de calculul statisticilor. | ||
| + | |||
| + | ==== Structura proiectului ==== | ||
| + | |||
| + | <code> | ||
| + | soil_moisture_monitor/ | ||
| + | │ | ||
| + | ├── platformio.ini | ||
| + | │ | ||
| + | ├── include/ | ||
| + | │ ├── config.h | ||
| + | │ ├── hardware.h | ||
| + | │ └── statistics.h | ||
| + | │ | ||
| + | ├── src/ | ||
| + | │ ├── main.cpp | ||
| + | │ ├── hardware.cpp | ||
| + | │ └── statistics.cpp | ||
| + | │ | ||
| + | ├── README.md | ||
| + | └── ChangeLog.md | ||
| + | </code> | ||
| + | |||
| + | ==== Fișiere implementate ==== | ||
| + | |||
| + | ^ Fișier ^ Rol ^ | ||
| + | | platformio.ini | Conține configurația PlatformIO pentru placa Arduino UNO și librăriile externe folosite. | | ||
| + | | include/config.h | Conține pinii folosiți, adresa LCD-ului, valorile de calibrare pentru senzor, pragul de umiditate și intervalele de timp pentru măsurare, logare și transmitere Bluetooth. | | ||
| + | | include/hardware.h | Declară funcțiile folosite pentru interacțiunea cu hardware-ul: LCD, RTC, microSD, Bluetooth, LED, buzzer, buton și senzor. | | ||
| + | | include/statistics.h | Definește structura de date pentru statistici și funcțiile asociate. | | ||
| + | | src/main.cpp | Conține logica principală a aplicației și bucla principală de execuție. | | ||
| + | | src/hardware.cpp | Implementează inițializarea și controlul componentelor hardware. | | ||
| + | | src/statistics.cpp | Implementează funcțiile pentru calculul statisticilor. | | ||
| + | | README.md | Conține descrierea proiectului, conexiunile și instrucțiuni de rulare. | | ||
| + | | ChangeLog.md | Conține istoricul modificărilor proiectului. | | ||
| ==== Librării folosite ==== | ==== Librării folosite ==== | ||
| ^ Librărie ^ Rol ^ | ^ Librărie ^ Rol ^ | ||
| - | | Wire.h | Comunicare I2C | | + | | Arduino.h | Funcționalități de bază pentru Arduino. | |
| - | | LiquidCrystal_I2C.h | Control LCD 16x2 cu I2C | | + | | Wire.h | Comunicare I2C pentru LCD și RTC DS3231. | |
| - | | RTClib.h | Citirea datei și orei de la DS3231 | | + | | LiquidCrystal_I2C.h | Controlul display-ului LCD 16x2 cu interfață I2C. | |
| - | | SPI.h | Comunicare SPI pentru modulul microSD | | + | | RTClib.h | Citirea datei și orei de la modulul RTC DS3231. | |
| - | | SD.h | Scriere și citire fișiere pe cardul microSD | | + | | SPI.h | Comunicare SPI pentru modulul microSD. | |
| - | | SoftwareSerial.h | Comunicare serială cu modulul Bluetooth HC-05 | | + | | SD.h | Scrierea datelor pe cardul microSD. | |
| + | | SoftwareSerial.h | Comunicare serială software cu modulul Bluetooth HC-05. | | ||
| + | |||
| + | ==== Configurarea pinilor ==== | ||
| + | |||
| + | Pinii proiectului sunt definiți în fișierul `config.h`. | ||
| + | |||
| + | <code c> | ||
| + | const uint8_t PIN_SOIL_SENSOR = A0; | ||
| + | const uint8_t PIN_BUTTON = 2; | ||
| + | |||
| + | const uint8_t PIN_BT_RX = 5; | ||
| + | const uint8_t PIN_BT_TX = 6; | ||
| + | |||
| + | const uint8_t PIN_LED = 7; | ||
| + | const uint8_t PIN_BUZZER = 8; | ||
| + | |||
| + | const uint8_t PIN_SD_CS = 10; | ||
| + | </code> | ||
| + | |||
| + | Pinii A4 și A5 sunt folosiți automat pentru comunicația I2C: | ||
| + | |||
| + | <code> | ||
| + | A4 -> SDA | ||
| + | A5 -> SCL | ||
| + | </code> | ||
| + | |||
| + | Pentru modulul microSD sunt folosiți pinii SPI ai plăcii Arduino UNO: | ||
| + | |||
| + | <code> | ||
| + | D10 -> CS | ||
| + | D11 -> MOSI | ||
| + | D12 -> MISO | ||
| + | D13 -> SCK | ||
| + | </code> | ||
| + | |||
| + | ==== Configurarea senzorului de umiditate ==== | ||
| + | |||
| + | Valorile de calibrare ale senzorului sunt definite în `config.h`: | ||
| + | |||
| + | <code c> | ||
| + | const int SENSOR_DRY_ADC = 750; | ||
| + | const int SENSOR_WET_ADC = 350; | ||
| + | const int HUMIDITY_THRESHOLD_PERCENT = 35; | ||
| + | </code> | ||
| + | |||
| + | Conversia valorii ADC în procent se face în funcția `convertAdcToHumidityPercent()`: | ||
| + | |||
| + | <code c> | ||
| + | int convertAdcToHumidityPercent(int adcValue) { | ||
| + | long humidity = map(adcValue, SENSOR_DRY_ADC, SENSOR_WET_ADC, 0, 100); | ||
| + | humidity = constrain(humidity, 0, 100); | ||
| + | return (int)humidity; | ||
| + | } | ||
| + | </code> | ||
| - | ==== Algoritmul ==== | + | ==== Algoritmul aplicației ==== |
| La pornirea sistemului: | La pornirea sistemului: | ||
| - | - se inițializează LCD-ul; | + | - se inițializează statisticile; |
| - | - se configurează pinii pentru senzor, LED, buzzer și buton; | + | - se configurează pinii pentru LED, buzzer și buton; |
| + | - se pornește comunicația serială și comunicația Bluetooth; | ||
| + | - se inițializează magistrala I2C; | ||
| + | - se pornește LCD-ul; | ||
| - se inițializează modulul RTC DS3231; | - se inițializează modulul RTC DS3231; | ||
| - se inițializează cardul microSD; | - se inițializează cardul microSD; | ||
| - | - se inițializează comunicația Bluetooth; | + | - se afișează mesajul de pornire pe LCD. |
| - | - se afișează un mesaj de pornire pe LCD. | + | |
| În bucla principală: | În bucla principală: | ||
| - | - se verifică starea butonului Start/Stop; | + | - se verifică dacă butonul Start/Stop a fost apăsat; |
| - dacă sistemul este oprit logic: | - dacă sistemul este oprit logic: | ||
| * LED-ul este stins; | * LED-ul este stins; | ||
| Line 263: | Line 356: | ||
| * LCD-ul afișează mesajul „Sistem oprit”; | * LCD-ul afișează mesajul „Sistem oprit”; | ||
| - dacă sistemul este pornit: | - dacă sistemul este pornit: | ||
| - | * se citește valoarea analogică a senzorului; | + | * se citește valoarea analogică de la senzor; |
| * valoarea ADC este convertită în procent de umiditate; | * valoarea ADC este convertită în procent de umiditate; | ||
| - | * se citește data și ora de la RTC; | + | * se stabilește dacă planta este în status „OK” sau „USCAT”; |
| - | * se stabilește statusul plantei: „OK” sau „UDAȚI PLANTA”; | + | |
| - | * se actualizează LCD-ul; | + | |
| - | * se controlează LED-ul și buzzerul; | + | |
| - | * se salvează datele pe cardul microSD; | + | |
| * se actualizează statisticile; | * se actualizează statisticile; | ||
| - | * se transmit datele prin Bluetooth. | + | * se controlează LED-ul și buzzerul; |
| + | * se actualizează LCD-ul; | ||
| + | * se salvează periodic datele pe cardul microSD; | ||
| + | * se transmit periodic datele prin Bluetooth. | ||
| + | ==== Funcții principale implementate ==== | ||
| + | |||
| + | ^ Funcție ^ Rol ^ | ||
| + | | initHardware() | Inițializează componentele hardware: pini, LCD, RTC, microSD, Bluetooth și Serial Monitor. | | ||
| + | | buttonPressedEvent() | Detectează apăsarea butonului Start/Stop folosind debounce. | | ||
| + | | readSoilAdc() | Citește valoarea analogică de la senzorul de umiditate. | | ||
| + | | convertAdcToHumidityPercent() | Transformă valoarea ADC într-un procent de umiditate. | | ||
| + | | setAlerts() | Pornește sau oprește LED-ul și buzzerul în funcție de statusul plantei. | | ||
| + | | updateLcd() | Actualizează informațiile afișate pe LCD. | | ||
| + | | getCurrentTime() | Citește data și ora de la modulul RTC DS3231. | | ||
| + | | logMeasurementToSd() | Salvează măsurătorile și statisticile pe cardul microSD. | | ||
| + | | sendBluetoothReport() | Trimite prin Bluetooth valorile curente și statisticile. | | ||
| + | | resetStats() | Resetează valorile statistice. | | ||
| + | | updateStats() | Actualizează media, minimul, maximul și numărul de măsurători. | | ||
| + | | getAverageHumidity() | Calculează media umidității. | | ||
| + | | getTrend() | Determină tendința umidității: creștere, scădere sau stabilă. | | ||
| + | | getDeltaFromFirst() | Calculează diferența față de prima măsurătoare. | | ||
| ==== Stabilirea statusului plantei ==== | ==== Stabilirea statusului plantei ==== | ||
| + | |||
| + | Statusul plantei este stabilit prin compararea umidității curente cu pragul definit în `config.h`. | ||
| + | |||
| + | <code c> | ||
| + | currentDry = currentHumidity < HUMIDITY_THRESHOLD_PERCENT; | ||
| + | currentStatus = currentDry ? "USCAT" : "OK"; | ||
| + | </code> | ||
| + | |||
| + | Dacă umiditatea este sub prag, LED-ul și buzzerul sunt pornite. Dacă umiditatea este peste prag, acestea sunt oprite. | ||
| <code c> | <code c> | ||
| - | if (umiditate < pragUmiditate) { | + | void setAlerts(bool systemActive, bool isDry) { |
| - | status = "USCAT"; | + | if (systemActive && isDry) { |
| - | digitalWrite(ledPin, HIGH); | + | digitalWrite(PIN_LED, HIGH); |
| - | digitalWrite(buzzerPin, HIGH); | + | digitalWrite(PIN_BUZZER, HIGH); |
| - | } else { | + | } else { |
| - | status = "OK"; | + | digitalWrite(PIN_LED, LOW); |
| - | digitalWrite(ledPin, LOW); | + | digitalWrite(PIN_BUZZER, LOW); |
| - | digitalWrite(buzzerPin, LOW); | + | } |
| } | } | ||
| </code> | </code> | ||
| Line 290: | Line 408: | ||
| ==== Structuri de date pentru statistici ==== | ==== Structuri de date pentru statistici ==== | ||
| - | Pentru calculul statisticilor se pot folosi variabile simple: | + | Statisticile sunt păstrate într-o structură numită `MoistureStats`. |
| <code c> | <code c> | ||
| - | int umiditateCurenta; | + | struct MoistureStats { |
| - | int umiditateMinima = 100; | + | bool initialized; |
| - | int umiditateMaxima = 0; | + | unsigned long count; |
| - | long sumaUmiditate = 0; | + | long sum; |
| - | unsigned long numarMasuratori = 0; | + | int minimum; |
| - | int primaMasurare; | + | int maximum; |
| - | int ultimaMasurare; | + | int first; |
| - | int masurareAnterioara; | + | int previous; |
| - | int masuratoriSolUscat = 0; | + | int current; |
| + | unsigned long dryCount; | ||
| + | }; | ||
| </code> | </code> | ||
| - | Statisticile calculate: | + | Această structură permite calcularea următoarelor informații: |
| - | * umiditatea curentă; | + | * numărul total de măsurători; |
| + | * suma valorilor măsurate; | ||
| * media umidității; | * media umidității; | ||
| * valoarea minimă; | * valoarea minimă; | ||
| * valoarea maximă; | * valoarea maximă; | ||
| - | * diferența față de prima măsurătoare; | + | * prima valoare măsurată; |
| - | * tendința: creștere, scădere sau stabilă; | + | * valoarea anterioară; |
| + | * valoarea curentă; | ||
| * numărul de măsurători în care solul a fost uscat. | * numărul de măsurători în care solul a fost uscat. | ||
| ==== Calculul statisticilor ==== | ==== Calculul statisticilor ==== | ||
| + | |||
| + | La fiecare măsurătoare, funcția `updateStats()` actualizează valorile statistice. | ||
| <code c> | <code c> | ||
| - | sumaUmiditate += umiditateCurenta; | + | void updateStats(MoistureStats &stats, int humidityPercent, bool isDry) { |
| - | numarMasuratori++; | + | if (!stats.initialized) { |
| + | stats.initialized = true; | ||
| + | stats.first = humidityPercent; | ||
| + | stats.previous = humidityPercent; | ||
| + | stats.current = humidityPercent; | ||
| + | stats.minimum = humidityPercent; | ||
| + | stats.maximum = humidityPercent; | ||
| + | } else { | ||
| + | stats.previous = stats.current; | ||
| + | stats.current = humidityPercent; | ||
| - | int media = sumaUmiditate / numarMasuratori; | + | if (humidityPercent < stats.minimum) { |
| + | stats.minimum = humidityPercent; | ||
| + | } | ||
| - | if (umiditateCurenta < umiditateMinima) { | + | if (humidityPercent > stats.maximum) { |
| - | umiditateMinima = umiditateCurenta; | + | stats.maximum = humidityPercent; |
| + | } | ||
| + | } | ||
| + | |||
| + | stats.sum += humidityPercent; | ||
| + | stats.count++; | ||
| + | |||
| + | if (isDry) { | ||
| + | stats.dryCount++; | ||
| + | } | ||
| } | } | ||
| + | </code> | ||
| - | if (umiditateCurenta > umiditateMaxima) { | + | Media este calculată astfel: |
| - | umiditateMaxima = umiditateCurenta; | + | |
| + | <code c> | ||
| + | int getAverageHumidity(const MoistureStats &stats) { | ||
| + | if (stats.count == 0) { | ||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | return (int)(stats.sum / (long)stats.count); | ||
| } | } | ||
| </code> | </code> | ||
| Line 333: | Line 485: | ||
| ==== Calculul tendinței ==== | ==== Calculul tendinței ==== | ||
| - | <code> | + | Tendința este calculată prin compararea valorii curente cu valoarea anterioară. |
| - | dacă umiditatea curentă este cu peste 2 puncte mai mare decât valoarea anterioară: | + | |
| - | tendință = CREȘTERE | + | |
| - | dacă umiditatea curentă este cu peste 2 puncte mai mică decât valoarea anterioară: | + | <code c> |
| - | tendință = SCĂDERE | + | const char* getTrend(const MoistureStats &stats) { |
| + | if (!stats.initialized || stats.count < 2) { | ||
| + | return "STABIL"; | ||
| + | } | ||
| - | altfel: | + | int diff = stats.current - stats.previous; |
| - | tendință = STABILĂ | + | |
| + | if (diff > 2) { | ||
| + | return "CRESTERE"; | ||
| + | } | ||
| + | |||
| + | if (diff < -2) { | ||
| + | return "SCADERE"; | ||
| + | } | ||
| + | |||
| + | return "STABIL"; | ||
| + | } | ||
| </code> | </code> | ||
| ==== Formatul fișierului CSV ==== | ==== Formatul fișierului CSV ==== | ||
| - | Datele salvate pe cardul microSD pot avea următorul format: | + | Datele sunt salvate pe cardul microSD în fișierul: |
| + | |||
| + | <code> | ||
| + | umid.csv | ||
| + | </code> | ||
| + | |||
| + | Header-ul fișierului este: | ||
| + | |||
| + | <code csv> | ||
| + | date,time,adc,humidity,status,average,min,max,trend,delta_from_first,dry_count,count | ||
| + | </code> | ||
| + | |||
| + | Exemplu de linie salvată: | ||
| <code csv> | <code csv> | ||
| - | data,ora,umiditate,status,media,minim,maxim,tendinta | + | 2026-05-08,16:40:00,522,28,USCAT,45,28,72,SCADERE,-14,3,10 |
| - | 2026-05-08,16:30:00,42,OK,48,35,72,SCADERE | + | |
| - | 2026-05-08,16:35:00,39,OK,47,35,72,SCADERE | + | |
| - | 2026-05-08,16:40:00,28,USCAT,45,28,72,SCADERE | + | |
| </code> | </code> | ||
| ==== Mesaj transmis prin Bluetooth ==== | ==== Mesaj transmis prin Bluetooth ==== | ||
| - | Prin modulul HC-05 se poate transmite un mesaj de forma: | + | Prin modulul HC-05 se transmite periodic un raport cu valorile curente și statisticile calculate. |
| <code> | <code> | ||
| - | [16:40:00] | + | ---------------- |
| + | Timp: 2026-05-08 16:40:00 | ||
| + | ADC: 522 | ||
| Umiditate: 28% | Umiditate: 28% | ||
| - | Status: UDATI PLANTA | + | Status: USCAT |
| Media: 45% | Media: 45% | ||
| Minim: 28% | Minim: 28% | ||
| Maxim: 72% | Maxim: 72% | ||
| Tendinta: SCADERE | Tendinta: SCADERE | ||
| - | LED: ON | + | Diferenta fata de start: -14 pct |
| - | Buzzer: ON | + | Masuratori sol uscat: 3 |
| + | SD: OK | ||
| </code> | </code> | ||
| - | ==== Funcții planificate ==== | + | ==== Funcționarea butonului Start/Stop ==== |
| + | |||
| + | Butonul Start/Stop este configurat folosind rezistența internă de pull-up a microcontrollerului. | ||
| <code c> | <code c> | ||
| - | void initHardware(); | + | pinMode(PIN_BUTTON, INPUT_PULLUP); |
| - | int citesteUmiditateADC(); | + | |
| - | int convertesteInProcent(int valoareADC); | + | |
| - | void actualizeazaLCD(int umiditate, String status); | + | |
| - | void controleazaAvertizari(int umiditate); | + | |
| - | void actualizeazaStatistici(int umiditate); | + | |
| - | String calculeazaTendinta(int umiditateCurenta, int umiditateAnterioara); | + | |
| - | void salveazaPeSD(DateTime timp, int umiditate, String status); | + | |
| - | void trimiteBluetooth(DateTime timp, int umiditate, String status); | + | |
| - | void verificaButon(); | + | |
| </code> | </code> | ||
| - | ===== Rezultate Obţinute ===== | + | Astfel: |
| - | În urma realizării proiectului, se urmărește obținerea unui sistem funcțional care: | + | |
| - | + | ||
| - | * măsoară umiditatea solului; | + | |
| - | * afișează umiditatea și statusul plantei pe LCD; | + | |
| - | * aprinde LED-ul roșu atunci când planta trebuie udată; | + | |
| - | * pornește buzzerul activ cât timp umiditatea este sub pragul stabilit; | + | |
| - | * permite pornirea și oprirea monitorizării cu ajutorul unui buton; | + | |
| - | * citește data și ora de la modulul RTC DS3231; | + | |
| - | * salvează măsurătorile pe cardul microSD într-un fișier CSV; | + | |
| - | * calculează statistici simple despre evoluția umidității; | + | |
| - | * transmite datele și statisticile prin Bluetooth către un dispozitiv extern. | + | |
| - | + | ||
| - | Exemplu de afișare pe LCD când planta are umiditate suficientă: | + | |
| <code> | <code> | ||
| - | Umiditate: 42% | + | buton neapăsat -> HIGH |
| - | Status: OK | + | buton apăsat -> LOW |
| </code> | </code> | ||
| - | Exemplu de afișare pe LCD când planta trebuie udată: | + | Funcția `buttonPressedEvent()` detectează apăsarea butonului și schimbă starea sistemului între pornit și oprit. |
| - | <code> | + | <code c> |
| - | Umiditate: 28% | + | if (buttonPressedEvent()) { |
| - | Status: UDATI | + | systemActive = !systemActive; |
| + | } | ||
| </code> | </code> | ||
| - | Exemplu de linie salvată pe cardul microSD: | + | ==== Timpi de execuție ==== |
| - | <code csv> | + | Intervalele de timp sunt definite în `config.h`. |
| - | 2026-05-08,16:40:00,28,USCAT,45,28,72,SCADERE | + | |
| - | </code> | + | |
| - | Exemplu de mesaj transmis prin Bluetooth: | + | <code c> |
| - | + | const unsigned long MEASURE_INTERVAL_MS = 1000; | |
| - | <code> | + | const unsigned long LCD_INTERVAL_MS = 1000; |
| - | Ora: 16:40:00 | Umiditate: 28% | Status: UDATI PLANTA | Media: 45% | + | const unsigned long SD_LOG_INTERVAL_MS = 10000; |
| + | const unsigned long BT_SEND_INTERVAL_MS = 3000; | ||
| </code> | </code> | ||
| - | <note tip> | + | Astfel: |
| - | Descrierea codului aplicaţiei (firmware): | + | |
| - | * mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR) | + | |
| - | * librării şi surse 3rd-party (e.g. Procyon AVRlib) | + | |
| - | * algoritmi şi structuri pe care plănuiţi să le implementaţi | + | |
| - | * (etapa 3) surse şi funcţii implementate | + | |
| - | </note> | + | |
| - | <note tip> | + | * senzorul este citit la fiecare 1 secundă; |
| - | Care au fost rezultatele obţinute în urma realizării proiectului vostru. | + | * LCD-ul este actualizat la fiecare 1 secundă; |
| - | </note> | + | * datele sunt salvate pe microSD la fiecare 10 secunde; |
| + | * raportul Bluetooth este trimis la fiecare 3 secunde. | ||
| ===== Concluzii ===== | ===== Concluzii ===== | ||
| Line 442: | Line 592: | ||
| <note warning> | <note warning> | ||
| - | O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului: surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-). | + | Codul sursă al proiectului este disponibil pe GitHub: [[https://github.com/Izabela-Focsa/soil_moisture_monitor.git|soil_moisture_monitor]] |
| Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:pm:prj20??:c?** sau **:pm:prj20??:c?:nume_student** (dacă este cazul). **Exemplu:** Dumitru Alin, 331CC -> **:pm:prj2009:cc:dumitru_alin**. | Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:pm:prj20??:c?** sau **:pm:prj20??:c?:nume_student** (dacă este cazul). **Exemplu:** Dumitru Alin, 331CC -> **:pm:prj2009:cc:dumitru_alin**. | ||