Problem talking to nRF24l01 module

Hi, I’m doing a school project to turn a slot car into being controlled by RF. I’m using a PIC18F2620 and the Olimex RF unit since the MiRF v2 was sold out when we had to buy parts and we’re under time constraints. I am using the library from Brennon’s library from diyembedded.com and I am having trouble talking over SPI.

After searching on these forums I found some SPI comm test code, basically writing to the TX_ADDR register and then reading back to see if the values are the same. Here is the code I’m using:

#include <p18f2620.h>
#include <spi.h>

//function prototypes
unsigned char spi_Send_Read(unsigned char);

// Defines
#define SPI_SCK     LATCbits.LATC3      // Clock pin, PORTC pin 3
#define SPI_SO      LATCbits.LATC5      // Serial output pin, PORTC pin 5
#define SPI_SI      PORTCbits.RC4          // Serial input pin, PORTC pin 4
#define SPI_CSN		LATCbits.LATC0      // CSN output pin, PORTC pin 2
#define SPI_CE      LATCbits.LATC6      // CE output pin, PORTC pin 1
#define SPI_IRQ     PORTBbits.RB0      // IRQ input pin, PORTB pin 0
#define SPI_SCALE   4                    // postscaling of signal

// Macros
#define nop() _asm nop _endasm

void main(void)
{
   unsigned char status = 0;
  // unsigned char data[5];
   unsigned char one = 0;
	unsigned char two = 0;
	unsigned char three = 0;
	unsigned char four = 0;
	unsigned char five = 0;

   PORTA = 0x00;
   ADCON1 = 0x0F;      // set up PORTA to be digital I/Os
   TRISA = 0x00;      // set up all PORTA pins to be digital outputs
   TRISCbits.TRISC3 = 0;   // SDO output
   TRISCbits.TRISC5 = 0;   // SCK output
   TRISCbits.TRISC0 = 0;   // CSN output
   TRISCbits.TRISC6 = 0;   // CE output
   SPI_CSN = 1;      // CSN high
   SPI_SCK = 0;      // SCK low
   SPI_CE   = 0;      // CE low
   nop();

   OpenSPI(SPI_FOSC_64, MODE_00, SMPMID);
   

   //write TX_ADDRESS register
   SPI_CSN = 0;         //CSN low
   spi_Send_Read(0x30);
   spi_Send_Read(0x11);
   spi_Send_Read(0x22);
   spi_Send_Read(0x33);
   spi_Send_Read(0x44);
   spi_Send_Read(0x55);
   SPI_CSN = 1;         //CSN high



   //read TX_ADDRESS register
   SPI_CSN = 0;         //CSN low
   status = spi_Send_Read(0x10);
   one = spi_Send_Read(0x00);
   two = spi_Send_Read(0x00);
   three = spi_Send_Read(0x00);
   four = spi_Send_Read(0x00);
   five = spi_Send_Read(0x00);
   SPI_CSN = 1;         //CSN high

   while (1)
      ;
}

/********************************************************************
 * Function:        unsigned char spi_Send_Read(unsigned char bytein)
 *
 * Description:     This function outputs a single byte onto the
 *                  SPI bus, MSb first. And it gets the response.
 *******************************************************************/
unsigned char spi_Send_Read(unsigned char bytein)
{
	SSPBUF = bytein;
	
	while(!DataRdySPI());
	
	return SSPBUF;
}

the values I get back are the following:

status = 0x11

one = 0x80

two = 0x00

three = 0x00

four = 0x00

five = 0x00

I’ve tried this with both of my modules and I get the same response for both. When I unplug the module, everything is 0x00. It definitely seems like it’s talking to something, I just don’t know what’s going on with it.

One possibility is that earlier while working with it, I had forgotten to attach my ground bus to ground, so I wasn’t getting any response. During this time I had attached it to 5V thinking that it needed the same reference voltage as my PIC. I later found out that 5V was over the max voltage and since I discovered my ground issue, I have only attached it to 3.3V. Could this cause the behavior I’m seeing? I stupidly did this with both modules even after I told myself not to attach both to 5V in case that it was too much, I’d be able to prove it later if only one was attached. But when one wasn’t working I switched to check the other and forgot about the 5V thing.

Anyway, any help would be greatly appreciated.

If you’re using my library, you’re going to have to change the #defines for all your pins in nrf24l01.h, as well as the #include for your uC’s include library.

yes, I changed all of the p18f452.h includes to be p18f2620.h, as well as the defines for the CSN and CE pins in nrf24l01.h

i technically have it working now, but not with the PIC hardware SPI functions. I used the software SPI send_read_byte function that was provided with that SPI comm test code I found on here. I thought I had tried that before I posted on here, but when I just tried using the software SPI driver it worked. I don’t really understand why the hardware function did not work though.

You will likely have to change the settings in my InitializeIO function. You have to make sure that CSN, MOSI, and SCK are outputs and MISO and IRQ are inputs. First, disconnect the 24L01 from your circuit. Then verify that CSN works by just calling my void nrf24l01_set_csn() and void nrf24l01_clear_csn() functions in a loop with delay between them and watch the output on a multimeter (you may need a delay 2 or 3 seconds between the calls).

If that works, tie CSN to your IRQ pin (at the point where you would normally connect the 24L01) and see if setting/clearing CSN has the same effect on the IRQ pin’s bit in the PIC (you may just do a printf or something like that to show that IRQ has changed state). Do this by using the nrf24l01_irq_pin_active() function in my library. It is important to check things at the same place you hook up your 24L01 to also check for wiring problems.

With that working, your non-SPI-related IO should work properly. You can test that the SPI pins are at least being set to the proper direction in the same fashion as with the CSN and IRQ pins (make sure you remove any calls to OpenSPI() ). Also be sure to do these tests at the same place you would hook up your 24L01.

Assuming your PIC only has 1 SPI port (I didn’t check), the OpenSPI() function call that is in my code should also work for yours. Tie the MISO and MOSI pins together and send data over MOSI. Make sure you can read the exact same value you sent on MOSI on MISO in the PIC. You should test all values from 0 to 255 (easily accomplished in a loop). See the datasheet to find the flag that indicates SPI reception is complete to do this.

At this point, only the SCK pin is left to be tested, and the only good way to do that is on a scope. Without a scope, at this point you would reconnect the 24L01 as you had it and test the code in my tutorials and see if it takes off from there.

thanks for the reply. I tested everything you said, the SPI port is confusing my though. I’m using MPLAB with ICD2 to debug while it’s running. I tried two different send_read_byte functions. One is what you had in your library:

<main>
	while(1)
	{
		
	       result = SPI_TEST(test);

		if (result != test)
		{
			status = status;
			while(1)
			{
				test++;
			}
		}
		if (result == 255)
		{
			status = status;
			while(1);
		}
		test++;
   }
</main>

unsigned char SPI_TEST(unsigned char byte)
{
        unsigned char temp = 0;
	SSPBUF = byte;
	
	while(!DataRdySPI())
        {
             temp++;
        }
	
	return SSPBUF;
}

When I run this it appears to work at first. It gets into the ‘test == 255’ if statement. But if I don’t attach the MISO-MOSI lines together, it will still get to that line as if everything is fine. However, if I put a breakpoint inside the while loop, it will then fail if the lines are disconnected, and will pass if it’s connected. I tried putting a delay inside, and it still didn’t fail when it was disconnected. I don’t understand why it’s doing that.

However if I comment out the OpenSPI and use this code for the send_read_byte function everything works as expected:

unsigned char spi1_send_read_byte(unsigned char bytein)
{
	static unsigned char i;         // Loop counter
   	static unsigned char j;         // Delay counter
   	unsigned char byteout = 0;      // return bit

    SPI_SCK = 0;                        // Ensure SCK is low
    for (i = 0; i < 8; i++)         // Loop through each bit
    {
        if (bytein & 0x80)          // Check if next outbit is a 1
        {
            SPI_SO = 1;                 // If a 1, pull SO high
        }
        else
        {
            SPI_SO = 0;                 // If a 0, pull SO low
        }
        byteout = byteout << 1;     // Shift outbyte left for next bit
        if (SPI_SI == 1)                // Check if next inbit is a 1
        {
            byteout |= 0x01;         // If a 1, set next bit to 1
        }
        else
        {
            byteout &= 0xFE;         // If a 0, set next bit to 0
        }
        SPI_SCK = 1;                    // Bring SCK high to latch bit
        for (j = 0; j < SPI_SCALE; j++)
      {
         nop();                      // Avoid violating Thi
      }
        SPI_SCK = 0;                    // Bring SCK low for next bit
        bytein = bytein << 1;       // Shift byte left for next bit
    }
   return byteout;
}

using this last spi function, I can talk to the unit, but I’m still having problems with things. I never get the TX_DS interrupt from the unit and I noticed that when I try to TX and after the CE pin goes low-high-low, if I read registers, things are off. For example, if I read the EN_AA register after using your default initialization routine, it reads 0x00, but if I read it after triggering the CE pin, it will be 0x3F.

So that’s where I am right now, using the SW SPI function I can talk with it, but it still seems like something’s wrong. And the HW SPI buffer doesn’t seem to be shifting right or something.

Assuming you have your UART connected to your PC’s serial port, you could try something like this:

#include "nrf24l01.h"

void main()
{
	unsigned int txvar;
	unsigned int rxvar;

	Initialize();

	for(txvar = 0; txvar <= 255; txvar++)
	{
		rxvar = txvar;
		nrf24l01_spi_send_read(&rxvar, 1, true);

		if(rxvar != txvar)
			printf("rx (0x%x) != tx (0x%x)\r\n", rxvar, txvar);
	}
}

I didn’t actually compile this code, but it will give you a very starting point to test if you’re actually getting the SPI to work with the 24L01 library.

i don’t have a serial port on my computer (laptop). i’ve just been connecting everything on a breadboard and looking at things with MPLAB while debugging.

here is my code currently, I added a ‘nrf24l01_check_debug_initialization’, which just issues a ‘read all registers’ command and then checks to see if the value in each register matches what should be in it. I pass that if statement, but I never exit the while loop that checks the IRQ. I know that the pin is accepting input and I have updated the masks based on the tests mentioned in the fourth post. I checked the CE output on an osciliscope and it is high for ~12.4us. I’m at a loss as to what is going on.

/*****************************************************************************
*
* File: maintutorial1local.c
* 
* Copyright S. Brennen Ball, 2007
* 
* The author provides no guarantees, warantees, or promises, implied or
*	otherwise.  By using this software you agree to indemnify the author
* 	of any damages incurred by using it.
* 
*****************************************************************************/

//This code depends on a clock frequency of 40 MHz (I use a 10 MHz crystal and
//  the PLL.  If you are using a slower clock, then you must change the SPI and
//  UART rates, as well as the delay lengths in delays.c

#include <p18f2620.h>
#include <stdio.h>
#include "spi1.h"
#include "delays.h"
#include "nrf24l01.h"

void Initialize(void);
void InitializeIO(void);

//main routine
void main(void)
{
	unsigned char data = 'A', test, status; //register to hold letter sent and received

	Initialize(); //initialize IO, SPI, set up nRF24L01 as TX

	status = nrf24l01_initialize_debug_check(false, 1, false);
	if (status != 0xFF)
	{
		status = nrf24l01_read_register(nrf24l01_CONFIG, &test, 1);
	}
		
	nrf24l01_write_tx_payload(&data, 1, true);

	while(!(nrf24l01_irq_pin_active() && nrf24l01_irq_tx_ds_active()));

	nrf24l01_irq_clear_all();

	while(1);
}


//initialize routine
void Initialize(void)
{
	OSCCON = 0x7C;
	InitializeIO(); //set up IO (directions and functions)
	nrf24l01_initialize_debug(false, 1, false); //initialize the 24L01 to the debug configuration as TX, 1 data byte, and auto-ack disabled
}

//initialize IO pins
void InitializeIO(void)
{
	ADCON1 = 0x7; //disable AD converter functionality on PORTA
	TRISA = 0xFB; //make PORTA2 an output to control LED
	PORTAbits.RA2 = 1; //turn on LED
	
	TRISBbits.TRISB0 = 1; //make sure that PORTB.0 is input since it is IRQ pin
	TRISCbits.TRISC4 = 1; //SDI input
	
	PORTC = 0x01; //set CSN bit
   	TRISCbits.TRISC3 = 0;   // SDO output
   	TRISCbits.TRISC5 = 0;   // SCK output
  	TRISCbits.TRISC0 = 0;   // CSN output
   	TRISCbits.TRISC6 = 0;   // CE output
	
}

roppetty:
I pass that if statement, but I never exit the while loop that checks the IRQ.

You need to separate the while statement into two parts to see which condition you're not passing. If you're not passing "!(nrf24l01_irq_pin_active()", then you almost certainly have something wrong with either your register settings for IRQ, the #defines in nrf24l01.h for IRQ, or the wiring between the nrf24l01's IRQ pin and your PIC's IO pin you're using to read IRQ. All of these have to be verified.

You should be able to run this code with a scope on the IRQ line. I would disconnect the nrf24l01’s IRQ pin from the PIC, and instead connect a pullup resistor to the PIC’s IRQ input. At this point, run your code and watch the nrf24l01’s IRQ pin on a scope. If it doesn’t start high and then go low after you tried to send the packet, then you haven’t set up the 24l01 properly. If it does send the packet, then the problem is either wiring or PIC setup.

If the above situation verifies that the 24L01 is working properly, remove the pullup from the PIC and reconnect your 24L01’s IRQ line. Run the same test as above, but connect your oscilloscope probe directly to the PIC pin that is connected to IRQ (or as close as possible to try to detect a wiring fault). If you still see the value start high and go low when you send the packet, you have not configured something correctly internally in the PIC or you don’t have a correct define in nrf24l01.h for IRQ. You will have to fix that problem before you can proceed if you still want to use the IRQ pin.

It is possible to bypass the IRQ pin entirely. Simply remove the call to nrf24l01_irq_pin_active() in the while loop you’re getting stuck in. Then it will only read the value of the TX_DS interrupt from the 24L01 internally. You could try this to verify that you are setting up the 24L01 correctly and that you indeed have a problem with only the IRQ pin.

i can’t do anything until i get to our lab where the programmers/scopes are locked up, but I have already checked the interrupt pin and it does not appear to go low. i don’t know how i could be setting it up wrong, i use the functions in your library and i even read them to make sure they are what was set to.

i’ve noticed that after the transmit_payload function, it will mess up the registers. for example, before the call, if i read the EN_AA register, it will be 0, but after the call it will read something like 0x3F. I later traced it to the CE pulse. If I pass false to the transmi_payload function and read a register, it’s fine, then if i pulse the CE line by calling transmit function and read the register, it’s messed up. is this supposed to happen?

after classes i’ll get to the lab and double check the irq pin

Have you checked to make sure that the power supply is sufficient? The only thing my transmit function does is pulse the CE pin. It doesn’t even write to the SPI, so there’s no way that would be changing your values if everything is set up properly. You don’t maybe have the CE and IRQ pins crossed when they are wired to the PIC do you? That could cause a short with IRQ which could easily corrupt the registers inside the 24L01.

Is there any way you could copy the section of nrf24l01.h that deals with the IO registers and post it here when it’s available to you?

i know that’s all it does, which is why it doesn’t make sense to me lol. there’s nothing weird about the olimex wiring is there? i left the tx/rx pins out and connected the rest as described. tested everything from the end of the connector that goes to the module so i know it’s getting signals…

as for power, how can i tell that? i have a 5v regulator with 12v input. 5v going to the PIC and then a voltage divider with 1k/2.2k resistors to bring the 5v down to ~3.3V for the nrf24l01 input. this is my first time doing stuff like this, so i have no idea how much power is needed or what kind of effect it has on things. i saw that the nrf24l01 didn’t need much current, so i figured a 1k/2.2k would be fine, do i need to go lower to allow more current through?

i plan on going to the lab at noon so i’ll be able to test some more things here in a bit. here is the section from the .h file

#include <p18f2620.h>

//defines for uC pins CE pin is connected to
//This is used so that the routines can send TX payload data and 
//	properly initialize the nrf24l01 in TX and RX states.
//Change these definitions (and then recompile) to suit your particular application.
#define nrf24l01_CE_IOREGISTER		PORTC
#define nrf24l01_CE_PINMASK			0x40

//defines for uC pins CSN pin is connected to
//This is used so that the routines can send properly operate the SPI interface
// on the nrf24l01.
//Change these definitions (and then recompile) to suit your particular application.
#define nrf24l01_CSN_IOREGISTER		PORTC
#define nrf24l01_CSN_PINMASK		0x01

//defines for uC pins IRQ pin is connected to
//This is used so that the routines can poll for IRQ or create an ISR.
//Change these definitions (and then recompile) to suit your particular application.
#define nrf24l01_IRQ_IOREGISTER		PORTB
#define nrf24l01_IRQ_PINMASK		0x01

It looks like your configuration in nrf24l01.h is consistent with what you have in InitializeIO(). Since the nrf24l01.h has 5V-tolerant I/O (check the front page of the datasheet if you don’t believe me), you don’t need any type of level conversion between your PIC and the 24L01 (for either direction). The output of the 24L01 is high enough that it will register properly on your PIC, and the output of the PIC isn’t so high it’ll fry the chip. A straight wire is all you need between the pins.

If you’re using the MiRF-v2 from SparkFun as your RF unit, you can feed the 5V from the Olimex PIC dev board into VCC on the MiRF-v2. Its regulator can handle up to 7V on the input, IIRC. It says whatever it is on its respective product page.

brennen:
If you’re using the MiRF-v2 from SparkFun as your RF unit, you can feed the 5V from the Olimex PIC dev board into VCC on the MiRF-v2. Its regulator can handle up to 7V on the input, IIRC. It says whatever it is on its respective product page.

i’m using the Olimex RF unit because both of the MiRF-v2 versions were sold out and i was on a time constraint. everything is on a breadboard right now, the PIC, power supply, etc.

i have no level conversion between the nrf and the pic, just straight weires like you said. the only conversion is to get 3.3V for the power input to the nrf24l01 since i do not believe the olimex unit has a regulator

roppetty:
the only conversion is to get 3.3V for the power input to the nrf24l01 since i do not believe the olimex unit has a regulator

This is a really bad idea. Depending on the load that the 24L01 is pulling, that voltage could do down severely. You NEED a voltage regulator here. If I were you, I would get either a 3.3V fixed or an adjustable regulator set to 3.3V to power the 24L01. Assuming the regulator supports it, you should be able to connect it to the 12V supply or the 5V coming from your 18F452 board (although you may want a low-dropout regulator if you use 5V as its input). This is almost certainly the reason you're seeing weird things happening.

A voltage divider only works if whatever is connected to its output has a really high impedance. When you’re pulling a really high current through the 24L01, it’s pulling a really high current through those resistors, which will drop its supply voltage (the voltage divider basically sees a low resistance at its output).

k, i’m in the lab and the interrupt does get triggered, but only for 200-300us and then it goes back high even though i don’t clear the interrupt, it may be resetting itself due to voltage drop. even when it gets triggered, it only drops to 1V, which is why i didn’t notice it getting triggered before, i didn’t have my trigger up high enough. i will get an adjustable voltage regulator and try it that way. thanks for all the help so far, hopefully this voltage thing is it. don’t know how long it would take me to figure that out on my own, really wish i had the mirfv2 right now haha.

i made a couple regulators out of some op amps and i got everything working as it should. i’ll have some adjustable regulators for my actual implementation. thanks a ton for all of the help. sorry for the noobish talk, never done stuff like this before really

Good to hear! We all have to start somewhere… 8)