fbpx

W poprzednim wpisie pokazałem Ci jak w najprostszy sposób skomunikować ze sobą dwa Nucleo przy pomocy nRF24L01+. Do wysyłania oraz odbierania danych wykorzystałem najprostszą metodę, czyli polling. O ile przy sprawdzeniu, czy coś przyszło nie robiło to za wiele złego, bo czytałem tylko jeden rejestr w układzie, tak przy wysyłce próżno czekałem aż transfer się zakończy. W dodatku wysyłane i odbierane były pakiety o stałej długości danych. Tym razem pokażę Ci jak wysyłać dane różnej długości oraz jak odbierać komunikaty w trybie przerwaniowym

Przypominam Ci również, że moduły takie możesz kupić bezpośrednio u mnie jednocześnie wspierając moją działalność.

Cała seria o nRF24L01+:

Dynamic Payload

W poprzedniej części wspomniałem o najważniejszych właściwościach układów nRF24L01+. Jednym z bajerów, jakie te układy oferują jest dynamiczny Payload. Payload to jest ta część transmitowanej ramki, w której znajdują się nasze dane, które wysyłamy/odbieramy. To jest to, na co mamy nasz wpływ, bo resztą budowy i dekodowania ramki zajmuje się już sam układ.

Aby włączyć dynamiczny Payload, wystarczy ustawić bit EN_DPL w rejestrze FEATURE, aby uruchomić sam ficzer oraz włączyć ten ficzer w rejestrze DYNPD dla kanału w którym chcemy go używać np. Pipe 0.

W mojej bibliotece zrobiłem stałą NRF24_DYNAMIC_PAYLOAD, która decyduje czy włączyć dynamic Payload przy inicjalizacji. Włącza akurat dla wszystkich Pipe’ów. Biblioteka również będzie odpowiednio się zachowywała.

Gdy już masz włączony Dynamic Payload pozostaje już tylko go używać.

Po stronie nadającej nie trzeba nic specjalnego robić. Wystarczy wpisać wszystkie dane do nRFa. Układ sam policzy ile ma bajtów do wysłania i nada pakiet.

Po stronie odbiorczej układ też sobie policzy ile tych danych w Payload przyszło, ale tutaj już musisz odczytać z układu ile jest dostępnych bajtów w kolejce FIFO odbiornika. Przecież trzeba skądś tę informację wziąć.

Odczytuje się to komendą R_RX_PL_WID. W odpowiedzi układ podaje ile bajtów ma pierwsza w kolejce wiadomość. Teraz wystarczy odczytać znaną ilość bajtów komendą R_RX_PAYLOAD.

Proste prawda? Tak prezentują się funkcje które to wykonują.

void nRF24_WriteTXPayload(uint8_t * data, uint8_t size)
{
    nRF24_WriteRegisters(NRF24_CMD_W_TX_PAYLOAD, data, size);
}

uint8_t nRF24_GetDynamicPayloadSize(void)
{
    uint8_t result = 0;

    result = nRF24_ReadRegister(NRF24_CMD_R_RX_PL_WID);

    if (result > 32) // Something went wrong :)
    {
        nRF24_FlushRX();
        nRF24_Delay_ms(2);
        return 0;
    }
    return result;
}

void nRF24_ReadRXPaylaod(uint8_t *data, uint8_t *size)
{
    *size = nRF24_GetDynamicPayloadSize();
    nRF24_ReadRegisters(NRF24_CMD_R_RX_PAYLOAD, data, *size);

    nRF24_WriteRegister(NRF24_STATUS, (1<NRF24_RX_DR));
    if(nRF24_ReadStatus() & (1<<NRF24_TX_DS))
        nRF24_WriteRegister(NRF24_STATUS, (1<<NRF24_TX_DS));
}

Teraz jestem w stanie przesyłać wiadomości różnej długości. Wygląda to mniej więcej tak.

Przerwanie od odebrania ramki

Najpierw omówię to, w jaki sposób poradziłem sobie z przerwaniem od odbioru. Poniekąd już wykorzystałem to przerwanie w trybie pollingu. W jaki sposób? Otóż “włączenie” przerwań włącza jedynie ich odzwierciedlenie na pinie IRQ. Rejestry przy “wyłączonym” przerwaniu nadal zachowują się w taki sposób, że zapalane są odpowiednie bity w rejestrze statusowym. Stąd chcąc dowiedzieć się, czy coś przyszło, czytałem rejestr STATUS, który to zawiera informacje o przerwaniach i sprawdzałem, czy jest zapalony bit RX_DR, czyli Receiver Data Ready.

Takie ciągłe odpytywanie marnuje lekko moc mikrokontrolera. Lekko, bo to tylko jeden rejestr, ale zawsze to chwilę trwa przy transferze I²C czy SPI. Lepiej sprawdzać np. flagę w RAM, która powie nam czy przerwanie wystąpiło w ostatnim czasie.

Ustawiając bit RX_DR w rejestrze CONFIG spowoduję, że na pinie IRQ pojawi się stan aktywny (niski) za każdym razem, gdy będzie zapalony bit RX_DR w rejestrze STATUS. Oznacza to tyle, że rejestr ten odczytam tylko i wyłącznie wtedy, gdy będzie tam jakaś cenna dla mnie informacja. W tej chwili będzie to gotowość danych do odczytu po odebraniu.

Napisałem krótką obsługę przerwania, która zapala odpowiednie flagi w bibliotece w zależności od tego co się stało.

void nRF24_IRQ_Handler(void)
{
	uint8_t status = nRF24_ReadStatus();
	uint8_t ClearIrq = 0;
	// RX FIFO Interrupt
	if ((status & (1 << NRF24_RX_DR)))
	{
		nrf24_rx_flag = 1;
		ClearIrq |= (1<<NRF24_RX_DR); // Interrupt flag clear

	}
	// TX Data Sent interrupt
	if ((status & (1 << NRF24_TX_DS)))
	{
		nrf24_tx_flag = 1;
		ClearIrq |= (1<<NRF24_TX_DS); // Interrupt flag clear
	}
	// Max Retransmits interrupt
	if ((status & (1 << NRF24_MAX_RT)))
	{
		nrf24_mr_flag = 1;
		ClearIrq |= (1<<NRF24_MAX_RT); // Interrupt flag clear
	}

	nRF24_WriteStatus(ClearIrq);
}

Flagi te później odpowiednio w kodzie obsługuję. Dzieje się to już poza przerwaniem, bo przerwania z reguły nie mogą trwać za długo.

Do obsługi przerwań napisałem funkcję eventową oraz odpowiednie callbacki na wzór tych z HALa.

__weak void nRF24_EventRxCallback(void)
{

}

__weak void nRF24_EventTxCallback(void)
{

}

__weak void nRF24_EventMrCallback(void)
{

}

void nRF24_Event(void)
{
	if(nrf24_rx_flag)
	{
		nRF24_EventRxCallback();
		nrf24_rx_flag = 0;
	}

	if(nrf24_tx_flag)
	{
		nRF24_EventTxCallback();
		nrf24_tx_flag = 0;
	}

	if(nrf24_mr_flag)
	{
		nRF24_EventMrCallback();
		nrf24_mr_flag = 0;
	}
}

Jak widzisz callbacki domyślnie są puste oraz mają sybol __weak. Należy je nadpisać w swoim programie, co też zrobiłem w pliku main.c

Funkcję nRF24_Event wrzucasz do pętli głównej programu. Zdarzenia, czyli odpowiednie callbacki w środku wykonają się, tylko jeśli któraś z flag odpowiadających przerwaniom zostanie zapalona.

Pamiętaj też o włączeniu przerwania EXTI_GPIO na pinie IRQ oraz ustawienia callbacka tego przerwania!

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(Pin == NRF24_IRQ_Pin)
	{
	
		nRF24_IRQ_Handler();
	}
}

Odczyt po przerwaniu

Ok wystąpiło przerwanie, zapaliła się flaga od odbioru i wywołał się callback. Co w takim callbacku zrobić? Jeśli jest to callback od odebrania danych to… odebrać te dane 🙂

Napisałem w main.c taką funkcję nadpisującą.

void nRF24_EventRxCallback(void)
{
	do
	{
		nRF24_ReceivePacket(Message, &MessageLength);
		Message[MessageLength] = 0; // end of string
		MessageLength = sprintf(Message, "%s\n\r", Message);
		HAL_UART_Transmit(&huart2, Message, MessageLength, 1000);
	}while(!nRF24_IsRxEmpty());
}

Co tutaj robię? Odbieram dane przez nRF24_ReceivePacket dopóki te dane są dostępne. O dostępności danych mówi nam funkcja nRF24_IsRxEmpty().

W przykładzie dane te po prostu printuję na UART, ale w Twoim programie może to trafić np. na bufor kołowy, a w evencie od bufora zostać odpowiednio przeparsowane. Tutaj masz dowolność, jednak musisz zachować odbiór i sprawdzenie czy jest już FIFO puste.

Pamiętaj też, że funkcja nRF24_ReceivePacket zwraca przez drugi argument ile danych zostało pobranych z nRFa (Dynamic Payload).

Podsumowanie

Tak wyglądałaby obsługa Dynamic Payload oraz przerwania od odbioru. Nadal wysyłane dane są blokujące, bo biblioteka aktywnie czeka na transmisję odpytując układ czy już się zakończyło. Do tego też przydałoby się przerwanie, ale podejdę do tego nieco inaczej.

W kolejnym artykule dołożę bufory kołowe dla nadawania i odbierania, które milusio wykorzystam w nieblokującej obsłudze nadawania.

Cała seria:

Jeśli artykuł Ci się spodobał, możesz mnie wesprzeć kupując coś u mnie 🙂 https://sklep.msalamon.pl/

Pełny projekt wraz z biblioteką znajdziesz jak zwykle na moim GitHubie: NADAJNIK, ODBIORNIK

Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś, chciałbyś coś dodać istotnego lub po prostu uważasz, że chciałbyś podyskutować na ten temat, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.

5/5 - (6 votes)

Podobne artykuły

.

7 komentarzy

dambo · 15/06/2020 o 22:17

Aj – nie miałem powiadomień o odp i dopiero teraz odpisuję.

Co do zabezpieczenia SPI – ja to zawsze przerzucam na RTOSa – omutexowany dostęp do tego typu magistral lub nawet dedykowany task do komunikacji. W tym przypadku możesz albo komunikację przerzucić na “poziom maina” i tam ją obsługiwać zamiast równolegle tu i tu, albo jakoś taki specjalny przypadek oznaczyć i finalnie i tak to przerzucić na poziom maina – bo przecież skoro coś już zaczęło komunikację to raczej musi to dokonczyć – bo równie dobrze z tego samego SPI może też korzystać biblioteka od jeszcze innych modułów.

Co do wydzielania bibliotek itp – daj mi z 2 miesiące na reorganizację bloga i to będzie jeden z pierwszych tematów to zaspamuje wtedy tu 😛

    Mateusz Salamon · 15/06/2020 o 22:20

    Właśnie w kolejnej iteracji z użyciem buforów kołowych całą komunikację robię już w mainie. Przerwanie ma tylko zapalić odpowiednią flagę, że wystąpiło a dekodowanie go jest już w “wolnej chwili”.

    Oooo przebudowujesz swojego bloga? Super! Nie mogę się doczekać w takim razie 🙂 Koniecznie daj znać jak skończysz.

      dambo · 15/06/2020 o 22:27

      no dokładnie – w ten sposób jesteś “nieinwazyjny” dla reszty rzeczy, które mogłyby korzystać z tego samego peryferium. A weź to potem odtwórz i zdebuguj – małe udręczki typowe dla embedded.

      Co do bloga – może nie “przebudowuje” – stary sobie zostaje dla potomnych już i tak wygasł – czas na jakiś świeży start 🙂 Treści się zbierały z notatek w czasie pracy jakby “w tle” – trzeba to jakoś poubierać dla ludzi i dla siebie jako ładny notatnik.

dambo · 28/05/2020 o 20:07

To ja trochę ponarzekam 🙂
Nie do końca się zgadzam z ideą “nRF24_EventRxCallback” – moim zdaniem jest tam za dużo logiki odnośnie NRFa, o której użytkownik nie powinien wiedzieć -> IMO lepszym miejscem na callback dla użytkownika byłoby samo przekazanie pointera na odebrane właśnie dane do wykorzystania i wtedy w takim callbacku w twoim przypadku znalazłaby się sama funkcja formatująca i przesyłająca na uart.
Inna kwestia – w przerwaniu komunikujesz się z układem, ale też z poziomu funkcji main – nie masz zabezpieczenia przed podwójnym dostępem do SPI – co jak oba “poziomy” jednocześnie będą chciały korzystać z SPI? Nie weryfikujesz HAL_SPI_Transmit itp – zakładasz, że to się zawsze uda.

    dambo · 28/05/2020 o 20:10

    Aj jeszcze jedno miałem dodać – czemu na gicie masz to jako całe projekty – zamiast wydzielić całkiem osobno część “użyteczną” jako bibliotekę i ewentualnie dać w niej exampla, lub całkiem osobny repa i to ładnie podlinkować z poziomu GITa? Teraz inni muszą pobrać twoje repo i powycinać ze środka bibliotekę.

      Mateusz Salamon · 28/05/2020 o 20:39

      Tu odpowiedź mam całkiem prostą. Nie potrafię 🙂 W sensie nie wiem jak wydzielić dla gita samą libkę, aby fajnie to na GutHubie zaaranżować. Dlatego wrzucam cały projekt 🙁 Gdybyś miał do tego jakieś materiały to się douczę i poprawię.

    Mateusz Salamon · 28/05/2020 o 20:37

    No w końcu ktoś narzeka 🙂 Dobrze. Przyznam Ci rację, że jest tam za dużo logiki! Bezsensownie trzeba o tym w tej chwili pamiętać. Jeszcze nie skończyłem pisać o nRFie i tak samo nie skończyłem rozwijać biblioteki. Chcę to upchać w bufory kołowe i dać użytkownikowi tylko je. Zobaczymy jak mi to wyjdzie 🙂
    Co do SPI. Tu też masz rację i chybao tym zapomniałem pisząc kod. Generalnie to jak SPI będzie HAL_BUSY w trakcie przerwania to nie wykona się i nie zostanie skasowane w nRF. Coś o tym pomyślę, dzięki. Może masz jakiś fajny pomysł?

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *