poniedziałek, 2 maja 2016

nRF24L01+ Poradnik część I



   1. Opis układu

        nRF24L01, czy też jego rozszerzona wersja nRF24L01+ to świetne moduły komunikacji bezprzewodowej działające na paśmie 2,4  – 2,527 GHz z relatywnie dobrym zasięgiem (jak głosi plotka której jeszcze nie sprawdziłem, nawet do około 900m na świeżym powietrzu w przypadku układów z wbudowanym wzmacniaczem i anteną). Znaleźć je można w większości urządzeń bezprzewodowych(myszkach, klawiaturach etc.), a przy dobrym zrozumieniu zagadnienia i zasady działania wykorzystać do tworzenia dość zaawansowanych sieci czy też hackowania urządzeń o nie opartych. To świetne układy ułatwiające pracę wielu domorosłym lub też dorosłym konstruktorom, a z braku dobrze napisanego poradnika opartego o poprawnie napisaną bibliotekę postanowiłem takowy stworzyć. Moduł nRF24L01 wykonuje sporą część pracy za nas, buforując dane czy sprawdzając poprawność transmisji. Transmisja danych odbywać się może na poziomie 1-2 Mbps, lub też 250 Kbps jeśli chcemy zwiększyć zasięg komunikacji. Osobiście pracowałem jedynie na wersji nRF24L01+ z 8 pinami wyjściowymi, na takowych też oprę ten poradnik. Program obsługi nRF'a napisany jest dla Atmegi16, jednak po drobnych zmianach można implementować go do dowolnego avr-a.



        Oto jedna z najpopularniejszych z dostępnych w internecie wersji wraz z numeracją pinów. Swoisty plug & play, gdzie moduł wlutowany jest w układ na płytce PCB.


Opis linii:

 1. GND & VCC
      Standardowo: VCC - napięcie, GND - masa. Moduł zasilany jest napięciem 3,3V( do 3,7V), na piny natomiast przyjmuje maksymalnie około 5,5V, dzięki czemu napięcie obniżyć musimy jedynie na linii zasilającej. Zdecydowanie nie zalecam zasilania go napięciem rzędu 5V na dłuższą metę(Zdarzało mi się puścić mu tyle przez programator i jeszcze żyje, w projektach natomiast zasilam go bez wyjątku napięciem 3,3V).

 2. CE(Chip Enable)
      Linia sterująca przy pomocy której, dzięki zmianom stanów z wysokiego na niski przełączamy nRF'a ze stanu spoczynku, w stan odbioru lub transmisji danych.. Łączymy ją z dowolnym wolnym pinem mikroprocesora (Oczywiście ustawionym jako wyjście).

 3. CSN(Chip Select Not)
     Także łączymy ją z dowolnym, niewykorzystywanym pinem mikroprocesora ustawionego jako wyjście. Służy do transmisji komend do nRF’a i ich odczytu z wykorzystaniem interfejsu komunikacyjnego SPI.

 4. SCK(Sercial Clock)
      Łączymy z pinem SCK mikroprocesora. Służy do taktowania transmisji SPI.

 5. MOSI(Master output, Slave input)
      Łączymy z pinem MOSI mikroprocesora. Linia danych przesyłanych z układu Master do Slave.

 6.MISO(Master input, Slave output)
      Łączymy z pinem MISO mikroprocesora. Linia danych przesyłanych z układu Slave do Master.

 7.IRQ(Interrupt Request)
     Jej podłączenie nie jest niezbędne do działania nRF'a, ale znacznie ułatwia nam pracę.  Możemy ustawić aby wystawiany był stan wysoki w przypadku otrzymania danych, lub też ich transmisji, a stąd już prosta droga do przerwań zewnętrznych znacznie optymalizujących program. W przypadku mojej biblioteki podłączyłem go do pinu PD2, na którym wywoływane jest przerwanie INT0.
      

   2. Trochę teorii i test interfejsu SPI

      Aby ułatwić sobie pisanie, w pliku .h biblioteki do obsługi nRF'ów zawarłem kilka prostych makrodefinicji, które pokrótce objaśnię, aby wyeliminować ewentualne niejasności.



       Wartość PAYLOAD to ilość bajtów przesyłana jednorazowo przez nRF-a. Wynosić ona może od 1 aż do 32, co daje nam sporą przepustowość połączenia. Obecnie ustawiony jest jeden, nic nie stoi jednak na przeszkodzie aby zwiększyć tę ilość. Biblioteka została napisana tak, aby po zmianie wartości PAYLOAD w pliku .h, można było po prostu wysłać tak długą daną bez przeszukiwania i poprawiania pozostałych linijek kodu. 

          UWAGA! Wartość PAYLOAD w odbiorniku jak i nadajniku musi być taka sama, w innym przypadku odbiornik odbierze tylko część danych, lub też niepoprawnie je zinterpretuje, zwracając błąd transmisji.


      R_ADDR to adres naszego odbiornika(lub nadajnika w przypadku, kiedy oczekuje on potwierdzenia o udanej transmisji, ale o tym później). T_ADDR to adres nadajnika(Dokładniej rzecz biorąc adres, który nadajnik nadaje).


           Adres odbiornika i nadajnika musi być identyczny, aby transmisja przebiegła pomyślnie. W przypadku większej ilości odbiorników można zmieniać adresy „w locie”.

Przykład:

Nadajnik:
R_ADDR  0x01
T_ADDR  0x0f

Odbiornik:
R_ADDR  0x0f
T_ADDR  0x01

         Adresy mogą wyglądać również  tak, jak w przykładzie powyżej. W tym przypadku nadajnik nadaje na adres 0x0f, podczas gdy odbiornik takowego nasłuchuje, zaś w przypadku transmisji zwrotnej(na przykład w celu potwierdzenia) odbiornik nada na adres 0x01, a nasz nadajnik na takowym odbierze.
      Adresy muszą mieć długość w zakresie 2-5 bajtów, tutaj ułatwiłem sobie sprawę pisząc jedynie jeden bajt, jednak jak później zobaczycie w pliku .c powtarzam go pięciokrotnie, w efekcie otrzymując adres: 0x01 0x01 0x01 0x01 0x01.


      TR_MODE i RC_MODE to ustawienie trybu odbioru lub transmisji nRF'a, poprzez zmianę stanu bitu PRIM_RX. Funkcja nRF_Config zostanie szerzej opisana w późniejszym czasie, służy jednak ona do zapisu danych do rejestrów urządzenia. Poszczególne bity które ustawiłem opisane zostały w komentarzach na zdjęciu.


         sbi (set bit) i cbi (clear bit) to makrodefinicje ustawiania lub zerowania wybranego pinu na wybranym porcie. Poprawia czytelność kodu i ułatwia proces pisania.


       CE i CSN to definicje pinów portu, do którego podpięte są linie sygnałowe CE i CSN. W razie nieoczekiwanych zmian mogę zmienić ich ułożenie bez żmudnego poprawiania całej biblioteki - po prostu wpisuję ich nowe umiejscowienie tutaj.


    CE_lo ; CE_hi ; CSN_lo ; CSN_hi to dodatkowe uproszczenie. Tymi definicjami bezpośrednio ustawiam CE lub CSN w stan wysoki lub niski.


       Skoro podstawowe elementy części teoretycznej mamy już za sobą, możemy przejść do etapu komunikacji z nRF'em, czyli odczytu i zapisu rejestrów. Jak już nadmieniłem, do komunikacji wykorzystujemy sprzętowy interfejs komunikacyjny SPI(lub USI w przypadku chociazby ATTINY), którego opis pominę przez wzgląd na dużą ilość dobrych materiałów dostępnych na ten temat w sieci, zarówno w języku polskim jak i angielskim. Załączę jedynie swoje funkcje które wykorzystuję, przez wzgląd na pewną ich niestandardowość. Jeśli jednak byłyby jakiekolwiek komplikacje lub problemy, mogę opisać także obsługę SPI oraz odpowiedzieć na pytania w komentarzach czy mailowo :). Zachęcam więc do nawązania konwersacji w przypadku jakichkolwiek niejasności.

      Wracając do tematu, funkcja zapisująca określoną wartość do dowolnego rejestru nRF'a wygląda tak:


Jako pierwszy argument "reg" wpisujemy nazwę rejestru(ich definicje znajdują się w pliku .h, dostępnym na końcu poradnika), pod "value" zaś wartość, którą chcemy do niego wpisać.

     Cały proces zapisu zaczyna się od ustawienia linii CSN w stan niski, co sprawia, iż nRF zaczyna "słuchać" naszych poleceń. Wysyłamy więc do niego najpierw nazwę rejestru wraz z doklejoną "czynnością", a następnie wartość, jaką chcemy zapisać. W tym przypadku jest to bit "1"(W_REGISTER) oznaczający, iż chcemy zapisać coś do danego rejestru i adres rejestru(jego nazwa). Niezbyt skomplikowane, prawda? Później wystarczy tylko, aby ustawić linię CSN na powrót w stan wysoki, a nRF powraca w stan czuwania.


       Tutaj funkcja SPI_Shift. Jak widzimy, jest to standardowa funkcja służąca to wysłania i odczytania 1 bajtu poprzez SPI.



      Proces odczytu rejestru przebiega bardzo podobnie do zapisu, z tą różnicą, że adres sklejany jest teraz z czynnością odczytu(R_REGISTER) czyli zerem zamiast jedynką wysyłaną na początku wraz z adresem. Następnie wysyłamy pusty bajt "NOP", aby pobrać z nRF'a interesującą nas wartość, którą zapisujemy do zmiennej lokalnej "data". Potem wystarczy dopisać, aby funkcja zwracała tę zmienną jako jej wynik. Na tym etapie możemy już sprawdzić zarówno poprawność połączeń jak i działanie samego nRF'a, odczytując z niego jakiś rejestr. Nasz testowy program mógłby więc wyglądać tak:


      Odczytaną daną można przesłać także poprzez UART do komputera i wyświetlić w terminalu, lub na dowolnym LCD(na przykład tym zgodnym z hd44780 jak robiłem to ja w trakcie testów). W efekcie, jeśli wszystko powyżej zostało wykonane poprawnie, powinna nam się zaświecić podłączona do pinu 0 portu A dioda, lub wyświetlić wartość 0x0E na którymś z wyświetlaczy. 0x0E to bazowa wartość rejestru "STATUS", co odczytać można w nocie katalogowej, którą także załączam poniżej. Niniejszym na tym zakończę pierwszą część, a na dniach dodam kolejną, opisującą zagadnienia związane z poprawną inicjalizacją nRF'a oraz komunikacją pomiędzy dwoma układami. W przypadku dostrzeżenia jakiegokolwiek błędu czy to w zapisie, rozumowaniu, czy też wiedzy bardzo proszę o informację, jestem hobbystą, więc nie wykluczam błędów, jednakże starałem się takowych nie popełniać. Miłego programowania!


Link do pełnej wersji biblioteki nRF24L01.h:

https://github.com/Jeiiy/nRF24L01-

Link do datasheeta nRF24L01+(Opis rejestrów strona 54):

https://www.sparkfun.com/datasheets/Components/SMD/nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf

     
      

Brak komentarzy:

Prześlij komentarz