[ARDUINO] Bezprzewodowy czujnik temperatury z modułami nRF24L01
Moduły nRF24L01 to idealne rozwiązanie do utworzenia między mikrokontrolerami dwukierunkowej komunikacji radiowej na niewielkie odległości.
Moduły takie pracują na częstotliwości nośnej 2,4 GHz, wykorzystując modulację GFSK (modulacja FSK z filtracją gaussowską, znana chociażby z urządzeń DECT czy Bluetooth). Dzięki zintegrowanej antenie mikropaskowej, nie potrzeba żadnych zewnętrznych elementów do ich poprawnej pracy z wysoką sprawnością. Dodatkowo, moduł nRF24L01 pozwala na wybór jednej z czterech mocy nadawania, przy czym najwyższa możliwa moc jest na poziomie 0 dBm, co odpowiada wartości 1 mW.
Komunikacja modułu z mikrokontrolerem odbywa się za pośrednictwem magistrali SPI, sam moduł natomiast pozwala na wygenerowanie przerwania w momencie odebrania danych – dzięki czemu możliwe jest np. wybudzenie procesora ze stanu wstrzymania.
W projekcie użyto dwóch płytek Arduino UNO R3, pary modułów nRF24L01, a także czujnika temperatury DS18B20 i płytki rozszerzającej do Arduino – LCD Keypad Shield.
W dalszej części artykułu zestaw Arduino + sensor temperatury + moduł nRF nazywany będzie nadajnikiem, zaś odbiornikiem nazywane będzie połączenie Arduino z nakładką LCD Keypad Shield i drugim modułem radiowym.
Zadaniem nadajnika jest przesyłanie do odbiornika informacji o temperaturze, odczytywanej z układu DS18B20. Odbiornik zaś wyświetla odebrane dane na wyświetlaczu ciekłokrystalicznym oraz przekazuje je do komputera wraz z wartością instrukcji MILLIS(), której działanie zostanie opisane później.
Moduły radiowe dołączone zostały za pomocą przewodów połączeniowych do modułu Arduino UNO. Programy do obu mikrokontrolerów zostały napisane w Arduino, z użyciem dodatkowych bibliotek (umieszczone w załączniku do artykułu): MIRF (biblioteka układu nRF24L01), oneWire oraz DallasTemperature (obie ostatnie służą do obsługi układu DS18B20).
Ze względu na ograniczenia konstrukcyjne, zasilanie układu nRF24L01 nie może przekraczać 3,6 V, dlatego zostało podłączone do punktu na płytce Arduino, w którym wyprowadzono napięcie 3,3 V. Linie sygnałowe modułu radiowego mogą natomiast przyjąć napięcia do 5 V, więc nie jest wymagane użycie konwertera poziomów napięć. Ten sposób podłączenia zasilania dotyczy obu modułów nRF24L01.
Nadajnik składa się z Arduino, czujnika DS18B20, modułu nRF24L01 oraz diody świecącej.
Miganie LED sygnalizuje nadawanie pakietu, zawierającego wynik pomiaru temperatury – taki pomiar wykonywany jest co około 2 sekundy.
Do obsługi czujnika DS18B20 wykorzystano biblioteki DallasTemperature oraz OneWire. Nadawanie wykonywane jest przy wsparciu modułu nRF24L01 biblioteką MIRF, dzięki czemu program jest prosty i przejrzysty. Deklarację bibliotek oraz funkcję setup(), inicjującą i konfigurującą podstawowe funkcje układu, przedstawiono na listingu 1.
Listing 1. Deklaracje i funkcja setup() w kodzie nadajnika
#include #include #include #include #include #include #define ONE_WIRE_BUS 2 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); void setup() { pinMode(10, OUTPUT); // linia 10 jako wyjście dla LED digitalWrite(10, LOW); // dioda nie świeci sensors.begin(); // inicjacja czujnika Mirf.spi = &MirfHardwareSpi; // konfiguracja SPI do użycia z biblioteką MIRF Mirf.init(); // inicjacja MIRF Mirf.setRADDR((byte *)"67890"); // Ustawienie adresu do odbioru Mirf.setTADDR((byte *)"12345"); // Ustawienie adresu do nadawania Mirf.payload = sizeof(float); // Maksymalny przesył przez moduły radiowe ustawiony na wielkość zmiennej FLOAT (4 bajty) Mirf.config(); // Konfiguracja biblioteki MIRF }
Warto zwrócić uwagę na instrukcje „Mirf.setTADDR()” oraz „Mirf.setRADDR()” – służą one do ustalania adresu, pod który będzie nadawana informacja lub z którego będzie ona odbierana. Adres taki może mieć od 3 do 5 bajtów (co można ustawić w rejestrze AW modułu nRF24L01).
Za pomocą instrukcji Mirf.payload ustawiono wielkość przesyłanych danych – odpowiada ona wielkości zmiennej float (4 bajty). Maksymalnie można przekazać 32 bajty naraz.
Warto zaznaczyć, że w tym projekcie mamy jeden nadajnik i jeden odbiornik, jednakże moduły radiowe przystosowane są do pracy dwukierunkowej (każdy moduł może nadawać lub odbierać).
W pętli „void loop()” wykonywany jest pomiar temperatury, zapis wyniku do zmiennej oraz wysłanie jej przez SPI do modułu nRF24L01. W dużej części wykorzystano tu biblioteki – dzięki czemu np. odczyt temperatury czy nadanie zmiennej sprowadza się do wykonania jednej instrukcji.
Każda z instrukcji ma określony czas, przez który jest wykonywana – w przypadku odwołania do biblioteki, żądając pomiaru temperatury, wprowadzane jest opóźnienie rzędu 700 mS, które pozwala czujnikowi na odpowiednie przygotowanie zebranych danych do przekazania. Stąd, przybliżony czas wykonania pętli „void loop()” wynosi ponad 2 s. Treść pętli głównej przedstawiono na listingu 2.
Listing 2. Pętla główna void loop (kod nadajnika)
void loop() { float tempr = 0; // Zmienna typu float sensors.requestTemperatures(); // Żądanie pomiaru temperatury za pomocą czujnika DS18B20 (czas wykonania < 1s) tempr = sensors.getTempCByIndex(0); // Przypisanie wyniku pomiaru do zmiennej FLOAT Mirf.send((byte *)&tempr); // Nadanie zmiennej przez moduł nRF24L01 while(Mirf.isSending()){ // Zapętlenie programu do czasu zakończenia wysyłania danych } digitalWrite(10,HIGH); // Mignięcie diody, sygnalizujące zakończenie nadawania delay(300); digitalWrite(10,LOW); delay(1000); // Opóźnienie powtórnego wykonania pętli - // Suma opóźnień w całym programie wynosi około 2s - jest to także okres pomiaru. }
Odbiornik składa się z Arduino UNO R3, płytki z wyświetlaczem oraz modułu radiowego. Sposób podłączenia modułu nRF24L01 rożni się od zalecanego połączenia – spowodowane to jest użyciem płytki LCD Keypad Shield, której złącza sygnałowe zajęły sygnały CSN oraz CE. Dlatego też w funkcji setup() znajdują się przypisania: Mirf.csnPin = 2; Mirf.cePin = 3; Przewody SPI zostały podłączone do złącza programowania ISP, gdyż jest to jedyny punkt na płytce LCD Keypad Shield, gdzie znajdują się wyprowadzenia linii takich, jak MOSI, MISO, SCK. W funkcji setup() konfigurowana jest także transmisja szeregowa (prędkość 9600 b/s, 8 bitów danych, brak bitów parzystości, 1 bit stopu), inicjowany jest wyświetlacz HD44780 oraz konfigurowana jest linia sterująca jego podświetleniem. Po włączeniu zasilania podświetlenie wyświetlacza powinno mignąć.
Listing 3. Kod programu odbiornika: deklaracje i funkcja setup()
#include #include #include #include #include LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Konfiguracja linii wyświetlacza (zgodnie ze schematem Arduino LCD Shield) void setup(){ Serial.begin(9600); // Inicjacja transmisji szeregowej o prędkości 9600 b/s lcd.begin(16, 2); // Inicjacja wyświetlacza HD44780 o organizacji 16x2 lcd.clear(); // Czyszczenie całego wyświetlacza pinMode(10,OUTPUT); //wyjście sterujące podświetleniem LCD digitalWrite(10,LOW); //wygaś podświetlenie LCD delay(100); //opóźnienie 0,1 s digitalWrite(10,HIGH); //włącz podświetlenie LCD Mirf.csnPin = 2; // Konfiguracja pinu CSN biblioteki MIRF inaczej, niż domyślnie Mirf.cePin = 3; // Konfiguracja pinu CE biblioteki MIRF inaczej, niż domyślnie Mirf.spi = &MirfHardwareSpi; // Wykorzystanie SPI do komunikacji pryz użyciu modułów nRF24L01 Mirf.init(); // inicjacja modułu MIRF Mirf.setTADDR((byte *)"67890"); // ustawienie adresu do nadawania Mirf.setRADDR((byte *)"12345"); // Ustawienie adresu do odbioru Mirf.payload = sizeof(float); // Maksymalny przesył przez moduły radiowe ustawiony na wielkośc zmiennej FLOAT (4 bajty) Mirf.config(); // Konfiguracja biblioteki MIRF }
W pętli głównej programu odbierana jest zmienna zawierająca informację przesłaną z nadajnika. Zostaje ona wyświetlona na LCD, a także przesłana przez UART do komputera, w towarzystwie wartości instrukcji MILLIS(), która liczy czas (w mS) od momentu dołączenia zasilania do płytki ARDUINO. Dzięki tej informacji, można łatwo analizować zmiany temperatur w czasie pracy układu (uwaga – po dłuższym czasie wartość instrukcji MILLIS() przepełni się, zaczynając liczenie od początku). Na ekranie LCD, poza temperaturą, wyświetlania jest także gwiazdka „*” w momencie odebrania z toru radiowego informacji wysłanej przez wcześniej opisany moduł nadajnika. Jest to sygnał dla użytkownika, że transmisja radiowa jest aktywna, a przedstawiana temperatura – aktualna. Miganie gwiazdki na wyświetlaczu odbiornika powinno być zsynchronizowane z miganiem LED w nadajniku. Listing 4 przedstawia pętlę „void loop()”.
Listing 4. Pętla główna void loop() w programie odbiornika
void loop(){ float data; //zmienna data typu FLOAT if(Mirf.dataReady()) // Warunek wykonywany tylko, gdy coś zostanie odebrane { lcd.setCursor(15,0); //Kursor na ostatniej pozycji pierwszej linii lcd.write("*"); // Wyświetlenie gwiazdki symbolizującej odebranie danych delay(300); lcd.setCursor(15,0); lcd.write(" "); // Nadpisanie gwiazdki pustym polem Mirf.getData((byte *) &data); // Odbiór zmiennej data przy pomocy modułu nRF24L01 lcd.setCursor(0,0); // Kursor na początku lcd.write("Temp: "); lcd.setCursor(6,0); lcd.print(data); // Wyświetlenie zmiennej FLOAT (temperatura podawana w postaci XX.XX lub XXX.XX,a także ze znakiem "-") lcd.write(0xDF); // Znak stopnia lcd.write("C "); Serial.print(data); // Wystawienie zmiennej na UART Serial.print(" ; "); // Rozdzielenie Serial.println(millis()); // Wystawienie wartości instrukcji MILLIS(), zwięszanej automatycznie od czasu uruchomienia płytki Arduino } }
Termometr może mierzyć temperatury z zakresu -55 do +125 stopni Celsiusza.
Sposób podłączenia modułów do Arduino opisano w komentarzach na początku programów.
Należy zwrócić uwagę na kolejność uruchamiania urządzeń – zaleca się uruchomienie jako pierwszego odbiornika.
Producent szacuje zasięg radiowy modułów na około 100m w otwartej przestrzeni.
Pełny kod (nadajnik):
/* * Konfiguracja linii dołączonych do modułu nRF24L01: * * MISO: 12 * MOSI: 11 * SCK: 13 * CE: 8 * CSN: 7 * * Konfiguracja linii LED: * Katoda: GND * Anoda: 10 przez R = 1kOhm * *Linia czujnika DS18B20: 2 UWAGA - linia 1-Wire musi być podciągnięta do zasilania (+5V) rezystorem o wartości 4,7k? */ #include #include #include #include #include #include #define ONE_WIRE_BUS 2 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); void setup() { pinMode(10, OUTPUT); // linia 10 jako wyjście dla LED digitalWrite(10, LOW); // dioda nie świeci sensors.begin(); // Inicjalizacja czujnika Mirf.spi = &MirfHardwareSpi; // konfiguracja SPI do użycia z biblioteką MIRF Mirf.init(); // Inicjalizacja MIRF Mirf.setRADDR((byte *)"67890"); // Ustawienie adresu do odbioru Mirf.setTADDR((byte *)"12345"); // ustawienie adresu do nadawania Mirf.payload = sizeof(float); // Maksymalny przesył przez moduły radiowe ustawiony na wielkośc zmiennej FLOAT (4 bajty) Mirf.config(); // Konfiguracja biblioteki MIRF } void loop() { float tempr = 0; // Zmienna typu float sensors.requestTemperatures(); // Żądanie pomiaru temperatury za pomocą czujnika DS18B20 (czas wykonania < 1s) tempr = sensors.getTempCByIndex(0); // Przypisanie wyniku pomiaru do zmiennej FLOAT Mirf.send((byte *)&tempr); // Nadanie zmiennej przez moduł nRF24L01 while(Mirf.isSending()){ // Zapętlenie programu do czasu zakończenia wysyłania danych } digitalWrite(10,HIGH); // Mignięcie diody, sygnalizujące zakończenie nadawania delay(300); digitalWrite(10,LOW); delay(1000); // Opóźnienie powtórnego wykonania pętli - // Suma opóźnień w całym programie wynosi około 2s - jest to także okres pomiaru. }
Pełny kod (odbiornik):
/* * Konfiguracja linii dołączonych do modułu nRF24L01: * * MISO: 12 * MOSI: 11 * SCK: 13 * CE: 3 * CSN: 2 * Konfiguracja linii dołączonych do wyświetlacza HD44780: * * EN: 9 * RS: 8 * D4: 4 * D5: 5 * D6: 6 * D7: 7 * PODŚW.: 10 */ #include #include #include #include #include LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Konfiguracja linii wyświetlacza (zgodnie ze schematem Arduino LCD Shield) void setup(){ Serial.begin(9600); // Inicjalizacja transmisji szeregowej o prędkości 9600 b/s lcd.begin(16, 2); // Inicjalizacja wyświetlacza HD44780 o organizacji 16x2 lcd.clear(); // Czyszczenie całego wyświetlacza pinMode(10,OUTPUT); //wyjście sterujące podświetleniem LCD digitalWrite(10,LOW); //wygaś podświetlenie LCD delay(100); //opóźnienie 0,1 s digitalWrite(10,HIGH); //włącz podświetlenie LCD Mirf.csnPin = 2; // Konfiguracja pinu CSN biblioteki MIRF inaczej, niż domyślnie Mirf.cePin = 3; // Konfiguracja pinu CE biblioteki MIRF inaczej, niż domyślnie Mirf.spi = &MirfHardwareSpi; // Wykorzystanie SPI do komunikacji pryz użyciu modułów nRF24L01 Mirf.init(); // Inicjalizacja modułu MIRF Mirf.setTADDR((byte *)"67890"); // ustawienie adresu do nadawania Mirf.setRADDR((byte *)"12345"); // Ustawienie adresu do odbioru Mirf.payload = sizeof(float); // Maksymalny przesył przez moduły radiowe ustawiony na wielkośc zmiennej FLOAT (4 bajty) Mirf.config(); // Konfiguracja biblioteki MIRF } void loop(){ float data; //zmienna data typu FLOAT if(Mirf.dataReady()) // Warunek wykonywany tylko, gdy coś zostanie odebrane { lcd.setCursor(15,0); //Kursor na ostatniej pozycji pierwszej linii lcd.write("*"); // Wyświetlenie gwiazdki symbolizującej odebranie danych delay(300); lcd.setCursor(15,0); lcd.write(" "); // Nadpisanie gwiazdki pustym polem Mirf.getData((byte *) &data); // Odbiór zmiennej data przy pomocy modułu nRF24L01 lcd.setCursor(0,0); // Kursor na początku lcd.write("Temp: "); lcd.setCursor(6,0); lcd.print(data); // Wyświetlenie zmiennej FLOAT (temperatura podawana w postaci XX.XX lub XXX.XX,a także ze znakiem "-") lcd.write(0xDF); // Znak stopnia lcd.write("C "); Serial.print(data); // Wystawienie zmiennej na UART Serial.print(" ; "); // Rozdzielenie Serial.println(millis()); // Wystawienie wartości instrukcji MILLIS(), zwięszanej automatycznie od czasu uruchomienia płytki Arduino } }