W tym poście chciałbym przedstawić program pozwalający na komunikację między dwoma układami 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ń:
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:
Inicjalizacja SPI:
Ustawienie parametrów w układzie nrf24l01:
Funkcja odczytująca konfigurację układu:
Ustawienie wzmocnienia oraz szybkości transmisji danych:
Ustawienie adresu dla nadajnika oraz odbiornika:
Funkcja odpowiedzialna za przesyłanie danych:
Odczytanie statusu przesyłanych informacji:
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.
Funkcja odpowiedzialna za uruchomienie trybu przesyłania danych:
W celu odebrania przesyłanych danych wykorzystywana jest następująca funkcja:
Aby przesłać dane do wykorzystuje następującą funkcje:
Konfiguracja pinów oraz CS oraz CE:
- static void nrf24l01_InitPins(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- NRF24L01_CS_CLOCK();
- NRF24L01_CE_CLOCK();
- HAL_GPIO_WritePin(NRF24L01_CE_PORT, NRF24L01_CE_PIN, GPIO_PIN_RESET);
- HAL_GPIO_WritePin(NRF24L01_CS_PORT, NRF24L01_CS_PIN, GPIO_PIN_RESET);
- /* CS Pin configuration */
- GPIO_InitStruct.Pin = NRF24L01_CE_PIN;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(NRF24L01_CE_PORT, &GPIO_InitStruct);
- /* CE Pin configuration */
- GPIO_InitStruct.Pin = NRF24L01_CS_PIN;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(NRF24L01_CS_PORT, &GPIO_InitStruct);
- NRF24L01_CS_OFF();
- NRF24L01_CE_LOW();
- }
Inicjalizacja SPI:
- static void nrf24l01_InitSpi(void)
- {
- hspi3.Instance = SPI3;
- hspi3.Init.Mode = SPI_MODE_MASTER;
- hspi3.Init.Direction = SPI_DIRECTION_2LINES;
- hspi3.Init.DataSize = SPI_DATASIZE_8BIT;
- hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
- hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
- hspi3.Init.NSS = SPI_NSS_SOFT;
- hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
- hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
- hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
- hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- hspi3.Init.CRCPolynomial = 10;
- if (HAL_SPI_Init(&hspi3) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- }
Ustawienie parametrów w układzie nrf24l01:
- uint8_t Nrf24l01_Initialize(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t channel, uint8_t payloadSize)
- {
- nrf24l01_InitPins();
- nrf24l01_InitSpi();
- nrf24l01_InitInterruptPin();
- payloadSize = checkThenSetPayloadSize(payloadSize);
- setDataInStructure(nrf24l01_t, channel, payloadSize);
- nrf24l01_SoftwareReset();
- nrf24l01_SetChannel(nrf24l01_t, channel);
- setPipelineValueToMax(nrf24l01_t);
- Nrf24l01_SetRF(nrf24l01_t, nrf24l01_t->DataRate, nrf24l01_t->OutPwr);
- nrf24l01_WriteReg(NRF24L01_REG_CONFIG, NRF24L01_CONFIG);
- nrf24l01_t->ValueConfigRegister = NRF24L01_CONFIG;
- nrf24l01_WriteReg(NRF24L01_REG_EN_AA, NRF24L01_ENABLE_AUTO_ACK_ALL_PIPES);
- nrf24l01_t->ValueRegEnAA = NRF24L01_ENABLE_AUTO_ACK_ALL_PIPES;
- nrf24l01_WriteReg(NRF24L01_REG_EN_RXADDR, NRF24L01_ENABLE_RX_ADDR);
- nrf24l01_t->ValueRegEnRxAddr = NRF24L01_ENABLE_RX_ADDR;
- nrf24l01_WriteReg(NRF24L01_REG_SETUP_RETR, NRF24L01_SET_RETRA_DEL);
- dynamicLengthConfiguration();
- nrf24l01_FlushRx();
- nrf24l01_FlushTx();
- nrf24l01_WriteReg(NRF24L01_REG_STATUS, NRF24L01_CLEAR_INTERRUPTS);
- Nrf24l01_PowerUpRx();
- return 0;
- }
Funkcja odczytująca konfigurację układu:
- uint8_t Nrf24l01_ReadSettings(nrf24l01_DataStruct_t *nrf24l01_t)
- {
- uint8_t readConfiguration[5] = {0x00};
- uint8_t dataTXAddress[5];
- uint8_t dataRXAddress[5];
- uint8_t checkStatVal = 0x00;
- readConfiguration[0] = nrf24l01_ReadRegister(NRF24L01_REG_CONFIG);
- HAL_Delay(1);
- readConfiguration[1] = nrf24l01_ReadRegister(NRF24L01_REG_EN_AA);
- HAL_Delay(1);
- readConfiguration[2] = nrf24l01_ReadRegister(NRF24L01_REG_EN_RXADDR);
- HAL_Delay(1);
- readConfiguration[3] = nrf24l01_ReadRegister(NRF24L01_REG_STATUS);
- HAL_Delay(1);
- readConfiguration[4] = nrf24l01_ReadRegister(NRF24L01_REG_RF_SETUP);
- HAL_Delay(1);
- nrf24l01_ReadRegMultipleData(NRF24L01_REG_TX_ADDR, dataTXAddress, 5);
- HAL_Delay(1);
- nrf24l01_ReadRegMultipleData(NRF24L01_REG_RX_ADDR_P1, dataRXAddress, 5);
- for(uint8_t i=0; i<5; i++)
- {
- if(dataRXAddress[i] != nrf24l01_t->RxAddress[i])
- {
- checkStatVal |= 0x01;
- }
- if(dataTXAddress[i] != nrf24l01_t->TxAddress[i])
- {
- checkStatVal |= 0x02;
- }
- }
- if(readConfiguration[0] != nrf24l01_t->ValueConfigRegister) { checkStatVal |= 0x04; }
- if(readConfiguration[1] != nrf24l01_t->ValueRegEnAA) { checkStatVal |= 0x08; }
- if(readConfiguration[2] != nrf24l01_t->ValueRegEnRxAddr) { checkStatVal |= 0x10; }
- if(readConfiguration[3] != nrf24l01_t->ValueRegStatus) { checkStatVal |= 0x20; }
- if(readConfiguration[4] != nrf24l01_t->ValueRegRFSetup) { checkStatVal |= 0x40; }
- return checkStatVal;
- }
Ustawienie wzmocnienia oraz szybkości transmisji danych:
- void Nrf24l01_SetRF(nrf24l01_DataStruct_t *nrf24l01_t, const nrf24l01_DataRate_t DataRate, const nrf24l01_OutPowe_t OutPwr)
- {
- uint8_t valueToWrite = 0;
- nrf24l01_t->DataRate = DataRate;
- nrf24l01_t->OutPwr = OutPwr;
- if (nrf24l01_t->DataRate == NRF24L01_DR_2M) { valueToWrite |= 1 << NRF24L01_RF_DR_HIGH; }
- else if (nrf24l01_t->DataRate == NRF24L01_DR_250k) { valueToWrite |= 1 << NRF24L01_RF_DR_LOW; }
- if (nrf24l01_t->OutPwr == NRF24L01_OP_0dBm) { valueToWrite |= 3 << NRF24L01_RF_PWR; }
- else if (nrf24l01_t->OutPwr == NRF24L01_OP_M6dBm) { valueToWrite |= 2 << NRF24L01_RF_PWR; }
- else if (nrf24l01_t->OutPwr == NRF24L01_OP_M12dBm) { valueToWrite |= 1 << NRF24L01_RF_PWR; }
- nrf24l01_WriteReg(NRF24L01_REG_RF_SETUP, valueToWrite);
- nrf24l01_t->ValueRegRFSetup = valueToWrite;
- }
Ustawienie adresu dla nadajnika oraz odbiornika:
- uint8_t Nrf24l01_SetSensorAddress(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t* rxAddr, const uint8_t addrSize)
- {
- if(addrSize != 5){
- return 0xFF;
- }
- for(uint8_t i = 0; i<5; i++){
- nrf24l01_t->RxAddress[i] = *(rxAddr + i);
- }
- NRF24L01_CE_LOW();
- nrf24l01_WriteRegMultipleData(NRF24L01_REG_RX_ADDR_P1, rxAddr, addrSize);
- NRF24L01_CE_HIGH();
- return 0;
- }
- uint8_t Nrf24l01_SetTransmitAddress(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t* txAddr, const uint8_t addrSize)
- {
- if(addrSize != 5){
- return 0xFF;
- }
- for(uint8_t i = 0; i<5; i++){
- nrf24l01_t->TxAddress[i] = *(txAddr + i);
- }
- nrf24l01_WriteRegMultipleData(NRF24L01_REG_RX_ADDR_P0, txAddr, addrSize);
- nrf24l01_WriteRegMultipleData(NRF24L01_REG_TX_ADDR, txAddr, addrSize);
- return 0;
- }
Funkcja odpowiedzialna za przesyłanie danych:
- uint8_t Nrf24l01_TransmitData(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataToSend)
- {
- uint8_t count = nrf24l01_t->PayloadSize;
- HAL_StatusTypeDef operationStatus = HAL_OK;
- NRF24L01_CE_LOW();
- nrf24l01_PowerUpTx();
- nrf24l01_FlushTx();
- NRF24L01_CS_LOW();
- #ifdef USE_REGISTER_COMMANDS
- spiSendData(NRF24L01_SPI, NRF24L01_W_TX_PAYLOAD_MASK);
- spi_WriteMultipleData(NRF24L01_SPI, data, count);
- #else
- uint8_t addr[1] = { NRF24L01_W_TX_PAYLOAD_MASK };
- operationStatus = HAL_SPI_Transmit(&hspi3, &addr, 1, 1000);
- if(operationStatus == HAL_OK)
- {
- operationStatus = HAL_SPI_Transmit(&hspi3, dataToSend, count,1000);
- }
- #endif
- NRF24L01_CS_HIGH();
- NRF24L01_CE_HIGH();
- return operationStatus;
- }
Odczytanie statusu przesyłanych informacji:
- nrf24l01_TransmisionStat_t Nrf24l01_ReadTransmissionStatus(void)
- {
- uint8_t status = nrf24l01_GetStatus();
- if (NRF24L01_CHECK_BIT(status, NRF24L01_TX_DS))
- {
- return NRF24L01_TransmisionStat_Ok;
- }
- else if (NRF24L01_CHECK_BIT(status, NRF24L01_MAX_RT))
- {
- return NRF24L01_TransmisionStat_Lost;
- }
- return NRF24L01_TransmisionStat_Sending;
- }
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.
- void Nrf24l01_PowerUpRx(void)
- {
- NRF24L01_CE_LOW();
- NRF24L01_CS_ON();
- #ifdef USE_REGISTER_COMMANDS
- spiSendData(NRF24L01_SPI, NRF24L01_FLUSH_RX_MASK);
- #else
- HAL_SPI_Transmit(&hspi3, (uint8_t *)NRF24L01_FLUSH_RX_MASK, 1, 1000);
- #endif
- NRF24L01_CS_OFF();
- nrf24l01_ClearInterrupts();
- nrf24l01_WriteReg(NRF24L01_REG_CONFIG, NRF24L01_CONFIG | 1 << NRF24L01_PWR_UP | 1 << NRF24L01_PRIM_RX);
- NRF24L01_CE_HIGH();
- }
Funkcja odpowiedzialna za uruchomienie trybu przesyłania danych:
- void Nrf24l01_PowerUpTx(void)
- {
- nrf24l01_ClearInterrupts();
- nrf24l01_WriteReg(NRF24L01_REG_CONFIG, NRF24L01_CONFIG |
- (0 << NRF24L01_PRIM_RX) |
- (1 << NRF24L01_PWR_UP));
- }
W celu odebrania przesyłanych danych wykorzystywana jest następująca funkcja:
- void Nrf24L01_GetData(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t* data)
- {
- NRF24L01_CS_LOW();
- spiSendData(NRF24L01_SPI, NRF24L01_R_RX_PAYLOAD_MASK);
- spiWriteMultiData(NRF24L01_SPI, data, data, nrf24l01_t->PayloadSize);
- /* Pull up chip select */
- NRF24L01_CS_HIGH();
- /* Reset status register, clear RX_DR interrupt flag */
- nrf24l01_WriteReg(NRF24L01_REG_STATUS, (1 << NRF24L01_RX_DR));
- }
Aby przesłać dane do wykorzystuje następującą funkcje:
- uint8_t Nrf24l01_TransmitData(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataToSend)
- {
- uint8_t count = nrf24l01_t->PayloadSize;
- HAL_StatusTypeDef operationStatus = HAL_OK;
- NRF24L01_CE_LOW();
- Nrf24l01_PowerUpTx();
- nrf24l01_FlushTx();
- NRF24L01_CS_LOW();
- #ifdef USE_REGISTER_COMMANDS
- spiSendData(NRF24L01_SPI, NRF24L01_W_TX_PAYLOAD_MASK);
- spi_WriteMultipleData(NRF24L01_SPI, dataToSend, count);
- #else
- operationStatus = HAL_SPI_Transmit(&hspi3, (uint8_t *)NRF24L01_W_TX_PAYLOAD_MASK, 1, 1000);
- if(operationStatus == HAL_OK)
- {
- operationStatus = HAL_SPI_Transmit(&hspi3, dataToSend, count,1000);
- }
- #endif
- NRF24L01_CS_HIGH();
- NRF24L01_CE_HIGH();
- return operationStatus;
- }
Program Nadajnik:
Nadajnik uruchamiany jest w taki sam sposób jak odbiornik:
- Nrf24l01_Initialize(&nrf24l01_t, 15, 32);
- Nrf24l01_SetRF(&nrf24l01_t, NRF24L01_DR_2M, NRF24L01_OP_M18dBm);
- Nrf24l01_SetSensorAddress(&nrf24l01_t, OwnAddress, sizeof(OwnAddress));
- Nrf24l01_SetTransmitAddress(&nrf24l01_t, TxAddress, sizeof(TxAddress));
- Nrf24l01_ReadSettings(&nrf24l01_t);
Funkcja umieszczona w pętli głównej odpowiadająca za nadawanie danych wygląda następująco:
- void Nrf24l01_TransmiterWhileLoop(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataToSend, uint8_t *transmissionStat)
- {
- static uint16_t sendDataCounter = 3000;
- if(sendDataCounter == 0)
- {
- Nrf24l01_Transmit(nrf24l01_t, dataToSend);
- uint16_t loopCounter = 300;
- do {
- *(transmissionStat + 0) = Nrf24l01_ReadTransmissionStatus();
- loopCounter--;
- HAL_Delay(1);
- } while ((*(transmissionStat + 0) == NRF24L01_TransmisionStat_Sending) && (loopCounter > 0));
- Nrf24l01_PowerUpRx();
- }
- if(sendDataCounter > 0)
- {
- HAL_Delay(1);
- sendDataCounter--;
- }
- }
Nadajnik przerwania
Program Odbiornik:
Wersja podstawowa bez przerwań:
Uruchomienie układu w pętli main wygląda następująco:
- Nrf24l01_Initialize(&nrf24l01_t, 15, 32);
- Nrf24l01_SetRF(&nrf24l01_t, NRF24L01_DR_2M, NRF24L01_OP_M18dBm);
- Nrf24l01_SetSensorAddress(&nrf24l01_t, OwnAddress, sizeof(OwnAddress));
- Nrf24l01_SetTransmitAddress(&nrf24l01_t, TxAddress, sizeof(TxAddress));
- Nrf24l01_ReadSettings(&nrf24l01_t);
Pętla while wygląda następująco:
- void Nrf24l01_TransmiterWhileLoop(nrf24l01_DataStruct_t *nrf24l01_t, uint8_t *dataInput, uint8_t *transmissionStat)
- {
- if (Nrf24l01_DataReady())
- {
- Nrf24L01_GetData(nrf24l01_t, dataInput);
- uint8_t loopCounter = 200;
- do {
- *(transmissionStat + 0) = Nrf24l01_ReadTransmissionStatus();
- loopCounter--;
- } while ((*(transmissionStat + 0) == NRF24L01_TransmisionStat_Sending) && (loopCounter > 0));
- Nrf24l01_PowerUpRx();
- }
- }
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:
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
- {
- if(GPIO_Pin == NRF24L01_IRQ_PIN)
- {
- if(Nrf24l01_DataReady())
- {
- Nrf24L01_GetData(&nrf24l01_t, dataIn);
- Nrf24l01_PowerUpRx();
- }
- }
- }
Biblioteki do projektu można pobrać z dysku Google pod tym linkiem.