Working with STM32 and NRF24L01 Wireless RF Part1: Configuration

NRF24L01 is one of the legal RF communicators for modern applications. NRF24L01 is the cheapest one and it comes with great features. A single module communicates at 2.4 GHz frequency which makes it legal. It can transmit and receive data by a single module. Transreceiving is not its only ability, it can communicate with a total of 6 other same NRF24L01 modules at a single time. The device interfaces with the Arduino application and covers all kinds of remote-control applications. This wireless module uses SPI communication protocol and offers 10MBs data rate with 125 address range which makes it reliable the most reliable RF module. The RF module uses the GFSK module to transceiver the data.

In this guide, we shall cover the following:

  • nRF24L01
  • Connection with STM32.
  • Configuring the required peripheral.
  • Configuring nRF24L01.

1. nRF24L01:

In NRF24L01 there aren’t any special pins, all pins it offers to communicate are present in all microcontrollers and board. The device will interface with an external microcontroller/Arduino through these pins to operate. It consists of 8 pins. All available pins are:

Power supply pins

VCC

The power pin of the module is VCC, which connects itself with the power supply.

GND

nRF24L01 operates with another microcontroller and it will need a common ground to operate with it. GND pin will solve the requirement of the common ground.

Communication Pins

CE

CE is an enable pin, which activates the transmission/receiving of the module. It will only activate the device when there is a HIGH state on itself.

CSN

This pin is for activating the data listening and processing from the microcontroller. To keep the data communication between the microcontroller and the module it should be HIGH.

SCK

It is the clock pulse pin of SPI communication in nRF24L01. The data will move between the module and the microcontroller according to the clock pulse on the SCK pin.

MOSI

The data transmitted from the microcontroller through SPI pins are going to receive by nRF24L01 at the MOSI pin.

MISO

The instructions transmitted from the nRF24L01 using SPI pins are going to receive by the microcontroller at the MISO pin.

IRQ Interrupt Pin

IRQ is an interrupt pin, which generates the event whenever a new data is available for SPI pins. It helps to send feedback to the transmitter.

NRF24L01 RF Module Feature

  • It works at 2.4GHz frequency which makes it legal in almost every country.
  • A single module can act as both a transmitter or receiver.
  • A built-in antenna can send the data up to 100 meters.
  • A module nRF24L01 can communicate with a maximum of 6 other modules at a time.
  • It requires 3.3 volts to operate but voltages can only extend up to 3.6V otherwise it won’t take much time to heat up and burn.
  • The device has a built-in oscillator of 16MHz.
  • The transmission speed of nRF24L01 is 256kbps to 2Mbps.
  • The device has 125 channel range which gives the feature of operating 125 different networks at a single place.
  • The channel frequencies variate from 2400MHz to 2525MHz.

NRF24L01 Applications

  • In the creation of a small mesh network, nRF24L01 is the best choice to use.
  • Remote control applications at developing and commercial works wonderfully with nRF24L01.
  • Most IoT applications at home level have this wireless module but at a small level only.

How to use the NRF24L01 Communication Module

nRF24L01 is usable with all the microcontrollers and smart boards but to use it, some pins and data information should be understood. To use the module connect it with another microcontroller with SPI protocol. First, give the power input to the devices and then attach their SPI pins according to the given circuit.

2. Connection with STM32:

In this guide, we need the following:

  • 2x STM32F boards
  • 2x LEDs
  • 2x nRF24L01
  • Hookup wires.

The connection shall be as following:

STM32nRF24L01
PA5SCK
PA6MISO
PA7MOSI
PA0nCS
PA1CE
PA4Anode of the LED

3. Configuring the peripheral:

Since the module is using SPI, we shall use SPI1 of STM32F4 to communicate with the module.

We start off by configuring the SPI as following:

In spi.h file:

#ifndef SPI_H_
#define SPI_H_
#include "stdint.h"
void spi_init();
void spi1_transmit(uint8_t *data,uint32_t size);
void spi1_receive(uint8_t *data,uint32_t size);
#endif /* SPI_H_ */

for the spi.c source file:

#include "spi.h"
#include "stm32f4xx.h"

void spi_init()
	{

	#define AF05  (0x05)
	//enable clock for GPIOA
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN;
	//set PA5, PA6 and PA7 to alternate function mode
	GPIOA->MODER|=GPIO_MODER_MODE5_1|GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1;
	//set which type of alternate function is
	GPIOA->AFR[0]|=(AF05<<20)|(AF05<<24)|(AF05<<28);
	//enable clock access to SPI1
	RCC->APB2ENR|=RCC_APB2ENR_SPI1EN;
	//set software slave managment
	SPI1->CR1|=SPI_CR1_SSM|SPI_CR1_SSI;
	//set SPI in master mode
	SPI1->CR1|=SPI_CR1_MSTR;
	//SPI1->CR1|=SPI_CR1_BR_0;
	SPI1->CR1|=SPI_CR1_SPE;
	}

void spi1_transmit(uint8_t *data,uint32_t size)
{
	uint32_t i=0;
	uint8_t temp;

	while(i<size)
	{
		/*Wait until TXE is set*/
		while(!(SPI1->SR & (SPI_SR_TXE))){}

		/*Write the data to the data register*/
		SPI1->DR = data[i];
		i++;
	}
	/*Wait until TXE is set*/
	while(!(SPI1->SR & (SPI_SR_TXE))){}

	/*Wait for BUSY flag to reset*/
	while((SPI1->SR & (SPI_SR_BSY))){}

	/*Clear OVR flag*/
	temp = SPI1->DR;
	temp = SPI1->SR;
}

void spi1_receive(uint8_t *data,uint32_t size)
{
	while(size)
	{
		/*Send dummy data*/
		SPI1->DR =0;

		/*Wait for RXNE flag to be set*/
		while(!(SPI1->SR & (SPI_SR_RXNE))){}

		/*Read data from data register*/
		*data++ = (SPI1->DR);
		size--;
	}
}

Since we are using LED for notification, we need to create two files, led.c and led.h

For the led.h:

#ifndef LED_H_
#define LED_H_

void led_init();
void led_off();
void toggle_led();


#endif /* LED_H_ */

For led.c

#include "led.h"
#include "stm32f4xx.h"

void led_init()
	{
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN;
	GPIOA->MODER|=GPIO_MODER_MODE4_0;
	GPIOA->MODER&=~GPIO_MODER_MODE4_1;
	}

void led_on()
	{
	GPIOA->BSRR=GPIO_BSRR_BS4;
	}

void led_off()
	{
	GPIOA->BSRR=GPIO_BSRR_BR4;
	}

void toggle_led()
	{
	GPIOA->ODR^=GPIO_ODR_OD4;
	}

Also, we need to delay in milliseconds, in this case, we shall use systick to generate interrupt each 1 milliseconds as following:

For delay.h:

#ifndef DELAY_H_
#define DELAY_H_

#include "stdint.h"
void delay_init(uint32_t freq);
uint64_t millis();
void delay(uint32_t time);
#endif /* DELAY_H_ */

and delay.c

#include "delay.h"
#include "stm32f4xx.h"

#define	CTRL_ENABLE					(1U<<0)
#define CTRL_CLKSRC					(1U<<2)
#define CTRL_COUNTFLAG				(1U<<16)
#define CTRL_TICKINT				(1U<<1)


volatile uint64_t mil;

void delay_init(uint32_t freq){

	SysTick->LOAD  = (freq/1000) - 1;

	/*Clear systick current value register */
	SysTick->VAL = 0;

	/*Enable systick and select internal clk src*/
	SysTick->CTRL = CTRL_ENABLE | CTRL_CLKSRC ;

	/*Enable systick interrupt*/
	SysTick->CTRL  |= CTRL_TICKINT;

}



uint64_t millis()
	{
	__disable_irq();
	uint64_t ml=mil;
	__enable_irq();
	return ml;
	}



void delay(uint32_t time)
	{

	uint64_t start=millis();
	while((millis() - start) < time);

	}

void SysTick_Handler(void)
{
	mil++;
}

4. Configuring nRF24L01:

Before we start configuring the module, we need to create functions that allow us to perform the following:

  • Chip select and chip enable
  • Write to register.
  • Multiple bytes write.
  • Read a register.
  • Read multiple bytes from a register.
  • Write a command.

For those functions:


void CS_Select (void)
{
GPIOA->BSRR=GPIO_BSRR_BR1;
}

void CS_UnSelect (void)
{
GPIOA->BSRR=GPIO_BSRR_BS1;
}


void CE_Enable (void)
{
	GPIOA->BSRR=GPIO_BSRR_BS0;

}

void CE_Disable (void)
{
	GPIOA->BSRR=GPIO_BSRR_BR0;

}


// write a single byte to the particular register
void nrf24_WriteReg (uint8_t Reg, uint8_t Data)
{
	uint8_t buf[2];
	buf[0] = Reg|1<<5;
	buf[1] = Data;

	// Pull the CS Pin LOW to select the device
	CS_Select();

	spi1_transmit(buf, 2);

	// Pull the CS HIGH to release the device
	CS_UnSelect();
}

//write multiple bytes starting from a particular register
void nrf24_WriteRegMulti (uint8_t Reg, uint8_t *data, int size)
{
	uint8_t buf[2];
	buf[0] = Reg|1<<5;
//	buf[1] = Data;

	// Pull the CS Pin LOW to select the device
	CS_Select();

	spi1_transmit(buf,1);
	spi1_transmit(data, size);

	// Pull the CS HIGH to release the device
	CS_UnSelect();
}


uint8_t nrf24_ReadReg (uint8_t Reg)
{
	uint8_t data=0;

	// Pull the CS Pin LOW to select the device
	CS_Select();

	spi1_transmit(&Reg, 1);
	spi1_receive(&data,1);

	// Pull the CS HIGH to release the device
	CS_UnSelect();

	return data;
}


/* Read multiple bytes from the register */
void nrf24_ReadReg_Multi (uint8_t Reg, uint8_t *data, int size)
{
	// Pull the CS Pin LOW to select the device
	CS_Select();

	spi1_transmit(&Reg, 1);
	spi1_receive(data, size);
	// Pull the CS HIGH to release the device
	CS_UnSelect();
}


// send the command to the NRF
void nrfsendCmd (uint8_t cmd)
{
	// Pull the CS Pin LOW to select the device
	CS_Select();

	spi1_transmit( &cmd, 1);

	// Pull the CS HIGH to release the device
	CS_UnSelect();
}

Then we need to configure the CE and nCS as following:

void ncs_cs_init()
	{
  	/*enable clock access ti GPI)A*/
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN;
  	/*Set PA0 and PA1 as output*/
	GPIOA->MODER|=GPIO_MODER_MODER0_0|GPIO_MODER_MODER1_0;
	GPIOA->MODER&=~(GPIO_MODER_MODER0_1|GPIO_MODER_MODER1_1);

	}

For initializing the nRF24L01:

void NRF24_Init (void)
{
	spi_init();
	ncs_cs_init();
	// disable the chip before configuring the device
	CE_Disable();


	// reset everything
	nrf24_reset (0);

	nrf24_WriteReg(CONFIG, 0);  // will be configured later

	nrf24_WriteReg(EN_AA, 0);  // No Auto ACK

	nrf24_WriteReg (EN_RXADDR, 0);  // Not Enabling any data pipe right now

	nrf24_WriteReg (SETUP_AW, 0x03);  // 5 Bytes for the TX/RX address

	nrf24_WriteReg (SETUP_RETR, 0);   // No retransmission

	nrf24_WriteReg (RF_CH, 0);  // will be setup during Tx or RX

	nrf24_WriteReg (RF_SETUP, 0x0E);   // Power= 0db, data rate = 2Mbps

	// Enable the chip after configuring the device
	CE_Enable();

}

For the header file:

#ifndef NRF_H_
#define NRF_H_

#include "stdint.h"

void NRF24_Init (void);

/* Memory Map */
#define CONFIG      0x00
#define EN_AA       0x01
#define EN_RXADDR   0x02
#define SETUP_AW    0x03
#define SETUP_RETR  0x04
#define RF_CH       0x05
#define RF_SETUP    0x06
#define STATUS      0x07
#define OBSERVE_TX  0x08
#define CD          0x09
#define RX_ADDR_P0  0x0A
#define RX_ADDR_P1  0x0B
#define RX_ADDR_P2  0x0C
#define RX_ADDR_P3  0x0D
#define RX_ADDR_P4  0x0E
#define RX_ADDR_P5  0x0F
#define TX_ADDR     0x10
#define RX_PW_P0    0x11
#define RX_PW_P1    0x12
#define RX_PW_P2    0x13
#define RX_PW_P3    0x14
#define RX_PW_P4    0x15
#define RX_PW_P5    0x16
#define FIFO_STATUS 0x17
#define DYNPD	    0x1C
#define FEATURE	    0x1D

/* Instruction Mnemonics */
#define R_REGISTER    0x00
#define W_REGISTER    0x20
#define REGISTER_MASK 0x1F
#define ACTIVATE      0x50
#define R_RX_PL_WID   0x60
#define R_RX_PAYLOAD  0x61
#define W_TX_PAYLOAD  0xA0
#define W_ACK_PAYLOAD 0xA8
#define FLUSH_TX      0xE1
#define FLUSH_RX      0xE2
#define REUSE_TX_PL   0xE3
#define NOP           0xFF



#endif /* NRF_H_ */

In part 2, we shall configure the module to transmit data

Happy coding 🙂

Add Comment

Your email address will not be published. Required fields are marked *