NRF24L01+ Driver : Part 5 : Hardware Specific Functions
The following functions and setup procedures are specific to STM32 and more specifically the F103 series. Furthermore I use the LL API to set up the peripherals , but feel free to use whatever method you'd like.
The first thing I do which is not used by the driver is to initialize all my pins I have made all these defines to make my code more readable, this is all in my main application file NOT the driver. And this is for readability purposes only. I repeat this is not an SPI tutorial! So dont cry about the lack of detail here.
//-----------| NRF Macros / Variables |-----------
#define NRF_SPI SPI1
#define NRF_CE_PIN LL_GPIO_PIN_2 //chip enable pin to make the transceiver transmit or listen
#define NRF_CE_PORT GPIOA
#define NRF_IRQ_PIN LL_GPIO_PIN_4 // [ interrupt pin active Low ]
#define NRF_IRQ_PORT GPIOA
#define NRF_CSN_PIN LL_GPIO_PIN_3 //Low when SPI active
#define NRF_CSN_PORT GPIOA
// [ SPI : keep at or below 2Mbs ]
#define NRF_CLK_PIN LL_GPIO_PIN_5
#define NRF_MOSI_PIN LL_GPIO_PIN_7
#define NRF_MISO_PIN LL_GPIO_PIN_6
#define NRF_PINS_CLOCK_ENABLE() (RCC->APB2ENR |= RCC_APB2ENR_IOPAEN ) //given the pins are on GPIOA
uint8_t NRFSTATUS = 0x00;
uint8_t tx_data_buff[32];
uint8_t rx_data_buff[32];
uint8_t multibyte_buff[10] = { 0 };
uint8_t flag = 0x55;
-----------------------------------------------------------------------------------------------------------------------------
And here is the function tht uses all those defines. This functions initializes the CE, CSN and IRQ pin as well as the interrupt for the IRQ pin.
void init_pins(void)
{
//clock enable GPIOA
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
LL_GPIO_InitTypeDef nrfPins;
LL_GPIO_StructInit(&nrfPins);
// CE pin as output : active high/ normally low
// CSN chip select for spi active low / normally high
nrfPins.Pin = NRF_CE_PIN | NRF_CSN_PIN;
nrfPins.Mode = LL_GPIO_MODE_OUTPUT;
nrfPins.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
nrfPins.Speed = LL_GPIO_SPEED_FREQ_HIGH;
LL_GPIO_Init(NRF_CE_PORT, &nrfPins); // CE & CSN on same port only need this once
// IRQ pin as input with interrupt enabled
nrfPins.Pin = NRF_IRQ_PIN;
nrfPins.Mode = LL_GPIO_MODE_INPUT;
nrfPins.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(NRF_IRQ_PORT, &nrfPins);
LL_EXTI_InitTypeDef myEXTI = { 0 };
LL_EXTI_StructInit(&myEXTI);
myEXTI.Line_0_31 = LL_EXTI_LINE_4;
myEXTI.LineCommand = ENABLE;
myEXTI.Mode = LL_EXTI_MODE_IT;
myEXTI.Trigger = LL_EXTI_TRIGGER_FALLING;
LL_EXTI_Init(&myEXTI);
LL_GPIO_SetOutputPin(GPIOA, NRF_CSN_PIN);
LL_GPIO_ResetOutputPin(GPIOA, NRF_CE_PIN);
NVIC_EnableIRQ(EXTI4_IRQn); //enable IRQ on Pin 4
}
-----------------------------------------------------------------------------------------------------------------------------
Here is my function to initialize the SPI peripheral, take note of the prescaler and how high it is. The NRF can handle SPI communication at 10Mbps max I believe.
void init_spi1(void)
{
// CLOCK [ Alt Function ] [ GPIOA ] [ SPI1 ]
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_SPI1EN;
// GPIO [ PA5:SCK:output:push ] [ PA6:MISO:input:float/pullup ] [ PA7:MOSI:output:push ]
LL_GPIO_InitTypeDef spiGPIO;
LL_GPIO_StructInit(&spiGPIO);
spiGPIO.Pin = NRF_MOSI_PIN | NRF_CLK_PIN;
spiGPIO.Mode = LL_GPIO_MODE_ALTERNATE;
spiGPIO.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
spiGPIO.Speed = LL_GPIO_SPEED_FREQ_MEDIUM;
LL_GPIO_Init(GPIOA, &spiGPIO);
spiGPIO.Pin = NRF_MISO_PIN;
spiGPIO.Mode = LL_GPIO_MODE_FLOATING;
spiGPIO.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(GPIOA, &spiGPIO);
// SPI
LL_SPI_InitTypeDef mySPI;
LL_SPI_StructInit(&mySPI);
mySPI.Mode = LL_SPI_MODE_MASTER;
mySPI.NSS = LL_SPI_NSS_SOFT;
mySPI.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV32;
LL_SPI_Init(NRF_SPI, &mySPI);
LL_SPI_Enable(NRF_SPI);
}
-----------------------------------------------------------------------------------------------------------------------------
The next 3 functions are the SPI communications functions needed to talk to the NRF. In the code below " __IO" is just defined to mean "volatile" to make a volatile variable so the compiler doesn't optimize it out for any reason. I could have easily used the LL transmit functions, and in fact I did, I just looked at what was inside the function and copied it and renamed it. Programmer of the year I know!! Let me explain the functions.
YOU FUNCTIONS MUST HAVE THE SAME ARGUMENTS IN THE SAME ORDER, THE DRIVER EXPECTS IT AS SUCH.
void spiSend(uint8_t data)
{
__IO uint8_t *spidr = ((__IO uint8_t *)&NRF_SPI->DR);
//while (!(NRF_SPI->SR & SPI_SR_TXE)) ;
NRF_SPI->DR = data;
while (NRF_SPI->SR & LL_SPI_SR_BSY) ;
}
void spiSendMultiByte(uint8_t * data_to_send, uint32_t len, uint8_t *rx_buffer)
{
//consider what to do with returned bytes usually when
//sending data returned bytes are not needed
__IO uint8_t *spidr = ((__IO uint8_t *)&NRF_SPI->DR);
for(uint8_t i = 0 ; i < len ; i++)
{
NRF_SPI->DR = *data_to_send++;
//if you mcu is really fast you might want to the the chekc busy flag code here
rx_buffer[i] = spiRead();
while (NRF_SPI->SR & SPI_SR_BSY) ; //this is the check busy flag code
}
}
uint8_t spiRead(void)
{
return (uint8_t)(NRF_SPI->DR);
}
- spiSend accepts a single byte of data and sends it and then waits for the SPI peripheral to not be busy. The fancy line that begins with __IO is interesting, what it does is take the SPI Data Register and makes an uint8_t pointer out of it and then asigns it the name NRF_SPI , then puts the data in there. It does it in this manner because the SPI data register is very picky, if you put data in there that is not the exact size it thinks its a 16 bit data and tries to send the single 8bit data twice. Anyways thats how its done.
- spiSendMultiByte This function is almost the same as the previous , it does the same thing with the __IO pointer thing. But now the actual sending is inside of a loop and the data it sends is inside of a pointer, and also this time it reads the incoming data and puts it into a specified rx_buffer all of this is with pointers. This function accepts a pointer to the data you want to send, the length of data to send, and a pointer to where you want to store the received bytes. Also waits for the peripheral to not be busy in-between sends.
- spiRead : this is as easy as it looks you just read the spi data register and return what you read
I REPEAT THAT THE FUNCTIONS CAN BE CALLED WHATEVER YOU WANT BUT THE ARGUMENTS NEED TO BE IN THE SAME ORDER AND OF THE SAME DATA TYPE, UNLESS YOU REWRITE THE DRIVER FUNCTIONS TO SUIT YOUR NEEDS.
-----------------------------------------------------------------------------------------------------------------------------
The functions that follow are used by the drive to toggle the CE and CSN pins when ever it needs to do so.
void CE_pin_HIGH(void)
{
NRF_CE_PORT->BSRR = NRF_CE_PIN;
}
void CE_pin_LOW(void)
{
NRF_CE_PORT->BRR = NRF_CE_PIN;
}
void CSN_pin_HIGH(void)
{
NRF_CSN_PORT->BSRR = NRF_CSN_PIN;
}
void CSN_pin_LOW(void)
{
NRF_CSN_PORT->BRR = NRF_CSN_PIN;
}
Comments