This shows you the differences between two versions of the page.
pm:prj2023:apredescu:statie_meteo [2023/05/27 23:34] cosmin.moscalu |
pm:prj2023:apredescu:statie_meteo [2023/05/28 16:34] (current) cosmin.moscalu |
||
---|---|---|---|
Line 32: | Line 32: | ||
Descriere sumară a modulelor software: | Descriere sumară a modulelor software: | ||
- | * ''main'' => Modulul principal care inițializează celelalte module și în care se găsește funcția loop | + | * ''main'': Modulul principal care inițializează celelalte module și în care se găsește funcția loop |
- | * ''station'' => Se ocupă de citirea și stocarea datelor de la senzorii de mediu, aici sunt salvate și o parte din setări | + | * ''station'': Se ocupă de citirea și stocarea datelor de la senzorii de mediu, aici sunt salvate și o parte din setări |
- | * ''button'' => Se ocupă de citirea butoanelor. Folosind întreruperi, semnalează modulului ''main'' dacă a fost apăsat un buton și se folosește de unul din ceasurile arduino-ului pentru a face debouncing | + | * ''button'': Se ocupă de citirea butoanelor. Folosind întreruperi, semnalează modulului ''main'' dacă a fost apăsat un buton și se folosește de unul din ceasurile arduino-ului pentru a face debouncing |
- | * ''menu'' => Conține implementarea pentru toate ecranele disponibile. Fiecare ecran implementează câte una din funcționalitățile descrise mai sus și interacționează cu alte module atunci când este nevoie | + | * ''menu'': Conține implementarea pentru toate ecranele disponibile. Fiecare ecran implementează câte una din funcționalitățile descrise mai sus și interacționează cu alte module atunci când este nevoie |
- | * ''time'' => Realizează sincronizarea cu ceasul extern și controlează celelalte ceasuri interne ale microcontroller-ului | + | * ''time'': Realizează sincronizarea cu ceasul extern și controlează celelalte ceasuri interne ale microcontroller-ului |
- | * ''alarm'' => Se folosește de ''time'' pentru a implementa metodele necesare pentru a configura și a declanșa o alarmă | + | * ''alarm'': Se folosește de ''time'' pentru a implementa metodele necesare pentru a configura și a declanșa o alarmă |
- | * ''remote'' => Interacționează cu receptorul și emițătorul infraroșu, salvează și retrimite la cerere un cod primit de la o telecomandă | + | * ''remote'': Interacționează cu receptorul și emițătorul infraroșu, salvează și retrimite la cerere un cod primit de la o telecomandă |
==== Interacțiunea dintre modulele software ==== | ==== Interacțiunea dintre modulele software ==== | ||
Line 87: | Line 87: | ||
<note warning> | <note warning> | ||
- | Pentru că unele biblioteci includeau funcții nefolosite care ocupau mult spațiu doar pentru că erau incluse, am modificat o parte din acestea pentru a reduce spațiul utilizat. Unde este cazul, se găsesc mai multe motive în descrierea modulelor. | + | Unele biblioteci includeau funcții pe care nu le foloseam sau foloseau la rând funcțiile millis() și delay() care au nevoie de timer-ul 0. Am modificat aceste biblioteci pentru a reduce dimensiunea ocupată dar și dependența de funcțiile care se bazează pe timer-ul 0. |
+ | Mai multe modificări sunt detaliate în descrierea modulelor, unde este cazul. | ||
Pentru biblioteca LiquidCrystal_I2C, am implementat deplasarea cursorului la stânga și la dreapta pentru că aceste funcționalități lipseau din biblioteca originală. | Pentru biblioteca LiquidCrystal_I2C, am implementat deplasarea cursorului la stânga și la dreapta pentru că aceste funcționalități lipseau din biblioteca originală. | ||
Line 187: | Line 188: | ||
Deoarece se memorează foarte multe date, pentru a reduce dimensiunea ocupată de acestea, valorile citite din mediu sunt păstrate în variabile de tip int16_t în loc de float. Pentru a putea păstra variabilele în acest mod a fost nevoie să modific bibliotecile DHT și Adafruit_BMP280 ca să întoarcă tipuri întregi în loc de float-uri. Astfel, temperatura păstrată într-o variabilă este temperatura reală in grade celsius cu două zecimale înmulțită apoi cu 100, umiditatea este cea reală înmulțită cu 10, iar presiunea este cea reală în pascali din care se scade 90000. Se scade 90000 din presiunea citită de la senzor, pentru că aceasta este valoarea minimă pe care senzorul o poate citi, conform datasheet-ului, și pentru că aceasta modificare permite stocarea presiunii într-o variabilă de tip int16_t în loc de una int32_t. Mai mult, modificarea unităților de măsură nu afectează semnificația acestor variabile, ci doar modul în care se afișează. | Deoarece se memorează foarte multe date, pentru a reduce dimensiunea ocupată de acestea, valorile citite din mediu sunt păstrate în variabile de tip int16_t în loc de float. Pentru a putea păstra variabilele în acest mod a fost nevoie să modific bibliotecile DHT și Adafruit_BMP280 ca să întoarcă tipuri întregi în loc de float-uri. Astfel, temperatura păstrată într-o variabilă este temperatura reală in grade celsius cu două zecimale înmulțită apoi cu 100, umiditatea este cea reală înmulțită cu 10, iar presiunea este cea reală în pascali din care se scade 90000. Se scade 90000 din presiunea citită de la senzor, pentru că aceasta este valoarea minimă pe care senzorul o poate citi, conform datasheet-ului, și pentru că aceasta modificare permite stocarea presiunii într-o variabilă de tip int16_t în loc de una int32_t. Mai mult, modificarea unităților de măsură nu afectează semnificația acestor variabile, ci doar modul în care se afișează. | ||
- | Astfel, aceste modificări permit afișarea temperaturii, de exemplu, folosind o implementare proprie fără a mai fi nevoie de anumite flag-uri de compilare pentru a include o variantă mai completă a funcției snprintf, care ar trebui să poată afișa și float-uri ((https://forum.arduino.cc/t/solved-why-cant-i-print-a-float-value-with-sprintf/367971))((https://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html#gaa3b98c0d17b35642c0f3e4649092b9f1)). | + | Astfel, aceste modificări permit afișarea temperaturii, de exemplu, folosind o implementare proprie fără a mai fi nevoie de anumite flag-uri de compilare pentru a include o variantă mai completă a funcției snprintf, care ar trebui să poată afișa și float-uri ((https://forum.arduino.cc/t/solved-why-cant-i-print-a-float-value-with-sprintf/367971)) ((https://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html#gaa3b98c0d17b35642c0f3e4649092b9f1)). |
<code cpp> | <code cpp> | ||
Line 274: | Line 275: | ||
disableButtonTimer(); | disableButtonTimer(); | ||
enableButtons(); | enableButtons(); | ||
- | // Se semnaleaza buclei principale ca trebuie verificate din nou | + | // Se semnaleaza buclei principale ca butoanele trebuie verificate din nou |
// Astfel, se realizeaza o repetare daca butonul este tinut apasat | // Astfel, se realizeaza o repetare daca butonul este tinut apasat | ||
checkButton = true; | checkButton = true; | ||
Line 400: | Line 401: | ||
AlarmMenu incorporează un AlarmManager și folosește modulul ''remote'' pentru a recepționa un cod care este memorat tot în acest meniu. De asemenea, AlarmMenu mai are o metodă care este folosită pentru a retransmite codul stocat în caz că alarma setată este activată și sună la momentul în care această metodă este apelată. Modulul ''main'' nu are cunoștiință că ar exista mai multe alarme, așa că apelează o funcție a acestui modul care verifică alarmele ambelor meniuri. | AlarmMenu incorporează un AlarmManager și folosește modulul ''remote'' pentru a recepționa un cod care este memorat tot în acest meniu. De asemenea, AlarmMenu mai are o metodă care este folosită pentru a retransmite codul stocat în caz că alarma setată este activată și sună la momentul în care această metodă este apelată. Modulul ''main'' nu are cunoștiință că ar exista mai multe alarme, așa că apelează o funcție a acestui modul care verifică alarmele ambelor meniuri. | ||
- | Tot în acest modul, funcția switchCurrentMenu(), care încarcă meniul următor, folosește o serie de variabile globale pentru a reveni întotdeauna la meniul principal după un interval de timp în care nu au mai fost apăsate butoane. Astfel, stația nu va afișa mult timp meniuri care necesită mai multe calcule sau comunicații cu alte componente. În funcție de butonul apăsat, un meniu poate să semnaleze că trebuie afișat alt meniu. Odată ce plăcuța se trezește în urma apăsării butonului, modulul ''main'' va apela switchCurrentMenu() pentru a realiza înlocuirea. | + | Tot în acest modul, funcția switchCurrentMenu(), care încarcă meniul următor, folosește o serie de variabile globale pentru a reveni întotdeauna la meniul principal după un interval de timp în care nu au mai fost apăsate butoane. Astfel, stația nu va afișa mult timp meniuri care necesită mai multe calcule sau comunicații cu alte componente. În funcție de butonul apăsat, un meniu poate să semnaleze că trebuie afișat alt meniu. Odată ce plăcuța se trezește în urma apăsării butonului, modulul ''main'' va apela switchCurrentMenu() pentru a realiza înlocuirea. De asemenea, după ce se revine la meniul principal, backlight-ul lcd-ului va rămâne aprins doar pentru o perioadă scurtă de timp, pentru a economisi energie. |
=== Main === | === Main === | ||
Line 409: | Line 410: | ||
Modulul principal care reunește celelalte module. Pe lângă funcțiile arduino setup() și loop(), aici mai sunt implementate alte funcții care nu puteau să aparțină altui modul, de exemplu disableOther(), care dezactivează anumite componente ale microcontroller-ului care nu sunt folosite sau powerDownUnused() care taie alimentarea din părți ale microcontroller-ului care nu sunt folosite. | Modulul principal care reunește celelalte module. Pe lângă funcțiile arduino setup() și loop(), aici mai sunt implementate alte funcții care nu puteau să aparțină altui modul, de exemplu disableOther(), care dezactivează anumite componente ale microcontroller-ului care nu sunt folosite sau powerDownUnused() care taie alimentarea din părți ale microcontroller-ului care nu sunt folosite. | ||
- | Dintre aceste funcții, funcția sleep() pune microcontroller-ul într-o stare de sleep, alegând starea de sleep în funcție în funcție de activitatea microcontroller-ului, mai exact va pune microcontroller-ul în idle dacă urmează să se reactiveze butoanele sau dacă timer-ul 2 este activat, adică meniul AlarmMenu așteaptă recepționarea unui cod infraroșu. Dacă nu există activitate, sleep() va pune plăcuța în modul power down. | + | Dintre aceste funcții, funcția sleep() pune microcontroller-ul într-o stare de sleep, alegând starea de sleep în funcție de activitatea microcontroller-ului, mai exact va pune microcontroller-ul în idle dacă urmează să se reactiveze butoanele sau dacă timer-ul 2 este activat, adică meniul AlarmMenu așteaptă recepționarea unui cod infraroșu. Dacă nu există activitate, sleep() va pune plăcuța în modul power down. |
Sumar, pentru a inițializa complet toate modulele și a pune stația în funcțiune, metoda setup() va face următoarele: | Sumar, pentru a inițializa complet toate modulele și a pune stația în funcțiune, metoda setup() va face următoarele: | ||
Line 439: | Line 440: | ||
===== Rezultate Obţinute ===== | ===== Rezultate Obţinute ===== | ||
+ | |||
+ | <html> | ||
+ | <iframe src="https://drive.google.com/file/d/1iXIgGZz2yNH7gDyl2DnmovMSbQ7cShUB/preview" allowfullscreen style="aspect-ratio: 16/9; width: 100%;" allow="autoplay; encrypted-media; picture-in-picture;"></iframe> | ||
+ | </html> | ||
+ | |||
+ | Pentru a desena graficul, valorile pentru presiune au fost inițializate în prealabil folosind un define configurabil în cod. | ||
+ | |||
+ | <html> | ||
+ | <iframe src="https://drive.google.com/file/d/1tEMMO7fOeBGECDVwJjXam1nvc634SNTq/preview" allowfullscreen style="aspect-ratio: 16/9; width: 100%;" allow="autoplay; encrypted-media; picture-in-picture;"></iframe> | ||
+ | </html> | ||
===== Concluzii ===== | ===== Concluzii ===== | ||
Line 447: | Line 458: | ||
* organizarea e importantă: inițial am aveam tot codul pus într-un număr mic de fișiere fără ca acesta să fie modularizat prea bine, ceea ce a creat foarte multe probleme atunci când am vrut să adaug mai multe funcționalități la stație. După ce am reorganizat codul în structura actuală, implementarea meniurilor s-a făcut destul de ușor | * organizarea e importantă: inițial am aveam tot codul pus într-un număr mic de fișiere fără ca acesta să fie modularizat prea bine, ceea ce a creat foarte multe probleme atunci când am vrut să adaug mai multe funcționalități la stație. După ce am reorganizat codul în structura actuală, implementarea meniurilor s-a făcut destul de ușor | ||
* float-urile sunt de evitat: original, foloseam float-uri pentru a stoca un număr mare de valori citite din mediu. Nu numai că ocupa foarte multă memorie ram, dar și multă memorie program pentru că era nevoie de o implementare software care să facă operațiile cu acestea. Sunt convins că eliminarea float-urilor a redus nu numai spațiul necesar dar și consumul de energie al stației pentru că s-a redus complexitatea calculelor care se fac pentru afișări | * float-urile sunt de evitat: original, foloseam float-uri pentru a stoca un număr mare de valori citite din mediu. Nu numai că ocupa foarte multă memorie ram, dar și multă memorie program pentru că era nevoie de o implementare software care să facă operațiile cu acestea. Sunt convins că eliminarea float-urilor a redus nu numai spațiul necesar dar și consumul de energie al stației pentru că s-a redus complexitatea calculelor care se fac pentru afișări | ||
- | * citește manualul (din nou): deoarece umblu la timere folosind registre și folosesc întreruperi definite manual, asta a făcut foarte greu să găsesc o bibliotecă pentru recepții și transmisii pe infraroșu care să accepte codul pe care îl aveam deja scris. Biblioteca IRremote este scrisă foarte bine și tratează aproape toate situațiile imaginabile, dar tot din cauza asta ocupă foarte mult spațiu (în jur de 8-11KB, și 400-500B de ram). Crezând că nu pot s-o folosesc, am trecut printr-o serie de alte biblioteci mai prost scrise care funcționau doar parțial (doar recepție sau transmisie, dar nu amândouă) sau de cele mai multe ori deloc. În final am revenit la biblioteca IRremote pentru că am găsit în codul sursă acele define-uri care pot exclude anumite componente, reducând dimensiunea ocupată la aproximativ 3KB si ram-ul utilizat la o valoare care să nu facă heap-ul să dea peste stivă | + | * citește manualul (din nou): deoarece umblu la timere folosind registre și folosesc întreruperi definite manual, asta a făcut foarte greu să găsesc o bibliotecă pentru recepții și transmisii pe infraroșu care să accepte codul pe care îl aveam deja scris. Biblioteca IRremote este scrisă foarte bine și tratează aproape toate situațiile imaginabile, dar tot din cauza asta ocupă foarte mult spațiu (în jur de 8-11KB, și 400-500B de ram). Crezând că nu pot s-o folosesc, am trecut printr-o serie de alte biblioteci mai prost scrise care funcționau doar parțial (doar recepție sau transmisie, dar nu amândouă) sau de cele mai multe ori deloc. În final am revenit la biblioteca IRremote pentru că am găsit în codul sursă acele define-uri care pot exclude anumite componente, reducând dimensiunea ocupată la aproximativ 3KB si ram-ul utilizat la o valoare care să nu facă stiva să dea peste heap |
* dacă vrei ceva făcut bine...: majoritatea bibliotecilor scrise pentru arduino sunt de cele mai multe ori prea simple, uneori nu interacționeză prea grozav unele cu altele și aproape sigur fac presupuneri "rezonabile", cum că funcția millis() ar fi disponibilă tot timpul, doar că în cazul meu nu este. | * dacă vrei ceva făcut bine...: majoritatea bibliotecilor scrise pentru arduino sunt de cele mai multe ori prea simple, uneori nu interacționeză prea grozav unele cu altele și aproape sigur fac presupuneri "rezonabile", cum că funcția millis() ar fi disponibilă tot timpul, doar că în cazul meu nu este. | ||
* fiecare octet contează: nu am dat deloc atenție utilizării ram până când am inclus biblioteca IRremote și am aflat că mi-ar trebui 2200 de octeti de memorie ram ca să încapă tot (asta înainte să descopăr define-urile pentru optimizări). După ce am început să fac alte optimizări și să mai reduc din variabile, am ajuns la 1600-1700 de octeți folosiți, dar nu funcționa nimic pentru că stiva există în continuare și, din nefericire, costul modularizării este că nu toate funcțiile pot fi făcute inline. În final, am descoperit că orice sub 1500 de octeți funcționează fără probleme în cazul meu | * fiecare octet contează: nu am dat deloc atenție utilizării ram până când am inclus biblioteca IRremote și am aflat că mi-ar trebui 2200 de octeti de memorie ram ca să încapă tot (asta înainte să descopăr define-urile pentru optimizări). După ce am început să fac alte optimizări și să mai reduc din variabile, am ajuns la 1600-1700 de octeți folosiți, dar nu funcționa nimic pentru că stiva există în continuare și, din nefericire, costul modularizării este că nu toate funcțiile pot fi făcute inline. În final, am descoperit că orice sub 1500 de octeți funcționează fără probleme în cazul meu | ||
Line 461: | Line 472: | ||
<note tip> | <note tip> | ||
+ | 28.05 - Adăugat demo funcționare, corectat greșeli din text, actualizat codul \\ | ||
27.05 - Adăugat milestone-ul software, actualizat poza pentru schema electrică și poza pentru hardware \\ | 27.05 - Adăugat milestone-ul software, actualizat poza pentru schema electrică și poza pentru hardware \\ | ||
21.05 - Adăugat un modul software nou, actualizat diagramele vechi și adăugat o poză nouă \\ | 21.05 - Adăugat un modul software nou, actualizat diagramele vechi și adăugat o poză nouă \\ | ||
Line 478: | Line 490: | ||
Resurse folosite: | Resurse folosite: | ||
* codul sursă și exemplele incluse în bibliotecile folosite, în special cele din IRremote | * codul sursă și exemplele incluse în bibliotecile folosite, în special cele din IRremote | ||
- | * datasheet-ul ATmega328p pentru configurările cu registre: [[https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf|ATmega328P_Datasheet]] | + | * datasheet-ul ATmega328p pentru configurările cu registre: [[https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf|ATmega328P]] |
+ | * datasheet-ul DS3231M: [[https://www.analog.com/media/en/technical-documentation/data-sheets/DS3231M.pdf|DS3231M]] | ||
+ | * datasheet-ul BMP280: [[https://cdn-shop.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf|BMP280]] | ||
+ | * datasheet-ul LCD2004A: [[https://uk.beta-layout.com/download/rk/RK-10290_410.pdf|LCD2004A]] | ||
<html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | <html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | ||