nRF24L01 timing issues

Using PIC 18F2455 right now with [LMC6482A and an MOSFET to supply 3V. I “ported” the sparkfun PIC code to C18 and am using the C18 SPI functions from spi.h.

It seems that the RX side only actually receives data from the transmitter if it is sent three (or more) times (sending a 4-byte payload like the SF example). The RX_IRQ goes low on the first transmit, but nothing gets read out of the RX FIFO (but all zeros) unless I wait for the third transmission.

Code can get pasted here if ya’ll want it.](http://www.national.com/pf/LM/LMC6482.html)

Fixed. Seems like the nRF24L01 in ShockBurst RX mode needs you to lower CE to read out of the RX FIFOs. Doesn’t seem to be very clear in the data sheet except maybe:

p11: “The MCU can access the FIFOs at any time, in power down mode, in standby modes, and during RF packet transmission.”

or

p15: 6. MCU sets the CE pin low to enter Standby-I mode.

  1. MCU can clock out the payload data at a suitable rate via the SPI interface.

Not terribly obvious to me, but what do I know :), I just started playing with these.

Welcome to the clunkiest data sheet around. There is a whole lot of stuff that is not in there.

There doesn’t seem to be much of a repository of working code for these things. Am I missing something?

No you are not. I have some C code I will share, but it is all for Atmel AVR and it probably won’t work on anything else. I found out the hard way that porting code can be problematic for the RF2401 becaue of timing issues.

Jim Lake

I’m guessing that the post I made about a month ago with .h and .c include files for the nRF24L01 went unnoticed. It implements pretty much every function available on the chip and is very well-commented.

brennen:
I’m guessing that the post I made about a month ago with .h and .c include files for the nRF24L01 went unnoticed. It implements pretty much every function available on the chip and is very well-commented.

I downloaded it and tried compiling it, but some header files were missing. What compiler did you use?

Leon

I used the 4.1 version of GCC that came with the newer version of WinARM to compile. Upon re-inspecting the code today, I noticed that in nrf24l01.c, you can take out the “ lcd.h” line since that code isn’t used (I was testing the code using an LCD to spit values out and forgot to remove the include).

As far as nrf24l01.h, lines 44-46 must be changed for your own personal chip. CE_IOREGISTER must be defined as whatever register on your microcontroller that handles the actual pin output for your CE pin. CE_PINMASK is the mask that is used to clear and set this pin within CE_IOREGISTER. Since I’m using the LPC2148, I have included the file lpc214x.h since it contains the definitions for the registers I’m defining.

For example, my project is using pin P0.21 on my microcontroller to control the CE pin on the nRF24L01. Hence, register FIO0PIN controls that pins on or off status, and that is why I have the statement “#define CE_IOREGISTER FIO0PIN”. Also, the mask for pin 21 is 0x200000 (this is a 32 bit register), which is the reason for the statement “#define CE_PINMASK 0x200000”.

The last thing you have to do yourself is provide the function “void spi1_send(unsigned char * data, unsigned int len)”. The purpose of this function is to allow you to send an array of characters (unsigned char * data) with a specified length (unsigned int len) while constantly holding CSN low. I had to do this because the LPC2148 wouldn’t work properly in any of its automatic modes, so I made the function manually hold CSN low.

Other than that, I can’t think of anything else right off hand that you should need. In the case that anyone is using an LPC21xx with an SSP controller on it, I can provide you with my SPI code as well.

brennen:
I used the 4.1 version of GCC that came with the newer version of WinARM to compile. Upon re-inspecting the code today, I noticed that in nrf24l01.c, you can take out the “ lcd.h” line since that code isn’t used (I was testing the code using an LCD to spit values out and forgot to remove the include).

As far as nrf24l01.h, lines 44-46 must be changed for your own personal chip. CE_IOREGISTER must be defined as whatever register on your microcontroller that handles the actual pin output for your CE pin. CE_PINMASK is the mask that is used to clear and set this pin within CE_IOREGISTER. Since I’m using the LPC2148, I have included the file lpc214x.h since it contains the definitions for the registers I’m defining.

For example, my project is using pin P0.21 on my microcontroller to control the CE pin on the nRF24L01. Hence, register FIO0PIN controls that pins on or off status, and that is why I have the statement “#define CE_IOREGISTER FIO0PIN”. Also, the mask for pin 21 is 0x200000 (this is a 32 bit register), which is the reason for the statement “#define CE_PINMASK 0x200000”.

The last thing you have to do yourself is provide the function “void spi1_send(unsigned char * data, unsigned int len)”. The purpose of this function is to allow you to send an array of characters (unsigned char * data) with a specified length (unsigned int len) while constantly holding CSN low. I had to do this because the LPC2148 wouldn’t work properly in any of its automatic modes, so I made the function manually hold CSN low.

Other than that, I can’t think of anything else right off hand that you should need. In the case that anyone is using an LPC21xx with an SSP controller on it, I can provide you with my SPI code as well.

Thanks. I’m using the CrossWorks tools which are based on gcc. I’ve just got a bit further with it.

The following routines actually seem to be required: spi1_clear_fifo(), DelayUS(), spi1_send() and spi1_read().

Inserting stubs for them and a dummy main(), the whole thing seems to build OK.

Leon

brennen:
I’m guessing that the post I made about a month ago with .h and .c include files for the nRF24L01 went unnoticed. It implements pretty much every function available on the chip and is very well-commented.

I looked at your code. Thanks for posting it. A few more prototypes are needed for spi reads, but otherwise, it seems nicely complete.

However, a quick glance at it yesterday I didn’t notice the flipping of the CE pin during RX FIFO read. Of course, it was just a cursory glance, so I may be wrong. Did it work for you without putting the chip out of enable?

As far as I can tell, there are no problems with the read code (I’m using it as I type this message). You do have to be careful about clearing your interrupts and fifos when reading from and writing to the nRF24L01s (this is done automatically in my code).

brennen:
As far as I can tell, there are no problems with the read code (I’m using it as I type this message). You do have to be careful about clearing your interrupts and fifos when reading from and writing to the nRF24L01s (this is done automatically in my code).

Hrm. The version of your code does not clear any interrupts during the nrf24l01_read_rx_payload(…) operation. It does clear the spi fifo, which I will look into on my side. Also, I figure you’re talking about the code that uses your nrf library.

My procedure for RX is:

  • CE = 0

  • spi send 0x61 (R_RX_PAYLOAD)

  • spi read payload

  • reset interrupt (0x27)

  • flush RX FIFO (0xE2)

  • CE = 1

Do you see anything wrong with that? The version of your library (just the RX function) I have is pasted below.

Thanks.

void nrf24l01_read_rx_payload(unsigned char * data, unsigned int len, bool includestatus)
{
	char buffer[33];
	unsigned int count;
	unsigned int numbytes;
	
	spi1_clear_fifo();
	
	if(includestatus == false)	
		numbytes = len + 1;
	else
		numbytes = len;
	
	buffer[0] = nrf24l01_R_RX_PAYLOAD;

	spi1_send(buffer, numbytes);
	
	for(count = 0; count <= len; count++)
		buffer[count + 1] = data[count];
		
	if(includestatus == false)
		spi1_read();
	
	for(count = 0; count < len; count++)
		data[count] = spi1_read();
}

Looks like you have pointed out a discrepancy in my comments. Instead of calling nrf24l01_clear_flush() in my call to nrf24l01_read_rx_payload() in the library, I actually call it in my main() function in my main.c file. I don’t make this call when I transmit at all though, as the TX_DS interrupt apparently clears itself when you write to the TX FIFO again.

As far as CE goes, the only time I make any change to it in my receiver code is in the nrf24l01_initialize() function (you’re not supposed to make changes to registers with it high). I can’t find anywhere else that changes it at all for the receiver, be it in the library or my other code. The transmitter will toggle CE in order to send the data payload, but, like I said, I don’t have to change CE to read from the 24L01 (at least not with the ones I have). YMMV, though.

Thanks for the insight. It may still be a timing problem on my side that is alleviated (slightly) by toggling the CE. I will try messing with my SPI buffer flush now and report back.

ok, update.

my method of turning of CE to read from RX FIFOs seems to work.

brennen’s method of clearing the RX_FIFO before reading the payload works as well.

so, i have code that does

  1. flush rx fifo

  2. read rx payload

  3. reset interrupts

and that works fine.

It seems counter-intuitive to me to flush the FIFOs before reading the payload. I thought the payload would already be in the FIFOs? I will revisit the datasheet.

thanks for the help, brennen. I now have code that works and is fairly clean. I didn’t end up using your code, but it is definitely a good starting point now that I understand the RX FIFO should be cleared.

If you’re talking about flushing the SPI FIFOs, you want to flush them first just in case there’s something in there that you don’t want (my spi_read function depends on the FIFO being empty to terminate reading).

If you’re talking about flushing the nRF24L01’s FIFOs before reading, however, that would be a bad thing. I flush the RX FIFO on the 24L01 AFTER I read it, but certainly not before. The reason I do this is that there seems to be a silicon error in the 24L01 in that the RX FIFO does not clear itself like the datasheet says it is supposed to. You must clear the FIFO yourself in order to get the chip to continue reading stuff.

When I think about it, however, this may be a good reason why you do your CE toggle. Maybe it’s not a silicon error, but simply that I don’t take CE low to read from the RX FIFO that is the reason my RX FIFO never empties. I will try implementing the CE toggle on receive in my next round of code to see if it allows me to not have to clear the FIFO manually.

Looks like we have helped each other out on this one. Thanks, mchang.

So we’re on the same page, here is a code snippet that works for me. It is called when the RX_IRQ goes low. Nothing else is called before this routine.

void rx_getdata(void) {
  unsigned char instruction, data, i;
  
  // Flush RX FIFO
  instruction = 0xE2;
  RX_CSN = 0;
  spi_write(instruction);
  RX_CSN = 1;

  // read the RX payload
  instruction = 0x61;
  RX_CSN = 0;
  spi_write(instruction);

  // read the four byte payload
  for( i=0; i<4; i++ ) {
    spi_read(&data_array[i]);
  }
  RX_CSN = 1;

  // reset interrupt
  instruction = 0x27;
  data = 0x70;
  RX_CSN = 0;
  spi_write(instruction);
  spi_write(data);
  RX_CSN = 1;
}

The same with CE toggle and no FIFO flush works as well:

void rx_getdata(void) {
  unsigned char instruction, data, i;

  RX_CE = 0;
  
  // read the RX payload
  instruction = 0x61;
  RX_CSN = 0;
  spi_write(instruction);

  // read the four byte payload
  for( i=0; i<4; i++ ) {
    spi_read(&data_array[i]);
  }
  RX_CSN = 1;

  // reset interrupt
  instruction = 0x27;
  data = 0x70;
  RX_CSN = 0;
  spi_write(instruction);
  spi_write(data);
  RX_CSN = 1;  
    
  // Flush RX FIFO
  instruction = 0xE2;
  RX_CSN = 0;
  spi_write(instruction);
  RX_CSN = 1;

  RX_CE = 1;
}

But in the second case, not flushing the FIFO after reading causes the error you talk about. I am not flushing the SPI interface before the read, at the moment, and it works okay.

I don’t know why it works to flush the RX FIFO before reading from it. Like I said (and you said), it doesn’t seem right.

For now, I’m going to stick with:

  1. CE = 0

  2. Flush SPI buffers

  3. Read RX payload

  4. Reset interrupts

  5. Flush RX FIFO

That seems to work reliably. Any thoughts on the before read flush working?

Also, one thing that might be different is in how we initiate the R_RX_PAYLOAD.

Yours has:

spi1_send(buffer, numbytes);

where only buffer[0] has the R_RX_PAYLOAD command and the rest of the bytes are garbage.

Mine just sends the 0x61 command (R_RX_PAYLOAD) and follows it with the appropriate number of SPI reads.

The datasheet is unclear from Table 8, since it says the R_RX_PAYLOAD instruction can carry a number of data bytes (which should mean nothing).

Probably not a big deal, just an observation.

From what I’ve gathered from the datasheet, what you clock into MOSI on the nRF24L01 doesn’t really matter after you get the R_RX_PAYLOAD instruction in there. What matters is how many bytes you clock in. That essentially decides how many bytes you get out of it. I can’t really find any reason to clock any formatted or initialized data into the 24L01 in this situation once the command byte is clocked in.

Apparently it doesn’t make any difference when you flush the RX FIFO when you don’t toggle CE, since you do it before the read and I do it after the read. This leads me to believe that when CE is left high, the actual bytes in the FIFO don’t change at all. This makes sense to me, since the datasheet says that you shouldn’t change register status while CE is left high (apparently the chip won’t let you change these registers). The net effect is simply that in combination with clearing the RX_DS interrupt flag, you are able to receive data again.

It still seems strange that even when you do the CE toggle routine you still have to clear the RX FIFO. I would think that with the CE pin being low, you could read the RX FIFO until it emptied itself (like the datasheet says it will).

As far as the spi_clear_fifo(), I only do that so that I am sure to only have the just-read data in the SPI FIFO. Like I pointed out, my spi1_read function simply returns all the data in the SPI FIFO until it has been emptied. For this reason, I doubt that it would matter if you cleared out your SPI FIFO or not unless your spi_read works like mine (which it does not appear to).

Yay. Forum spam.