poniedziałek, 15 kwietnia 2019

SI4463 - transciver RF firmy Silicon Labs do zastosowań bateryjnych. Pierwsze uruchomienie na PIC32MM firmy Microchip.

Po udanej randce z modułem radiowym SI4432 firmy Silicon Labs postanowiłem odpytać ulepszoną wersję tego modułu o oznaczeniu SI4463. Numeracyjnie poszło daleko do przodu :).  Na module tym oparto udaną i dobrze opiniowaną przez użytkowników konstrukcję HC12 czyli radiowy port UART dalekiego zasięgu.
W module SI4463 znacznie poprawiono część radiową , zwiększono czułość do 126 dBm, zwiększono czterokrotnie szybkość transmisji do 1 Mbps. Rozszerzono tryby modulacji : 2GFSK i 4GFSK. Ponadto mamy szybsze czasy Wake-Up i bardziej rozbudowane możliwości w zakresie budzenia. Czujnik temperatury i monitorowanie napięcia zasilania w standardzie. Moduł przy maksymalnej mocy +20dB  poniesie bez problemu dane na odległość ponad 2 km. Mam tutaj potwierdzenie tego faktu z dwóch niezależnych źródeł. Jedno z nich znajduję się w linkach poniżej artykułu. Prądy w uśpieniu rzędu 30-50nA . Dla porównania popularny u nas moduł nRF24L01+ w trybie uśpienia pobiera 900 nA. Moduły SI4463 zakupiłem bez problemu na dalekowschodniej giełdzie na literkę A..... . Na modułach tych chciałbym oprzeć  jakąś sprytną konstrukcję energooszczędną. Obecnie dodaję je do moich nowych płytek developerskich.

Poniżej obrazek jak moduł prezentuje się na mojej płytce deweloperskiej dla
ATSAMD21 

ATSAML10/11


Na razie jednak moduł uruchamiam na mojej płytce developerskiej dla PIC32MM. Tak mi jest najłatwiej ,najpierw przetestować soft na PIC32 a potem przenieść go na ARM-a. Poniżej płytka dla PIC32MM na której będę robił moje pierwsze testy. Na płytce znajduje się PIC32MM0256GPM048 :


Na wstępie opis wyprowadzeń i rozmiary modułu RF jaki posiadam :


Moduł jest naprawdę malutki jak na "powera" jaki sobą reprezentuje :)
Antena dedykowana dla 433 MHz  zakupiona oddzielnie ale u tego samego dostawcy co moduł radiowy. Zysk energetyczny podawany przez dalekowschodniego producenta to 2.5 dBm ale w/g mnie to lipa a realny parametr to ok. 1.5 dBm. Nie mniej wykonana jest estetycznie i nawet bym rzekł, że jest ładna.


Do sterowania modułem użyję SPI2 i pinologi jak poniżej :



Warstwę sprzętową w PIC32MM, tj ustawienie zegara, pinów, SPI2 etc wykonuję przy pomocy MCC w MPLABX-IDE 5.15 . Moduł RF można maksymalnie gonić po SPI na 10 MHz. Ale to są na początku sprawy drugorzędne najpierw musimy rozpoznać  jak będzie wyglądała funkcja do wymiany danych po SPI z modułem RF. No tu akurat się nie wysilimy i skorzystamy z gotowej funkcji oferowanej przez MCC. Nazwa tej funkcji to SPI2_Exchange8bit( ) i znajdziemy ją w pliku spi2.c wygenerowanym automatycznie przez MCC. Na tej jednej funkcji oprę całą wymianę danych po SPI  z naszym modułem RF czyli nadawanie i jednoczesne odbieranie.

Teraz wypadałoby się zorientować z grubsza jak wygląda struktura przesyłanych danych  z/do modułu RF. A tutaj zostaniemy zaskoczeni bo jest ona kompletnie inna niż znana nam z modułów starszych serii SI4432


Powyższy obrazek pokazuję ogólną ideę przepływu danych. Najpierw wysyłana jest komenda . Komenda może zawierać argumenty, które wysyłamy bezpośrednio po komendzie co symbolizuje obrazek poniżej :


Po wysłaniu komendy z argumentami (jeśli występują) moduł RF przetwarza odebrane dane i ustawia potwierdzenie zakończenia ich przetwarzania.Po potwierdzeniu , moduł RF prześle strumień odpowiedzi (jeśli taki przysługuje dla danej komendy). Mamy do dyspozycji trzy metody potwierdzenia : 

metoda programowa - polega na wysłaniu (po wysyłanej komendzie z argumentami patrz obrazek wyżej) do modułu RF sekwencji dwóch bajtów jednego 0x44 (komenda READ_CMD_BUFF) i drugiego obojętnie jakiego np. 0x00. Jeśli wcześniej wysłana komenda zostanie pozytywnie przetworzona to na wysłanym do modułu RF drugim bajcie sekwencji czyli na 0x00 ,otrzymamy z modułu bajt potwierdzający 0xFF (CTS = 0xFF) i po nim strumień danych odpowiedzi.


metoda GPIO - polega na wystawieniu stanu np. wysokiego jako potwierdzenia na skonfigurowanym do tego pinie.
metoda Interrupt - polega na wygenerowaniu przerwania jako sygnału potwierdzenia.

Ale uwaga nie wszystkie komendy wymagają potwierdzenia. Te które tego wymagają mają w API w tabelkach w sekcji Reply Stream pierwszy bajt 0x00 opisany jako CTS. W pozostałych przypadkach nie jest wymagane potwierdzenie.

Dla lepszego zrozumienia zagadnienia potwierdzenia, pokażę jak to na rzeczywistych sygnałach wygląda w przypadku wysłania do modułu RF np. komendy PART_INFO (hex 0x01). Spis i opis wszystkich komend znajdziemy w linku SI4463 API rev C2. Struktura komendy PART_INFO wygląda jak poniżej :



Wysłanie komendy PART_INFO zaczyna się kodem tej komendy czyli wysłaniem na linię MOSI 0x01. Komenda PART_INFO nie posiada argumentów tylko zwraca 8 bajtów (bajt nr 0x00 - CTS nie liczymy i pomijamy). Czyli po wysłaniu bajtu 0x01 reprezentującego komendę PART_INFO od razu wysyłamy sekwencję/ zapytanie o status potwierdzenia czyli kolejne dwa bajty wysłane na linię MOSI  to 0x44 i 0x00. Jeśli na wysłanym bajcie 0x00 otrzymamy na linii MISO bajt 0x00 to znaczy, że komenda PART_INFO nie została jeszcze przetworzona i strumień danych zwrotnych  nie jest jeszcze gotowy do przesłania do MCU. Taką sytuację mamy na poniższym obrazku :


Skoro dane nie są przetworzone jeszcze przez moduł RF, to musimy ponownie zapytać o status potwierdzenia czyli ponownie wysłać  sekwencję 0x44 i 0x00 na linię MOSI. W drugiej wysłanej sekwencji nadal na bajcie 0x00 mamy na linii MISO 0x00. Jesteśmy uparci i wysyłamy trzecią sekwencję na której wreszcie dostajemy bajt potwierdzający CTS = 0xFF



Potwierdzenie musi pojawić się na 0x00 bajcie a nie na 0x44. Teraz spodziewamy się, że kolejne bajty , które zostaną wystawione na linii MISO to strumień danych zwrotnych jakie otrzymamy z modułu. Otrzymujemy zatem kolejno na linii MISO 8 bajtów :
0x11 , 0x44 , 0x63 , 0x00, 0x00 , 0x0F, 0x00, 0x03 i koniec strumienia danych zwrotnych. Pokazują to rysunki poniżej :








Nie zmieściła mi się cała transmisja na jednym obrazku dlatego rozbita została na 5-części powyżej. Co oznaczają poszczególne bajty zwróconych przez moduł RF argumentów, doczytamy w linku ze spisem komend. W sumie najważniejsza informacja to bajt 2 i 3 czyli 0x44 , 0x63 co podejrzanie dziwnie się składa na model naszego modułu RF czyli SI 4463. Stąd wiemy , że nasz dalekowschodni brat nie zrobił nas w balona i sprzedał model modułu taki jaki chcieliśmy mieć :)

Jeśli nadal koncepcja potwierdzenia nie jest zrozumiała to odsyłam do Programming Guide i tam należy się wczytać w to zagadnienie. Ja też od pierwszego spojrzenia nie zaczaiłem do końca dopiero przy drugim podejściu mi się rozjaśniło. A najlepiej to widać na analizatorze tylko najpierw trzeba mieć napisaną bibliotekę :) . Może to uporczywe potwierdzanie wydawać by się mogło cokolwiek upierdliwe, no w pewnym sensie tak jest. Ale mamy tu do czynienia z potwierdzeniem wykonania operacji przez moduł RF czyli nabywamy pewność, że polecenie zostało wykonane z powodzeniem a to jest pozytywna cecha w systemach o wymaganej dużej pewności działania. W sumie jeśli bibliotekę oprzemy o potwierdzenie stanem sygnału na pinie GPIO modułu RF to możemy sobie zwiększyć wygodę działania w ten sposób. Moduł RF ma do dyspozycji cztery konfigurowalne piny GPIO. Ale zapewniam, że zaprezentowana przeze mnie biblioteka dla SI4463 będzie w prosty sposób implementowała programowe potwierdzenia wykonanej operacji przez moduł RF.
Na koniec zagadnienia wspomnę, że moduł RF ma cztery konfigurowalne rejestry  Fast Response Registers, które nie wymagają procedury czekania na potwierdzenie wykonania operacji. Możemy podpiąć pod te rejestry najczęściej wykonywane operacje i sprawa załatwiona. SI4463 coraz bardziej mi się podoba a jeszcze nie zacząłem z nim w pełni działać.

Kończymy zagadnienie i idziemy dalej. Zajmiemy się teraz konfiguracją modułu RF.  Mamy tu do dyspozycji elegancki soft WDS - Wireless Development Suite przygotowany przez firmę Silicon Labs. Jedyna wada to działanie tylko po Windą. Ponieważ ja z windą rozstałem się bardzo dawno temu dlatego musiałem do swojego Linuxa doinstalować Virtual Box i w nim postawić Windę ale w sumie to nie był żaden problem bo program WDS użyjemy najwyżej kilka razy i zapomnimy o nim. Za pomocą programu WDS konfigurujemy moduł RF w/g własnych wizji i na koniec generujemy plik konfiguracyjny , który dołączamy do naszej biblioteki. Przyznam , że jest to bardzo wygodne rozwiązanie. Obsługa programu jest prosta i intuicyjna nie sprawi nam żadnych problemów a nawet przysporzy wyśmienitej zabawy. Przejdźmy zatem przez etap konfiguracji razem bo w kupie raźniej :). Jeśli na początku nie mamy własnej wizji co do ustawień np. toru radiowego bo się na tym nie znamy to możemy się podeprzeć mądrzejszymi od nas ludźmi (to nie wstyd , że tacy są :) ) i na przykład skorzystać z gotowych wizji z tego linku

Po zainstalowaniu aplikacji WDS w Windows uruchamiamy ją.



Wybieramy z górnej belki opcję Start Simulation. W okienku który wyskoczy znajdujemy SI4463 i zaznaczamy go.



Na dole wewnętrznego okienka "klikamy" Select Radio, wyskoczy nam kolejne okienko (widok poniżej) w którym "klikamy" Select Aplication .



Zwieńczeniem naszego "klikania" jest pojawienie się zasadniczego okna konfiguracyjnego dla naszego modułu RF :


Generalnie aplikacja WDS ma o wiele większe możliwości niż sama bierna konfiguracja ale udajmy , że tego nie dostrzegamy co by nas głowa nie rozbolała od nadmiaru nowości :) Zakasamy rękawy i zaczynamy zabawę w konfigurowanie modułu RF. Fajnie lubię to :)
Na początek trzeba rozpoznać dwa parametry , na jaką częstotliwość bazową nasz moduł jest nastrojony czyli innymi słowy jaką częstotliwością fali nośnej się posługuje i jaki kwarc ma na pokładzie (standardowo jest to wewnętrzny kwarc 30 MHz). W naszym przypadku są to 433 MHz i zewnętrzny kwarc 26 MHz. Zewnętrzny kwarc jak podaje sprzedawca jest wysokiej precyzji i małym dryfcie temperaturowym, jeśli to jest prawda to moduł powinien sobą reprezentować wysoką kulturę pracy w zakresie parametrów częstotliwościowych a to jest podstawa stabilnego działania toru RF. Należy tutaj zauważyć , że moduł może pracować w zakresie 425-525 MHz i ma do dyspozycji 255 kanałów. Gdybyśmy sobie zażyczyli pracę na np. 255 kanale to nasza częstotliwość pracy byłaby przy odstępie 250kHz na kanał 496.75 MHz. W ustawieniach zostajemy na kanale nr 0. Ustawiamy zatem :
Base frequency = 433 MHz
Crystal frequency = 26 MHz,
Ponieważ używamy zewnętrznego kwarca należy "zaptaszkować" opcję Use external TCXO / Ref Source. Nasze dotychczasowe ustawienia przedstawia zdjęcie poniżej :

 
Ostatnim ustawieniem jakie poczynimy w zakładce Frequency and Power to wybranie opcji Driven by RC oscillator dla 32kHz. Oscylator RC 32kHz przyda się do np. wybudzania. Poniżej podsumowanie wszystkich naszych ustawień w zakładce Frequency and Power :


Wspomnę jeszcze o ustawieniu mocy. Widzimy, że maksymalna liczba dla niej w okienku PA power level to 0x7F czyli 127 , minimalnej mocy odpowiada cyfra 0. Niestety nie ma prostej formuły aby przyporządkować poszczególną cyfrę do poziomu mocy ponieważ jest to wartość nieliniowa. Można się ewentualnie wspierać tutaj wykresami jakie znajdziemy w datasheet . Wiemy , że maksymalna moc to +20 dBm a minimalna to -20 dBm . Trzeba przyznać , że jest to stosunkowo szeroki zakres zmian mocy. Zostawiamy to ustawienie na wartości maksymalnej czyli 0x7F

Czas zmienić zakładkę bo bieżąca już nam się znudziła :). Przełączamy się na zakładkę RF Parameters i zastajemy tam taki obraz :


Pierwszą rzeczą jaką sobie ustawimy to typ modulacji na 2GFSK. Zakładam, że nie jesteśmy ekspertami od typów modulacji. Warto jednak wiedzieć, że modulacja GFSK jest powiązana z Panem Gaussem i jest bardzo dobrym wyborem jeśli np jesteśmy ograniczeni szerokością pasma a chcemy użyć dużej ilości kanałów w jego obrębie. Cechą charakterystyczną tej modulacji jest m.in szybki spadek widma na krawędziach. A sygnał 0 lub 1 powstaje za pomocą odchylenia częstotliwości w lewo lub prawo np -50 kHz dla 0 i +50 kHz dla 1. Po stronie modulatora schemat blokowy dla modulacji GFSK wygląda jak poniżej :



Widmo jakie uzyskujemy na wyjściu modulatora to "dzwon" Pana Gaussa. A obrazowa zasada zamiany sygnału cyfrowego na składowe częstotliwości  na rysunku poniżej  :


Modulacja 2GFSK jest optymalizacją m.in w zakresie szerokości pasma co oznacza , że np w tej samej szerokości pasma prześlemy więcej danych niż dla modulacji GFSK lub tyle samo danych w połowie pasma. Warto zauważyć, że nasz moduł ma również modulację 4GFSK. Ale my nie będziemy przesadzać z żyłowaniem pasma i optymalizacją w zakresie przepływności binarnej bo wszystko ma swoją cenę a w tym przypadku np. spadek czułości w odbiorze.

Teraz naszą uwagę skupiamy na trzech parametrach : Data Rate, Deviation i RX Bandwidth. Data Rate to przepływność binarna czyli ile bitów przeleci w jednostce czasu. Deviation to odległość pomiędzy dwoma częstotliwościami nośnej i modulującej . RX Bandwidth to szerokość pasma jaką zajmie nam jeden kanał transmisyjny. Powyższe parametry są przybliżone wzorem :

BW = 2Fd + Rb
BW - RX Bandwidth
Fd - Frequency Deviation 
Rb - Data Rate

Wzór jest pomocny przy ustaleniu trzech wymienionych powyżej parametrów toru radiowego dla modulacji GFSK. Punktem wyjścia dla nas jest przepływność binarna Data Rate, jeśli chcemy uzyskać duże zasięgi transmisji to ten parametr musimy skracać, coś za coś. Załóżmy , że nie jest nam potrzebna duża przepływność binarna bo nie przesyłamy dużych strumieni danych. Przyjmuję , że 10 kbps jest zwymiarowane w sam raz na moje niezbyt wygórowane potrzeby. Drugim krokiem jest określenie parametru Deviation  dla wybranej przepływności binarnej. Tutaj zasada jest prosta , dzielimy przepływność binarną przez 2 . Stąd otrzymujemy 10 kbps/2 = 5 kHz. Gdyby przepływność binarna była np. 500 bps to dewiację ustalamy na 250 Hz.
Ostatnim krokiem jest ustalenie szerokości pasma czyli RX Bandwidth. Wyliczamy ją z powyżej podanego wzoru na BW. Czyli w naszym przypadku :

BW = 2*5 (kHz) + 10 (kbps) = 20 kHz

Mogą frapować tutaj jednostki, że dodaję kbps i kHz i wychodzi mi z tego kHz. Ale to tak się liczy i nic na to nie poradzę :). Trzeba jednak zauważyć ,że jest w programie opcja do automatycznego wyliczenia szerokości pasma (bez podania wartości wyliczenia) ale ja chcę mieć nad tym kontrolę i sam to wyliczam.
Mamy zatem w optymalny i świadomy sposób wybrane podstawowe parametry dla toru radiowego. W sumie ta zabawa w projektanta "fal radiowych" zaczyna mi się podobać. Z reguły ludzie bawiący się modułami radiowymi olewają wnikanie w szczegóły związane z parametrami toru radiowego , czasami nawet te parametry są chowane przed użytkownikiem ot taka czarna skrzynka . Wrzucamy królika na wejście a na wyjściu mamy gotowy pasztet. No wszystko fajnie ale w taki sposób rośnie pokolenie wytresowanych i leniwych umysłowo konsumentów. Czyli idealny obywatel i konsument dla rządzących elit. Ja mówię takiemu stanowi rzeczy stanowczo nie i tam gdzie tylko mogę staram się wnikać w szczegóły i używać mózgu co by mi neurony w nim nie zanikły .

Tak w ramach ciekawości zerknąłem na parametry toru radiowego popularnego u nas modułu  nRF24L01+. W porównaniu do modułu SI4463 , moduł nRF24L01+ wygląda bardzo blado. W zasadzie w każdym punkcie parametrów toru radiowego, SI4463 bije na głowę konkurenta a jedyną zaletą modułu nRF24L01+ jest cena i z tego powodu prawdopodobnie wynika popularność tych modułów na naszym rynku.

Nanosimy nasze ustawienia w programie. Zaznaczamy dodatkowo opcję Enable PLL AFC i Enable adaptive Ch.Fil.BW. AFC (Automatic Frequency Control) to automatyczne dostrajanie częstotliwości do odbieranego sygnału. Przestrajane są filtry odbiorcze i utrzymywane  parametry w zakresie ich rezonansu. Jeśli używamy AFC trzeba wydłużyć preambułę ale warto to zrobić dla poprawy działania odbioru. Podsumujmy zatem nasze ustawienia w zakładce RF parameters :



Przechodzimy do kolejnej zakładki Packet , stan jaki tam zastajemy poniżej :




W zakładce tej modelujemy obraz i parametry wysyłanego pakietu danych. Możliwości modelowania struktury ramki są naprawdę imponujące. Początkowe ustawienie to pole z Preamble , pole ze słowem synchronizującym SyncWord i pole Field 1 kontener na dane .

Najpierw skupiamy uwagę na Preamble configuration, preambuła to początek ramki i tam znajduje się powtarzana na zdefiniowanej długości sekwencja 1010. Służy to do dostrojenia odbiornika do nadawanego sygnału i poinformowanie go , że leci właśnie ramka z danymi :)
Do ustawienia preambuły aby nie robić tego na pałę możemy posłużyć się tabelką z datasheet :


Pamiętamy, że AFC mamy włączone. W tabelce nie mamy jednak danych dla modulacji 2GFSK jaką sobie wybraliśmy. Ale nie ma co się martwić tym faktem, na pewno długość preambuły musi być na poziomie modulacji GFSK i możemy tutaj w stosunku do niej przyjąć zapas tzn długość zamiast 5 bajtów dać 8 bajtów. Nasze docelowe ustawienie breambuły wygląda jak poniżej :


czyli w sumie tak jak zastaliśmy na początku.
Klikamy na pole SyncWord i zerkamy jak wygląda słowo synchronizujące :



widzimy tam dwa bajty 2D i D4. Słowo synchronizujące może mieć rozmiar maksymalnie 4 bajty. My wykorzystamy ten fakt i rozbudujemy zastane słowo o dodatkowe dwa bajty do postaci 2D D4 B4 2B. W tym celu zmieniamy wartość w polu Sync word lenght na 4 i dopisujemy dwa dodatkowe bajty B4 i 2B. Efekt poniżej :



Ponieważ nasza ramka jest zbyt uboga a moduł umożliwia robienie modelingu jej. Skorzystamy z tej możliwości i rozbudujemy naszą ramkę m.in o dodatkowe pole w którym będziemy przesyłać informację o długości przesyłanych danych. W tym celu przełączamy się z zakładki Packet config na zakładkę Variable length config. Zastany widok poniżej :


Ptaszkujemy opcję Enable variable packet length.
Widzimy , że struktura naszej ramki zmieniła się teraz mamy oprócz pola Preamble i SyncWord dwa pola F1L i <F2>. Pole F1L jest kontenerem w którym przesyłana będzie infomacja o długości pakietu danych w polu <F2>. Klikamy w grafikę pola F1L i patrzymy co tam mamy.


Widzimy, że pole ma ustawioną stałą długość 1 bajt i tak zostawiamy. Zaznaczamy opcję Use data whitening. Mechanizm ten zamienia długi czasowo stan wysoki lub niski w przesyłanych danych na sekwencje krótkich zmian sygnału 0/1. Powoduje to lepszy obraz widma. Podkreślenie zielone, pola F1L oznacza , że mechanizm Whiteningu zostanie zastosowany w tym polu. Chcemy jeszcze aby pole F1L było brane pod uwagę przez mechanizm sumy kontrolnej CRC. Zaznaczamy w tym celu czerwoną opcję Enable CRC over this field. Efekt jak poniżej :


Teraz czas na zajrzenie do pola <F2>. Klikamy w ikonkę o tym oznaczeniu. Widok jaki zastajemy poniżej :


Pole <F2> to kontener na nasze dane ale o zmiennej długości. Zmienna długość bo fizycznie rozmiar pola w przesyłanej ramce zależy od ilości przesyłanej informacji, jeśli przesyłamy np 4 bajty danych to nie leci ramka 63 bajtowa (bo taką długość maksymalną zadeklarujemy za chwilę) w tym nasze 4 bajty z danymi ale leci tylko ramka o długości 4 bajtów. Czyli zmienia się długość przesyłanej fizycznie ramki w zależności od ilości przesyłanych danych. Stąd pole <F2> jest polem o zmiennej długości z naszymi danymi. Jest to bardzo ciekawa funkcjonalność modułu. Oczywiście jeśli mamy takie życzenie pole to możemy ustawić jako pole o stałej długości niezależnie od ilości przesyłanej informacji , ramka po całej długości będzie przesyłana nawet z pustymi polami. Ustawmy długość maksymalną naszego kontenera na dane czyli pola <F2>. Dostępna maksymalna wartość to 63 bajty i ustawmy taką wartość w polu Max lenght . 1 bajt został skradziony przez pole F1L. Tutaj mała dygresja , moduł ma możliwość przesłania więcej niż 64 bajty danych w jednej ramce i nie mówię tutaj o strumieniowym przesyłaniu danych bo taka możliwość też jest. Mamy możliwość przesłania aż 128 bajtów w jednej ramce ale nie ustawimy tego w programie WDS , musimy to zrobić manualnie ustawiając odpowiednią opcję . Poniżej pokazuję fragment z API gdzie tę opcję ustawimy :


Wracamy do ustawień pola <F2>. Zaznaczamy opcję Use data whitening, chcemy aby pole to również miało ten mechanizm. Następnie dołączamy mechanizm CRC zaznaczając pole Enable CRC over this field. Dodatkowo zaznaczamy opcję Transmit CRC at the end of this field. Spowoduje to, że suma kontrolna CRC będzie wstawiona za polem <F2>.  Końcowy efekt ustawień pola <F2>. Jak poniżej :



Będąc w widoku zakładki Variable length config odznaczmy opcję Add lenght to RX FIFO. Jeśli dobrze zrozumiałem to odznaczenie tej opcji spowoduje, że w odebranym strumieniu danych , które znajdą się w sprzętowym buforze odbiorczym  RX FIFO modułu pominięte zostaną dane o długości odebranych danych. Chodzi o to, żebyśmy ściągali z bufora RX FIFO tylko same dane a nie inne techniczne dane o ramce. Po co nam pałętająca się dana o długości danych w samych danych. Ale to nie znaczy, że nie będziemy mieć dostępu do danej o długości odebranych danych.


Na wszelki wypadek pokażę jeszcze ustawienie dla CRC, w tym celu klikamy w zakładkę CRC config i obrazek jaki powinien być poniżej :


Podsumujmy fizyczną strukturę naszej ramki transmisyjnej , która będzie składać się z :
  •  Preambuła - 8 bajtów
  •  SyncWord - 4 bajty (to możemy wykorzystać do adresowania urządzeń)
  •  F1L (liczba danych fizycznie transmitowanych w polu <F2>) - 1 bajt
  •  <F2> (pole z danymi) - 63 bajty
  •  CRC - 2 bajty
Czyli jeśli się nie pomyliłem w dodawaniu to długość całkowita transmitowanej ramki wynosi 78 bajtów , nie w kij dmuchał :)
Warto tu wspomnieć , że struktura ramki może być o wiele bardziej skomplikowana a przedstawia to obrazek poniżej :


Jedynym ograniczeniem w konstrukcji ramki jest nasza wyobraźnia i ewentualnie ograniczenie w postaci maksymalnie 5 oddzielnych bloków funkcjonalnych w ramach dostępnych 64 bajtów. Możemy sobie np. zażyczyć wydzielonego bloku na adres urządzeń, adres wiadomości tak jak w CAN na przykład i dla każdych z tych bloków oddzielny CRC etc. Elastyczność w konfiguracji struktury ramki jest mocną stroną SI4463.

Kończymy działania w zakładce Packet i przechodzimy do kolejnej zakładki Interrupts konfigurującej przerwania. Zastane ustawienia na obrazku poniżej :


Nasz moduł ma jeden pin oznaczony nIRQ i tym pinem będziemy rozpoznawać np. czy została odebrana ramka lub nadana  A po co nam informacja o nadanej ramce ? ano po to aby np. uśpić moduł po transmisji. Głupio byłoby usypiać podczas trwania nadawania. Możliwości zdarzeń na które będzie reagować przerwanie jest sporo ,my jednak na skupiamy się na włączeniu kilku podstawowych.  Moja propozycja włączenia przerwań  poniżej :


Powoli zbliżamy się do końca wszystkich ustawień. Zmieniamy zakładkę na GPIO and FRR. W zakładce tej konfigurujemy m.in funkcje dla pinów GPIO (GPIO0...GPIO3) i dla szybkich rejestrów FRR ,nie wymagających sprawdzania statusu przetworzonej operacji (CTS).


Jak otworzymy spis funkcjonalności dla np. GPIO0 to się za głowę złapiemy ile mamy możliwości. Nie bać się psia mać, człowiek to wymyślił i człowiek to ogarnie :)
Ja ustawiam GPIO0 aby dawał sygnał CTS i NIRQ aby sygnalizował przerwanie stanem niskim oraz pull-up dla nIRQ . Reszty na tym etapie nie tykam dopóki nie rozpoznam co tu może jeszcze mi się przydać a moja wiedza jest jeszcze niepełna w tym zakresie.


Brawo dotarliśmy do końca ustawień naszego modułu RF. Jeśli ktoś dotarł do tego miejsca to szczerze gratuluję :). Konfiguracja naszego modułu RF w programie WDS jest naprawdę bardzo komfortowa i szybka ,rozwleka się tylko pisanie o tym :). Proponuję nie zrażać się jeśli czegoś jeszcze nie rozumiemy. Moduł SI4463 jest wart poznania bo zrobiła go firma amerykańska , która ma duże doświadczenie z modułami RF i nie tylko. Warto wspomnieć , że Silicon Labs posiada w swoim portfolio jeden z najbardziej oszczędnych energetycznie modułów WiFi na świecie ale to już jako ciekawostka przyrodnicza. No ale nawet najbardziej oszczędny energetycznie moduł WiFi i tak nie ma szans z naszym SI4463.

Na koniec przeglądam jeszcze nasze ustawienie czy czegoś nie przeoczyłem.Rzuca mi się w oczy w zakładce Frequency and power wartość Channel spacing = 250 kHz. Przy naszej szerokości pasma 20 kHz jest to
aż nadto ale zostawiamy jak jest.

Zwieńczeniem naszych trudów jest wygenerowanie pliku konfiguracyjnego w języku C, który dołączymy do naszej biblioteki. Aby wygenerować ten plik klikamy na samym dole po prawej Generate source. Wyskoczy nam pole wyboru z którego wybieramy opcję Save custom radio configuration header file.


Utworzony zostanie plik o nazwie radio_config_Si4463.h w którym zawarta jest cała konfiguracja naszego modułu. Konfigurację tę musimy w procesie inicjalizacji w naszym programie wgrać do modułu. A robi się bardzo prosto. Na razie jednak musimy rozszyfrować dwie rzeczy. Po pierwsze co to za format przesyłania poszczególnych właściwości/komend i jak z takiej dziwnej konstrukcji tablicy ujętej w postaci #define wyłuskać dane. Najpierw zajmiemy się formatem przesyłanych komend i właściwości, opis tego formatu znajdziemy w dokumencie Programming Guide


Teraz skorelujmy to z tym co widzimy w pliku nagłówkowym radio_config_Si4463.h dla wybranej grupy właściwości . Wybrałem grupę właściwości jak poniżej konfigurującą m.in SyncWord . Pamiętamy , że nasze ustawienie dla słowa synchronizującego to 2D D4 B4 2B


Na obrazku powyżej widzimy format w jakim konstruowane są kolejne bajty wysyłanej komendy i właściwości.  Nasz przykładowy zbiór właściwości jest opisany  jako RF_SYNC_CONFIG_6. Spróbujmy rozszyfrować ten szyfr. Najpierw przyjrzyjmy się jak wygląda właściwość dotycząca konkretnie słowa SyncWord od środka , w tym celu musimy sobie otworzyć ściągnięty SI4463 API rev C2 czyli spis komend i właściwości API. Znajdujemy tam naszą właściwość pod nazwą Sync_Bits


Na górze tabelki mamy strukturę bitową naszej właściwości a na dole wartość domyślną defaults.
No dobrze wracamy do naszego szyfru , który chcemy rozszyfrować czyli :

#define RF_SYNC_CONFIG_6 0x11, 0x11, 0x06, 0x00, 0x03, 0xB4, 0x2B, 0x2D, 0xD4, 0x00

I co tu jest komendą a co właściwością etc ?

Bajt nr 1 = 0x11 --> kod komendy SET_PROPERTY (Sets the value of one or more properties), znajdziemy ją w spisie komend. Komenda do ustawienia właściwości jednej lub kilku.
Bajt nr 2 = 0x11 --> Group ID

Bajt nr 3 = 0x06 --> Number of properties (liczba właściwości 6 czyli to co zaczyna się od SYNC_CONFIG w dół )
Bajt nr 4 = 0x00 --> Start ID
Bajt nr 5 = 0x03 --> SYNC_CONFIG
Bajt nr 6 = 0xB4 --> SYNC_Bits
Bajt nr 7 = 0x2B --> SYNC_Bits
Bajt nr 8 = 0x2D --> SYNC_Bits
Bajt nr 9 = 0xD4 --> SYNC_Bits
Bajt nr 10 = 0x00 --> SYNC_CONFIG2

Powyższy przykład dotyczy przypadku kiedy coś ustawiamy i używamy wtedy komendy SET_PROPERTY (0x11). A co jeśli chcemy odczytać ustawione wartości ?? wtedy należy użyć komendy GET_PROPERTY (0x12) i wysłać kolejne 3 bajty. Wyglądałoby to tak :

Bajt nr 1 = 0x12 --> kod komendy GET_PROPERTY (Gets the value of one or more properties), znajdziemy ją w spisie komend. Komenda do zapytania o ustawieniu  właściwości
Bajt nr 2 = 0x11 --> Group ID
Bajt nr 3 = 0x06 --> Number of properties (liczba właściwości o które pytamy to 6 )
Bajt nr 4 = 0x00 --> Start ID

W efekcie otrzymamy strumień danych złożony z 6 bajtów jako odpowiedź. Pamiętać jednak należy o sygnale potwierdzenia CTS.

Na początku może to się wydawać trochę zagmatwane ale nie pękać. Ja sam dopiero zaczaiłem podczas pisania artykułu o co tu biega i to jest naprawdę proste jak się zrozumie:) Żeby być bliżej zrozumienia należy mieć otwarty plik konfiguracyjny radio_config_Si4463.h i spis komend i właściwości API i lampić się tak długo aż klapki  w mózgu się pootwierają :)

No dobrze przejdźmy teraz do rozszyfrowania tego co znajduje się w definicji #define RADIO_CONFIGURATION_DATA_ARRAY{}


W tej  dziwnej trochę definicji tablicy jest zawarta cała esencja wszystkich ustawień i taką tablice musimy przesłać ciurkiem w procesie inicjalizacji modułu. Najpierw jednak usuniemy z tej definicji pierwszy element opisany jako SI446X_PATCH_CMDS , nie bawimy się w "patchowanie" modułu. Postać definicji tablicy z usuniętym pierwszym elementem :


Zerknijmy na fragment w którym widzimy rozważany wyżej RF_SYNC_CONFIG_6 wygląda on tak :

....0x0A, RF_SYNC_CONFIG_6....

O ile wiemy już jak wygląda definicja RF_SYNC_CONFIG_6 ,ale co to jest za bajt 0x0A czyli w dec 10. Jest to liczba bajtów jaka będzie wysyłana w tym przypadku chodzi ile bajtów zawiera RF_SYNC_CONFIG_6 , jak policzymy to wyjdzie nam 10. Ot i cała filozofia. 
Teraz jak taką definicję tablicy RADIO_CONFIGURATION_DATA_ARRAY{} z pliku nagłówkowego zamienić na tablicę , którą możemy się normalnie posługiwać w programie pokazuję poniżej :

uint8_t Radio_Conf_Array[] = RADIO_CONFIGURATION_DATA_ARRAY ;

Prawda , że proste ? No tak proste jak się wie jak to zrobić :)

UWAGA bardzo ważna rzecz . WDS generuje nam plik konfiguracyjny domyślnie przygotowany do "patchowania" ,usuwaliśmy niektóre elementy z nim związane . Ale w czasie uruchamiania modułu wykryłem , że w definicji Power Up na drugim bajcie jest wartość 0x81 , która informuje moduł , że trzeba się "patchować", blokuje to skutecznie możliwość inicjalizacji modułu.  Musimy zmienić koniecznie tę wartość manualnie na 0x01. Wykrycie tego zajęło mi prawie 3 dni. Prawdopodobnie przeciętny użytkownik polegnie na tym zagadnieniu i nie uda mu się uruchomić modułu.

Podsumuję w tym miejscu co trzeba zrobić z nowo wygenerowanym pliku konfiguracyjnym a są to dokładnie trzy rzeczy :

1. usunąć lub zakomentować poniższy fragment z pliku konfiguracyjnego :
#include "..\drivers\radio\Si446x\si446x_patch.h"
 
2. usunąć z definicji tablicy RADIO_CONFIGURATION_DATA_ARRAY fragment SI446X_PATCH_CMDS, \ czyli pierwszy wiersz tablicy

3. w definicji #define RF_POWER_UP 0x02, 0x81, 0x01, 0x01, 0x8C, 0xBA, 0x80 zmienić drugi bajt na 0x01 , po zmianie wygląd jak poniżej :
#define RF_POWER_UP 0x02, 0x01, 0x01, 0x01, 0x8C, 0xBA, 0x80

Druga ważna rzecz to struktura procesu inicjalizacji i rola w niej pinu SDN. Posłużmy się rysunkiem :


Po włączeniu zasilania musimy ustawić na pinie SDN przez 10uS  stan wysoki. Potem trzeba dać zwłokę maksymalnie 6 ms na wykonanie się procedury wewnętrznej dla POR (Power On Reset). Potem możemy przesyłać dane konfiguracyjne z Power Up na czele. Po każdej przesłanej komendzie czekamy na CTS. Z rysunku powyżej widać, że na każdym etapie inicjalizacji możemy posłużyć się sprawdzaniem stanów na pinach GPIO1 i nIRQ ale moim zdaniem to zbędny trud. Podanie odpowiednich zwłok jest równie skuteczne. Cały proces konfiguracji od włączenia zasilania do pełnej gotowości trwa 15 ms.

Teraz trochę o przerwaniach generowanych przez moduł RF na pinie nIRQ.
To na co ma reagować przerwanie ustawiamy w WDS a jest tego trochę.
Pin modułu nIRQ , musi być skonfigurowany tak jak pokazywałem wyżej, ustawienie pull-up jest potrzebne w przypadku uśpienia pinem SDN. Od strony MCU musimy wygospodarować pin i monitorować stan na nim. Ja wybrałem do tego pin RB9. Po wykryciu stanu niskiego na pinie musimy wykonać komendę GET_INT_STATUS (kod 0x20) , komendę wysyłamy bez argumentów lub z trzema argumentami o wartości 0x00. Spowoduje to wyzerowanie wszystkich statusów, wskazujących na wygenerowane przerwania i jednocześnie zresetujemy pin nIRQ , sprowadzając go do stanu wysokiego. Poniżej przykład kodu jak to może wyglądać w przypadku np. wysyłania danych i reagowania na status wysłanej ramki TX:

if(!IO_nIRQ_GetValue()){ //nIRQ Low ?
           SI4463_Get_Interrupt(inter_buff); //get interrupt status and clear
        if(inter_buff[2] & 0x20) {//PACKET_SENT_PEND interruption , see API Documentation GET_INT_STATUS?
          }
       }


W pętli głównej programu sprawdzamy, czy na pinie nIRQ jest stan niski ( od strony MCU  za pomocą stanu na pinie RB9), jeśli tak to wykonujemy komendę GET_INT_STATUS (kod 0x20) ukrytą w funkcji SI4463_Get_Interrupt() , która pobiera do bufora użytkownika wszystkie statusy przerwań. Następnym krokiem jest sprawdzenie  stan bitu PACKET_SENT_PEND za pomocą wyrażenia if(inter_buff[2] & 0x20). Jeśli wynik porównania będzie prawdą wiemy, że przerwanie wygenerowało wysłanie ramki TX. A poniżej z dokumentacji API, zawartość GET_INT_STATUS





Nadawanie TX :

Przyjrzyjmy się prostemu przykładowi aby zobaczyć jak wygląda praktyczna realizacja nadawania.
Najpierw powołujemy do życia bufor nadawczy do którego będziemy wrzucać nasze dane do wysłania :

uint8_t send_buff[]="DATA";

Następnie tworzymy sobie wygodną funkcję do wysyłania danych jej postać poniżej :

//------------------------------- SI4463_Send_Data ------------------------------
void SI4463_Send_Data( uint8_t *send_dt, uint8_t size, uint8_t channel )
{
    // Red diode SENDING -> ON
    SI4463_nSEL = 0;                                  
   
            SI4463_Clear_TX_FIFO( );
            SI4463_TX_FIFO ( send_dt, size );
            SI4463_TX_Start( channel, size );
   
    SI4463_nSEL = 1;
    // Red diode SENDING -> OFF
}


Argumentami funkcji są : wskaźnik na bufor z naszymi danymi do wysłania, ile danych będziemy wysyłać i numer kanału na którym będzie realizowana transmisja, mamy do dyspozycji 255 kanałów radiowych. Wywołanie funkcji , która wyśle nam automatycznie dane wygląda przykładowo jak poniżej :

SI4463_Send_Data( send_buff, 4, 0 );

Funkcja wysyłająca dane wykonuje trzy operacje : czyści bufor FIFO TX znajdujący się w module RF, przepisuje nasze dane do bufora w module FIFO TX, inicjuje transmisję na wybranym kanale.

Odbiór TX :
Przyjrzyjmy się prostemu przykładowi aby zobaczyć jak wygląda praktyczna realizacja odbioru. Najpierw powołujemy do życia bufor odbiorczy do którego będziemy przepisywać dane z bufora FIFO modułu RF :

uint8_t receive_buff[64];

Kolejnym krokiem jest wprowadzenie modułu w tryb odbioru :
 
SI4463_RX_Start( 0, 0 );

pierwszy argument funkcji to numer kanału na którym moduł będzie nasłuchiwał a drugi parametr reprezentuje długość oczekiwanej ramki z danymi, jeśli podamy zero to odebrana zostanie ramka z danymi o długości zdefiniowanej w polu F1L i ani bajta więcej.

Następnie musimy śledzić stan pinu nIRQ, stan niski informuje nas, że zaszło zdarzenie , które zmieniło status w GET_INT_STATUS Trzeba zatem sprawdzić czy przypadkiem nie jest to zmiana w polu PACKET_RX_PEND (odczytana wartość 1) jeśli tak to wiemy, że nastąpił odbiór danych na wybranym kanale. Musimy się upewnić czy dane posiadają CRC bez błędów. Poinformuje nas o tym pole CRC_ERROR_PEND jeśli CRC dla danych jest poprawny czyli w polu tym odczytamy wartość 0 to możemy przepisywać dane z bufora FIFO modułu do naszego bufora użytkownika. Aby sprawdzić dostępne statusy po wykryciu stanu niskiego na pinie nIRQ musimy wywołać funkcję, która nam te statusy odczyta z modułu . A jest to funkcja SI4463_Get_Interrupt() argumentem funkcji jest bufor w którym znajdą się odczytane dane o wszystkich statusach w module.

Cała operacja przedstawiona jest w przykładowym kawałku kodu poniżej :

 if(!IO_nIRQ_GetValue()){ //nIRQ Low ?
            SI4463_Get_Interrupt(inter_buff); //get interrupt status and clear
            if(inter_buff[2] & 0x10) {//PACKET_SENT_PEND interruption , see API Documentation GET_INT_STATUS??
               if(!(inter_buff[2] & 0x08)) {//CRC data interruption Error ?)
                SI4463_RX_FIFO ( receive_buff, 4 );
                LED_TOG;
              }
            }
           SI4463_Clear_RX_FIFO();
           SI4463_RX_Start( 0, 0 ); 
           SI4463_Get_Interrupt(inter_buff); //get interrupt status and clear
    }


Ważna rzecz aby na końcu operacji związanych z odbiorem, wyczyścić bufor FIFO RX, ponowić RX START i wyzerować statusy za pomocą funkcji  SI4463_Get_Interrupt(), funkcja ta oprócz pobierania aktualnych statusów zeruje pin nIRQ.

Przykład wykorzystania API :

Warto pokazać jak się poruszać po API , które jest zaimplementowane w module. Jak to załapiemy to możemy sobie potem sami pisać własne funkcje w miarę potrzeb. Pokażę prosty schemat, który możemy sobie replikować na inne funkcję API. API podzielone zostało na komendy i właściwości (property). Komendy służą do wykonywania operacji a za pomocą właściwości dokonamy zmiany ustawień modułu. W dokumentacji API , która jest w postaci dokumentu html mamy wszystko ładnie i czytelnie poukładane. Weźmy sobie na widelec komendę PACKET_INFO (0x16) , jest to jedna z komend w sekcji RX COMANNDS


Jej szczegółowa postać poniżej :


Czytamy w oryginale , że komenda służy do :
Summary: Returns information about the length of the variable field in the last packet received, and (optionally) overrides field length.

Generalnie chodzi o to , że komenda ta może nam zwrócić  wartość przesyłaną w polu określającym długość danych odebranych w polu o zmiennej długości. Ale co to jest pole o zmiennej długości ? Przypomnijmy sobie strukturę ramki jaką ustawiliśmy w programie konfiguracyjnym WDS :


Pole o zmiennej długości reprezentuje na obrazku <F2>, zmienna długość bo fizycznie pole to w przesyłanej ramce zależy od ilości przesyłanej informacji, jeśli przesyłamy np 4 bajty danych to nie leci ramka 63 bajtowa (bo taką długość maksymalną zadeklarowaliśmy) w tym nasze 4 bajty z danymi ale leci tylko ramka o długości 4 bajtów. Czyli zmienia się długość przesyłanej fizycznie ramki w zależności od ilości przesyłanych danych. Stąd pole <F2> jest polem o zmiennej długości z naszymi danymi. Ponieważ moduł nie ma automatu do wyliczania długości danych przesyłanych w polu <F2> , stworzyliśmy sobie drugie pole oznaczone jako F1L o stałej długości 1 bajta. W polu F1L przesyłamy informację o ilości przesyłanych danych w polu <F2>. Za pomocą komendy PACKET_INFO w prosty sposób wyłuskamy dane zawarte w polu F1L po stronie odbiornika. Idea wykorzystania tej komendy polega na tym, że przed przepisaniem i pobraniem danych z bufora sprzętowego FIFO RX w procesie odbioru , pożądaną informacją jest długość odebranych danych. Na podstawie tej informacji wiemy ile danych mamy przepisać z bufora sprzętowego FIFO RX do  bufora odbiorczego użytkownika celem ich dalszej analizy lub prezentowania na wyświetlaczu. Tu jeszcze jedna uwaga, daną o długości w polu F1L po stronie nadajnika musimy sami wyliczyć i wpisać na pierwszą pozycję bufora nadawczego ale jest to trywialna operacja i nawet początkującemu nie powinna przysporzyć kłopotu. W moim przykładowym programie będzie pokazane jak to zrobić. Na wszelki wypadek fragment kodu , który to robi :

uint8_t data_buff[]="DATA";
uint8_t send_buff[64];

uint8_t i, len;
   

 len = sizeof(data_buff); //calculate the data length
 send_buff[0] = len;  //enter the data length on the first position of the transmit buffer
          
    for (i=1 ; i < len; i++){
       send_buff[i] =  data_buff[i-1]; //enter the data into the transmitting buffer starting from the second position
    }


Komendę PACKET_INFO musimy wysłać z 5-cioma argumentami od 0x01 do 0x05. Dla nas najważniejszym argumentem jest pole FIELD_NUMBER jego struktura poniżej :


Ponieważ chcemy aby komenda zwróciła nam dane znajdujące się w polu F1L, które jest polem numer 1 (pole <F2> jest nr 2) to argumentem jakim się posłużymy będzie wartość 0x01 (Override the programmed value of PKT_FIELD_1_LENGTH, or the value of RX_LEN in the START_RX command)
Reszta argumentów będzie miała wartość 0x0. Zatem postać bajtowa komendy PACKET_INFO wraz z argumentami powinna wyglądać tak :

0x16, 0x01, 0x00, 0x00, 0x00, 0x00

Czyli kod komendy PACKET_INFO- 0x16 i pięć argumentów. Pierwszy argument 0x01 informuje moduł , że ma nam zwrócić informację o zawartości pola nr 1 czyli F1L.  Powyższą postać bajtową przesyłamy do modułu RF a on w przypływie swojej inteligencji wie o co nam chodzi.  Dane zwracane przez moduł to dwa bajty (bajtu CTS nie uwzględniamy pomimo, że jest podany jako bajt nr  0x00 stream'u.) reprezentujące wartość przesyłaną w polu F1L. Teraz jak fizycznie będzie wyglądała nasza funkcja wysyłająca komendę PACKET_INFO i zwracająca zawartość pola F1L . Moja propozycja takiej funkcji wygląda następująco :

//------------------------------- SI4463_Get_Packet_Info ----------------------------
uint8_t SI4463_Get_Packet_Info( void )
{
    uint8_t tx_buff[] = { 0x16, 0x01, 0x00, 0x00, 0x00, 0x00 };
    uint8_t rx_buff[2];
    SI4463_Send_With_CTS( tx_buff, 6 );
    SI4463_Read_Buffor( rx_buff, 2 );
    return rx_buff[1];
}


Jak widzimy w funkcji tworzymy sobie dwa bufory. Bufor tx_buff[] zawiera postać bajtową komendy PACKET_INFO wysyłanej do modułu RF a do bufora rx_buff[2] trafi odpowiedź modułu czyli wartość pola F1L. W tym przypadku interesuje nas wartość młodszego bajtu czyli drugiego przesyłanego bajtu.
Funkcja SI4463_Send_With_CTS, realizuje nam fizycznie wysyłkę bufora tx_buff[] po SPI. Jej drugim argumentem jest ilość wysyłanych bajtów. Funkcja SI4463_Read_Buffor(), przechwytuje odpowiedź modułu na wysłaną komendę
PACKET_INFO. Jak możemy umiejscowić naszą funkcję w procesie odbioru pokazuje fragment kodu poniżej :

if(!IO_nIRQ_GetValue()){ //nIRQ Low ?
            SI4463_Get_Interrupt(inter_buff); //get interrupt status and clear
            if(inter_buff[2] & 0x10) {//PACKET_SENT_PEND interruption ??, see API Documentation GET_INT_STATUS ??
                if(!(inter_buff[2] & 0x08)) {//CRC data interruption Error ?)
               
                SI4463_RX_FIFO ( receive_buff, SI4463_Get_Packet_Info()  );
               
                LED_TOG;                       
              
              }
            }
           SI4463_Clear_RX_FIFO();
           SI4463_RX_Start( 0, 0 )
           SI4463_Get_Interrupt(inter_buff); //get interrupt status and clear
    }


Uśpienie i wybudzenie modułu : 

Poniżej tabelka z dostępnymi trybami jakie mamy w module :


Najbardziej optymalnym trybem z punktu widzenia poboru prądu i czasu wybudzenia jest tryb  Standby State. Pobór prądu w tym trybie jest na poziomie 50 nA. i czas wybudzenia do pełnej aktywności 440 uS. Porównajmy te parametry do popularnego na naszym rynku modułu nRF24L01+, który w najniższym trybie pobiera 900 nA z czasem wybudzenia 1.5 ms . Z tego porównania wynika jasno , że do zastosowań bateryjnych moduł SI4463 bije na głowę nRF24L01+.

Aby wprowadzić moduł w tryb Standby State należy użyć komendy CHANGE STATE (kod 0x34). Aby wyprowadzić moduł z trybu uśpienia musimy wysłać cokolwiek po SPI aby zadziałał event SPI po stronie modułu. Zaistnienie eventu wykryjemy pinem nIRQ i sprawdzeniem statusu CHANGE STATUS PEND za pomocą komendy GET INT STATUS. Od momentu zaistnienia eventu SPI i wykrycia go , możemy wprowadzać moduł w dowolny tryb TX lub RX. Wygląda na to, że usypianie i wybudzanie w trybie Standby State jest relatywnie bardzo proste. Możemy oczywiście posłużyć się automatycznym "wubudzaczem" WUT i zegarkiem RC 32 kHz ale ja myślę, że to MCU powinien tutaj nadzorować wybudzanie i sterować całym kramem. Pomysł wybudzania za pomoca SPI jest fajny. Celowo nie wspominam o pinie SDN i trybie głębokiego uśpienia gdzie uzyskamy 30 nA poboru prądu bo czas wybudzenia z tego trybu na poziomie 15 ms jest słabo akceptowalny szczególnie w zastosowaniach sieciowych gdzie pracuje więcej modułów i chcemy to spiąć za pomocą synchronizacji czasowej.


No dobrze , mamy część teoretyczną za sobą czas przejść wreszcie do konkretów . Czyli jak tę naszą teoretyczną wiedzę zamienić w praktyczną :) Czyli czas na pisanie jakiegoś kompletnego softu, który będzie rozmawiał z naszym modułem. Sam nie mogę doczekać się efektów czyli pierwszej udanej komunikacji i wymiany danych.

Program testowy mam już zrobiony wszystko działa jak malina, pierwsze bojowe testy mam za sobą. Test jaki wykonuję jest prosty jak budowa cepa . Nadaję w jednym module 100 oddzielnych pakietów w odstępach czasowych 200ms . Moduł odbierający sprawdza poprawność sumy CRC (automatycznie bez naszej ingerencji, sprawdzamy tylko status tej operacji) odebranej informacji, jeśli jest OK to zaliczamy pakiet jako poprawny i inkrementujemy licznik poprawnie odebranych pakietów. Liczbę poprawnie odebranych pakietów wyświetlamy na LCD. Dane przesyłam z prędkością 10 kbps, danymi fizycznie jest string "DATA". Antena 7 cm ok 1.5 dB zysku. Moc +20dB. Modulacja 2GFSK. Nośna 433 MHz. Starałem się zoptymalizować parametry toru radiowego w/g swojej najlepszej wiedzy. Efekty znacznie przerosły moje oczekiwania.

UWAGA !!!!. Jestem pod ogromnym wrażeniem pierwszych testów modułu SI4463. W dużym piętrowym i podpiwniczonym obiekcie ok 400 m2  uzyskałem pełne pokrycie w najbardziej odległych zakamarkach obiektu. Pakiety przebiły się bez najmniejszego problemu przez dwa solidne żelbetowe stropy półmetrowe i 4 ściany po 30 cm każda. Ani jeden pakiet nie został utracony !!!!!! na 100 przesłanych w kilku próbach.  

Na badanym kiedyś module SI4432 nie uzyskałem takich spektakularnych wyników. Poprawa toru transmisyjnego w SI4463 i dodanie lepszych modulacji typu 2GFSK i 4GFSK robi swoje. Już po tych pierwszych prostych testach mogę nie tylko polecić te moduły ale z całą odpowiedzialnością "cholernie" je polecić :). Moim zdaniem można z tych modułów naprawdę sporo wycisnąć , szczególnie jeśli dodamy do nich jakąś minimalną nawet wiedzę z zakresu toru radiowego i jego parametrów. Bardzo wygodne narzędzie WDS do tworzenia konfiguracji i generowania gotowego pliku nagłówkowego, który dołączamy do naszego projektu dopełnia bardzo pozytywny obraz całości. To co mi się bardzo podoba to duża elastyka w konfigurowaniu ramki. Mamy do dyspozycji pięć pól , które możemy dowolnie modelować jako stało bajtowe lub o zmiennej długości. Każde z pól może mieć swoją oddzielną sumę CRC. Jeśli chcemy stworzyć sieć to warto sobie stworzyć pole adresowe jako pierwsze pole ramki. Podstawą działania takiej sieci powinna być synchronizacja czasowa czyli każdy moduł komunikuje się np z Masterem w wyznaczonym tunelu czasowym a uśpienie modułu może nastąpić dopiero po odebraniu ramki z synchronizacją czasu i parametrem tunelu czasowego. Możemy sobie zaimplementować jakiś pomysł na potwierdzenia wysyłanych danych to nie są trudne zagadnienia ale bardzo ciekawe i inspirujące. Warto też zauważyć, że moduł ma tryb w którym możemy rozszerzyć maksymalny obszar na dane z 64 bajtów do 128 bajtów !!!!! i tyle danych w jednej ramce przesłać.

Muszę jednak wspomnieć o trudnościach na jakie natrafiłem na drodze do uruchomienia modułu. O ile sam proces konfiguracji był prosty i nawet fajny o tyle dojście do prawidłowej inicjalizacji modułu bez użycia "patchowania" skradł mi 3 dni  życia. Bez korekty manualnej drugiego bajtu w komendzie Power Up z 0x81 na 0x01 nie uruchomimy modułu. Na tym zagadnieniu polegnie prawdopodobnie nie jeden człek jeśli nie znajdzie w sobie dużej woli walki i determinacji :) Jeśli jednak przebrniemy przez ten trud to moduł nas oczaruje swoimi możliwościami.

Należy jeszcze zauważyć, że Silicon Labs ma bardzo dobrą dokumentację i z przyjemnością się ją czyta .

W linkach poniżej znajduje się projekt , który wgrałem na GitHub. Sciągamy go do MPLABX-IDE za pomocą Team --> Remote --> Clone lub w konsoli Linuxa :

git clone https://github.com/PICmajsterPIC32MM_SI4463_RF_module.git

W jednym projekcie znajduje się wsad dla modułu nadającego TX i modułu odbierającego RX. Projekt na starcie jest ustawiony dla modułu odbierającego dane RX. Aby przygotować projekt dla strony nadającej TX musimy w pliku main.c zakomentować wiersze z opisem //for RX i blok /*RX*/  /*end RX*/ a odkomentować wiersze z opisem //for TX i blok /*TX*/  /*end TX*/
W katalogu WDS znajduje się plik z którego odtworzymy konfigurację w tym programie oraz plik wygenerowany przez WDS i dodatkowo plik w którym są dane poszczególnych pól rejestrów.

Co robi fragment kodu dla modułu nadającego TX ? 

Moduł pracujący w trybie nadawnia TX. Przesyła 100 ramek z danymi i wyłącza swoją aktywność. Na wyświetlaczu zobaczymy dekrementowany do zera licznik.

Na początku tworzymy dwa bufory  data_buff[] i send_buff[]. W pierwszym umieszczamy string "DATA" i traktujemy to jako nasze dane, które chcemy przesłać drogą radiowa do modułu odbierającego. Drugi bufor posłuży nam jako kontener na formowanie danych i danych technicznych (długość pakietu danych) w jeden spójny pakiet. Na pierwszym bajcie bufora send_buff[] będzie dana techniczna czyli infomacja o długości pakietu danych. Kolejne bajty tego bufora to nasze dane czyli string "DATA". I to wszystko jeśli chodzi o bufory potrzebne dla strony nadającej.
Zmienna licznik będzie dekrementowana w dół po każdej prawidłowo wysłanej ramce.
W pierwszym bloku /*TX*/  /*end TX*/ wyliczamy za pomocą bibliotecznej funkcji sizeof() ilość bajtów z naszymi danymi jakie znajdują się w data_buff[]. Może zdziwić , że wynikiem tej operacji jest wartość 5 a nie 4 bo przecież nasz string "DATA" optycznie składa się z 4 bajtów . To nie pomyłka ale taka specyfika wpisywania stringów do tablic, ostatni bajt jest zawsze bajtem zerowym. Gdybyśmy wpisywali jeden znak w apostrofie 'A' wtedy tego  dodatkowego bajtu na końcu nie zobaczymy.
Wyliczona długość danych jest wpisywana do bufora send_buff[] na pierwszym miejscu send_buff[0] = len; W kolejnym etapie przepisujemy dane z bufora data_buff[] do send_buff[] pamietając , że pierwszy bajt bufora send_buff[] zajęty jest przez daną techniczną (długość pakietu danych).

Wchodzimy do pętli głównej i skupiamy uwagę na to co jest w bloku  /*TX*/  /*end TX*/ . Ponieważ założyłem sobie, że kolejna transmisja  ze 100 przesyłanych będzie miała miejsce co 200 ms dlatego powołałem timer programowy bazujący na timerze sprzętowym tykającym co 25 ms. Co 200 ms ustawiana jest flaga Soft_Timer1 i spełniony warunek pierwszego if-a. Wykonywana jest opreacja wysyłania danych za pomocą jednej funkcji :

SI4463_Send_Data( send_buff, sizeof(data_buff)+1 , 0 ) ;

Argumentami tej funkcji są : wskaźnik na bufor do wysłania, rozmiar bufora z danymi plus 1 i numer kanału radiowego na którym będą wysłane dane. Proste jak świński ogon. Zamiast ponownie używać funkcji sizeof() można dać tam zmienną len wcześniej wyliczoną i zwiekszyć ją o 1. Dodanie 1 bo len = 5 a musimy pamietać o danej technicznej na pierwszym bajcie bufora send_buff[] . Dlaczego nie przyłożyłem funkcji sizeof() do bufora send_buff[], dlatego, że otrzymany wynik byłby 64 czyli tyle ile zadeklarowaliśmy w tej tablicy. Jeśli ktoś uważa , że przynudzam bo takie trywialne aspekty poruszam to niech sobie walnie głową w ścianę i przypomni jak sam był zerem parafrazując hasło "od zera do developera" :). Moją intencją jest aby to co piszę trafiało do ludzi , którzy są na poczatku swojej przygody z językiem C i mikrokontrolerami. Bo na co moja pisanina osobą zaawansowanym.

W drugim if-ie dla sekcji TX sprawdzamy czy moduł RF nie ustawił pinu nIRQ w stan niski. Jeśli tak to przechodzimy do odczytu statusów za pomocą wywołania funkcji SI4463_Get_Interrupt(inter_buff); argumentem tej funkcji jest bufor powołany do życia w pliku si4463.c i zadeklarowany w pliku si4463.h. W buforze tym znajdzie się strumień 8 bajtów jako odpowiedź modułu na zapytanie o aktualne statusy. W kolejnym if-e sprawdzamy czy wśród statusów odebranych z modułu jest status pozytywnie wysłanej ramki PACKET_SENT_PEND jeśli tak to dekrementujemy w kolejnym if-e zmienną licznik i sprawdzamy jednocześnie czy nie jest zerem jeśli tak to zatrzymujemy Timer i kończymy wysyłanie ramek.
Wyświetlamy zmienną licznik i mrugamy diodą LED.

Starałem się w miarę prosto napisać ten soft tak aby był zrozumiały dla przeciętnego zjadacza chleba. 

Co robi fragment kodu dla modułu nadającego RX ?


Moduł pracujący w trybie odbioru RX czeka na przesłane ramki i wyświetla licznik prawidłowo odebranych danych i postać danych.

Tutaj powołujemy do życia bufor odbiorczy receive_buff[] do którego w procesie odbioru przepiszemy dane z bufora sprzętowego modułu FIFO RX. Zmienna licznik startuje z wartością 0, tą zmienną będziemy inkrementować po każdej poprawnie odebranej ramce. Poprawnie tzn z poprawną suma kontrolną CRC pola F1L i <F2>. O tym czy mamy poprawną sumę kontrolną w odebranej ramce ponformuje nas status PACKET RX PEND.
Przed wejściem w pętlę while() musimy wprowadzioć moduł w tryb odbioru RX. Robimy to wywołując funkcję SI4463_RX_Start( 0, 0 ); pierwszy argument dotyczy numeru kanału radiowego na którym będzie moduł nasłuchiwał mamy 255 kanałów tak dla przypomnienia. Drugi argument dotyczy długości odebranego pakietu , przy ustawieniu 0 otrzymamy tyle danych na ile wskazuje pole F1L. Wchodzimy do pętli while() do bloku pomiędzy /*RX*/  /*end RX*/
Pierwszą czynnością jest sprawdzenie czy na pinie nIRQ nie wystąpił stan niski, który sygnalizuje zaistnienie jakiegoś zdarzenia(eventu). Od strony MCU monitorujemy stan na pinie RB9. Jeśli zostanie wykryty stan niski pobieramy do bufora inter_buf 8 bajtów statusowych z modułu RF. Robimy to wywołując funkcję SI4463_Get_Interrupt(). Wsród pobranych bajtów statusowych sprawdzamy bit PACKET_RX_PEND , który znajduje się na drugim bajcie statusowym. Bit ten monitoruje status odebranej ramki, jesli jest poprawnie odebrana to na bicie tym ustawiona zostanie wartość 1. Sprawdzenia stanu bitu robimy za pomocą wyrażenia z maską : inter_buff[2] & 0x10.
Jeśli status odebranej ramki poinformuje nas , że ramka została odebrana przechodzimy do sprawdzenia czy odebrana ramka ma poprawną sumę kontrolną CRC pola F1L i <F2>. Robimy to fizycznie za pomocą wyrażenia !(inter_buff[2] & 0x08). Tym razem sprawdzamy czy bit statusowy CRC_ERROR_PEND ma wartość 0 bo tylko taka wartość sygnalizuje poprawne CRC. Jeśli CRC mamy poprawne przechodzimy do operacji przepisania odebranych danych z bufora sprzętowego FIFO RX modułu do bufora użytkownika receive_buff[]. Całą operację przepisania realizujemy funkcją :

SI4463_RX_FIFO ( receive_buff, SI4463_Get_Packet_Info()  ); 

Ilość danych do przepisania wyłuskuje nam z pola F1L funkcja SI4463_Get_Packet_Info(). Dana techniczna czyli ilość przesyłanych bajtów jest automatycznie pomijana. Następnie inkrementujemy zmienną licznik i wyświetlamy ją na LCD. Ponadto wyświetlamy zawartośc bufora użytkownika receive_buff[]  w którym mamy naszego stringa "DATA"

Wszystko działa bez zająknięcia. Jedyne co pozostaje to zmniejszyć moc nadawania i znaleźć jej optimum dla indwywidualnych potrzeb. Pełna moc +20dB jest typowo jak wynika z moich testów za duża , no chyba że chcemy łączyć obiekty oddalone o 2 km

Moduł moim zdaniem jest the best i warto poświęcić mu czas.



Linki :

SI4463 - Datasheet
SI4463 - Radio Testing 
WDS - Wireless Development Suite
WDS - User Guide
Programming Guide
SI4463 - Packet Handler
SI4463 API rev C2
Projekt GitHub
 

Brak komentarzy:

Prześlij komentarz