Skocz do zawartości

Kącik AA (Astro Arduino)


Rekomendowane odpowiedzi

Arduino to otwarta platforma elektroniczna bazująca na łatwej w implementacji elektronice oraz prostym oprogramowaniu http://arduino.cc/ .  Dzięki prostocie, darmowemu i łatwemu w obsłudze środowisku do programowania Arduino IDE oraz ogromnej ilości gotowych bibliotek, przykładów, płytek, modułów i shieldów pozwala skupić się na implementacji własnych rozwiązań i pomysłów, a nie na zgłębianiu coraz to nowych tajemnic dostępnych jedynie dla wybrańców.

W sprzedaży jest dostępna spora ilość różnego typu modułów Arduino, poniżej Arduino Uno, czyli pierwsze :)

post-1260-0-10528000-1405416728.jpg

 

Arduino Uno może być zasilane bezpośrednio z portu USB komputera lub z dodatkowego źródła zasilania o napięciu 7-16V. Po podłączeniu płytki do komputera zainstalowane zostaną sterowniki portu szeregowego i układ jest gotowy do pracy. Mając zainstalowane darmowe środowisko Arduino IDE możemy napisać pierwszy prosty program:

void setup() {
 digitalWrite(2, LOW); 
}

void loop() {
  digitalWrite(2, HIGH);
  delay(1000);
  digitalWrite(2, LOW);
  delay(1000);
}

Kod Arduino zawiera dwie predefiniowane funkcje setup() oraz loop(). Pierwsza z nich wykonywana jest jeden raz po uruchomieniu albo restarcie urządzenia. W naszym przykładzie w bloku startowym ustawiamy wartość pinu 2 Arduino na niską. Pętla loop wykonywana jest w czasie działania urządzenia cały czas bez końca. W naszym prostym przykładzie zamienia ona cyklicznie stan pinu 2 pomiędzy wysokim i niskim a pomiędzy zmianami umieściliśmy funkcję delay która wprowadza dodatkowe opóźnienie równe u nas 1000 milisekund. Po podłączeniu teraz do pinu 2 urządzenia przez dodatkowy rezystor np diody LED i załadowaniu naszego programu do Arduino będzie ona cyklicznie włączana i wyłączana. 

 

W wątku tym będę prezentował cyklicznie różne proste rozwiązania w oparciu o Arduino, które mogą się przydać astroamatorom. W następnym poście opiszę sterowany z komputera układ umożliwiający zdalne włączanie i wyłączanie różnych urządzeń. Oczywiście jestem otwarty na wszystkie propozycje układów do opisania tutaj.

 

 

  • Like 8
Odnośnik do odpowiedzi
Udostępnij na innych stronach
  • Odpowiedzi 141
  • Utworzony
  • Ostatnia odpowiedź

Najczęściej odpowiadający w tym temacie

Najczęściej odpowiadający w tym temacie

Popularne odpowiedzi

Arduino to otwarta platforma elektroniczna bazująca na łatwej w implementacji elektronice oraz prostym oprogramowaniu http://arduino.cc/ .  Dzięki prostocie, darmowemu i łatwemu w obsłudze środowisku

Dodane obrazy

Dałoby radę :) Arduino ma też tą fajną cechę, że można w nim zmieniać program przez ten sam kabel USB przez który później z nim się komunikujemy, a więc można np zaktualizować oprogramowanie w urządzeniu które już jest przypięte do teleskopu a potem zaraz go używać bez żadnego pędzlowania kablami. 

Czas na pierwsze zastosowanie - zdalne włączanie i wyłączanie urządzeń za pomocą Arduino. Zasada działania będzie prosta - poleceniami wysyłanymi z komputera chcemy włączać i wyłączać przekaźniki, które następnie mogą zasilać interesujące nas urządzenia. Poniżej poglądowy schemat jak połączyć kilka elementów w interesującą nas całość:

post-1260-0-11852700-1405427091_thumb.jp

 

Oprócz Arduino i przekaźnika będziemy potrzebowali jakiś tranzystor NPN małej mocy (BC237, BC548 albo podobny) oraz dwa rezystory (górny 4.7kohm, dolny 100kohm) i diodę zabezpieczającą (dowolna dioda, np 1N4148 albo podobna). Schemat przedstawia tylko jeden kanał sterujący, ale do każdego wyjścia Arduino możemy podpiąć kolejny przekaźnik i sterować wieloma jednocześnie.

Styki przekaźnika mogą włączać napięcie 12V ale też napięcie sieciowe 230V. W drugim przypadku należy oczywiście zachować wszystkie środki ostrożności przy pracy z napięciem sieciowym. Można też zastosować optoizolator pomiędzy Arduino i tranzystorem sterującym, ale żeby to miało sens uzwojenie przekaźnika należałoby zasilać osobnym napięciem zasilania 12V a nie tym, którym zasilamy Arduino i resztę układu.

Opcjonalna dioda LED z rezystorem 1kohm będzie nam pokazywała, kiedy przekaźnik jest włączony.

 

Kod:

#define RELAY_PIN 2

String inputString = "";         // zmienna przechowująca polecenia z portu szeregowego
boolean stringComplete = false;  // oznacza zakończenie transmisji komendy


void setup() {
  // ustawiamy wyjście przekaźnika w stan niski
  digitalWrite(RELAY_PIN, LOW);
  // inicjalizujemy port szeregowy
  Serial.begin(9600);
  // rezerwujemy 5 bajtów na dane wejściowe
  inputString.reserve(5);  
}

void loop() {
  // jeśli pobrano komendę 
  if (stringComplete) {
    // rozpoznajemy ją: (R:0, R:1)
    if(inputString.charAt(0) == 'R') {
      // i jej argument
      if(inputString.charAt(2) == '0') {
        digitalWrite(RELAY_PIN, LOW);
      } else {
        digitalWrite(RELAY_PIN, HIGH);
      }
    }
    // czyścimy bufor polecenia
    inputString = "";
    stringComplete = false;
  }  
}

/*
  Zdarzenie SerialEvent jest wywoływane kiedy nowe dane 
  napływają do portu szeregowego kontrolera. 
 */
void serialEvent() {
  while (Serial.available()) {
    // pobieramy bajt danych
    char inChar = (char)Serial.read(); 
    // dodajemy do zmiennej przechowującej komendę
    inputString += inChar;
    // jeśli pobrany znak to znak nowej linii
    // ustawiamy flagę oznaczającą koniec polecenia
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

Kodu jest dość sporo, ale większość to komentarze. W sekcji setup wyłączamy przekaźnik, inicjujemy port szeregowy Arduino oraz rezerwujemy zmienną na przesyłane polecenia. Zdarzenie serialEvent wywoływane jest automatycznie po odebraniu danych przez port szeregowy. Po odebraniu znaku nowej linii oznaczającego koniec polecenia ustawiana jest flaga stringComplete i w pętli loop z polecenia odczytywany jest jego argument i odpowiednio przełączany jest przekaźnik. Po załadowaniu kodu do Arduino możemy otworzyć dowolny monitor portu szeregowego (również ten wbudowany w Arduino IDE) i wpisując w jego okienko polecenie R:0 będziemy wyłączali przekaźnik a po wpisaniu R:1 przekaźnik zostanie włączony.

Zamiast samemu montować układ możemy do Arduino Uno zakupić gotowy relay shield wyposażony w kilka przekaźników i pozostanie nam jedynie zaprogramowanie układu:

post-1260-0-24027200-1405427497_thumb.jp

 

W następnej części rozbudujemy nieco kod sterujący tak, żeby umożliwić włączanie i wyłączanie przekaźników po określonym czasie albo według zadanego prostego planu. Oczywiście zachęcam do prezentowania swoich pomysłów i rozwiązań  B)

  • Like 4
Odnośnik do odpowiedzi
Udostępnij na innych stronach

Dziś wieczorem już dość poważny kawałek kodu - obiecuję, że kolejne zaplanowane odcinki (sterowanie PWM grzałkami i wentylatorami, pomiary temperatury i wilgotności, sterowanie silnikami) będą znacznie prostsze :) Prezentowany poniżej kod jest już dość zaawansowany i zawiera elementy, z których można budować rozbudowane programy.

Do schematu z poprzedniej części dodamy logikę, która umożliwi planowanie załączania i wyłączania przekaźnika według zadanego schematu. Sterowanie w dalszym ciągu będzie się odbywało przez port szeregowy, dostępne komendy będą wyglądały teraz tak:

 - R:1, R:0 - załączanie i wyłączanie przekaźnika

 - R:R - start kolejki zdarzeń

 - R:S - zatrzymanie kolejki

 - R:I  - wyświetlenie ilości elementów w kolejce

 - R:Q:1:1200 - zakolejkowanie włączenia przekaźnika na okres 1200ms

 - R:Q:0:350 - zakolejkowanie wyłączenia przekaźnika na okres 350ms

 

Na początku kodu zaimportowane są dwie biblioteki: Timer oraz QueueArray . Ta pierwsza wspomaga nas w prostym sterowaniu zdarzeniami, druga natomiast to typowa implementacja kolejki dowolnych obiektów. W naszym przypadku obiektami w kolejce będą struktury RelayEvent  zawierające dwie informacje: stan pinu oraz czas przez jaki ten stan ma być zachowany.

Blok setup nie zmienił się w porównaniu do poprzedniego przykładu. W pętli loop dodaliśmy obsługę kilku nowych komend dostarczanych przez port szeregowy. Dodatkowo na końcu pętli loop wywoływana jest aktualizacja timera, który odpowiada za wywoływanie kolejnych zdarzeń z kolejki. Po dodaniu kilku elementów do kolejki i uruchomieniu jej poleceniem R:R wywoływana jest metoda processQueue i jeśli kolejka zawiera jeszcze jakieś elementy, to kolejny element jest pobierany z kolejki, ustawiane jest odpowiednio wyjście sterujące przekaźnikiem, a na końcu do timera dodawane jest polecenie uruchomienia kolejny raz metody processQueue po upływie zadanego okresu czasu. Jeśli kolejka jest pusta timer jest zatrzymywany a wyjście sterujące przekaźnikiem jest ustawiane w stan niski.

Całości programu dopełniają wysyłane na port szeregowy komunikaty o postępie w przetwarzaniu kolejki. Ciekawą cechą implementacji jest możliwość dodawania do kolejki kolejnych zdarzeń podczas jej wykonywania. Nowe zdarzenia zostaną dopisane do kolejki i przetworzone jak istniejące już wcześniej.

Aby np włączyć przekaźnik na 30 sekund należy dodać polecenie R:Q:1:30000 i uruchomić kolejkę.

Aby włączyć przekaźnik na 15 minut ale po upływie 4 minut należy dodać polecenia: R:Q:0:240000 oraz R:Q:1:900000 i uruchomić kolejkę.

 

Oto kod:

#include <Timer.h>
#include <QueueArray.h>

struct RelayEvent {
    char pinState;
    long time;
};

#define RELAY_PIN 2

String inputString = "";         // zmienna przechowująca polecenia z portu szeregowego
boolean stringComplete = false;  // oznacza zakończenie transmisji komendy
QueueArray <RelayEvent> queue;   // kolejka zdarzeń
int queueTimer = 0;
Timer timer;


void setup() {
  digitalWrite(RELAY_PIN, LOW);
  Serial.begin(9600);
  inputString.reserve(15);  
}

void loop() {
  // R:0, R:1, R:R, R:S, R:Q:1:2300, R:Q:0:12
  if (stringComplete) {
    if(inputString.charAt(0) == 'R') {
      switch (inputString.charAt(2)) {
        case '0': if (queue.isEmpty()) digitalWrite(RELAY_PIN, LOW); break;
        case '1': if (queue.isEmpty()) digitalWrite(RELAY_PIN, HIGH); break;
        case 'R': processQueue(); break;
        case 'S': timer.stop(queueTimer); digitalWrite(RELAY_PIN, LOW); break;
        case 'Q': enqueueCommand(inputString); break;
        case 'I': Serial.println(queue.count()); break;
      }
    }    
    inputString = "";
    stringComplete = false;
  }  
  timer.update();
}

void enqueueCommand(String command) {
  RelayEvent event = {command.charAt(4), command.substring(6).toInt()};
  queue.enqueue(event); 
}

void processQueue() {
  if(queue.isEmpty()) {
    timer.stop(queueTimer); 
    digitalWrite(RELAY_PIN, LOW);
    Serial.println("Queue end");
  } else {
    RelayEvent event = queue.dequeue();
    Serial.println("Processing: " + String(event.pinState) + ", " + event.time + "ms");
    (event.pinState == '0') ? digitalWrite(RELAY_PIN, LOW) : digitalWrite(RELAY_PIN, HIGH);
    queueTimer = timer.after(event.time, processQueue);
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

Dołączam też użyte biblioteki, które oczywiście podobnie jak setki innych można znaleźć w internecie, a należy je rozpakować do folderu Moje dokumenty/Arduino/libraries . 

Do zabawy z Arduino polecam płytki stykowe do których możemy wpinać do testów przeróżne elementy i moduły i testować bezboleśnie nasze pomysły:

post-1260-0-86693000-1405539864_thumb.jp

 

To tyle tym razem, następnym razem już obiecuję ciekawsze rzeczy :) W najbliższej części sterowanie PWM grzałkami, wentylatorami, ewentualnie na przykład oświetleniem :D

QueueArray.zip

Timer.zip

  • Like 4
Odnośnik do odpowiedzi
Udostępnij na innych stronach

Tym razem będzie lżej - sterowanie PWM. Polega ono na tym, że sygnał sterujący to sygnał prostokątny, który jest włączony przez określony czas, a następnie wyłączony. Sygnał o wypełnieniu 100% jest włączony cały czas, 0% - cały czas wyłączony, a np sygnał 30% o częstotliwości 100Hz włączony jest przez 3ms a następnie wyłączony przez 7ms i tak w kółko. W ten sposób urządzenie sterowane jest 30% mocy. W przypadku Arduino domyślna częstotliwość wyjść PWM to 490Hz.

Oto przykładowy schemat dwukanałowego sterownika PWM:

post-1260-0-65239800-1405597332_thumb.jp

 

Umożliwi nam on sterowanie dwoma urządzeniami, np dwoma opaskami grzewczymi albo opaską i wentylatorem chłodzącym teleskop. Jak tranzystor sterujący (polowy - FET, MOSFET, HEXFET) należy wybrać taki o niskim napięciu Vgs, np IRLZ44 - model ten charakteryzuje się też małą rezystancją w stanie włączonym, i do prądu 3-4A nie musimy stosować żadnego radiatora. Na schemacie nie uwzględniono podłączenia zasilania Arduino - możemy go zasilać tym samym napięciem co tranzystory mocy, albo przez port USB.

Do sterowania wyjściami PWM w Arduino stosujemy komendę:

analogWrite(pin, value)

gdzie pin to numer wyjścia Arduino, a value to wartość z zakresu 0 do 255. W naszym przykładowym kodzie będziemy sterować wyjściami PWM za pomocą komend podawanych przez port szeregowy, a wpisane wartości będą zapisywane w pamięci nieulotnej (EEPROM) kontrolera, a więc po kolejnym włączeniu Arduino zostaną one automatycznie przywrócone. Kod nie zawiera żadnych niespodzianek - w sekcji setup wczytujemy zapamiętane wartości PWM oraz inicjujemy port szeregowy. W pętli loop nasłuchujemy nowych komend. Po otrzymaniu komendy zostaje ona rozbita na dwie części, następnie sprawdzamy czy podany numer pinu zawiera się w dozwolonym przedziale i zapamiętujemy w pamięci EEPROM podaną wartość dla danego pinu. Ostatnim krokiem jest zaktualizowanie wyjść PWM i podanie komunikatu na port szeregowy:

#include <EEPROM.h>

#define PWM1_PIN 5
#define PWM2_PIN 6

String inputString = "";         // zmienna przechowująca polecenia z portu szeregowego
boolean stringComplete = false;  // oznacza zakończenie transmisji komendy


void setup() {
  updatePWM();
  Serial.begin(9600);
  inputString.reserve(6);  
}

void loop() {
  // 5:0, 5:110, 6:255
  if (stringComplete) {
    byte pin = inputString.substring(0,1).toInt();
    byte value = inputString.substring(2).toInt();

    if(pin == PWM1_PIN || pin == PWM2_PIN) {
      value = constrain(value, 0, 255);
      EEPROM.write(pin, value);
      updatePWM();
      Serial.println("Pin " + String(pin) + " updated to " + String(value));
    } else {
       Serial.println("Pin " + String(pin) + " not allowed.");
    }
  
    inputString = "";
    stringComplete = false;
  }  
}

void updatePWM() {
  analogWrite(PWM1_PIN, EEPROM.read(PWM1_PIN));
  analogWrite(PWM2_PIN, EEPROM.read(PWM2_PIN));
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

Funkcja constrain gwarantuje, że podana wartość nie będzie spoza zadanego zakresu. Czyli np komenda:

 - 5:0 - wyłączy nam wyjście 5

 - 5:127 - włączy nam wyjście 5 z wypełnieniem 50%

 - 6:255 - włączy nam wyjście 6 z wypełnieniem 100%

 

Zapraszam do eksperymentów :) W następnej części pomiary temperatury i wilgotności.

  • Like 2
Odnośnik do odpowiedzi
Udostępnij na innych stronach

W dzisiejszym odcinku pomiary. Do Arduino podłączymy dwa popularne czujniki: tani i precyzyjny choć niezbyt szybki czujnik temperatury DS1820, oraz nieco droższy czujnik temperatury i wilgotności DHT22. Oba podłącza się w ten sam sposób jak na schemacie (rezystory o wartości 4.7kohm lub w pobliżu):

post-1260-0-98831600-1405949355_thumb.jp

 

i tyle, reszta w kodzie. Nasz kod nieco spuchł od ostatniego razu - doszły dedykowane biblioteki do obsługi czujników, biblioteka Timer która co sekundę odczytuje dane z czujników i zapisuje je do lokalnych zmiennych w Arduino oraz funkcja obliczająca temperaturę punktu rosy dla danych odczytanych z czujników. W sekcji setup inicjujemy port szeregowy oraz sprawdzamy czy czujniki zostały podłączone i wykryte. Dodatkowo zostaje zainicjowany timer, który co sekundę będzie wywoływał odczyt danych w funkcji updateSensors. W sekcji loop standardowo nasłuchujemy port szeregowy oraz aktualizujemy obiekt timer odpowiedzialny za wywołanie odczytu danych. Jeśli na porcie szeregowym pojawi się komenda 'M' przesyłamy z powrotem odczytane dane rozdzielone średnikiem.

 

Kod wygląda tak:

#include <OneWire.h>
#include <DallasTemperature.h>
#include <dht.h>
#include <Timer.h>

#define DHT22_PIN 2
#define DS8120_PIN 3
#define PWM1_PIN 5
#define PWM2_PIN 6

OneWire oneWire(DS8120_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress insideThermometer;
dht DHT;
Timer timer;

String inputString = "";         // zmienna przechowująca polecenia z portu szeregowego
boolean stringComplete = false;  // oznacza zakończenie transmisji komendy
boolean DS1820connected = false;
boolean DHT22connected = false;
float currentTemp;                  
float currentHum;                   
float currentDewpoint;             
float currentDSTemp;                 

void setup() {
  Serial.begin(9600);
  inputString.reserve(6);  
  
  int chk = DHT.read22(DHT22_PIN); 
  if(chk == DHTLIB_OK) DHT22connected = true;  
  
  sensors.begin(); 
  DS1820connected = sensors.getAddress(insideThermometer, 0);  
  if(DS1820connected) sensors.setResolution(insideThermometer, 10);
  
  timer.every(1000, updateSensors);
}

void loop() {
  if (stringComplete) {
    if(inputString.charAt(0) == 'M') {
      Serial.print(currentTemp);
      Serial.print(';');
      Serial.print(currentHum);
      Serial.print(';');
      Serial.print(currentDewpoint);
      Serial.print(';');
      Serial.println(currentDSTemp);
    }
  
    inputString = "";
    stringComplete = false;
  }  
  timer.update();
}

void updateSensors() {
  if (DS1820connected) {
    sensors.requestTemperaturesByAddress(insideThermometer); // przy 10 bitowej dokładności trwa 188ms
    currentDSTemp = sensors.getTempC(insideThermometer);
  }
  
  if (DHT22connected) {
    DHT.read22(DHT22_PIN);
    currentTemp = DHT.temperature;
    currentHum = DHT.humidity;
    currentDewpoint = dewPoint(currentTemp, currentHum);  
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

// dewPoint function NOAA
// reference (1) : http://wahiduddin.net/calc/density_algorithms.htm
// reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm
//
double dewPoint(double celsius, double humidity)
{
	// (1) Saturation Vapor Pressure = ESGG(T)
	double RATIO = 373.15 / (273.15 + celsius);
	double RHS = -7.90298 * (RATIO - 1);
	RHS += 5.02808 * log10(RATIO);
	RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ;
	RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ;
	RHS += log10(1013.246);

        // factor -3 is to adjust units - Vapor Pressure SVP * humidity
	double VP = pow(10, RHS - 3) * humidity;

        // (2) DEWPOINT = F(Vapor Pressure)
	double T = log(VP/0.61078);   // temp var
	return (241.88 * T) / (17.558 - T);
}

W załączniku użyte biblioteki (prosto z Internetu).  W następnej części podepniemy pod Arduino wyświetlacz LCD żeby uniezależnić się trochę od portu szeregowego, a potem dane z czujników (temperaturę, wilgotność i punkt rosy) wykorzystamy do sterowania przez wyjścia PWM grzałek oraz wentylatorów chłodzących lustro :)

PS - oczywiście opóźnienie 188ms wprowadzane przez odczyt z czujnika DS1820 jest zazwyczaj niedopuszczalne. W kolejnych odcinkach pokażę, jak dokonywać odczytu asynchronicznie, czyli bez blokowania kodu w oczekiwaniu na wynik pomiaru.

DallasTemperatureControl.zip

DHT.zip

OneWire.zip

Timer.zip

  • Like 3
Odnośnik do odpowiedzi
Udostępnij na innych stronach
  • 4 miesiące później...

Jako że to mój pierwszy post na tym forum witam Wszystkich użytkowników .

 

Jolo mam pytanie czy będziesz rozwijał ten temat?

Bardzo mnie interesuje temat arduino zastosowanego w astro.

Chcę wykonać samodzielnie sterownik do chłodzenia Canona który będzie utrzymywał temperaturę matrycy na zadanej temperaturze,

sterując peltierem i ewentualnie wentylatorem,

wraz z odczytem na LCD temp matrycy , wilgotności w obudowie aparatu, punktu rosy , PWM dla peltiera / wentylatora.

Odnośnik do odpowiedzi
Udostępnij na innych stronach

Kupowałem na allegro klony. Widzę, że użytkownik telmal_store ma znowu, u niego kupowałem kiedyś. Electropark też ma oryginały i klony na allegro i u siebie w sklepie http://electropark.pl/410-plytki-bazowe - u nich wiele razy kupowałem różne rzeczy.

Odnośnik do odpowiedzi
Udostępnij na innych stronach

W ten sposób można zbudować minimalnego klona JoloFocusera ( https://github.com/sirJolo/ascom-jolo-focuser/tree/Production_21 ) za 20 złotych dodając do Arduino Nano jedynie sterownik silnika krokowego L298D albo podobny, do czego zachęcam :) Skecz Arduino i sterownik ASCOM dostępny jest na podlinkowanym repozytorium na githubie.

  • Like 1
Odnośnik do odpowiedzi
Udostępnij na innych stronach

W ten sposób można zbudować minimalnego klona JoloFocusera ( https://github.com/sirJolo/ascom-jolo-focuser/tree/Production_21 ) za 20 złotych dodając do Arduino Nano jedynie sterownik silnika krokowego L298D albo podobny, do czego zachęcam :) Skecz Arduino i sterownik ASCOM dostępny jest na podlinkowanym repozytorium na githubie.

No ja czekam na tą wersje ful wypas. ;)

Odnośnik do odpowiedzi
Udostępnij na innych stronach
Dziś w kąciku dwie rzeczy - asynchroniczny odczyt z czujnika temperatury DS1820 oraz wstęp do obsługi wyświetlacza. Czujnik DS1820 jest dokładny, ale jego pewną wadą jest dość długi czas pomiaru temperatury. Przy rozdzielczości 10 bit trwa on 188ms, zwiększenie rozdzielczości powoduje wydłużenie tego czasu. Dla nas to mgnienie oka, ale kontroler w tym czasie może wykonać tysiące operacji i nie ma sensu blokować go na czas trwania odczytu. Na szczęście biblioteka DallasTemperature umożliwia pracę czujnika w trybie asynchronicznym. W tym celu musimy ustawić opcję setWaitForConversion na false, a następnie po rozpoczęciu pomiaru temperatury odczekać 188ms i odczytać temperaturę z czujnika. 
W programie poniżej metoda updateSensors wywoływana jest cyklicznie co 1000ms, a z niej za każdym razem po upływie 188ms wywoływana jest metoda readTemp, która odczytuje temperaturę z czujnika i następnie wyświetla ją na wyświetlaczu LCD. Właśnie, wyświetlaczu - możecie zobaczyć jak prosta jest obsługa wyświetlacza w Arduino. Po zdefiniowaniu wyświetlacza lcd oraz wskazaniu do których pinów Arduino jest on podpięty (po szczegóły podłączania odsyłam do http://arduino.cc/en/Tutorial/LiquidCrystal ) możemy go zainicjalizować w bloku setup podając ilość kolumn i wierszy wyświetlacza. Od tego momentu jest on gotowy do użycia. W metodzie updateLCD czyścimy wyświetlacz, wyświetlamy odczytaną temperaturę, następnie przestawiamy kursor wyświetlacza do drugiej linijki i wyświetlamy temperaturę przeliczoną na stopnie Fahrenheita. Całości dopełnia obsługa portu szeregowego z dostępną jedną komendą przesyłającą odczytaną temperaturę na żądanie.
 
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Timer.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>

#define DS8120_PIN 3

OneWire oneWire(DS8120_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress insideThermometer;
Timer timer;
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

String inputString = "";         // zmienna przechowująca polecenia z portu szeregowego
boolean stringComplete = false;  // oznacza zakończenie transmisji komendy
boolean DS1820connected = false;
float currentTemp;                  


void setup() {
  Serial.begin(9600);
  inputString.reserve(6);  
  
  sensors.begin(); 
  DS1820connected = sensors.getAddress(insideThermometer, 0);  
  if(DS1820connected) sensors.setResolution(insideThermometer, 10);
  sensors.setResolution(insideThermometer, 10);
  sensors.setWaitForConversion(false);  
  
  lcd.begin(16, 2);
  
  timer.every(1000, updateSensors);
}

void loop() {
  if (stringComplete) {
    switch(inputString.charAt(0)) {
      case 'M': printData(); break;
      default: Serial.println("Unknown command: " + inputString);
    }
    inputString = "";
    stringComplete = false;
  }  
  timer.update();
}

void printData() {
  Serial.println(currentTemp);
}

void updateSensors() {
  if (DS1820connected) {
    sensors.requestTemperaturesByAddress(insideThermometer); // przy 10 bitowej dokładności trwa 188ms
    timer.after(188, readTemp);
  }
}

void readTemp() {
  currentTemp = sensors.getTempC(insideThermometer); 
  updateLCD();
}

void updateLCD() {
  lcd.clear();
  lcd.print("Temp [C]: ");
  lcd.print(currentTemp);
  
  float tempF = (currentTemp * 9.0)/ 5.0 + 32.0;
  lcd.setCursor(0, 1);
  lcd.print("Temp [F]: ");
  lcd.print(tempF);
}


void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

W następnej części wrócimy do czujnika DHT22 i sterowania PWM.

 

  • Like 1
Odnośnik do odpowiedzi
Udostępnij na innych stronach

Ja jeszcze dodam dwa zdania odnośnie LCD. Ponieważ miałem problem ze znalezieniem odpowiedniego potencjometru do ustawiania kontrastu LCD konieczne było jego ustawianie w inny sposób i takowy znalazłem. Otóż kontrast ustawiam za pomocą PWM korzystając z jednego z wyjść Arduino. Do tego równolegle podpinam kondensator 100uF żeby uniknąć migotania - działa pięknie i do tego mam możliwość ustawiania kontrastu z poziomu kodu.

 

W sekcji setup dodaje:


  pinMode(9, OUTPUT);
  analogWrite(9, 50);

Pin 9 jako wyjscie PWM. 50 to wartość dla PWMa ustawiana eksperymentalnie.

U mnie dla LCD 4x20 dobra jest 50. Dla 2x16 musi byc 85. Zapewne kazdy inny bedzie musiał miec inaczej.

  • Like 1
Odnośnik do odpowiedzi
Udostępnij na innych stronach

Ciekawy pomysł z regulowaniem kontrastu - ja bym może dodał jeszcze w szereg może jakiś niewielki rezystor 10-20 omów, żeby nie obciążać za bardzo wyjścia Arduino. Będzie to wtedy taki prosty filtr RC.

Co to sterowania wyświetlacza przez I2C to nie jest to problem. Trzeba albo dokupić konwerter I2C -> LCD i go wpiąć w posiadany wyświetlacz, albo kupić od razu wyświetlacz z takim konwerterem:

400px-1602I2C_2.jpg

 

Dołączam też bibliotekę sprawdzoną przeze mnie do obsługi wyświetlacza przez I2C - ma ona tą zaletę, że oprócz możliwości domyślnej konfiguracji wyświetlacza:

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x38);  // Set the LCD I2C address

setup() {
  lcd.begin(16,2);               // initialize the lcd
etc.
.
.

można też samemu zmapować odpowiednie piny z rejestru w konwerterze I2C->LCD, bo niektóre z nich mają nieco zmienione wyprowadzenia. Używamy do tego nieco bardziej rozbudowanego konstruktora wtedy. Składnię wszystkich dostępnych konstruktorów możecie sprawdzić w pliku LiquidCrystal_I2C.h w dołączonym zestawie bibliotek.

post-1260-0-99190100-1417507604_thumb.jp

LiquidCrystal_V1.2.1.zip

Odnośnik do odpowiedzi
Udostępnij na innych stronach

 

Dziś w kąciku dwie rzeczy - asynchroniczny odczyt z czujnika temperatury DS1820 oraz wstęp do obsługi wyświetlacza. Czujnik DS1820 jest dokładny, ale jego pewną wadą jest dość długi czas pomiaru temperatury. Przy rozdzielczości 10 bit trwa on 188ms, zwiększenie rozdzielczości powoduje wydłużenie tego czasu. Dla nas to mgnienie oka, ale kontroler w tym czasie może wykonać tysiące operacji i nie ma sensu blokować go na czas trwania odczytu. Na szczęście biblioteka DallasTemperature umożliwia pracę czujnika w trybie asynchronicznym. W tym celu musimy ustawić opcję setWaitForConversion na false, a następnie po rozpoczęciu pomiaru temperatury odczekać 188ms i odczytać temperaturę z czujnika. 
W programie poniżej metoda updateSensors wywoływana jest cyklicznie co 1000ms, a z niej za każdym razem po upływie 188ms wywoływana jest metoda readTemp, która odczytuje temperaturę z czujnika i następnie wyświetla ją na wyświetlaczu LCD. Właśnie, wyświetlaczu - możecie zobaczyć jak prosta jest obsługa wyświetlacza w Arduino. Po zdefiniowaniu wyświetlacza lcd oraz wskazaniu do których pinów Arduino jest on podpięty (po szczegóły podłączania odsyłam do http://arduino.cc/en/Tutorial/LiquidCrystal ) możemy go zainicjalizować w bloku setup podając ilość kolumn i wierszy wyświetlacza. Od tego momentu jest on gotowy do użycia. W metodzie updateLCD czyścimy wyświetlacz, wyświetlamy odczytaną temperaturę, następnie przestawiamy kursor wyświetlacza do drugiej linijki i wyświetlamy temperaturę przeliczoną na stopnie Fahrenheita. Całości dopełnia obsługa portu szeregowego z dostępną jedną komendą przesyłającą odczytaną temperaturę na żądanie.
 
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Timer.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>

#define DS8120_PIN 3

OneWire oneWire(DS8120_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress insideThermometer;
Timer timer;
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

String inputString = "";         // zmienna przechowująca polecenia z portu szeregowego
boolean stringComplete = false;  // oznacza zakończenie transmisji komendy
boolean DS1820connected = false;
float currentTemp;                  


void setup() {
  Serial.begin(9600);
  inputString.reserve(6);  
  
  sensors.begin(); 
  DS1820connected = sensors.getAddress(insideThermometer, 0);  
  if(DS1820connected) sensors.setResolution(insideThermometer, 10);
  sensors.setResolution(insideThermometer, 10);
  sensors.setWaitForConversion(false);  
  
  lcd.begin(16, 2);
  
  timer.every(1000, updateSensors);
}

void loop() {
  if (stringComplete) {
    switch(inputString.charAt(0)) {
      case 'M': printData(); break;
      default: Serial.println("Unknown command: " + inputString);
    }
    inputString = "";
    stringComplete = false;
  }  
  timer.update();
}

void printData() {
  Serial.println(currentTemp);
}

void updateSensors() {
  if (DS1820connected) {
    sensors.requestTemperaturesByAddress(insideThermometer); // przy 10 bitowej dokładności trwa 188ms
    timer.after(188, readTemp);
  }
}

void readTemp() {
  currentTemp = sensors.getTempC(insideThermometer); 
  updateLCD();
}

void updateLCD() {
  lcd.clear();
  lcd.print("Temp [C]: ");
  lcd.print(currentTemp);
  
  float tempF = (currentTemp * 9.0)/ 5.0 + 32.0;
  lcd.setCursor(0, 1);
  lcd.print("Temp [F]: ");
  lcd.print(tempF);
}


void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

W następnej części wrócimy do czujnika DHT22 i sterowania PWM.

 

Mam jeszcze pytanie. Czy Twoje rozwiązanie tylko skracza ten czas jednej pętli do 188ms? Czy sam czujnik mierzy temp kiedy sie go o to za pyta czy on sobie ja mierzy caly czas i wysylajac zapytanie daje zwrotnie tylko ostatnia zapisana u siebie wartosc ktora jest jeszcze przed kolejnym pomiarem. Bo to by bylo dobre ale pewnie to tak nie dziala :)

Odnośnik do odpowiedzi
Udostępnij na innych stronach

Czujnik DS1820 zaczyna pomiar po poleceniu requestTemperaturesByAddress i następnie po upływie 188ms pomiar jest zakończony (przy ustawionej rozdzielczości 10bit) i temperatura jest odczytywana poleceniem getTempC

Jeśli chodzi o czasy to co 1000ms jest wywoływana metoda updateSensors a readTemp po opóźnieniu 188ms:

 

Czas [ms] wywołanie

1000      updateSensors

1188      readTemp

2000      updateSensors

2188      readTemp

Odnośnik do odpowiedzi
Udostępnij na innych stronach

To zależy jak masz ustawione flagi checkForConversion oraz waitForConversion. Domyślnie obie są ustawione na true i program jest blokowany na odpowiedni czas w metodzie DallasTemperature::blockTillConversionComplete (w zależności od ustawionej rozdzielczości od 94 do 750ms).  Jeśli stosujesz inną bibliotekę albo własne rozwiązanie to oczywiście temperaturę z rejestru czujnika możesz w każdej chwili odczytać, trzeba być tylko świadomym kiedy nastąpił pomiar, żeby nie odczytywać starej wartości.

Odnośnik do odpowiedzi
Udostępnij na innych stronach

Dziś może trochę mało życiowy przykład kończący opisy odczytów z czujników. Do Arduino mamy podpięte dwa czujniki: DHT22 mierzy temperaturę i wilgotność otoczenia, natomiast DS1820 przymocowany jest do zwierciadła teleskopu i mierzy jego temperaturę. Na podstawie odczytanych danych ustalana jest moc PWM dla sterowania dwóch urządzeń:

 - grzałek eliminujących roszenie na optyce (na podstawie bieżącej wartości wilgotności względnej)

 - wentylatora chłodzącego lustro (na podstawie różnicy temperatury pomiędzy otoczeniem i lustrem)

 

Kod jest dość prosty, bez obsługi LCD i portu szeregowego tym razem, żeby nie zaciemniać interesującej nas funkcjonalności. Moc grzejąca optykę obliczana jest w linijce 

dewHeatPower = map(currentHum, 50, 100, 0, 100);

natomiast moc sterująca wentylatorem obliczana jest w ten prosty sposób:

fanPower = constrain(map(10*(currentTemp - currentDSTemp), 10, 50, 0, 100), 0, 100);

 

Timer odpowiada za cykliczne wywoływanie pomiaru przez oba czujniki i następnie na podstawie zmierzonych wartości obliczane są odpowiednie współczynniki wypełnienia PWM, które sterują odpowiednio mocą grzałki i obrotami wentylatora. Grzałkę i silnik oczywiście należy podpiąć przez tranzystor mocy jak pokazano przykładowo w poście nr. 5.

Oczywiście można znaleźć dużo bardzie zaawansowane biblioteki służące do sterowania ogrzewaniem i chłodzeniem na podstawie odczytanych danych i każdy może tutaj odpowiednią moc obliczać w dowolny sposób. 

Cały kod wygląda tak:

#include <OneWire.h>
#include <DallasTemperature.h>
#include <dht.h>
#include <Timer.h>

#define DHT22_PIN 2
#define DS8120_PIN 3
#define PWM_HEATER_PIN 5
#define PWM_FAN_PIN 6

OneWire oneWire(DS8120_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress insideThermometer;
dht DHT;
Timer timer;

boolean DS1820connected = false;
boolean DHT22connected = false;
float currentTemp;               // bieżąca temperatura z czujnika DHT
float currentHum;                // bieżąca wilgotność z czujnika DHT
float currentDewpoint;           // obliczony punkt rosy na podstawie danych z czujnika DHT
float currentDSTemp;             // bieżąca temperatura z czujnika DS przymocowanego do lustra
byte dewHeatPower = 0;           // moc grzałki
byte fanPower = 0;               // moc wentylatora

void setup() {
  int chk = DHT.read22(DHT22_PIN); 
  if(chk == DHTLIB_OK) DHT22connected = true;  
  
  sensors.begin(); 
  DS1820connected = sensors.getAddress(insideThermometer, 0);  
  if(DS1820connected) sensors.setResolution(insideThermometer, 10);
  
  timer.every(1000, updateSensors);
}

void loop() {
  timer.update();
}

void updateSensors() {
  if (DS1820connected) {
    sensors.requestTemperaturesByAddress(insideThermometer); // przy 10 bitowej dokładności trwa 188ms
    currentDSTemp = sensors.getTempC(insideThermometer);
  }
  
  if (DHT22connected) {
    DHT.read22(DHT22_PIN);
    currentTemp = DHT.temperature;
    currentHum = DHT.humidity;
    currentDewpoint = dewPoint(currentTemp, currentHum);  
    dewHeatPower = map(currentHum, 50, 100, 0, 100);
  }
  
  if(DS1820connected && DHT22connected) fanPower = constrain(map(10*(currentTemp - currentDSTemp), 10, 50, 0, 100), 0, 100);
  updatePWM();
}

void updatePWM() {
  analogWrite(PWM_HEATER_PIN, map(dewHeatPower, 0, 100, 0, 255));
  analogWrite(PWM_FAN_PIN, map(fanPower, 0, 100, 0, 255));
}

// dewPoint function NOAA
// reference (1) : http://wahiduddin.net/calc/density_algorithms.htm
// reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm
//
double dewPoint(double celsius, double humidity)
{
	// (1) Saturation Vapor Pressure = ESGG(T)
	double RATIO = 373.15 / (273.15 + celsius);
	double RHS = -7.90298 * (RATIO - 1);
	RHS += 5.02808 * log10(RATIO);
	RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ;
	RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ;
	RHS += log10(1013.246);

        // factor -3 is to adjust units - Vapor Pressure SVP * humidity
	double VP = pow(10, RHS - 3) * humidity;

        // (2) DEWPOINT = F(Vapor Pressure)
	double T = log(VP/0.61078);   // temp var
	return (241.88 * T) / (17.558 - T);
}
  • Like 2
Odnośnik do odpowiedzi
Udostępnij na innych stronach

4 kanałowy kontroler PWM

 

Wystarczy tej teorii już, pora na jakieś praktyczne rozwiązania. Wielokanałowy kontroler PWM można zrobić na chyba setki różnych sposobów - w tym wątku, co zrozumiałe, pokażę sposób z Arduino. 

Najpierw schemat:

post-1260-0-90247400-1418068420_thumb.jp

 

Żadnej filozofii - Arduino, cztery potencjometry (liniowe), cztery tranzystory mocy, cztery gniazdka. 

I kod:

#include <Timer.h>

Timer timer;

byte pwmPins[] = {3, 4, 6, 10};
byte adcPins[] = {A0, A1, A2, A3};

void setup() {
  timer.every(1000, updatePWMs);
}

void loop() {
  timer.update();
}

void updatePWMs() {
  for(byte i = 0; i < 4; i++) {
    int adc = analogRead(adcPins[i]);
    analogWrite(pwmPins[i], map(adc, 0, 1023, 0, 255)); 
  }
}

Tutaj jeszcze prościej - co sekundę w pętli odczytujemy wartości napięcia na potencjometrach i ustawiamy na ich podstawie wartość współczynnika wypełnienia na wyjściach PWM. Przetworniki ADC w Arduino mają rozdzielczość 10bit stąd wartość 1023 w mapowaniu na 8 bitowy sygnał PWM.

Wykorzystując wiedzę z poprzednich wpisów możemy taki układ rozbudować o sterowanie na podstawie temperatury/wilgotności, wyświetlanie na LCD czy odczyt danych przez port szeregowy - możliwości jest mnóstwo. 

  • Like 2
Odnośnik do odpowiedzi
Udostępnij na innych stronach
  • 2 tygodnie później...

Dziś pokręcimy. Silniczkiem krokowym. Oprócz Arduino będziemy potrzebowali silnika krokowego bipolarnego i sterownika do silnika - w schemacie jest A4988. To bardzo popularny sterownik stosowany w drukarkach 3D, kosztuje kilkanaście złotych, a podłączony jak na schemacie poniżej skonfigurowany jest do pracy mikrokrokowej z krokiem 1/16.

post-1260-0-36203900-1418975183_thumb.jp

 

Układ zasilany jest napięciem 12V (na takie też napięcie powinien być nasz silnik krokowy). Napięcie to zasila Arduino i sterownik A4988. Dodatkowo do sterownika doprowadzone jest napięcie 5V zasilające jego część logiczną. Sterowanie odbywa się przez dwie linie - STEP i DIR. Impulsy na pierwszej z nich powoduję obracanie silnika krokowego. Wysoki lub niski stan na linii DIR określa kierunek obracania silnika.

I kod:

#include <AccelStepper.h>

String inputString = "";         // zmienna przechowująca polecenia z portu szeregowego
boolean stringComplete = false;  // oznacza zakończenie transmisji komendy
AccelStepper stepper = AccelStepper(AccelStepper::DRIVER, A1,A0);  

void setup() {
  Serial.begin(9600);
  inputString.reserve(8);  
  stepper.setMaxSpeed(400);
  stepper.setAcceleration(600);
  stepper.setCurrentPosition(0);
}


void loop() {
  if (stringComplete) {
    switch(inputString.charAt(0)) {
      case 'M': moveStepper(inputString.substring(2).toInt()); break;
      case 'R': Serial.println(stepper.currentPosition()); break;
      case 'I': Serial.println((stepper.distanceToGo() != 0) ? "true" : "false"); break;
      default: Serial.println("Unknown command: " + inputString);
    }
    inputString = "";
    stringComplete = false;
  }    
}

void moveStepper(int newPos) {
  if(newPos != stepper.currentPosition()) stepper.moveTo(newPos);
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    } 
  }
}

Użyta została biblioteka AccelStepper, dzięki której nasz silnik może płynnie się rozpędzać i hamować. W bloku setup ustawiamy parametry silnika i uruchamiamy port szeregowy (tu warto poczytać opis biblioteki AccelStepper). Następnie na porcie szeregowym nasłuchujemy na polecenia:

M:4566 - obrót silnika do pozycji 4566

R - zwraca na port szeregowy aktualną pozycję silnika

I - zwraca "true" jeśli silnik się obraca

 

Więcej o komunikacji i wydawaniu poleceń przez port szeregowy znajdziecie we wcześniejszych postach tego wątku. Program można rozbudować, ale już w takiej postaci umożliwia zdalne sterowanie silnikiem podpiętym np do wyciągu, koła filtrowego, czy nawet sterującego obracaniem kopuły obserwatorium :)

  • Like 1
Odnośnik do odpowiedzi
Udostępnij na innych stronach

jolo, mam pytanie. Wysyłam arduino ciąg znaków do portu szeregowego, są to dane pogodowe i kazda oddzielona "spacją" (ale moze byc inny znak jezeli trzeba). Chodzi mi o to jak czytać na drugim urządzeniu te dane, zeby wiedziec ze czytam np temperature a nie wilgotnosc? Jak przeskakiwać do kolejnych danych? Wiesz moze jak to zrobic? Wciągneło mnie arduino jak nie wiem co ;)

 

Zależy mi żeby pełna sekwencja danych była w jednej lini a nie kazda osobno.

Odnośnik do odpowiedzi
Udostępnij na innych stronach

jolo, mam pytanie. Wysyłam arduino ciąg znaków do portu szeregowego, są to dane pogodowe i kazda oddzielona "spacją" (ale moze byc inny znak jezeli trzeba). Chodzi mi o to jak czytać na drugim urządzeniu te dane, zeby wiedziec ze czytam np temperature a nie wilgotnosc? Jak przeskakiwać do kolejnych danych? Wiesz moze jak to zrobic? Wciągneło mnie arduino jak nie wiem co ;)

 

Zależy mi żeby pełna sekwencja danych była w jednej lini a nie kazda osobno.

Wystarczy zrobić jakiegoś IFa, że jeśli wystąpi twój separator, to masz zapisywać dane do kolejnej zmiennej.

Odnośnik do odpowiedzi
Udostępnij na innych stronach

Tak jak pisze Ilu powyżej. W systemach z tak małą ilością pamięci RAM jak Arduino warto na bieżąco takie dane analizować zaraz po odczytaniu każdej zmiennej, żeby niepotrzebnie nie przechowywać ich jako zmienne typu String.

Odnośnik do odpowiedzi
Udostępnij na innych stronach

Dołącz do dyskusji

Możesz dodać zawartość już teraz a zarejestrować się później. Jeśli posiadasz już konto, zaloguj się aby dodać zawartość za jego pomocą.

Gość
Dodaj odpowiedź do tematu...

×   Wklejono zawartość z formatowaniem.   Usuń formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Odnośnik został automatycznie osadzony.   Przywróć wyświetlanie jako odnośnik

×   Przywrócono poprzednią zawartość.   Wyczyść edytor

×   Nie możesz bezpośrednio wkleić obrazków. Dodaj lub załącz obrazki z adresu URL.

  • Ostatnio przeglądający   0 użytkowników

    Brak zarejestrowanych użytkowników przeglądających tę stronę.


×
×
  • Dodaj nową pozycję...

Powiadomienie o plikach cookie

Umieściliśmy na Twoim urządzeniu pliki cookie, aby pomóc Ci usprawnić przeglądanie strony. Możesz dostosować ustawienia plików cookie, w przeciwnym wypadku zakładamy, że wyrażasz na to zgodę.

© Robert Twarogal, forumastronomiczne.pl (2010-2020)