Witam!
Jakiś czas temu zbudowałem internetową stację meteorologiczną przesyłająca dane do thingspeak.
Niestety okazało się, że słaba jakość internetu u mnie w domu doprowadziła do porzucenia projektu jako, że często dane w ogóle nie docierały do serwera.
W przypływie wolnego czasu (wakacje) postanowiłem przerobić projekt na coś innego. I wybór padł na rejestrator danych pogodowych.
Ogólna koncepcja jest taka, że jest to urządzenie które odczytuje dane z czujników a następnie zapisuje je na karcie pamięci.
Szczerze mówiąc nie mam pojęcia w jaki sposób miałoby być to przydatne ale postanowiłem to zbudować (:
Założenia projektowe :
- Możliwie długi czas pracy na akumulatorze.
- Zbieranie danych co 20 sekund i zapisywanie ich na karcie micro SD.
- Pomiar prędkości i kierunku wiatru.
- Pomiar ciśnienia.
- Pomiar temperatury i wilgotności.
- Pomiar opadów deszczu.
- Możliwość załadowania danych do exela / libre offica w celu dalszej analizy.
Elektronika
Zasilanie
Całość jest zasilana z akumulatora żelowego o napięciu nominalnym 6V i pojemności 12 Ah (koszt około 40 zł) Taka pojemność zapewnia czas pracy do około 2 miesięcy. Napięcie z akumulatora trafia przez bezpiecznik 0.5A do przetwornicy Step-down D24V5F3 https://botland.com.pl/przetwornice-impulsowe/3109-przetwornica-step-down-d24v5f3-33v-05a.html.
Na wyjściu uzyskiwane jest stałe napięcie 3.3V. Co ciekawe sama przetwornica zużywa mniej niż 0.1mA. Dla porównania regulator LD11173V3 który chciałem zastosować na początku zużywa około 3 mA nawet gdy nie jest obciążony. Poza tym przetwornica zapewnia sprawność około 80%.
Dzięki temu natężenie prądu pobieranego z akumulatora waha się od około 15 mA w trybie pomiarowym do około 0.8 mA w trybie uśpienia.
Pin sterujący przetwornicy jest domyślnie podciągnięty do masy przez rezystor 22k i podłączony do pinu sterującego mikrokontrolera. Jest on podciągany do 3.3V zaraz po uruchomieniu programu. Przycisk pomiędzy napięciem akumulatora a pinem sterującym daje możliwość włączenia układu (na krótko uruchamia zasilanie aby mikrokontroler mógł się uruchomić). Taka konfiguracja zapewnia możliwość wyłączenia układu z poziomu mikrokontrolera. Kondensator C19 i dioda D2 zapewniają pewne opóźnienie pomiędzy podciągnięciem pinu sterującego do GND a wyłączeniem układu. Zapobiega to wyłączeniu układu w razie resetu mikrokontrolera. Dioda chroni również pin mikrokontrolera przed napięciem wyższym od 3.3V.
Reszta układu zasilającego to standardowe kondensatory filtrujące tam gdzie to niezbędne.
Sterowanie
Cały projekt jest oparty na mikrokontrolerze AVR Atmega328p. MIkrokontroler jest taktowany z zewnętrznego oscylatora kwarcowego 8MHz.
Mikrokontroler ma standardowy osprzęt w postaci kondensatorów filtrujących. Do mikrokontrolera po interface SPI podłączona jest karta micro SD na której zapisywane są dane.
Po I2C podłączony jest również moduł zegara RTC opartego na układzie PCF8563. Zapewnia on informację o tym kiedy dany pomiar został zrobiony.
Do pinu analogowego ADC0 podłączona jest przez dzielnik napięcia dodatnia klemra akumulatora. Zapewnia to możliwość pomiaru napięcia akumulatora przez mikrokontroler. Kiedy spadnie ono poniżej minimalnej wartości (5V dla akumulatora żelowego 6V), mikrokontroler wyłączy zasilanie aby chronić akumulator.
Do sygnalizacji pracy służą dwie diody LED. Jedna służy do sygnalizacji aktywności, druga do sygnalizacji niskiego poziomu naładowania akumulatora.
Czujniki
Jeśli chodzi o cyfrowe czujniki użyte w stacji to są to DHT22 (temperatura i wilgotność) i BMP180 (ciśnienie).
Czujnik DHT22 komunikuje się za pomocą jednoprzewodowego interface. Linia ta jest podciągnięta do VCC za pomocą rezystora 10k.
Szyna I2C jest podciągnięta do VCC przez rezystory wbudowane w moduł barometru i w moduł zegara.
Oba czujniki są umieszczone w zewnętrznej płytce umieszczonej w oddzielnej, przewiewnej obudowie.
W projekcie zostały również użyte czujniki analogowe.
Czujnik kierunku wiatru zmienia wartośćrezystancji w zależności od kierunku wiatru.
Natomiast czujnik prędkości wiatru oraz deszczomierz zawierają kontaktron który zwiera się przy pewnych warunkach.
Szczegóły dotyczące odczytu danych z tych czujników opisałem tutaj : https://majsterkowo.pl/internetowa-stacja-meteorologiczna/
Dobre informacje posiada również nettigo : http://akademia.nettigo.pl/czujnik_wiatru/
Czujniki te można kupić np tutaj:
https://www.maplin.co.uk/p/maplin-replacement-wind-direction-sensor-for-n96fyn96gy-n81nf
https://www.maplin.co.uk/p/maplin-replacement-rain-gauge-for-n25frn96fyn96gy-n77nf
https://www.maplin.co.uk/p/maplin-replacement-wind-speed-sensor-for-n96fy-n82nf
Za około 80 zł.
Firmware
Na początku miał to być projekt napisany całkowicie w C. Niestety o ile udało mi się uruchomić wszystkie czujniki to jednak przegrałem z kartą SD. (Dla chętnych mogę udostępnić kody które pozwoliły mi uruchomić zegar, czujnik BMP180 i DHT).
Tak więc postanowiłem przenieść projekt do Arduino ale zachowując część napisaną w C.
Kod firmware stacji :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
#include <SPI.h> #include <SD.h> #include <Wire.h> #include <Adafruit_BMP085.h> #include <DHT.h> #include <Rtc_Pcf8563.h> #include <avr/wdt.h> #include <avr/interrupt.h> //--------------------------------------- // Definicje //-------------------------------------- // diody sygnalizacyjne #define LED_PORT PORTD // PORTD #define LED_DDR DDRD // PIND #define LED1 4 // Dioda sygnalizacji aktywności układu #define LED2 5 // dioda sygnalizacji niskiego poziomu naładowania akumulatora //kontrola zasilania #define PWC_PORT PORTB #define PWC_DDR DDRB #define PWC 1 //piny analogowe #define BAT 0 // napięcie akumulatorA #define DIR 1 // kierunek wiatru // Czujniki #define DHTPIN 0 // PIN DHT224 #define RAIN_PORT PORTB #define RAIN 0 #define WIND_PORT PORTD #define WIND 3 // Inne definicje #define CARD_CS 10 // Pin CS karty // konfiguracja #define measure_f 2 #define PWD_level 5 #define WARN_level 5.2 //--------------------------------------- // Zmienne globalne //--------------------------------------- //liczniki volatile unsigned short wind = 0;// licznik wiatromierza volatile unsigned short counter = measure_f;// licznik wybudzeń (do opóźnień większych niż 8 sekund) unsigned int rain = 0;// licznik deszczu volatile bool rain_flag = false; //--------------------------------------- // Utworzenie obiektów //--------------------------------------- DHT dht(DHTPIN, DHT22);// obiekt czujnik DHT Adafruit_BMP085 bmp; //obiekt czujnik BMP180 Rtc_Pcf8563 rtc;// obiekt rtc //--------------------------------------- // void setup() //--------------------------------------- void setup() { // ustawienia roli i stanów pinów LED_DDR |= (1<<LED1)|(1<<LED2);// Ustaw LED jako wyjścia PWC_DDR |= (1<<PWC);// ustaw pin kontroli zasilania jako wyjście // zapobiegnij wyłączeniu układu PWC_PORT |= (1<<PWC); //--- Konfiguracja przerwania deszczomierza SREG |= (1<<7);//ustaw globalną flagę przerwań na 1 PCMSK0 |= (1<<PCINT0); PCICR |= (1<<PCIE0); //inicjalizacja reszty rzeczy adc_init();// uruchom przetwornik analogowo cyfrowy dht.begin(); if (!bmp.begin())// uruchom barometr { die(); } if (!SD.begin(CARD_CS)) { die(); } _delay_ms(1000); if(!check_rtc()) { die(); } // Zapal diody na sekundę na znak zakończonej inicjalizacji LED_PORT |= (1<<LED1)|(1<<LED2); _delay_ms(1000); LED_PORT &= ~((1<<LED1)|(1<<LED2)); } void loop() { if(rain_flag==true) { delay(1000); rain_flag = false; rain++; } if(counter==measure_f)//sprawdź czy nadszedł czas pomiaru { float vbat = measure_vbat();// sprawdź napięcie akumulatora sig(vbat);// wyświetl status File dataFile = SD.open("save.txt", FILE_WRITE); // pomiary i zapis danych disable_wdt(); dataFile.println(F("----")); // data i godzina dataFile.print(rtc.getDay()); dataFile.print(F(".")); dataFile.print(rtc.getMonth()); dataFile.print(F(".")); dataFile.print(rtc.getYear()); dataFile.print(F(" ")); dataFile.print(rtc.getHour()); dataFile.print(F(":")); dataFile.print(rtc.getMinute()); dataFile.print(F(":")); dataFile.println(rtc.getSecond()); // dane z barometru dataFile.print(F("Pressure: ")); dataFile.print(bmp.readPressure()); dataFile.println(F(" hPa")); // dane o wilgotności i temperaturze dataFile.print(F("Humidity: ")); dataFile.print(dht.readHumidity()); dataFile.println(F(" %")); dataFile.print(F("Temperature: ")); dataFile.print(dht.readTemperature()); dataFile.println(F(" *C")); // dane o prędkości wiatru dataFile.print(F("Windspeed: ")); dataFile.print(get_wind()); dataFile.println(F(" km/h")); // dane o kierunku wiatru dataFile.print(F("Wind direction: ")); dataFile.print(get_dir()); dataFile.println(F(" *")); // dane o opadach dataFile.print(F("Rain amount: ")); dataFile.print((rain*0.2794)); dataFile.println(F(" mm")); // dane o stanie akumulatora dataFile.print(F("Battery: ")); dataFile.print(vbat); dataFile.println(F(" V")); dataFile.close();// zamknij plik check_poweroff(vbat);// sprawdź warunki wyłączenia enable_wdt();// ustaw watchdoga counter = 0; } //---- Procedura wejścia w tryb uśpienia sleep(); } //--------------------------------------- // Wektory Przerwań //--------------------------------------- ISR(WDT_vect)// wektor przerwania watchdoga { counter++; } ISR(PCINT0_vect)// wektor przerwania PCI0 { rain_flag = true; } ISR(INT1_vect)// wektor przerwania INT1 { wind++;// zwiększ licznik wiatru o 1 } //--------------------------------------- // Funkcje kontroli zasilania //--------------------------------------- void poweroff()// Wyłączenie zasilania { //zamrugaj diodą na znak wyłączenia for(char i=0;i<=5;i++) { LED_PORT |= (1<<LED1)|(1<<LED2); _delay_ms(1000); LED_PORT &= ~((1<<LED1)|(1<<LED2)); _delay_ms(1000); } PWC_PORT &= ~(1<<PWC);//wyłącz główne zasilanie while(1);// nie rób nic więcej } void sleep()//Procedura wejścia w tryb uśpienia { SMCR |= (1<<SM1);//ustaw tryb uśpienia na powerdown SMCR |= (1<<SE);//ustaw bit sleep na 1 __asm__ __volatile__ ( "sleep" "\n\t" :: );//wykonaj instrukcję sleep //...Procesor jest uśpiony do czasu pojawienia się przerwania SMCR &= ~(1<<SE);//wyzeruj bit sleep } void check_poweroff(float vbat)// sprawdź warunki wyłączenia { if(vbat<PWD_level) { poweroff();// wyłącz } } //--------------------------------------- // Generalne funkcje //--------------------------------------- void sig(float vbat)// funkcja sygnalizująca status { LED_PORT |= (1<<LED1);// zapal diodę sygnalizującą aktywność if(vbat<WARN_level)// sprawdź warunek dla zapalenia diody sygnalizującej niski stan akumulatora { LED_PORT |= (1<<LED2); } _delay_ms(300);// poczekaj sekundę LED_PORT &= ~((1<<LED1)|(1<<LED2));// zgaś obie diody sygnalizacyjne } void die()// funkcja która zatrzymuje wykonywanie programu i zapala diodę na znak awarii { LED_PORT |= (1<<LED1); while(1) { // zachowaj możliwość wyłączenia na skutek zbyt niskiego napięcia zasilania float vbat=measure_vbat();// sprawdź napięcie akumulatora check_poweroff(vbat); } } void enable_wdt()// włącz watchdoga { MCUSR &= ~(1 << WDRF);// wyzeruj flagę statusu WDTCSR |= (1 << WDCE) | (1 << WDE);// włącz możliwość zmiany ustawieć i ustaw bit WDE WDTCSR = (1<< WDP3) | (1 << WDP0);//ustaw preskaler WDTCSR |= (1 << WDIE);// włącz tryb przerwań } void disable_wdt()// wyłącz watchdoga { wdt_reset(); wdt_disable(); } bool check_rtc() { bool state = false; if(rtc.getYear()>=15 && rtc.getYear()<=40)// sprawdź czy rok mieści się w przedziale od 2015 do 2040 { state = true; } return state; } //--------------------------------------- // Funkcje pomiarowe //--------------------------------------- float measure_vbat() { float vbat = adc_read(BAT); vbat = vbat*0.009389671; return vbat; } float get_wind()// procedura pomiaru i przelicznia prędkości wiatru { EICRA |= (1<<ISC10);//ustaw przerwanie INT1 na zmianę stanu pinu EIMSK |= (1<<INT1);//uruchom przerwanie wind = 0; delay(3000);// poczekaj 3 sekundy (czas pomiaru) EIMSK &= ~(1<<INT1);// Wyłącz przerwanie INT1 float win = wind; win=win*5;//*30/3/2 win= 2.75 / 12 * 3.14 * win * 60 / 5280; win= win * 3.5; win = win * 1.609; return win; } float get_dir()// procedura pomiaru i przeliczania kierunku wiatru { short value = adc_read(DIR); float dir = -1; if ((value >752) && (value < 760)){ dir=0; } if ((value >396) && (value < 404)){ dir=22.5; } if ((value >447) && (value < 455)){ dir=45; } if ((value >81) && (value < 89)){ dir=67.5; } if ((value >90) && (value < 94)){ dir=90; } if ((value >64) && (value < 72)){ dir=112.5; } if ((value >181) && (value < 189)){ dir=135; } if ((value >122) && (value < 130)){ dir=157.5; } if ((value >279) && (value < 287)){ dir=180; } if ((value >238) && (value < 236)){ dir=202.5; } if ((value >604) && (value < 612)){ dir=225; } if ((value >577) && (value < 585)){ dir=247.5; } if ((value >900) && (value < 908)){ dir=270; } if ((value >790) && (value < 798)){ dir=292.5; } if ((value >844) && (value < 852)){ dir=315; } if ((value >675) && (value < 883)){ dir=337.5; } if(dir==-1) { dir=value; dir=!dir; } return dir; } //--------------------------------------- // Funkcje ADC //--------------------------------------- //Notatka : 0,009389671 V na 1 jednostkę void adc_init() { // uruchom ADC ADCSRA |= (1<<ADEN); // ustaw źródło napięcia referencyjnego na VCC ADMUX |= (1<<REFS0); // ustaw preskaler zegara ADC na 128 ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2); } int adc_read(char ch) { // wybierz kanał konwersji ADMUX &= ~((1<<MUX0)|(1<<MUX1)|(1<<MUX2)|(1<<MUX3));//wyzeruj bity odpowiadające za ustawienie kanału (Ustaw kanał 0) switch(ch) { case 1: ADMUX |= 1<<MUX0; break; case 2: ADMUX |= 1<<MUX1; break; case 3: ADMUX |= (1<<MUX0)|(1<<MUX1); break; case 4: ADMUX |= 1<<MUX2; break; case 5: ADMUX |= (1<<MUX0)|(1<<MUX2); break; } ADCSRA |= (1<<ADSC);// rozpocznij konwersję while( ADCSRA & (1<<ADSC) ); //Poczekaj aż konwersja się zakończy return ADC;// zwróć wartość } |
Mam nadzieję, że dzięki komentarzom, program jest zrozumiały.
Zewnętrzne biblioteki niezbędne do skompilowania kodu to :
- https://github.com/adafruit/Adafruit-BMP085-Library
- https://github.com/adafruit/DHT-sensor-library
- https://bitbucket.org/orbitalair/arduino_rtc_pcf8563/downloads/
W załączniku dołączyłem skompilowany program (main.hex).
Można go wgrać do atmegi za pomocą AVRdude albo za pomocą arduino IDE wybierając płytkę arduino pro mini i procesor Atmega328p 3.3V 8MHz.
Fusebity (w postaci argumentów do AVRDUDE) to :
1 |
-U lfuse:w:0xff:m -U hfuse:w:0xda:m -U efuse:w:0xfd:m |
Działanie urządzenia
Po podłączeniu zasilania i wciśnięciu przycisku włączającego, najpierw na 1 sekundę zapalą się obie diody sygnalizując poprawną inicjalizację wszystkich podzespołów. Jeśli zapali się tylko czerwona dioda (wywołanie funkcji die()) to oznacza awarię któregoś z nich.
Następnie urządzenie rozpocznie procedurę pomiarów która wygląda następująco :
- Zmierzenie poziomu akumulatora
- Wyświetlenie statusu (mignięcie diodą czerwoną lub obiema gdy niski poziom akumulatora)
- Otwarcie pliku zapisu
- Wyłączenie i reset watchdoga.
- Zapisanie daty i godziny
- Pomiar i zapis ciśnienia
- Pomiar i zapis temperatury i wilgotności
- Pomiar i zapis prędkości wiatru
- Pomiar i zapis kierunku wiatru
- Sprawdzenie i zapis licznika opadów
- Zapis stanu akumulatora
- Zamknięcie pliku zapisu.
- Sprawdzenie czy napięcie na akumulatorze nie jest za niskie.
- Uruchomienia timera watchdoga i ustawienie go na 8 sekund
- Wejście w tryb uśpienia
Procedura wykonywana w trybie uśpienia sprowadza się do :
- Uruchomienia trybu uśpienia mikrokontrolera POWER_DOWN.
- Uśpienie na 8 sekund (wybudzanie przerwaniem z watchdoga lub przerwaniem z deszczomierza).
- Wyłączenia trybu uśpienia.
- Sprawdzenie flagi deszczomierza i aktualizacja jego licznika.
- Sprawdzenie czy czas na pomiary.
- Jeśli tak to następuje wejście w tryb pomiarowy.
- Jeśli nie to następuje powrót do trybu uśpienia.
Plik z danymi zebranymi przez stację specjalnie ma formę czytelną dla ludzi i wygląda tak :
Program do przetwarzania danych
Ponieważ plik zapisu ma formę czytelną dla człowieka ale nie dla libre-offica czy exela więc napisałem również prosty program który czyta plik z danymi a następnie konwerteruje dane do formatu czytelnego dla arkusza kalkulacyjnego.
Inną jego funkcją jest sortowanie danych według daty powstania oraz typu. Dane z osobnych dni będą zapisane do osobnych plików a dane z różnych sensorów wylądują w różnych folderach.
Program został napisany w C++ i powinien działać zarówno pod Linuxem jak i Windowsem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
#include "files.hpp" using namespace std; int main() { save_file file;// utwórz obiekt klasy save_file // utwórz folder na pliki file.create_dir("wyniki"); // utwórz podfoldery na poszczególne daty file.create_dir("wyniki/temperature"); file.create_dir("wyniki/humidity"); file.create_dir("wyniki/pressure"); file.create_dir("wyniki/speed"); file.create_dir("wyniki/direction"); file.create_dir("wyniki/rain"); file.create_dir("wyniki/battery"); fstream data;//dane string name = ""; bool clock_faliure = false; while(file.is_data) { file.get_data();// pobierz dane z jednego pakietu if(!clock_faliure) { clock_faliure = file.clock_faliure(); string time = file.get_time(); string filename = file.get_filename(); name = "wyniki/temperature/"; name += filename; data.open(name,ios::app); data << file.temperature; data << ";"; data <<time; data << ";"<<endl; data.close(); name = "wyniki/humidity/"; name += filename; data.open(name,ios::app); data << file.humidity; data << ";"; data <<time; data << ";"<<endl; data.close(); name = "wyniki/pressure/"; name += filename; data.open(name,ios::app); data << file.pressure; data << ";"; data <<time; data << ";"<<endl; data.close(); name = "wyniki/speed/"; name += filename; data.open(name,ios::app); data << file.windspeed; data << ";"; data <<time; data << ";"<<endl; data.close(); name = "wyniki/direction/"; name += filename; data.open(name,ios::app); data << file.winddir; data << ";"; data <<time; data << ";"<<endl; data.close(); name = "wyniki/rain/"; name += filename; data.open(name,ios::app); data << file.rain; data << ";"; data <<time; data << ";"<<endl; data.close(); name = "wyniki/battery/"; name += filename; data.open(name,ios::app); data << file.battery; data << ";"; data <<time; data << ";"<<endl; data.close(); } else { data.open("wyniki/temperature/temp.csv",ios::app); data << file.temperature; data << ";"<<endl; data.close(); data.open("wyniki/humidity/humidity.csv",ios::app); data << file.humidity; data << ";"<<endl; data.close(); data.open("wyniki/pressure/pressure.csv",ios::app); data << file.pressure; data << ";"<<endl; data.close(); data.open("wyniki/speed/speed.csv",ios::app); data << file.windspeed; data << ";"<<endl; data.close(); data.open("wyniki/direction/direction.csv",ios::app); data << file.winddir; data << ";"<<endl; data.close(); data.open("wyniki/rain/rain.csv",ios::app); data << file.rain; data << ";"<<endl; data.close(); data.open("wyniki/battery/battery.csv",ios::app); data << file.battery; data << ";"<<endl; data.close(); } } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#ifndef FILES_HPP_INCLUDED #define FILES_HPP_INCLUDED #include <sys/stat.h> #include <iostream> #include <fstream> #include <string> class save_file { public : void create_dir(const char*name);// utwórz folder void get_data();//pobierz pakiet danych bool clock_faliure(); std::string get_filename();//funkcja wytwarzająca nazwę pliku std::string get_time();//funkcja formatująca czas do formatu przyjmowanego prze exel / libre office std::string year = ""; std::string month = ""; std::string day = ""; std::string hour = ""; std::string minuit = ""; std::string second = ""; std::string temperature = ""; std::string humidity = ""; std::string pressure = ""; std::string windspeed = ""; std::string winddir = ""; std::string rain = ""; std::string battery = ""; bool is_data = true;//nowe dane unsigned int index = 0;// index odczytu pliku }; #endif // FILES_HPP_INCLUDED |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
#include "files.hpp" void save_file::create_dir(const char*name) { mkdir(name, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); } std::string save_file::get_filename() { std::string date = ""; date += day; date += "-"; date += month; date += "-"; date += year; date += ".csv"; return date; } std::string save_file::get_time() { std::string time = ""; time += hour; time += ":"; time += minuit; time += ":"; time += second; return time; } bool save_file::clock_faliure() { bool status = true; for(unsigned int i = 16;i<40;i++) { if(year==std::to_string(i)) { status = false; } } return status; } void save_file::get_data() { std::fstream save;//plik zapisu char buf; // wyzeruj zmienne aby zrobić miejsce dla nowych danych year = ""; month = ""; day = ""; hour = ""; minuit = ""; second = ""; temperature = ""; humidity = ""; pressure = ""; windspeed = ""; winddir = ""; rain = ""; battery = ""; save.open("SAVE.TXT",std::ios::in); //ustaw ozycję kursora na zmienną index index = index + 6;// przeskocz do dnia save.seekg(index); // procedura odczytu daty for(char i=0;i<3;i++) { save.get(buf); if(buf=='.') { break; } day += buf; } for(char i=0;i<3;i++) { save.get(buf); if(buf=='.') { break; } month += buf; } for(char i=0;i<4;i++) { save.get(buf); if(buf==' ') { break; } year += buf; } // przestaw pozycję licznika aby odczytać datę index = save.tellg(); index ++; save.seekg(index); // procedura odczytu godziny for(char i=0;i<3;i++) { save.get(buf); if(buf==':') { break; } hour += buf; } for(char i=0;i<3;i++) { save.get(buf); if(buf==':') { break; } minuit += buf; } for(char i=0;i<5;i++) { save.get(buf); if(buf!='0'&&buf!='1'&&buf!='2'&&buf!='3'&&buf!='4'&&buf!='5'&&buf!='6'&&buf!='7'&&buf!='8'&&buf!='9') { break; } second += buf; } // przenieś kursor do początku ciśnienia index = save.tellg(); index = index + 11; save.seekg(index); // odczytaj ciśnienie for(char i=0;i<10;i++) { save.get(buf); if(buf==' ') { break; } pressure += buf; } // wilgotność index = save.tellg(); index = index + 15; save.seekg(index); for(char i=0;i<7;i++) { save.get(buf); if(buf==' ') { break; } if(buf=='.') { buf = ','; } humidity += buf; } // temperatura index = save.tellg(); index = index + 16; save.seekg(index); for(char i=0;i<7;i++) { save.get(buf); if(buf==' ') { break; } if(buf=='.') { buf = ','; } temperature += buf; } //prędkość wiatru index = save.tellg(); index = index + 15; save.seekg(index); for(char i=0;i<7;i++) { save.get(buf); if(buf==' ') { break; } if(buf=='.') { buf = ','; } windspeed += buf; } //kierunek wiatru index = save.tellg(); index = index + 22; save.seekg(index); for(char i=0;i<7;i++) { save.get(buf); if(buf==' ') { break; } if(buf=='.') { buf = ','; } winddir+= buf; } //opady index = save.tellg(); index = index + 16; save.seekg(index); for(char i=0;i<7;i++) { save.get(buf); if(buf==' ') { break; } if(buf=='.') { buf = ','; } rain += buf; } //poziom baterii index = save.tellg(); index = index + 13; save.seekg(index); for(char i=0;i<7;i++) { save.get(buf); if(buf==' ') { break; } if(buf=='.') { buf = ','; } battery += buf; } index = save.tellg(); index = index +3; save.seekg(index); // sprawdź czy dalej są dane save.get(buf); is_data = false; if(buf=='-') { is_data = true; } save.close(); } |
W załączniku znajduje się projekt code::blocks zawierający gotowy do skompilowania kod programu. Niestety nie udało mi się ogarnąć kompilacji programów pod windowsa więc załączyłem tylko binarkę pod linuxa. ):
Plik z zapisem należy umieścić w tym samym folderze co plik wykonywalny a następnie uruchomić program.
Obok powinien się pojawić folder o nazwie “wyniki”
Zawierać on będzie następujące katalogi :
Każdy z nich zawiera pliki .csv z odczytami danego sensora oraz dokładnym czasem.
Nazwa każdego pliku to data z którego pochodzą dane odczyty.
Dane z danego pliku powinny się dać bezproblemowo zaimportować do arkusza kalkulacyjnego :
Część strukturalna
Pod względem konstrukcyjnym całość jest zrobiona z tego co miałem pod ręką.
Obudowa jest zrobiona z dużej plastikowej skrzynki.
Wewnątrz znajduje się akumulator (którego masa prawie 2kg stabilizuje całość) oraz elektronika.
Elektronika została dodatkowo opakowana w torebkę foliową która chroni ją przed wodą która może dostać się do środka. (Nie ufam mojej prowizorycznej uszczelce na pokrywce).
Jeśli chodzi o elektronikę to została ona zlutowana na płytce prototypowej a co cenniejsze elementy takie jak mikrokontroler, przetwornica i zegar można łatwo odłączyć na wypadek gdyby projekt wylądował na półce.
Czujniki analogowe są podłączone przez gniazda telefoniczne przylutowane do płytki.
Jedyny bardziej porządny element to obudowa płytki czujników.
Została ona zaprojektowana w 3D i wydrukowana z białego PLA. :
Modele 3D do pobrania na końcu artykułu
Jeśli chodzi o mocowanie masztu to jest ono następujące :
Maszt jest przełożony przez dziurę w pokrywce i przyklejony do nie za pomocą dużej ilości kleju na gorąco.
Jego koniec wchodzi natomiast w tulejkę która jest przyklejona do dna pudełka.
Może nie jest to eleganckie rozwiązanie ale działa :p
Na koniec jeszcze kilka zdjęć :
Kilka słów na koniec
No i to by było na tyle jeśli chodzi o ten projekt.
Być może w przyszłości go ulepszę dodając jakieś dodatkowe moduły po I2C, na przykład nadajnik albo dodatkowe sensory.
W załączniku znajduje się główny program stacji wraz ze kompilowanym plikiem .hex, modele do czujnika, program do analizy danych wraz z binarką pod linuxa oraz schemat w pliku .svg i jako źródło programu easy eda.
Komentarze, zarówno pozytywne jak i negatywne mile widziane.
Dobra robota ! Kiedyś zbuduję u siebie takką stację pogodową ale mam jeszcze sporo projektów w kolejce. Gdzie drukowałeś obudowę 3D którą zaprojektowałeś?
Dzięki (:
A co do obudowy to wydrukowałem u siebie na mojej drukarce 3D.
Super artykuł. Czekam na kolejne. Czy ramiona do czujników łączone z masztem też drukowałeś, czy były w zestawie?
Były w zestawie, kupiłem zestaw na botlandzie przepłacając przy tym 4.5 raza ):
Taniej wychodzi kupić czujniki osobno i zbudować jakiś uchwyt do masztu.
Nie znam się na elektronice i za pewne należą się gratulację. Pragnę jedynie zwrócić uwagę że estetyka wykonania projektu ma też duże znaczenie a ma na nią na pewno brak zaprojektowanej płytki co by wyeliminowało kłąb kabelków. I tak podziwiam ten projekt i proszę oczywiście nie zrażać się moją opinią. Powodzenia w realizacji dalszych pomysłów :)
Projekt fajny.
Pomysł również, jedynie psuje mi całość obudowa podstawy !!.
Oraz wydrukowałeś fajną obudowę a płytkę w środku kleisz na gorąco, psuje mi to efekt strasznie, nie będę się przyczepiał braku pcb ponieważ to można zawsze przerobić, ale takie klejenie na kolanie za bardzo do mnie nie przemawia.
Odemnie 5 za pomysł..
Fajne, ja bym zrobił nieco toporniejsze pudło na dole i na kółkach, do tego teleskopowy maszt z napinanymi linkami – taka mobilna stacja pogodowa by z tego wyszła. Do tego dopisać program co by na logice rozmytej ułożył “spektrum” warunków atmosferycznych i można byłoby sprawdzać np. jaki mikroklimat panuje po jednej stronie np. wzgórza a jaki po drugiej.
A i koniecznie precyzyjny czujnik natężenia światła by się przydał, wtedy byłaby idealna stacja do badania terenu pod “plantację” paneli solarnych :)
Ciekawy pomysł, może kiedyś go zrealizuję (:
Dziś trudno znaleźć wielkość fizyczną, dla której elektronicy i fizycy nie wypracowali przynajmniej kilku różnych rodzajów czujników. Co bardzo ważne, szczególnie w przypadku pomiarów tzw. wielkości nieelektrycznych (np. temperatury, oświetlenia czy wilgotności)