Experimental Communications R&D Blog -

Visit Our Sponsors:



Nordic 2.4 GHz Wireless Transceiver

(Updated: Thursday, October 24, 2013)


Nordic Semiconductor Single chip 2.4 GHz tranceiver - nRF2401A
Nordic Semiconductor Single chip 2.4 GHz tranceiver - nRF2401A

This is the Nordic Semiconductor nRF2401A a 2.4 GHz multi-channel transceiver, an 125 channels frequency hopping chip which operates from 1.9 to 3.6 V. I have decided to give it a try and probe its range of possibilities. At first glance this looks like a promissing tiny device. It is served open-faced on a breakout board which one can purchase pre-assembled by SparkFun.com. The device seems to promisse quite a bit for its small footprint, but unfortunatelly, as I found out later, it is not as reliable in streaming mode as you would expect! The problem might be the tiny chip antenna. But the packet drop levels I have found a bit discouraging at almost 45-50 percent drop rate. I was hoping to use this in video streaming applications, but I found the drop rate to be unsuitable in my opinion for such task! Why the drop rate is so high, is beyond me, but even in close proximity, the transceivers seem to drop half the packets in interleaved fashion. I am suspecting the receiving end, and I have not written significant code to allow it to frequency hop, but I suspect 2.4 GHz is a busy band so excessive external mobile interference could be the culprit here! However, needless to say, it is a fine device to perform fast sensor acquisition with and that might be something very acceptable as its ultimate use. Even some low bandwidth audio might work and I plan to do some testing on this in the future...The device is also a dual bander, offering two independent bands to send data on, so it might be possible to achieve a higher order of success sending duplicate data down the independent bands. I have not tried this yet, but I may do this for sanity check and try some frequency hopping while at it! More on this soon!

The manufacturing specs of the device from Nordic are as follows...
The manufacturing specs of the device from Nordic are as follows...

- True single chip GFSK transceiver in a small 24-pin package (QFN24 5x5mm)
- Data rate 0 to1Mbps
- Only 2 external components
- Multi channel operation
- 125 channels
- Channel switching time <200 us.
- Support frequency hopping
- Data slicer / clock recovery of data
- Address and CRC computation
- DuoCeiver topology
- ShockBurst mode for ultra-low power
- operation and relaxed MCU performance
- Power supply range: 1.9 to 3.6 V
- Low supply current (TX), typical 10.5mA peak @ -5dBm output power
- Low supply current (RX), typical 18mA peak in receive mode
- 100 % RF tested
- No need for external SAW filter
- World wide use

Schematic Diagram of nRF2401A
Schematic Diagram of nRF2401A

The device uses ShockBurst mode. The manufacturer specifies this as follows :
The ShockBurst technology uses on-chip FIFO to clock in data at a low data rate
and transmit at a very high rate thus enabling extremely power reduction.
When operating the nRF2401A in ShockBurst, you gain access to the high data
rates (1 Mbps) offered by the 2.4 GHz band without the need of a costly, high-speed
micro controller (MCU) for data processing.
By putting all high speed signal processing related to RF protocol on-chip, the
nRF2401A offers the following benefits:
- Highly reduced current consumption
- Lower system cost (facilitates use of less expensive micro controller)
- Greatly reduced risk of �on-air� collisions due to short transmission time
The nRF2401A can be programmed using a simple 3-wire interface where the data
rate is decided by the speed of the micro controller.
By allowing the digital part of the application to run at low speed while maximizing
the data rate on the RF link, the nRF ShockBurst mode reduces the average current
consumption in applications considerably

Wiring Diagram of nRF2401A
Wiring Diagram of nRF2401A

This is quite self-explanatory, but by all means do consult the Nordic manufacturer's PDF document related to the nRF2401A chip, which gives good details on how to configure their chipset and wire up the antenna...I have used the SparkFun.com board configuration, which I believe they call uMIRF. More info such as code samples for a PIC microcontroller and a PDF describing the pin layout can be found at SparkFun.com. I have used an Stellaris ARM Cortex device to control the Nordic chip instead of a PIC and this would almost be the equivalent of a DSPic and have had a decent rate of success with the exception of one area...When I connect the Data Ready pin of the receive transceiver to an input pin on the Stellaris Launchpad, I have had numerous issues with impedance! This translated to the Stellaris Launchpad 4F120h5 chip not reading the Data Ready pin correctly! However it does work fine if I disconnect the pin! Very mind boggling since the disconnected DR pin reads fine on my oscilloscope, but goes to half rail when connected on the Stellaris Launchpad...I got arround this dillema by creating a three stage state machine! I will discuss this in more detail in the next few sections, so bare with me!

The dual transceivers being connected to one Stellaris Launchpad board...
The dual transceivers being connected to one Stellaris Launchpad board...

This is probably not an optimal test configuration, but it worked great for me by using a three-state pipeline or semaphore for testing the transceiver communication! The reason we can get away with such a test-bench initially is because each transceiver has its own internal FIFO which can hold the entire 29 bytes of packet data! This is about 232 bits of data...In other words, the transceivers will wait for the code to unload by clocking out each bit out of the packet FIFO...This is quite neat, not having to build secondary FIFOs for each transceiver! This simplifies the whole process, but data buffers will still be needed to hold the transmission and receive data. I used the SparkFun examples as a starting reference and followed their usage of a shared array which gets zeroed out after each use...To share such data, we need a three-stage semaphore, which I have synchronized with the tri-colored LED available on the Stellaris Launchpad. The BLUE Led is Transmit Packet State, the GREEN Led is Receive Packet State and the RED Led is the Error State or Packed Dropped/Lost...This worked out great for my first initial test of the transceivers and the data can be pumped out at very high rates in my case excluding completely the polling of the Data Ready pin on the Receive side of the transceiver!

Config Mode Timing
Config Mode Timing

Configuration mode is done on both the Transmitter and the Receiver the only requirement is that Chip Select - CS is brought high to enter this mode...This can be done on both transmitter and receiver...I have checked on my oscilloscope that proper delays are set at 0.5 us for the clock transitions. This is what the spec requires for Thmin.
Configuration of the Transceivers is done from function UMIRF_Boot_Up().
void UMIRF_Boot_Up(void){
UART_OutString("Calling : UMIRF_Configure_Transmitter()...");
OutCRLF();
SysTick_Wait10ms(1);
UMIRF_Configure_Transmitter();
UMIRF_Configure_Receiver();
UART_OutString("UMIRF_Boot_Up() -> SUCCESS");
OutCRLF();
}
Here is the detail of one of those functions for a Stellaris Launchpad device...
void UMIRF_Configure_Transmitter(void)
{
unsigned short i, j, temp;
unsigned short config_setup[14];
//Config Mode
//GPIO_PORTB_DATA_R &= 0xF0; // clear the lower bits for tranmitter
// TX_CE = 0 ; TX_CS = 1
GPIO_PORTB_DATA_R &= ~TX_CE;
GPIO_PORTB_DATA_R |= TX_CS;
SysTick_Wait1us (5);
//Setup configuration array
//===================================================================
//Data bits 111-104 Max data width on channel 1 (excluding CRC and adrs) is 232
config_setup[0] = 232;
//Data bits 103-64 Channel 2 address - we don't care, set it to 200
config_setup[1] = 0;
config_setup[2] = 0;
config_setup[3] = 0;
config_setup[4] = 0;
config_setup[5] = 200;
//Data bits 63-24 Channel 1 address - set it to 17
config_setup[6] = 0;
config_setup[7] = 0;
config_setup[8] = 0;
config_setup[9] = 0;
config_setup[10] = 9;
//Data bits 23-16 Address width and CRC
config_setup[11] = 0x23; // 0b.0010.0011
//Data bits 15-8
config_setup[12] = 0x4f; // 0b.0100.1101; 0x6f - 1mbps
//Data bits 7-0
config_setup[13] = 0x02; // 0b.0000.1100;
//===================================================================
//Clock in configuration data
for(i = 0 ; i < 14 ; i++)
{
temp = config_setup[i];
for(j = 0 ; j < 8 ; j++)
{
//TX_DATA = temp.7;

// Look at the 7th bit if set, set TX_DATA
if (temp & 0x80)
GPIO_PORTB_DATA_R |= TX_DATA;
else // Otherwise reset...
GPIO_PORTB_DATA_R &= ~TX_DATA;

GPIO_PORTB_DATA_R |= TX_CLK1 ;// TX_CLK1 = 1
// Insert delay here via SysTick Interrupt ?
// Wait 5 + us on CLK Hi pulses
SysTick_Wait1us(1);
GPIO_PORTB_DATA_R &= ~TX_CLK1 ;// TX_CLK1 = 0
SysTick_Wait1us(1);
temp <<= 1;
}
}
SysTick_Wait1us (5);
//Configuration is actived on falling edge of CS (page 10)
// TX_CE = 0 ; TX_CS = 0
GPIO_PORTB_DATA_R &= ~TX_CE;
GPIO_PORTB_DATA_R &= ~TX_CS;
//UART_OutString("UMIRF_Configure_Transmitter -> SUCCESS");
//OutCRLF();
}

Shock Burst Transmit Configuration and Data Transmission...
Shock Burst Transmit Configuration and Data Transmission...

Post-configuration, ShockBurst Transmission is quite simple! Bring Chip Enable - CE High while making sure Chip Select - CS is Low and you're ready to go!
void UMIRF_transmit_data(void)
{
unsigned short i, j, temp, rf_address;
//DisableInterrupts();
//TX_CE = 1;
GPIO_PORTB_DATA_R |= TX_CE;
SysTick_Wait1us (5);
//Clock in address
rf_address = 9;
for(i = 0 ; i < 8 ; i++)
{
// Look at the 7th bit if set, set TX_DATA
if (rf_address & 0x80)
GPIO_PORTB_DATA_R |= TX_DATA;
else // Otherwise reset...
GPIO_PORTB_DATA_R &= ~TX_DATA;

GPIO_PORTB_DATA_R |= TX_CLK1 ;// TX_CLK1 = 1
// Insert delay here via SysTick Interrupt ?
// Wait 5 + us on CLK Hi pulses
SysTick_Wait1us(1);
GPIO_PORTB_DATA_R &= ~TX_CLK1 ;// TX_CLK1 = 0
SysTick_Wait1us(1);
rf_address <<= 1;
}
//Clock in the data_array
for(i = 0 ; i < 29 ; i++) //29 bytes
{
temp = data_array[i];
for(j = 0 ; j < 8 ; j++) //One bit at a time
{
// Look at the 7th bit if set, set TX_DATA
if (temp & 0x80)
GPIO_PORTB_DATA_R |= TX_DATA;
else // Otherwise reset...
GPIO_PORTB_DATA_R &= ~TX_DATA;

GPIO_PORTB_DATA_R |= TX_CLK1 ;// TX_CLK1 = 1
// Insert delay here via SysTick Interrupt ?
// Wait 500 + ns on CLK Hi pulses
SysTick_Wait1us(1);
GPIO_PORTB_DATA_R &= ~TX_CLK1 ;// TX_CLK1 = 0
SysTick_Wait1us(1);
temp <<= 1;
}
}
//TX_CE = 0; //Start transmission
GPIO_PORTB_DATA_R &= ~TX_CE;
//EnableInterrupts();
}

BLUE LED means TRANSMIT Packet Data Mode...
BLUE LED means TRANSMIT Packet Data Mode...

After configuration of both Transmitter and Receiver as specified in the manufacturer spec, I use an interrupt based on SysTickCounter to call UMIRF_transmit_data() and perform the BLUEON state of the State Machine...My state machine is simple and even though I specified it is a three phase state machine, it actually consists of six steps! Each step consists of two states, LED ON and LED OFF! This can be easily visualized with this enumeration :
enum LED_STATE {REDON,REDOFF,BLUEON,BLUEOFF,GREENON,GREENOFF};
Thus :
case BLUEON: // uMIRF - Transmit Data
GPIO_PORTF_DATA_R = GPIO_PORTF_DATA_R^0x04; // toggle PF2 (Blue - ON)
LedState = BLUEOFF;
packet_number ++;
// On int rollover, increment packet_number
if (packet_number > 255)
packet_number = 1;
counter = 0;
//Put some data in the ougoing array
data_array[0] = packet_number;
for(i = 1 ; i < 29 ; i++)
data_array[i] = ++counter;
UMIRF_transmit_data();
break;
I use a simple scheme for packet recognition...Incremental IDs from 0 to 255...This is to conform to the size of a byte, which allows 0xFF or 255 values. This way, I can verify quickly on the receiver that the previous packet receive incremented by one is the new packet id...This is a quick and dirty way to verify packets without going into extreme coding scenarios...

ShockBurst RECEIVE Mode...
ShockBurst RECEIVE Mode...

The State Machine Code for ShockBurst RECEIVE Mode is as follows :
case GREENON: // uMIRF - Receive Data

// if((GPIO_PORTD_DATA_R >> 1) == 0x01) //We have data!
GPIO_PORTF_DATA_R = GPIO_PORTF_DATA_R^0x8; // toggle PF3 (Green - ON)
UART_OutString ("Calling : UMIRF_receive_data...");
OutCRLF();
UMIRF_receive_data();
// Enumerate packets starting with 1 after int rollover!

if (curr_packet_id == last_packet_id)
{
DisplayData = 1;
LedState = GREENOFF;
Error = 0;
packets_received ++;

}
else // Packet id missmatch - Something went wrong (lost packet) !
{
Error = 1;
LedState = GREENOFF;
packets_failed ++;
}
break;
case GREENOFF:
GPIO_PORTF_DATA_R = GPIO_PORTF_DATA_R^0x8; // toggle PF3 (Green - OFF)
if (Error)
LedState = REDON;
else
LedState = BLUEON;

curr_packet_id ++;

if (curr_packet_id > 255)
curr_packet_id = 1;
break;
default:
LedState = GREENON;
break;
}
As soon as CE goes high, you are in RECEIVE mode...Data Ready line goes up, but I have tremendous impedance issues with the Stellaris device on the LaunchPad, so I disconnected this pin to save me some headaches! I used the simple state machine I wrote to enter the other mode and I just simply clock out the packets at this point! If no reliable data is there, the packet id's generally return zero (0). This caused me to use a simpe trick by incrementing the expected packet number to be the last reliable packet plus one. I thus avoid numbering packets starting with zero (0) and use that trick to enter packet dump mode or REDON in the state machine hierarchy!

GREEN LED means RECEIVE Packet Data Mode...
GREEN LED means RECEIVE Packet Data Mode...

Finally, we reach the last part of this quick tutorial and final point I'm going to make! To receive ShockBurst data, you can poll the Data Ready line of the first channel! However, I was having major impedance issues, so much so, that I built a transistor level converter, to push more current to the Stellaris input pin! This was also somewhat unsuccessful...I got jitter and some other unexplainable issues, including cross-talk and data appearing on the Data Ready line! Whaaa ??? Quite a numbrer of issues that I don't quite understand! It almost seems to me that the Nordic device goes into tri-state mode and that might be why it is showing the signal at half rail...I have no quick solution on how to fix this...I would expect that half-rail means floating level, which implies neither a high or a low...But a high impedance state generally knows as Z state...I'll leave it alone for now, because it seems to reconfigure the whole chip to a weird state otherwise! I have also noticed that using the SparkFun,com code example, bringing CE low, cause the Nordic device to enter low power mode, which wreaks havoc with the interpretation of the DATA line! I removed the line that causes the device to enter Low Power Mode and everything worked fine from that point! Just some gotchas for ya to watch out for with Cortex M3 ARM based devices such as the TI Stellaris! It could be something I'm doing wrong also, but it is quite hard to determine what that something wrong could be! ;)
void UMIRF_receive_data(void)
{
unsigned short i, j, temp;
//DisableInterrupts();
//RX_CE = 0;//Power down RF Front end
//GPIO_PORTB_DATA_R &= ~RX_CE;
//Erase the current data array so that we know we are looking at actual received data
for(i = 0 ; i < 29 ; i++)
data_array[i] = 0x00;
//Clock out the data
for(i = 0 ; i < 29 ; i++) //29 bytes
{
temp = 0;
for(j = 0 ; j < 8 ; j++) //8 bits each
{
temp <<= 1;

if ((GPIO_PORTB_DATA_R >> 7) == 1)
temp |= 1;
else
temp |= 0;
GPIO_PORTB_DATA_R |= RX_CLK1 ;// RX_CLK1 = 1
// Insert delay here via SysTick Interrupt ?
// Wait 500 + ns on CLK Hi pulses
SysTick_Wait1us(1);
GPIO_PORTB_DATA_R &= ~RX_CLK1 ;// RX_CLK1 = 0
SysTick_Wait1us(1);
}
data_array[i] = temp; //Store this byte
}
//RX_CE = 1; //Power up RF Front end
GPIO_PORTB_DATA_R |= RX_CE;
last_packet_id = data_array[0];
}

RED Led means Failure or Packet Dumped Mode!
RED Led means Failure or Packet Dumped Mode!

Hmmm...There is nothing interesting going on in state REDON, so where is the Error stuff ? REDON only flips the Led States on and off...
case REDON:
GPIO_PORTF_DATA_R = GPIO_PORTF_DATA_R^0x02; // toggle PF1 (Red - ON)
LedState = REDOFF;
break;
case REDOFF:
GPIO_PORTF_DATA_R = GPIO_PORTF_DATA_R^0x02; // toggle PF1 (Red - OFF)
LedState = BLUEON;
break;
It is actually processed in the GREENOFF state! LOL It sounds confusing, but the RED Led only comes on to indicate the packet was dumped! No additional processing is performed there but a RED visual cue is thus given!

Here is the resulting packet processing debugging screen...
Here is the resulting packet processing debugging screen...

All the debugging messaging is called from a main() function infinite loop...All the state printouts and failures should be in that loop, since we don't want to bog down the interrupts! This is what the section looks like :
int main(void){
PLL_Init(); // set system clock to 50 MHz
GPIO_Init(); // initialize GPIO
UART_Init(); // initialize UART
UMIRF_Init(); // initialize UMIRF modules
OutCRLF();
UART_OutString("<<<< uMIRF RF Modules Test >>>>");
OutCRLF();
UART_OutString("-- Created by : Mario Ghecea - Spiralwaveform.com --");
OutCRLF();
UART_OutString("Calling : UMIRF_Boot_Up() ...");
OutCRLF();
UMIRF_Boot_Up();
while(1)
{
if (LedState == REDOFF && Error == 1 && DisplayData == 1)
{
// Loop forever...Processing is interrupt triggered in PeriodicSysTickInts.c
UART_OutString ("UMIRF_receive_data - Failed on Packet # ");
UART_OutUDec (data_array[0]);
OutCRLF();
UART_OutString ("UMIRF Received : ");
UART_OutUDec (packets_received);
UART_OutString (" Failed : ");
UART_OutUDec (packets_failed);
OutCRLF();
Error = 0;
}
else if (LedState == GREENOFF && DisplayData == 1)
{
UART_OutString ("UMIRF_receive_data - Received Packet # ");
UART_OutUDec (data_array[0]);
OutCRLF();
UART_OutString ("UMIRF Received : ");
UART_OutUDec (packets_received);
UART_OutString (" Failed : ");
UART_OutUDec (packets_failed);
OutCRLF();
DisplayData = 0;
}
}

Speed of semaphore / tri-state leds interrupt is configurable...
Speed of semaphore / tri-state leds interrupt is configurable...

SysTick_Init (somevalue) should do the trick! You can go as low as 300 per my experiments. Higher values up to 50,000 give one second processing, but your packet drops will stay the same at either high or low interrupt speeds!
void GPIO_Init(void){

// Port F (RBG pins functionality)
SYSCTL_RCGC2_R |= SYSCTL_RCGC2_GPIOF; // activate port F

GPIO_PORTF_DIR_R |= 0x0E; // make PF2 out (built-in blue LED)
GPIO_PORTF_AFSEL_R &= ~0x0E;// disable alt funct on PF2
GPIO_PORTF_DEN_R |= 0x0E; // enable digital I/O on PF2
// configure PF2 as GPIO

GPIO_PORTF_PCTL_R = (GPIO_PORTF_PCTL_R&0xFFFFF0FF)+0x00000000;
GPIO_PORTF_AMSEL_R = 0; // disable analog functionality on PF

//EnableInterrupts();
SysTick_Init(300); // initialize SysTick timer
// while(1){ // interrupts every 1ms, 500 Hz flash
// WaitForInterrupt();
// }
}

Further experimentation required!
Further experimentation required!

This is a flexible device, but it does have its hidden quirks and gotchas! I expected the ride to be so much smoother, but it was not as quick as I expected and a bit tough to debug! Without my scope and signal analyzer, I would have been clueless as to what might have gone terribly wrong! Just that low power mode setting with CE being brought low, caused me to scratch my head for quite a while...I almost thought the Nordic chips were bad from the factory! I said, what the heck, lets try this and comment it out and data finally started clocking out to my complete surprise! Firmware can sometimes be fun and other times daunting! I'll leave it at that!

Final thoughts and note to self! Well sort of!
Final thoughts and note to self! Well sort of!

My final thoughts on this device, is that it could be useful but not a perfect cure! I see about 50 % packets being dropped...This is quite disappointing! I hear the device has a range issue also...The forum on SparkFun.com points at a bit of discontent in the community with the range of the device and SparkFun.com does not sugarcoat it either, explaining that there is a large packet loss occuring! I would like to experiment further by doing some frequency hopping experiments and transmit on alternate channels as I simultaneously switch states! Also duplicating transmission on dual-channels simultaneously, might remedy the packet drops situation! Perhaps a hybrid solution of frequency hopping and dual channel might give better results...Otherwise this device can be used for repetitive data that does not change structure much...The device is fast enough to recover by repeating the information at very fast rates...Sensor information might be a good candidate and perhaps lossy audio! Only the future can tell what other uses it may have! Hope you found this tutorial useful for your own applications! Contact me with questions and suggestions or needs you may have!


Primary Engineers:

  • Mario Ghecea

  • Thank you for visiting this page! Hope to see you back soon!

    Please note that the technology described hereign is copyrighted and patent pending material!
    Do not try to replicate this technology for private, commercial or distribution purposes.
    Obtaining permission and written consent from Spiral Waveform Technologies prior to its use!

    Submit all inquiries to:
    dreamsmatrix@gmail.com.