Modem radiowy nRF24L01 – programowanie

Teraz przyszła pora na zaprogramowanie urządzeń. Pierwszym programem niech będzie tradycyjne już “Hello World” [Witaj Świecie].

Sprawię, że jedno urządzenie z modemem będzie wysyłało napis do drugiego urządzenia. Drugie urządzenie zaś wyśle odebrany napis do komputera, żeby wyświetlić go w Monitorze Portu Szeregowego Arduino.

Do do programowania modemu nRF24L01 potrzebujesz biblioteki RF24. Znajdziesz ja na stronie projektu RF24 na Github.

Wystarczy kliknąć przycisk “Download ZIP” i pobrać plik biblioteki.

Bibliotekę możesz zainstalować w Arduino IDE poprzez menu Szkic->Importuj bibliotekę->Dodaj bibliotekę. Inny sposób to rozpakować plik zip do katalogu domowego Arduino/librares na Linuksie lub do DokumentyArduinolibrares w Windows.

Program Nadajnika wygląda tak:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8);

const byte rxAddr[6] = "00001";

void setup()
{
  radio.begin();
  radio.setRetries(15, 15);
  radio.openWritingPipe(rxAddr);
  
  radio.stopListening();
}

void loop()
{
  const char text[] = "Hello World";
  radio.write(&text, sizeof(text));
  
  delay(1000);
}

Na początku informuje program jakich będę używał bibliotek.

  • SPI.h – do obsługi interfejsu komunikacji z modemem
  • nRF24L01.h – do obsługi tego konkretnego sterownika modemu
  • RF24.h – biblioteka która ułatwia nam sterowanie modemem radiowym

W dalszej części tworzę obiekt o nazwie “radio”.

RF24 radio(7, 8);

Obiekt ten reprezentuje podłączony do Arduino modem. Argumenty 7 i 8 to numery pinów cyfrowych, do których podłączone są sygnały CE i CSN. Jeśli podłączyłeś je do innych pinów to możesz zmienić argumenty na inne :-).

Potem tworzę globalną tablicę o nazwie “rxAddr”.

const byte rxAddr[6] = "00001";

Zapisałem w niej adres modemu do którego Arduino będzie nadawać dane. Adres ma wartość “00001”, jednak jeśli chcesz możesz mu nadać inną jako dowolny 5 literowy napis.

Adres jest po to, że jeśli w sieci masz kilka modemów, to dzięki adresowi możesz wybrać jeden konkretny modem do którego wysyłasz dane.

W funkcji “setup” wywołuję metodę “radio.begin();” [radio.rozpocznij]. Aktywuje ona modem do działania.

Następnie ustawiam metodę “radio.setRetires(15, 15);” [radio.ustaw kiedy mam sobie darować]. Ustala ona co jaki czas i ile razy modem ma ponawiać wysłanie danych jeśli odbiorca ich nie dostał.

Pierwszy argument ustala co jaki czas ponawiać próbę. Jest on wielokrotnością 250 mikrosekund. 15 * 250 = 3750. Czyli jeśli odbiorca nie odbierze danych modem będzie starał się je wysyłać co 3,75 milisekund. 

Drugi argument to liczba prób. Czyli, że ma próbować wysyłać 15 razy zanim da sobie spokój i uzna, że odbiornik jest po za zasięgiem, albo jest wyłączony.

Metoda “radio.openWritingPipe(rxAddr);” [radio.otwórz rurę/kanał do zapisu] ustawia adres odbiornika do którego program ma wysyłać dane. Jej argumentem jest utworzona wcześniej tablica z adresem odbiornika.

Ostatnia metoda funkcji “setup” to “radio.stopListening();” [radio.skończ nasłuchiwać]. Przestawia ona modem w tryb nadawania danych.

W funkcji “loop” zaczynam od stworzenia napisu który chcę wysłać przez modem.

const char text[] = "Hello World";

Jest to tablica znaków/liter typu “char” [znak] o nazwie “text” do której przypisałem napis “Hello World” [Witaj Świecie]. 

Następnie za pomocą metody “radio.write(&text, sizeof(text));” [radio.zapisz] wysyłam tekst przez radio do modemu, którego adres ustaliłem wcześniej przez “openWritingPipe”.

Pierwszy argument metody to wskaźnik na zmienną, która przechowuje dane do wysłania. Dlatego używam przed nazwą zmiennej “&”, żeby zrobić z niej wskaźnik.

Drugi argument to ilość bajtów jakie radio ma pobrać ze zmiennej do wysłania. Użyłem tu funkcji “sizeof()[rozmiar/wielkość], która automatycznie oblicza ile bajtów ma napis w zmiennej “text”.

Przez tą metodę można wysłać maksymalnie 32 bajty na raz. Bo taki jest maksymalny rozmiar danych pojedynczego pakietu modemu.

Jeśli potrzebne ci jest potwierdzenie, że odbiornik odebrał dane, to metoda “radio.write” zwraca wartośc typu “bool”. Jak zwróci “true” [prawda], to dane dotarły do odbiornika. Jeśli zwróci “false” [fałsz/kłamstwo/bzdura] to dane nie zostały odebrane.

Metoda “radio.write” blokuje działanie programu do czasu potwierdzenia odebrania danych lub do czasu aż skończą się wszystkie próby nadawania ustalone w metodzie “radio.setRetires”.

Ostatnią częścią funkcji “loop” jest wywołanie funkcji “delay(1000);”. Blokuje ona program na 1000 milisekund, czyli jedną sekundę. Sprawia, że program będzie co sekundę wysyłał napis “Hello World” do odbiornika.

Program odbiornika w drugim urządzeniu z modemem będzie wyglądał tak:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8);

const byte rxAddr[6] = "00001";

void setup()
{
  while (!Serial);
  Serial.begin(9600);
  
  radio.begin();
  radio.openReadingPipe(0, rxAddr);
  
  radio.startListening();
}

void loop()
{
  if (radio.available())
  {
    char text[32] = {0};
    radio.read(&text, sizeof(text));
    
    Serial.println(text);
  }
}

Program wygląda z grubsza dość podobnie do programu nadajnika. Najpierw wybierane są biblioteki, których będziemy używać, potem tworzony jest obiekt “radio” z wybranymi pinami sterującymi, w kolejnej linii widać tablicę z adresem odbiornika – takim samym jak w programie nadajnika.

W funkcji “setup” na początku ustawiam obiekt “Serial” do komunikacji Arduino z komputerem.

while (!Serial);

Ten fragment czeka, aż port USB w Arduino przełączy się w tryb portu szeregowego COM:

Metoda “Serial.begin(9600);” [Port Szeregowy.rozpocznij] ustawia prędkość komunikacji z komputerem przez USB/COM.

Następna częśc funkcji to ustawianie modemu. Rozpoczyna je znana z poprzedniego programu metoda “radio.begin();” ustawiająca modem do działania z programem.

Kolejna linia programu to “radio.openReadinPipe(0, rxAddr);” [radio.otwórz rurę/kanał do odczytu], która ustala jaki nasz modem ma adres pod którym odbiera dane.

Pierwszy argument to numer rury/strumienia. Można stworzyć do 6 strumieni odczytu reagujących na różne adresy. Ja utworzyłam adres tylko dla strumienia numer 0.

Drugi argument to adres na jaki ma reagować strumień, żeby odebrać dane. Ustawiłem tu adres przypisany do tablicy “rxAddr”.

Kolejną czynnością jest włączenie odbierania danych przez modem za pomocą metody “radio.startListening();”. Od tej chwili modem czeka na dane wysłane pod ustalony adres.

W funkcji “loop” program wykonuje następujące operacje.
Najpierw sprawdza czy dotarły jakieś dane na adres modemu za pomocą metody “radio.available();”. Metoda ta zwraca wartość “true” [prawda]jeśli czekają na nas jakieś dane, lub “false” [fałsz] jeśli nikt nic nie wysłał.

char text[32] = {0};

Jeśli dane czekają, to tworzę 32-elementową tablicę typu “char” o nazwie “text” wypełnioną zerami, do której zapiszę odebrane dane.

By odczytać dane używam metody “radio.read(&text, sizeof(text));”.

Pierwszym argumentem metody jest wskaźnik do zmiennej do której mają być zapisane dane odebrane przez modem. By przedstawić zmienną jako wskaźnik zastosowałem znak “&” przed jej nazwą.

Drugim argumentem jest ilość danych do zapisania w zmiennej. Zastosowałem tu znowu funkcję “sizeof()” [rozmiar/wielkość], która automatycznie obliczy rozmiar tablicy “text”. 

Po odebraniu danych, przekazuje je do metody “Serial.println(text);”, która wysyła odebrany tekst do komputera, gdzie można go podglądać w “Monitorze portu szeregowego” za pomocą Arduino IDE.

Przy poprawnych połączeniach i działaniu programu, monitor powinien zwracać takie wartości.

Modem nRF24L01 możesz kupić w sklepie Nettigo:

sprae

5 Replies to “Modem radiowy nRF24L01 – programowanie”

  1. Hej, zastanawiam się nad zakupem tych modemów w wersji ze wzmacniaczem bo widziałem takie u Was. Czy istnieje możliwość przesłania zmiennych int/float bezpośrednio jakimś magicznym poleceniem? Z tego co tutaj widzę to taka sama zabawa jak przy RF 433MHz, czyli żeby przesłać 10 zmiennych int muszę stworzyć im “klucze/hasła” (żeby odbiornik wiedział, który pakiet przypisać do której zmiennej), i każdą zmienną w raz z kluczem przekonwertować do char rozdzielając je umownym znakiem, a w odbiorniku ta sama zabawa tylko w drugą stronę – z char do zmiennej i pętla segregująca dane po kluczach – tak to rozwiązałem przy RF 433, a szukam jakiegoś prostszego rozwiązania.

    Druga sprawa jak te moduły zachowują się przy transmisji 3 nadajniki do 1 odbiornika? Czy odbiornik jest w stanie odbierać na 3 kanałach jednocześnie czy muszą być przerwy żeby przełączył się. Na RF433 największy problem mam z synchronizacją – odbiornik elegancko zbiera dane z 3 nadajników i je segreguje dopóki transmisja z 2 lub 3 się na siebie nie nałoży, wtedy dane przepadają. Wymyśliłem już nawet kilka sposobów jak to rozwiązać, ale każdy ma jakieś swoje wady i teraz zastanawiam się czy warto kupić nRF24L01….

    1. Jeśli chodzi o przesyłanie danych w pakietach to w C możesz zrobić strukturę (struct) i jeżeli odbierasz dane na takim samym sprzęcie to binarnie (bajt po bajcie) przesłane dane struct utworzą identyczną strukturę (o ile w kodzie będzie identyczna definicja).

      Jeśli odbierasz na innej architekturze to musisz zainteresować się funkcją pack jak np Pythonie https://docs.python.org/2/library/struct.html by odebrać strukturę. Przykład takiego odbierania masz np tutaj: https://docs.python.org/2/library/struct.html

      Co do drugiej części pytania – NRF ma adres nadawczy i odbiorczy na każdym kanale. Może jednocześnie pracować na kilku kanałach, a na każdym kanale może pracować jednocześnie wiele NRF. Każdy pakiet wysyłany jest na konkretny adres odbiorcy.

      1. Dzięki za szybką odpowiedz. A czy można jakoś zapisać zmienną float w strukturze? Przyznam że nie jestem programistą, a bardziej hobbystycznie tworzę sobie po prostu sieć czujników w domu i ogrodzie. Na razie rozwiązałem to pozbywając się przecinka poprzez mnożenie i zmianie na int do przesłania, w odbiorniku analogicznie odwrotnie, ale niestety Arduino z mnożenia powinno dostać dwóje!!! Taki prosty przykład napisany dla jaj:

        float liczBa = 77.77;
        int toJeWynik;

        void setup() {

        Serial.begin (9600);

        }

        void loop() {

        toJeWynik = liczBa * 100;
        Serial.println(toJeWynik);

        delay (1000);
        }

        Na stronie produktu piszecie: “Modem automatycznie sprzętowo tworzy pakiety danych.” więc liczyłem po cichu że on jakoś sprzętowo potrafi przetwarzać i przesyłać zmienne numerując pakiety tak że odbiornik dostaje pakiet w raz z jego praznaczeniem. Mógł byś napisać przykład takiej struktury? Nie wiem czy dobrze rozumuję: muszę wszystkie zmienne int, float itd. zmienić na byte i zadeklarować strukturę tak?

        struct daneDoWysylki {
        byte zmienna1;
        byte zmienna2;
        byte zmienna3;
        byte zmienna4;
        };
        Dobrze? I co dalej, jaką komendą wyśle struct: radio.write(&daneDoWysylki, sizeof(daneDoWysylki)); <- dobrze kombinuje?
        Teraz najważniejsze jak to po odebraniu zapisać z powrotem do 4 zmiennych 🙂

        Co do drugiego pytania chodziło mi o sytuacje w której transmisje na dwóch rożnych kanałach nałożą się w czasie, albo np. co się stanie jeśli Ardunino nie zdąży odebrać z NRF pakietów to czy one się jakoś zakolejkują i przy następnym obrocie pętli Arduino "zabiera" wszytko na raz, czy np. w oczekiwaniu może być tylko jeden pakiet i muszę go odebrać zanim odbiornik będzie mógł odebrać kolejny?

        1. W moim poprzednim komentarzu dwa razy wkleiłem ten sam link do doców w Pythonie.

          Tutaj masz przykład jak wysyłać dane przez NRF i strukturę :
          https://github.com/nettigo/tinybrd-examples/blob/master/connectionTester/connectionTester.ino

          Przykład jest dla naszych bibliotek i tinyBrd, ale na UNO bedzie działał, oczywiście z naszą biblioteką NRF https://github.com/nettigo/RadioNRF24

          Odbieranie danych na RPi z użyciem pack: https://github.com/nettigo/tinyBrdScripts/blob/master/connectionTeser/connectionTester.py

          1. Phytona nie znam i za bardzo mnie nie interesuje, przynajmniej na chwilę obecną chcę się skupić na jednym 🙂 Ale dzięki za nakierowanie, struktury działają elegancko mam tylko problem z rurami. Gdy nadaje 1 do 1 działa idealnie, gdy próbuję nadawać dane z dwóch TX do jednego RX odbiera z obu, ale dane mieszają się. Tak to wygląda na chwilę obecną:

            #include
            #include
            #include

            RF24 radio(7, 8);

            const byte addrTX1[6] = “06001”;
            const byte addrTX2[6] = “15001”;

            typedef struct {

            float Boj;
            float Sol;
            int Pom;
            }
            RX1;

            RX1 paczka1;

            typedef struct {

            float Roj;
            float Rol;
            int Rom;
            }
            RX2;

            RX2 paczka2;

            void setup()
            {
            while (!Serial);
            Serial.begin(9600);

            radio.begin();
            radio.setChannel(121);
            radio.setPALevel (RF24_PA_MAX);
            radio.setDataRate (RF24_250KBPS);

            //radio.openReadingPipe(0, addrTX1);
            //radio.openReadingPipe(1, addrTX2);
            radio.startListening();

            }

            void loop()
            {

            radio.openReadingPipe(0, addrTX1);
            if (radio.available())
            {

            radio.read(&paczka1, sizeof(paczka1));

            Serial.print(“Pakiet 1 “);
            Serial.print(paczka1.Boj);
            Serial.print(” “);
            Serial.print(paczka1.Sol);
            Serial.print(” “);
            Serial.println(paczka1.Pom);

            }
            radio.closeReadingPipe(0);

            radio.openReadingPipe(1, addrTX2);
            if (radio.available())
            {
            radio.read(&paczka2, sizeof(paczka2));

            Serial.print(“Pakiet 2 “);
            Serial.print(paczka2.Roj);
            Serial.print(” “);
            Serial.print(paczka2.Rol);
            Serial.print(” “);
            Serial.println(paczka2.Rom);
            }
            radio.closeReadingPipe(1);
            }

            Dlaczego to tak nie chce działać? Próbowałem jeszcze dodać dla każdej rury radio.startListening(); i radio.stoptListening();, ale wtedy nie działało wcale.
            Natomiast udało mi się to rozwiązać tak:

            #include
            #include
            #include

            RF24 radio(7, 8);

            const byte addrTX1[6] = “06001”;
            const byte addrTX2[6] = “15001”;

            typedef struct {

            float Boj;
            float Sol;
            int Pom;
            }
            RX1;

            RX1 paczka1;

            typedef struct {

            float Roj;
            float Rol;
            int Rom;
            }
            RX2;

            RX2 paczka2;

            void setup()
            {
            while (!Serial);
            Serial.begin(9600);

            radio.begin();
            radio.setChannel(121);
            radio.setPALevel (RF24_PA_MAX);
            radio.setDataRate (RF24_250KBPS);

            radio.openReadingPipe(0, addrTX1);
            radio.openReadingPipe(1, addrTX2);
            radio.startListening();

            }

            void loop()
            {
            uint8_t numerRury;
            if (radio.available(&numerRury))
            {
            Serial.print(“Dane z rury “);
            Serial.print(numerRury);
            Serial.print(” “);
            if (numerRury == 0)
            {
            radio.read(&paczka1, sizeof(paczka1));

            Serial.print(“Pakiet 1 “);
            Serial.print(paczka1.Boj);
            Serial.print(” “);
            Serial.print(paczka1.Sol);
            Serial.print(” “);
            Serial.println(paczka1.Pom);
            }
            if (numerRury == 1)
            {
            radio.read(&paczka2, sizeof(paczka2));

            Serial.print(“Pakiet 2 “);
            Serial.print(paczka2.Roj);
            Serial.print(” “);
            Serial.print(paczka2.Rol);
            Serial.print(” “);
            Serial.println(paczka2.Rom);
            }
            }
            }

            i działa elegancko 🙂 Czy jest może jakiś “mądrzejszy” sposób rozwiązania tego zagadnienia?
            PS. Możliwe że mam jeden moduł “rąbnięty” – jak go założę jako RX to jaki bym numer rury nie wpisał odbiera dane tylko z jednego nadajnika, nawet jak wpiszę zmyślony to i tak odbiera mi dane tylko z 06001, a po zamianie miejscami działa normalnie.
            PS2. W jaki sposób można nasłuchiwać dwa kanały jednocześnie?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.