A portable NRF24L01 C Library for multiple hardware platform integration in Wireless Sensor Networking

For the last couple of months I have been working on a library for the NRF24L01 modules such that it would be easy to integrate multiple hardware platforms using wireless connections. The idea is to develop a library in C and make sure that the functions can be clearly separated, which would make things easier when porting the library for a different hardware platform. There can be similar libraries in the Internet at the moment but I did not try any of them except the Energia library which can be found here.

I believe Energia is a very good platform for rapid prototyping, but the Arduino like language is not close to the actual hardware modules. Following is the solution I have prepared which is written in C and is very much close to hardware. I believe this library can be used to connect different types of hardware platforms through nrf24l01 module. This is very important in Wireless Sensor Networking solutions because wireless nodes can have MCUs with different architectures. It reduces firmware complexity and module compatibility due to the uniformity of configuration.

Description:

The library is divided into three logical sections.

  • Basic
  • Intermediate
  • Advanced

Basic section contains the minimal functions required for a successful data transfer. These functions can be used to perform communication using the default module settings.

Intermediate section contains functions which can be used to change the internal module configurations. This enables the user to set data rates, power levels, etc. Also, there are functions to enable interrupts for the module.

Advanced section contains functions which can be used to set the registers of the module. The user is expected to have in-depth know how of the NRF24L01 module in order to successfully control it. But this provides the maximum flexibility to the user.

PDLIB_NRF24L01 V1.01 can be used through polling as well as interrupts.

Communication between the MCU and the NRF24L01 module is through an SPI channel and few other pins (CE and CSN). Here I have used an SPI library library I developed for this task. It can be changed according the needs by modifying a few functions easily. If interrupts are enabled, a separate interrupt pin (IRQ) is required.

Following diagram provides a pictorial view of the functions which directly communicate with two hardware modules.

CommunicationThe next set of functions falls under ‘Advanced APIs’ group. They are the only functions which directly communicate with the SPI module and the CSN pin. There is a pin named ‘CE’ in the NRF24L01 modules, but the CE pin is not related to the communication between the modules It is used to enable TX/RX modes.

AdvancedThe idea here is to make sure that we can port the library easily with very minimal change to the functions. If we need to port the library to another platform, we need to change the SPI library and eight other functions which directly gets called by the rest of the APIs. These will be explained at the end of the post.

Exposed APIs:

The ‘Basic APIs’ are shown in the following figure. These set of functions are enough to perform a basic communication between two devices. But there is almost no flexibility. Function descriptions are available in the code itself.

  • NRF24L01_Init()
  • NRF24L01_SendData()
  • NRF24L01_SendDataTo()
  • NRF24L01_WaitForDataRx()
  • NRF24L01_GetRxDataAmount()
  • NRF24L01_GetData()


The ‘Intermediate APIs‘ are being used internally when a ‘Basic API’ gets called. But they offer more flexibility when used separately. The user is expected to have good knowledge on the NRF24L01 module in order successfully use the APIs listed here. The ‘Intermediate APIs’ can be sub divided into three sections.

  • Configuration APIs
  • RX device APIs
  • TX device APIs

The ‘Configuration APIs’ are used to change the NRF24L01 module parameters. It provides great flexibility to users. Following figure shows the configuration APIs.

  • NRF24L01_RegisterInit()
  • NRF24L01_InterruptInit()
  • NRF24L01_PowerDown()
  • NRF24L01_PowerUp()
  • NRF24L01_SetAirDataRate()
  • NRF24L01_SetLNAGain()
  • NRF24L01_SetPAGain()
  • NRF24L01_SetRFChannel()
  • NRF24L01_SetARC()
  • NRF24L01_SetARD()
  • NRF24L01_SetAddressWidth()
  • NRF24L01_GetStatus()


The ‘RX device APIs’ are used to control the receiver type device. Using these APIs we can control the RX interface with higher flexibility than what is possible through the ‘Basic APIs’.

  • NRF24L01_FlushRX()
  • NRF24L01_SetRxAddress()
  • NRF24L01_SetRXPacketSize()
  • NRF24L01_EnableRxMode()
  • NRF24L01_DisableRxMode()
  • NRF24L01_IsDataReadyRx()
  • NRF24L01_ReadRxPayload()
  • NRF24L01_CarrierDetect()

The ‘TX device APIs’ are the final set of APIs and they are used to control a TX type of device.

  • NRF24L01_FlushTX()
  • NRF24L01_SetTXAddress()
  • NRF24L01_SetTxPayload()
  • NRF24L01_SubmitData()
  • NRF24L01_EnableTxMode()
  • NRF24L01_DisableTxMode()
  • NRF24L01_IsTxFifoFull()
  • NRF24L01_IsTxFifoEmpty()
  • NRF24L01_AttemptTx()
  • NRF24L01_WaitForTxComplete()

IMG_20150403_150328

Usage examples:

The Simplest

(Please refer pdlib_nrf24l01_rx and pdlib_nrf24l01_tx examples)

Following flowcharts provide the easiest method to send data from one device to another. All the functions are blocking functions, so it is not that effective anyway. But it is very easy to manipulate the library such that you can use interrupts to perform the data TX and RX. Since, by default the Dynamic Payload capability is not available we need to make sure that RX and TX packet sizes are equal.

tx1
In these flowcharts the error checking is not implemented. We need to check the return codes and take required control actions to make sure we have smooth operation. For example, the SendData() API can fail if the TX FIFO is full or if the RX device is not available. In these conditions we can either Flush the TX FIFO and retry transmission or we can retry transmission without flushing the TX FIFO. It is up to the developer to take that action. Since there can be many use cases, I will not go through all the scenarios. If you come across any issue, please put a comment below.

Using interrupts

(Please refer pdlib_nrf24l01_interrupt_rx and pdlib_nrf24l01_interrupt_tx examples)

Under Intermediate APIs there is a function called NRF24L01_InterruptInit(). This function is used to configure the interrupt pin and should be ported according to the underlying processor requirements. Then within the Interrupt Service Routine of the processor, we need to add the interrupt handler function. Following are the interrupt handlers I used for the Stellaris Launchpad. Please note that this is only one way of implementing it. It may contain flows.


void TransmitDataISR(){
	int status;
	long interrupts;

	// Disable global interrupts
	ROM_IntMasterDisable();

	// Disable GPIO interrupt for PORTE
	ROM_GPIOPinIntDisable(GPIO_PORTE_BASE, GPIO_PIN_3);

	// Get the raw interrupt pin status
	interrupts = GPIOPinIntStatus(GPIO_PORTE_BASE,false);

	// Check whether triggered interrupt is the one we need
	if(interrupts & GPIO_PIN_3){

		// Clear the interrupt
		ROM_GPIOPinIntClear(GPIO_PORTE_BASE, GPIO_PIN_3);

		/* Check the interrupt status from module to see whether
		 * the interrupt is due to Data-Sent or ARC reached.
		 */

		status = NRF24L01_WaitForTxComplete(0);

		if(PDLIB_NRF24_TX_ARC_REACHED == status){

			// Retry transmission
			NRF24L01_EnableTxMode();

		}else if(PDLIB_NRF24_SUCCESS == status){

			if(0 == NRF24L01_IsTxFifoEmpty()){
				// Since TX FIFO is not empty we'll retry sending the rest of the data
				NRF24L01_EnableTxMode();
			}else{
				// No data in the FIFO we'll disable the TX mode
				NRF24L01_DisableTxMode();
				NRF24L01_PowerDown();
			}
		}
	}

	// Enable GPIO interrupts for PORTE pin 3
	ROM_GPIOPinIntEnable(GPIO_PORTE_BASE, GPIO_PIN_3);

	// Enable global interrupts
	ROM_IntMasterEnable();
}

void ReceiveDataISR(){
	int status;
	char pipe_no = 0;
	char temp;
	char data[32];
	long interrupts;

	// Disable global interrupts
	ROM_IntMasterDisable();

	// Disable GPIO interrupt for PORTE
	ROM_GPIOPinIntDisable(GPIO_PORTE_BASE, GPIO_PIN_3);

	// Get the raw interrupt pin status
	interrupts = GPIOPinIntStatus(GPIO_PORTE_BASE,false);

	// Check whether triggered interrupt is the one we need
	if(interrupts & GPIO_PIN_3){
		// Clear the interrupt
		ROM_GPIOPinIntClear(GPIO_PORTE_BASE, GPIO_PIN_3);

		// Check which pipe contains data
		status = NRF24L01_IsDataReadyRx(&pipe_no);

		if(PDLIB_NRF24_SUCCESS == status)
		{
			// Get data amount in the pipe
			temp = NRF24L01_GetRxDataAmount(pipe_no);
			//PrintRegValue("Data Available in: ",pipe_no);
			//PrintRegValue("Data amount available : ",temp);
			memset(data,0x00,32);

			// Get data from the pipe
			status = NRF24L01_GetData(pipe_no, data, &temp);
			//PrintString("Data Read: ");
			//PrintString((const char*)data);
			//PrintString("\n\r");
		}
	}

	// Enable GPIO interrupts for PORTE pin 3
	ROM_GPIOPinIntEnable(GPIO_PORTE_BASE, GPIO_PIN_3);

	// Enable global interrupts
	ROM_IntMasterEnable();
}

By using the ‘Intermediate APIs’ and ‘Advanced APIs’ one can easily configure all the other options.

IMG_20150403_180542

Porting the library:

If you change the library, and if you are willing please send me the SPI library so that I can merge that library to the main code. Or send me pull request. Please use a separate #define to separate the changes.

To change the SPI library you need to change the following functions.

  • NRF24L01_Init
  • NRF24L01_RegisterWrite_8
  • NRF24L01_RegisterWrite_Multi
  • NRF24L01_RegisterRead_8
  • NRF24L01_RegisterRead_Multi
  • NRF24L01_SendCommand
  • NRF24L01_SendRcvCommand

To change the processor you need to change following functions,

  • NRF24L01_Init
  • NRF24L01_InterruptInit
  • _NRF24L01_CELow
  • _NRF24L01_CEHigh
  • _NRF24L01_CSNLow
  • _NRF24L01_CSNHigh

Testing:

The operation of the library was tested using two Stellaris Launchpads. Launchpads were connected to the NRF24L01 modules as below.

PE1 <-> CE
PE2 <-> CSN
PD0 <-> SCK
PD3 <-> MOSI
PD2 <-> MISO
PE3 <-> IRQ

24L01Pinout-800

Important:

  • Never connect the NRF24L01 modules to 5V. They will not tolerate the supply voltage. But the logic level 5V is not harmful.
  • Make sure the TX and RX packet size is correct. This needs to be done because Dynamic Payload support is not added. (Hopefully in next release)

Git repository:

The library can be found in the following GIT repository. Within the repository you can find a few example projects created for Texas Instruments Stellaris Launchpad. Also, I have included datasheets and other relevant documents in the folder too. Please go through the Readme files for more information.

https://github.com/pradeepa-s/pdlib_nrf24l01.git

Please feel free to share your comments regarding the usability and any bugs in the library.

Thank you.

IMG_20150403_150625

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s