środa, 8 sierpnia 2018

[38] STM32F4 - NRF24L01+

W tym poście chciałbym przedstawić program pozwalający na komunikację między dwoma układami nRF24L01+.

Znalezione obrazy dla zapytania nRF24L01+
[Źródło: https://www.gotronik.pl/nrf24l01-modul-wireless-do-komunikacji-bezprzewodowej-24ghz-wifi-p-1563.html]

Charakterystyka układu:


nRF24L01+ jest to moduł radiowy pełniący funkcję nadajnika jak i odbiornika. Działa w paśmie 2.4GHz. Komunikacja odbywa się poprzez interfejs SPI. Zasięg deklarowany przez producenta wynosi 100m i jest on zależny od poziomu wzmocnienia, a dokładniej jego redukcji. 

Układ przy wzmocnieniu 0dB pozwala na osiągnięcie zasięgu rzędu 30m, na otwarty terenie. Ponoć w idealnych warunkach można osiągnąć dystans wynoszący 100m, mi nie udało się uzyskać takich rezultatów (mowa tutaj o układzie z wbudowaną anteną na płytce PCB). Wraz ze zmniejszaniem wzmocnienia zasięg komunikacji ulega zmniejszeniu do kilku metrów.

Aby uzyskać duże zasięgi transmisji należy zakupić moduł w wersji z dużą anteną. Jest on trochę droższy natomiast zasięg będzie znacznie lepszy.

Zaletą tego modułu są małe rozmiary oraz łatwy montaż.

Opis wyprowadzeń:

  • GND - masa
  • VCC - zasilanie musi znajdować się pomiędzy 3.3V do 3.7V. 
  • CE - ta linia pozwala na przełączanie układu ze stanu spoczynku, odbioru czy transmisji danych. Wykorzystywanie pinu z zmiany układu z wysokiego na niski.
  • CSN - wybór układu dla komunikacji po SPI.
  • SCK - zegar linii SPI
  • MOSI - linia przesyłania danych z układu Master do Slave
  • MISO - linia przesyłania danych z układu Slave do Master
  • IRQ - generowanie przerwania. Można ustawić jego wywoływanie gdy dane zostaną otrzymane, bądź transmisji danych.

Opis biblioteki:


Poniżej przejdę przez opisanie wszystkich funkcji udostępnionych w bibliotece.

W pliku nagłówkowym znajdują się potrzebne ustawienia dotyczące wybranego układu SPI, przypisanych mu pinów, przypisania pinów CE oraz CSN oraz uruchomienia opcji odczytu przerwań z układu.

Konfiguracja pinów oraz CS oraz CE:

  1. static void nrf24l01_InitPins(void)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.     NRF24L01_CS_CLOCK();
  5.     NRF24L01_CE_CLOCK();
  6.     HAL_GPIO_WritePin(NRF24L01_CE_PORT, NRF24L01_CE_PIN, GPIO_PIN_RESET);
  7.     HAL_GPIO_WritePin(NRF24L01_CS_PORT, NRF24L01_CS_PIN, GPIO_PIN_RESET);
  8.     /* CS Pin configuration */
  9.     GPIO_InitStruct.Pin = NRF24L01_CE_PIN;
  10.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  11.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  12.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  13.     HAL_GPIO_Init(NRF24L01_CE_PORT, &GPIO_InitStruct);
  14.     /* CE Pin configuration */
  15.     GPIO_InitStruct.Pin = NRF24L01_CS_PIN;
  16.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  17.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  18.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  19.     HAL_GPIO_Init(NRF24L01_CS_PORT, &GPIO_InitStruct);
  20.     NRF24L01_CS_OFF();
  21.     NRF24L01_CE_LOW();
  22. }

Inicjalizacja SPI:

  1. static void nrf24l01_InitSpi(void)
  2. {
  3.     hspi3.Instance = SPI3;
  4.     hspi3.Init.Mode = SPI_MODE_MASTER;
  5.     hspi3.Init.Direction = SPI_DIRECTION_2LINES;
  6.     hspi3.Init.DataSize = SPI_DATASIZE_8BIT;
  7.     hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
  8.     hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
  9.     hspi3.Init.NSS = SPI_NSS_SOFT;
  10.     hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
  11.     hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  12.     hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  13.     hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  14.     hspi3.Init.CRCPolynomial = 10;
  15.     if (HAL_SPI_Init(&hspi3) != HAL_OK)
  16.     {
  17.       _Error_Handler(__FILE__, __LINE__);
  18.     }
  19. }


Ustawienie parametrów w układzie nrf24l01:

  1. uint8_t Nrf24l01_Initialize(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t channel, uint8_t payloadSize)
  2. {
  3.     nrf24l01_InitPins();
  4.     nrf24l01_InitSpi();
  5.     nrf24l01_InitInterruptPin();
  6.  
  7.     payloadSize = checkThenSetPayloadSize(payloadSize);
  8.  
  9.     setDataInStructure(nrf24l01_t, channel, payloadSize);
  10.  
  11.     nrf24l01_SoftwareReset();
  12.  
  13.     nrf24l01_SetChannel(nrf24l01_t, channel);
  14.  
  15.     setPipelineValueToMax(nrf24l01_t);
  16.  
  17.     Nrf24l01_SetRF(nrf24l01_t, nrf24l01_t->DataRate, nrf24l01_t->OutPwr);
  18.  
  19.     nrf24l01_WriteReg(NRF24L01_REG_CONFIG, NRF24L01_CONFIG);
  20.     nrf24l01_t->ValueConfigRegister = NRF24L01_CONFIG;
  21.  
  22.     nrf24l01_WriteReg(NRF24L01_REG_EN_AA, NRF24L01_ENABLE_AUTO_ACK_ALL_PIPES);
  23.     nrf24l01_t->ValueRegEnAA = NRF24L01_ENABLE_AUTO_ACK_ALL_PIPES;
  24.  
  25.     nrf24l01_WriteReg(NRF24L01_REG_EN_RXADDR, NRF24L01_ENABLE_RX_ADDR);
  26.     nrf24l01_t->ValueRegEnRxAddr = NRF24L01_ENABLE_RX_ADDR;
  27.  
  28.     nrf24l01_WriteReg(NRF24L01_REG_SETUP_RETR, NRF24L01_SET_RETRA_DEL);
  29.  
  30.     dynamicLengthConfiguration();
  31.  
  32.     nrf24l01_FlushRx();
  33.     nrf24l01_FlushTx();
  34.  
  35.     nrf24l01_WriteReg(NRF24L01_REG_STATUS, NRF24L01_CLEAR_INTERRUPTS);
  36.  
  37.     Nrf24l01_PowerUpRx();
  38.  
  39.     return 0;
  40. }

Funkcja odczytująca konfigurację układu:

  1. uint8_t Nrf24l01_ReadSettings(nrf24l01_DataStruct_t *nrf24l01_t)
  2. {
  3.     uint8_t readConfiguration[5] = {0x00};
  4.     uint8_t dataTXAddress[5];
  5.     uint8_t dataRXAddress[5];
  6.     uint8_t checkStatVal = 0x00;
  7.     readConfiguration[0] = nrf24l01_ReadRegister(NRF24L01_REG_CONFIG);
  8.     HAL_Delay(1);
  9.     readConfiguration[1] = nrf24l01_ReadRegister(NRF24L01_REG_EN_AA);
  10.     HAL_Delay(1);
  11.     readConfiguration[2] = nrf24l01_ReadRegister(NRF24L01_REG_EN_RXADDR);
  12.     HAL_Delay(1);
  13.     readConfiguration[3] = nrf24l01_ReadRegister(NRF24L01_REG_STATUS);
  14.     HAL_Delay(1);
  15.     readConfiguration[4] = nrf24l01_ReadRegister(NRF24L01_REG_RF_SETUP);
  16.     HAL_Delay(1);
  17.     nrf24l01_ReadRegMultipleData(NRF24L01_REG_TX_ADDR, dataTXAddress, 5);
  18.     HAL_Delay(1);
  19.     nrf24l01_ReadRegMultipleData(NRF24L01_REG_RX_ADDR_P1, dataRXAddress, 5);
  20.     for(uint8_t i=0; i<5; i++)
  21.     {
  22.         if(dataRXAddress[i] != nrf24l01_t->RxAddress[i])
  23.         {
  24.             checkStatVal |= 0x01;
  25.         }
  26.         if(dataTXAddress[i] != nrf24l01_t->TxAddress[i])
  27.         {
  28.             checkStatVal |= 0x02;
  29.         }
  30.     }
  31.     if(readConfiguration[0] != nrf24l01_t->ValueConfigRegister) { checkStatVal |= 0x04; }
  32.     if(readConfiguration[1] != nrf24l01_t->ValueRegEnAA)        { checkStatVal |= 0x08; }
  33.     if(readConfiguration[2] != nrf24l01_t->ValueRegEnRxAddr)    { checkStatVal |= 0x10; }
  34.     if(readConfiguration[3] != nrf24l01_t->ValueRegStatus)      { checkStatVal |= 0x20; }
  35.     if(readConfiguration[4] != nrf24l01_t->ValueRegRFSetup)     { checkStatVal |= 0x40; }
  36.     return checkStatVal;
  37. }

Ustawienie wzmocnienia oraz szybkości transmisji danych:

  1. void Nrf24l01_SetRF(nrf24l01_DataStruct_t *nrf24l01_t, const nrf24l01_DataRate_t DataRate, const nrf24l01_OutPowe_t OutPwr)
  2. {
  3.     uint8_t valueToWrite = 0;
  4.  
  5.     nrf24l01_t->DataRate = DataRate;
  6.     nrf24l01_t->OutPwr = OutPwr;
  7.  
  8.     if (nrf24l01_t->DataRate == NRF24L01_DR_2M)         { valueToWrite |= 1 << NRF24L01_RF_DR_HIGH; }
  9.     else if (nrf24l01_t->DataRate == NRF24L01_DR_250k)  { valueToWrite |= 1 << NRF24L01_RF_DR_LOW;  }
  10.  
  11.     if (nrf24l01_t->OutPwr == NRF24L01_OP_0dBm)         { valueToWrite |= 3 << NRF24L01_RF_PWR; }
  12.     else if (nrf24l01_t->OutPwr == NRF24L01_OP_M6dBm)   { valueToWrite |= 2 << NRF24L01_RF_PWR; }
  13.     else if (nrf24l01_t->OutPwr == NRF24L01_OP_M12dBm)  { valueToWrite |= 1 << NRF24L01_RF_PWR; }
  14.  
  15.     nrf24l01_WriteReg(NRF24L01_REG_RF_SETUP, valueToWrite);
  16.     nrf24l01_t->ValueRegRFSetup = valueToWrite;
  17. }

Ustawienie adresu dla nadajnika oraz odbiornika:

  1. uint8_t Nrf24l01_SetSensorAddress(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t* rxAddr, const uint8_t addrSize)
  2. {
  3.     if(addrSize != 5){
  4.         return 0xFF;
  5.     }
  6.     for(uint8_t i = 0; i<5; i++){
  7.         nrf24l01_t->RxAddress[i] = *(rxAddr + i);
  8.     }
  9.     NRF24L01_CE_LOW();
  10.     nrf24l01_WriteRegMultipleData(NRF24L01_REG_RX_ADDR_P1, rxAddr, addrSize);
  11.     NRF24L01_CE_HIGH();
  12.     return 0;
  13. }
  14. uint8_t Nrf24l01_SetTransmitAddress(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t* txAddr, const uint8_t addrSize)
  15. {
  16.     if(addrSize != 5){
  17.         return 0xFF;
  18.     }
  19.     for(uint8_t i = 0; i<5; i++){
  20.         nrf24l01_t->TxAddress[i] = *(txAddr + i);
  21.     }
  22.     nrf24l01_WriteRegMultipleData(NRF24L01_REG_RX_ADDR_P0, txAddr, addrSize);
  23.     nrf24l01_WriteRegMultipleData(NRF24L01_REG_TX_ADDR, txAddr, addrSize);
  24.     return 0;
  25. }

Funkcja odpowiedzialna za przesyłanie danych:

  1. uint8_t Nrf24l01_TransmitData(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataToSend)
  2. {
  3.     uint8_t count = nrf24l01_t->PayloadSize;
  4.     HAL_StatusTypeDef operationStatus = HAL_OK;
  5.    
  6.     NRF24L01_CE_LOW();
  7.     nrf24l01_PowerUpTx();
  8.     nrf24l01_FlushTx();
  9.     NRF24L01_CS_LOW();
  10.     #ifdef USE_REGISTER_COMMANDS
  11.     spiSendData(NRF24L01_SPI, NRF24L01_W_TX_PAYLOAD_MASK);
  12.     spi_WriteMultipleData(NRF24L01_SPI, data, count);
  13.     #else
  14.    
  15.     uint8_t addr[1] = { NRF24L01_W_TX_PAYLOAD_MASK };
  16.    
  17.     operationStatus = HAL_SPI_Transmit(&hspi3, &addr, 1, 1000);
  18.     if(operationStatus == HAL_OK)
  19.     {
  20.         operationStatus = HAL_SPI_Transmit(&hspi3, dataToSend, count,1000);
  21.     }
  22.     #endif
  23.     NRF24L01_CS_HIGH();
  24.     NRF24L01_CE_HIGH();
  25.     return operationStatus;
  26. }

Odczytanie statusu przesyłanych informacji:

  1. nrf24l01_TransmisionStat_t Nrf24l01_ReadTransmissionStatus(void)
  2. {
  3.     uint8_t status = nrf24l01_GetStatus();
  4.     if (NRF24L01_CHECK_BIT(status, NRF24L01_TX_DS))
  5.     {
  6.         return NRF24L01_TransmisionStat_Ok;
  7.     }
  8.     else if (NRF24L01_CHECK_BIT(status, NRF24L01_MAX_RT))
  9.     {
  10.         return NRF24L01_TransmisionStat_Lost;
  11.     }
  12.     return NRF24L01_TransmisionStat_Sending;
  13. }

Funkcja przełączająca układ w tryb nadawania danych. Jest on rozpatrywany jako tryb domyślny. Wobec tego należy go uruchamiać ciągle z wyjątkiem czasu potrzebnego na przesłanie danych.

  1. void Nrf24l01_PowerUpRx(void)
  2. {
  3.     NRF24L01_CE_LOW();
  4.     NRF24L01_CS_ON();
  5.     #ifdef USE_REGISTER_COMMANDS
  6.     spiSendData(NRF24L01_SPI, NRF24L01_FLUSH_RX_MASK);
  7.     #else
  8.     HAL_SPI_Transmit(&hspi3, (uint8_t *)NRF24L01_FLUSH_RX_MASK, 1, 1000);
  9.     #endif
  10.     NRF24L01_CS_OFF();
  11.     nrf24l01_ClearInterrupts();
  12.     nrf24l01_WriteReg(NRF24L01_REG_CONFIG, NRF24L01_CONFIG | 1 << NRF24L01_PWR_UP | 1 << NRF24L01_PRIM_RX);
  13.     NRF24L01_CE_HIGH();
  14. }

Funkcja odpowiedzialna za uruchomienie trybu przesyłania danych:

  1. void Nrf24l01_PowerUpTx(void)
  2. {
  3.     nrf24l01_ClearInterrupts();
  4.     nrf24l01_WriteReg(NRF24L01_REG_CONFIG, NRF24L01_CONFIG |
  5.                         (0 << NRF24L01_PRIM_RX) |
  6.                         (1 << NRF24L01_PWR_UP));
  7. }

W celu odebrania przesyłanych danych wykorzystywana jest następująca funkcja:

  1. void Nrf24L01_GetData(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t* data)
  2. {
  3.     NRF24L01_CS_LOW();
  4.     spiSendData(NRF24L01_SPI, NRF24L01_R_RX_PAYLOAD_MASK);
  5.     spiWriteMultiData(NRF24L01_SPI, data, data, nrf24l01_t->PayloadSize);
  6.     /* Pull up chip select */
  7.     NRF24L01_CS_HIGH();
  8.     /* Reset status register, clear RX_DR interrupt flag */
  9.     nrf24l01_WriteReg(NRF24L01_REG_STATUS, (1 << NRF24L01_RX_DR));
  10. }

Aby przesłać dane do wykorzystuje następującą funkcje:

  1. uint8_t Nrf24l01_TransmitData(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataToSend)
  2. {
  3.     uint8_t count = nrf24l01_t->PayloadSize;
  4.     HAL_StatusTypeDef operationStatus = HAL_OK;
  5.     NRF24L01_CE_LOW();
  6.     Nrf24l01_PowerUpTx();
  7.     nrf24l01_FlushTx();
  8.     NRF24L01_CS_LOW();
  9.     #ifdef USE_REGISTER_COMMANDS
  10.     spiSendData(NRF24L01_SPI, NRF24L01_W_TX_PAYLOAD_MASK);
  11.     spi_WriteMultipleData(NRF24L01_SPI, dataToSend, count);
  12.     #else
  13.     operationStatus = HAL_SPI_Transmit(&hspi3, (uint8_t *)NRF24L01_W_TX_PAYLOAD_MASK, 1, 1000);
  14.     if(operationStatus == HAL_OK)
  15.     {
  16.         operationStatus = HAL_SPI_Transmit(&hspi3, dataToSend, count,1000);
  17.     }
  18.     #endif
  19.     NRF24L01_CS_HIGH();
  20.     NRF24L01_CE_HIGH();
  21.     return operationStatus;
  22. }

Program Nadajnik:


Nadajnik uruchamiany jest w taki sam sposób jak odbiornik:

  1.   Nrf24l01_Initialize(&nrf24l01_t, 15, 32);
  2.   Nrf24l01_SetRF(&nrf24l01_t, NRF24L01_DR_2M, NRF24L01_OP_M18dBm);
  3.   Nrf24l01_SetSensorAddress(&nrf24l01_t, OwnAddress, sizeof(OwnAddress));
  4.   Nrf24l01_SetTransmitAddress(&nrf24l01_t, TxAddress, sizeof(TxAddress));
  5.   Nrf24l01_ReadSettings(&nrf24l01_t);

Funkcja umieszczona w pętli głównej odpowiadająca za nadawanie danych wygląda następująco:

  1. void Nrf24l01_TransmiterWhileLoop(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataToSend, uint8_t *transmissionStat)
  2. {
  3.     static uint16_t sendDataCounter = 3000;
  4.     if(sendDataCounter == 0)
  5.     {
  6.         Nrf24l01_Transmit(nrf24l01_t, dataToSend);
  7.         uint16_t loopCounter = 300;
  8.         do {
  9.             *(transmissionStat + 0) = Nrf24l01_ReadTransmissionStatus();
  10.             loopCounter--;
  11.             HAL_Delay(1);
  12.         } while ((*(transmissionStat + 0) == NRF24L01_TransmisionStat_Sending) && (loopCounter > 0));
  13.         Nrf24l01_PowerUpRx();
  14.     }
  15.     if(sendDataCounter > 0)
  16.     {
  17.         HAL_Delay(1);
  18.         sendDataCounter--;
  19.     }
  20. }

Nadajnik przerwania


Program Odbiornik:


Wersja podstawowa bez przerwań:

Uruchomienie układu w pętli main wygląda następująco:

  1.   Nrf24l01_Initialize(&nrf24l01_t, 15, 32);
  2.   Nrf24l01_SetRF(&nrf24l01_t, NRF24L01_DR_2M, NRF24L01_OP_M18dBm);
  3.   Nrf24l01_SetSensorAddress(&nrf24l01_t, OwnAddress, sizeof(OwnAddress));
  4.   Nrf24l01_SetTransmitAddress(&nrf24l01_t, TxAddress, sizeof(TxAddress));
  5.   Nrf24l01_ReadSettings(&nrf24l01_t);

Pętla while wygląda następująco:

  1. void Nrf24l01_TransmiterWhileLoop(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataInput, uint8_t *transmissionStat)
  2. {
  3.     if (Nrf24l01_DataReady())
  4.     {
  5.         Nrf24L01_GetData(nrf24l01_t, dataInput);
  6.         uint8_t loopCounter = 200;
  7.         do {
  8.             *(transmissionStat + 0) = Nrf24l01_ReadTransmissionStatus();
  9.             loopCounter--;
  10.         } while ((*(transmissionStat + 0) == NRF24L01_TransmisionStat_Sending) && (loopCounter > 0));
  11.         Nrf24l01_PowerUpRx();
  12.     }
  13. }

Dane mogą być zarówno odbierane jak i nadawane przez jeden czytnik.

Tutaj należy odbierać dane w momencie wystąpienia zbocza niskiego na pinie IRQ. Poniżej obsługa przerwania od jednego z pinów:

  1. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  2. {
  3.     if(GPIO_Pin == NRF24L01_IRQ_PIN)
  4.     {
  5.         if(Nrf24l01_DataReady())
  6.         {
  7.             Nrf24L01_GetData(&nrf24l01_t, dataIn);
  8.             Nrf24l01_PowerUpRx();
  9.         }
  10.     }
  11. }

Biblioteki do projektu można pobrać z dysku Google pod tym linkiem.