This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nRF24L01 transmission issue on PIC24 uC

Hello,

I am trying to communicate between two nRF24L01+ radio modules, both of which are connected to separate PIC24FJ64GA002 uCs. It seems as though the transmitter unit is transmitting, because the MAX_RT bit is getting set multiple times. However, the receiver module is never getting data in the receive FIFO. The main functions for the TX and RX are not meant to work perfectly; at this point, I'm just trying to see that the RX module is getting data in the RX FIFO. 

I'll try to save some time and answer your questions before you ask them.

  1. We've tried at both low power and high power modes. 
  2. We've tried different data rates.
  3. The units are about 1 m away from each other. 
  4. The carrier detect register (RPD) is never 1. We've tried different channels.
  5. We've tried the default address (E7E7E7E7) for the TX and RX.
  6. We are using a power adapter with capacitors built-in to avoid power issues.
  7. We are able to read and write to any register that we want.

We truly think that we must be missing a small detail, but we have become so numb to this code that we are probably missing it. I'd appreciate any advice that you can give.

Thank you,

Nick

*Note that I did not include header files, and I removed some of the #include statements, as they contained my full name.

SPI Code:

#include <p24Fxxxx.h>
#include <xc.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>

void setupSPI(void){
    
    //---------------------------- PPS SETUP ---------------------------------------
    __builtin_write_OSCCONL(OSCCON & 0xbf); // unlock PPS
    RPOR3bits.RP7R = 7;     //assign RB7 as SPI1 data output p. 119 in 'general purpose info'
    RPOR4bits.RP8R = 8;     //assign RB8 as SPI1 clock output
    RPINR20bits.SDI1R = 9;  //assign RB9 as SDI1 data input
    __builtin_write_OSCCONL(OSCCON | 0x40); // lock PPS
    //------------------------------------------------------
    
    SPI1CON1 = 0;       //start at zeros during setup
    SPI1CON2 = 0;
    
    SPI1CON1bits.MSTEN = 1;         //Master mode
    SPI1CON1bits.DISSCK = 0;        //Internal SPIx clock is enabled
    SPI1CON1bits.DISSDO = 0;        //SDOx pin is controlled by the module
    SPI1CON1bits.MODE16 = 0;        //Communication is byte-wide (8 bits) MAY NEED TO CHANGE TO word-wide(set to 1)
    SPI1CON1bits.SMP = 1;           //Input data sampled at end of data output time MAY NEED TO CHANGE
    SPI1CON1bits.CKP = 0;           //Idle state for clock is a low level; active state is a high level
    SPI1CON1bits.CKE = 1;           //Serial output data changes on transition from active clock state to Idle clock state (see bit 6)
    SPI1CON1bits.SPRE = 0b110;          //set secondary pre-scaler to 2:1
    SPI1CON1bits.PPRE = 0b01;          //set primary pre-scaler to 16:1. SCK = 500 kHz (may need to change if this is too fast)
    
    SPI1STATbits.SPIROV = 0;        //clear the overflow bit
    
    IPC2bits.SPF1IP = 5;            //set the SPI interrupt to 5 (higher than the ADC or TMR interrupt)
    SPI1STATbits.SPIEN = 1;         //Enables module and configures SCKx, SDOx, SDIx and SSx as serial port pins
}

unsigned char writeSPI(unsigned char byte)     //this function sends a single SPI byte. Can be used to read the last byte sent to the slave
{   
	SPI1BUF = byte;                     //the transaction begins immediately after this
    while(SPI1STATbits.SPITBF);        //wait for the contents of SPI1TXB to get moved to SPI1SR
    while(!SPI1STATbits.SPIRBF);        //wait for the SPI1RBF (receive buffer) to fill up
    return SPI1BUF;
}	

void testSPI(unsigned char data)
{
    SPI1BUF = data;
    while(!SPI1STATbits.SPIRBF);        //wait until you receive data
}

void nrf24l01_spi_send_read(unsigned char * data, unsigned int len, bool copydata)      //this is someone else's function that we are using to test functionality
{
	unsigned int count;
	unsigned char tempbyte;

	for(count = 0; count < len; count++)
	{
		if(copydata != false)
			data[count] = writeSPI(data[count]);
		else
		{
			tempbyte = data[count];
			writeSPI(tempbyte);
		}
	}
}

#include <p24Fxxxx.h>
#include <xc.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>

void writeRegister(unsigned char reg, unsigned char val);
void set_TX_ADDR(void);
void set_RX_ADDR(void);
unsigned char readRegister(unsigned char regDest);
void sendTestPayload(unsigned char data);
unsigned char read_RX_payload(unsigned char* payloadData, unsigned int len);
void set_as_tx(void);
void set_as_rx(void);
void readPayload(unsigned char num, unsigned char* data);
unsigned int readBit(unsigned char reg, unsigned char bitNum);
void clearBit(unsigned char reg, unsigned char bitNum);
void setBit(unsigned char reg, unsigned char bitNum);
void clearInterrupts(void);
void set_as_tx(void);
void set_as_rx(void);
void flushTX(void);
void flushRX(void);
void initRadioTX(void);
void initRadioRX(void);
void setupTX(void);
void setupRX(void);

void setupTX(void){                         //setup function for the transmitter circuit (basically just configures the pins).
    
    CLKDIVbits.RCDIV = 0;                   //set the clock frequency to 16 MHz
    
    __builtin_write_OSCCONL(OSCCON & 0xbf); // unlock PPS
    RPINR0bits.INT1R = 15;     //assign RB15 as INT0 interrupt pin
    __builtin_write_OSCCONL(OSCCON | 0x40); // lock PPS
    
    TRISBbits.TRISB15 = 1;                  //IQR - input
    TRISBbits.TRISB9 = 1;                   //MISO - input
    TRISBbits.TRISB7 = 0;                   //MOSI - output
    TRISBbits.TRISB8 = 0;                   //SCK - output- may not even need this since we used PPS
    TRISBbits.TRISB13 = 0;                  //CSN - output
    TRISBbits.TRISB12 = 0;                  //CE - output
    
    INTCON2bits.INT1EP = 1;                 //interrupt on negative edge
    IEC1bits.INT1IE = 1;                    //enable the INT1 interrupt
    IFS1bits.INT1IF = 0;
}

void setupRX(void){                         //setup function for the receiver circuit. DOES THIS FUNCTION NEED TO CHANGE SINCE IT IS THE RECEIVER?
    
    CLKDIVbits.RCDIV = 0;                   //set the clock frequency to 16 MHz
    
    TRISBbits.TRISB15 = 1;                  //IQR - input
    TRISBbits.TRISB9 = 1;                   //MISO - input
    TRISBbits.TRISB7 = 0;                   //MOSI - output
    TRISBbits.TRISB8 = 0;                   //SCK - output- may not even need this since we used PPS
    TRISBbits.TRISB13 = 0;                  //CSN - output
    TRISBbits.TRISB12 = 0;                  //CE - output
}

void writeRegister(unsigned char reg, unsigned char val)
{
    CSN = 0;
    writeSPI(W_REGISTER | reg);
    writeSPI(val);
    CSN = 1;
}

void set_TX_ADDR(void){     //write multiple bytes to a register
    int i;
    unsigned char tx_rx_addr[5];      //initialize an array to hold the receiver and transmitter address

    tx_rx_addr[0] = 0xCC;
    tx_rx_addr[1] = 0xCC;
    tx_rx_addr[2] = 0xCC;
    tx_rx_addr[3] = 0xCC;
    tx_rx_addr[4] = 0xCC;
    
    CSN = 0;
    writeSPI(W_REGISTER | TX_ADDR);
    for(i = 0; i < 5; i++){
        writeSPI(tx_rx_addr[i]);
    }
    CSN = 1;
}

void set_RX_ADDR(void){     //write multiple bytes to a register
    int i;
    unsigned char tx_rx_addr[5];      //initialize an array to hold the receiver and transmitter address

    tx_rx_addr[0] = 0xCC;
    tx_rx_addr[1] = 0xCC;
    tx_rx_addr[2] = 0xCC;
    tx_rx_addr[3] = 0xCC;
    tx_rx_addr[4] = 0xCC;
    
    CSN = 0;
    writeSPI(W_REGISTER | RX_ADDR_P0);
    for(i = 0; i < 5; i++){
        writeSPI(tx_rx_addr[i]);
    }
    CSN = 1;
}

void flushTX(void){     //this function flushes the TX FIFO buffer
    CSN = 0;
    writeSPI(FLUSH_TX);
    CSN = 1;
}

void flushRX(void){     //this function flushes the RX FIFO buffer
    CSN = 0;
    writeSPI(FLUSH_RX);
    CSN = 1;
}
   
void initRadioTX(void){                     //initialize the radio in transmit mode  http://www.microchip.com/forums/m837540.aspx
    
    CSN = HIGH;
    msDelay(100);
    CE = LOW;
    msDelay(2);
    
    writeRegister(CONFIG, 0x0E);    //enable PWR_UP and EN_CRC 

    writeRegister(EN_AA, 0x01);     //enable auto-acknowledge on pipe-0
   
    writeRegister(EN_RXADDR, 0x01);      //Enable data pipe 0 RX address
    
    writeRegister(SETUP_AW, 0x03); //set the address width to 5 bytes (this reduces the potential for receiving packets from other 2.4 GHz devices)
    
    writeRegister(SETUP_RETR, 0xFF);     //enable retransmit; wait 4000 us; up to 15 attempts to retransmit on fail of AA
    
    writeRegister(RF_CH, 0x05); //set channel 5
    
    writeRegister(RF_SETUP, 0x20); //set data rate to 250 kbps; set RF power to -18 dBm (low power)
    
    writeRegister(RX_PW_P0, 0x01);       //allow 1 byte in the RX payload in pipe 0

    flushTX();
    flushRX();
    
    set_TX_ADDR();           //set the TX address to 0xBCDEFABCD
    set_RX_ADDR();
    
    set_as_tx();         //set the device to standby mode   
}

void initRadioRX(void){                     //initialize the radio in receive mode
    
    CE = LOW;
    
    writeRegister(CONFIG, 0x0F);    //enable PWR_UP and EN_CRC 

    writeRegister(EN_AA, 0x01);     //enable auto-acknowledge on pipe-0
    
    writeRegister(EN_RXADDR, 0x01);      //Enable data pipe 0 RX address
    
    writeRegister(SETUP_AW, 0x03); //set the address width to 5 bytes (this reduces the potential for receiving packets from other 2.4 GHz devices)
    
    writeRegister(SETUP_RETR, 0xFF);     //enable retransmit; wait 4000 us; up to 15 attempts to retransmit on fail of AA
    
    writeRegister(RF_CH, 0x05);          //set channel 5
    
    writeRegister(RF_SETUP, 0x20);       //set data rate to 1 MB; set RF power to 0 dBm

    writeRegister(RX_PW_P0, 0x01);      //allow 1 byte in the RX payload in pipe 0
    
    flushTX();
    flushRX();
    
    set_TX_ADDR();           //set the TX address to 0xBCDEFABCD
    set_RX_ADDR();
    
    set_as_rx();         //set the device to receive mode
}

unsigned char readRegister(unsigned char regDest)
{
    unsigned char rRegVal;
    CSN = 0;
    writeSPI(regDest);           //send a command to the radio that says that you want to read a specific register
    rRegVal = writeSPI(NOP);       //send a dummy command to receive data back from the slave
    CSN = 1;
    return rRegVal;
}

void sendTestPayload(unsigned char data)
{   
    CSN = 0;
    writeSPI(W_TX_PAYLOAD);
    writeSPI(data);
    CSN = 1;

    CE = 1;
    usDelay(20);   //Delay for 12 us (datasheet that a 10 us minimum period is required)
    CE = 0;
}

unsigned char read_RX_payload(unsigned char* payloadData, unsigned int len)
{
	unsigned char status;
    
	CE = 0;
	status = writeSPI(R_RX_PAYLOAD);         //tell the radio that we want to read the payload
    *payloadData = writeSPI(NOP);        //write a junk value to read the 
	CE = 1;
	
    clearInterrupts();
    
	return status;
}

void clearInterrupts(void){     //clears the RX_DR, TX_DS, and MAX_RT bits in the STATUS register by writing 1s to them
    setBit(STATUS, STATUS_TX_DS);
    setBit(STATUS, STATUS_RX_DR);
    setBit(STATUS, STATUS_MAX_RT);
}

void set_as_tx(void)
{
    CE = 0; //NOTE THAT THIS NEED TO GET CHANGED BACK TO 1 AT SOME POINT IN THE PROGRAM BEFORE TRANSMITTING
	clearBit(CONFIG, CONFIG_PRIM_RX);
    setBit(CONFIG, CONFIG_PWR_UP);
}

void set_as_rx(void)
{		
	setBit(CONFIG, CONFIG_PRIM_RX);
	CE = 1;
}

void clearBit(unsigned char reg, unsigned char bitNum){
    unsigned char originalVal, newVal;
    originalVal = readRegister(reg);
    
    if((originalVal & bitNum) == 0)
		return;
	
	newVal = originalVal & (~bitNum);
	
	writeRegister(reg, newVal);
}

void setBit(unsigned char reg, unsigned char bitNum){
    unsigned char originalVal, newVal;
    originalVal = readRegister(reg);
    
    if((originalVal & bitNum) == 0)
		return;
	
	newVal = originalVal | (bitNum);
	
	writeRegister(reg, newVal);
}

unsigned int readBit(unsigned char reg, unsigned char bitNum){        //returns a 1 or 0 depending on the value of the bit that you want to check
    unsigned char originalVal;
    originalVal = readRegister(reg);
    
    if((originalVal & bitNum) == 0){        //if the bit masked with the standard mask is 0, the bit is a 0, return 0
		return 0;
    }
	else{
        return 1;
    }
}

#include "xc.h"
#include <p24Fxxxx.h>
#include <xc.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>

// PIC24FJ64GA002 Configuration Bit Settings
// CW1: FLASH CONFIGURATION WORD 1 (see PIC24 Family Reference Manual 24.1)
#pragma config ICS = PGx1          // Comm Channel Select (Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1)
#pragma config FWDTEN = OFF        // Watchdog Timer Enable (Watchdog Timer is disabled)
#pragma config GWRP = OFF          // General Code Segment Write Protect (Writes to program memory are allowed)
#pragma config GCP = OFF           // General Code Segment Code Protect (Code protection is disabled)
#pragma config JTAGEN = OFF        // JTAG Port Enable (JTAG port is disabled)
// CW2: FLASH CONFIGURATION WORD 2 (see PIC24 Family Reference Manual 24.1)
#pragma config POSCMOD = NONE           // Primary Oscillator Select (Primary oscillator disabled. 
					// Primary Oscillator refers to an external osc connected to the OSC1 and OSC2 pins)
#pragma config I2C1SEL = PRI       // I2C1 Pin Location Select (Use default SCL1/SDA1 pins)
#pragma config IOL1WAY = OFF       // IOLOCK Protection (IOLOCK may be changed via unlocking seq)
#pragma config OSCIOFNC = ON       // OSC2/CLKO/RC15 functions as port I/O (RC15)
#pragma config FCKSM = CSECME      // Clock Switching and Monitor (Clock switching is enabled, 
                                   // Fail-Safe Clock Monitor is enabled)
#pragma config FNOSC = FRCPLL      // Oscillator Select (Fast RC Oscillator with PLL module (FRCPLL))

#define BUF_SIZE_POW  8  // buffer size = 2 ^ BUF_SIZE_POW
int potBuf0[1 << BUF_SIZE_POW], potBuf1[1 << BUF_SIZE_POW], potBuf2[1 << BUF_SIZE_POW], potBuf3[1 << BUF_SIZE_POW] = {0};         //size 1024 when BUF_SIZE_POW = 10

volatile int curIdx, lastIdx = 0;
volatile unsigned int testData = 0b0100000000000010;
volatile unsigned char testRegVal;
volatile unsigned int testInt, maxRT = 0;
volatile unsigned int statusTest, configTest;
unsigned char retransmitData;

void __attribute__ ((__interrupt__,__auto_psv__)) _ADC1Interrupt(void)       //AD interrupt
{
    _AD1IF = 0;                         //clear the AD interrupt bit
    
    potBuf0[curIdx] = ADC1BUF0;                 //store the current buffer value in the data buffer
    potBuf1[curIdx] = ADC1BUF1;
    potBuf2[curIdx] = ADC1BUF2;
    potBuf3[curIdx++] = ADC1BUF3;               //increment the buffer # on the final time
    
    curIdx &= (1 << BUF_SIZE_POW) - 1;          //increment the index value. Set back to 0 when it = the size of the buffer
}

void __attribute__((__interrupt__,__auto_psv__)) _T3Interrupt(void)     //This timer is used to control the A/D module
{
    IFS0bits.T3IF = 0;              //clear the Timer3 interrupt
}

void __attribute__((__interrupt__,__auto_psv__)) _INT1Interrupt(void)     //This interrupt monitors the IRQ pin
{
    IFS1bits.INT1IF = 0;
}

int main(void) {
    
    AD1PCFG = 0x9FF0;       //set all of the pins, except the analog pins for the pots, to digital
    
    setupADC();
    setupSPI();
    setupTX();                      //uncomment for final code.
    initRadioTX();
    
    unsigned char data = 0xBD;      //test data to be sent to the receiver
    
    while(1){

        statusTest = readRegister(STATUS);
        
        sendTestPayload(data);
        msDelay(500);
        retransmitData = readRegister(OBSERVE_TX);
        
        if(readBit(STATUS, STATUS_MAX_RT) == 1){
            maxRT++;
        }

        msDelay(1000);
          } 
}

#include "xc.h"
#include <p24Fxxxx.h>
#include <xc.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>

// PIC24FJ64GA002 Configuration Bit Settings
// CW1: FLASH CONFIGURATION WORD 1 (see PIC24 Family Reference Manual 24.1)
#pragma config ICS = PGx1          // Comm Channel Select (Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1)
#pragma config FWDTEN = OFF        // Watchdog Timer Enable (Watchdog Timer is disabled)
#pragma config GWRP = OFF          // General Code Segment Write Protect (Writes to program memory are allowed)
#pragma config GCP = OFF           // General Code Segment Code Protect (Code protection is disabled)
#pragma config JTAGEN = OFF        // JTAG Port Enable (JTAG port is disabled)
// CW2: FLASH CONFIGURATION WORD 2 (see PIC24 Family Reference Manual 24.1)
#pragma config POSCMOD = NONE           // Primary Oscillator Select (Primary oscillator disabled. 
					// Primary Oscillator refers to an external osc connected to the OSC1 and OSC2 pins)
#pragma config I2C1SEL = PRI       // I2C1 Pin Location Select (Use default SCL1/SDA1 pins)
#pragma config IOL1WAY = OFF       // IOLOCK Protection (IOLOCK may be changed via unlocking seq)
#pragma config OSCIOFNC = ON       // OSC2/CLKO/RC15 functions as port I/O (RC15)
#pragma config FCKSM = CSECME      // Clock Switching and Monitor (Clock switching is enabled, 
                                   // Fail-Safe Clock Monitor is enabled)
#pragma config FNOSC = FRCPLL      // Oscillator Select (Fast RC Oscillator with PLL module (FRCPLL))

unsigned char testRegVal;
int testInt, configVal1, configVal2;

int main(void) {
    
    AD1PCFG = 0x9FF0;       //set all of the pins, except the analog pins for the pots, to digital
    TRISBbits.TRISB6 = 0;       //enable output for the test LED at RB6
    
    setupSPI();
    setupRX();                      
    initRadioRX();

    while(1){
        

        
        while(!readBit(STATUS, STATUS_RX_DR));   //wait for data to be received (waits for the data to come into the RX FIFO)
        
        status = read_RX_payload(&data, 1);     //read out the contents of the packet
        
        testInt++;
        
        flushRX();
        
        if(data == 0xBD){
            LATBbits.LATB6 ^= 1;        //toggle the LED every time that a new transmission is received.
        }
    }
         
}

#include <p24Fxxxx.h>
#include <xc.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>

bool is_TX = FALSE;         //this boolean variable will be used in functions that need to know if it is the transmitter or receiver

void usDelay(int delay_in_us){
      int i = 0;
      for(i = 0; i < delay_in_us; i++){
        wait_1ms(); }
  }

void msDelay(int delay_in_ms){
      int i = 0;
      for(i = 0; i < delay_in_ms; i++){
        wait_1ms(); }
  }

void setupADC(void){
    
    // setup Timer 3
    T3CON = 0;
    TMR3 = 0;
    PR3 = 2500;                         //period is 1.25 ms
    T3CONbits.TCKPS = 2;                // pre 1:8
    
    // Setup A/D
    AD1CON1 = 0;                        //start all of the control registers @ 0
    AD1CON2 = 0;
    AD1CON3 = 0;

    TRISAbits.TRISA0 = 1;               //set RA0 as input for POT1
    TRISAbits.TRISA1 = 1;               //set RA1 as input for POT2
    TRISBbits.TRISB2 = 1;               //set RB2 as input for POT3
    TRISBbits.TRISB3 = 1;               //set RB3 as input for POT4
    
    AD1CON1bits.ASAM = 1;               // auto-sampling. P21, Sec. 17.7.3.3 in the ADC
    AD1CON1bits.SSRC = 0b010;           // Use Timer3 as the conversion trigger
    AD1CON1bits.SAMP = 0;               // A/D Sample-and-Hold amplifier is holding
    
    AD1CON2bits.CSCNA = 1;              //Scan inputs selected by the AD1CSSL register as the MUX A input
    AD1CON2bits.SMPI = 3;               //Interrupts at the completion of conversion for each 4th sample/convert sequence. 
    AD1CON2bits.BUFM = 0;               //Buffer configured as one 16-word buffer (ADC1BUF0 to ADC1BUFF)
    
    AD1CON3bits.ADCS = 2;               // enable 2* clock division to allow for TAD == 125 ns > 75 ns
    IFS0bits.AD1IF = 0;                 // clear AD interrupt flag just in case
    
    AD1CSSLbits.CSSL0 = 1;              // use CSSL0, CSSL1, CSSL4, and CSSL5. 
    AD1CSSLbits.CSSL1 = 1;
    AD1CSSLbits.CSSL4 = 1;
    AD1CSSLbits.CSSL5 = 1;
    
    IEC0bits.AD1IE = 1;                 // Enable A/D interrupt
    AD1CON1bits.ADON = 1;               // Turn on A/D module
    
    IEC0bits.T3IE = 1;                  // Enable TMR3 interrupt
    T3CONbits.TON = 1;
   
    AD1CON1bits.SAMP = 1;               //start sampling
}

unsigned int ADtoDeg(unsigned int servoNum, unsigned int bufferVal){
    unsigned int dummyInt = 0;  //added to remove warning when compiling. Remove later
    return dummyInt;
}

Assembly Code (for delay functions):

.include "xc.inc"
.text                       ;BP (put the following data in ROM(program memory))
; This is a library, thus it can *not* contain a _main function: the C file will
; deine main().  However, we
; we will need a .global statement to make available ASM functions to C code.
; All functions utilized outside of this file will need to have a leading
; underscore (_) and be included in a comment delimited list below.
.global _wait_1us, _wait_1ms

_wait_1us:
    repeat #9
    nop 

    return
    
    
_wait_1ms:
    repeat #15993
    nop
    
    return
    
.end

  • Hi Nick,

     

    I cannot spot anything out of the ordinary. Given that the RPD bit is never set to '1', it almost sounds like you are communicating on different addresses.

    My first recommendation is to strip down the example to the bare minimum, rely on using the reset values (0xe7e7e7e7, 3 bytes payload, etc) and just set the PRIM_RX = 0 on your transmitter, and 1 on your receiver.

    It would also be interesting to see what goes over the SPI lines.

    Q1: Could you scope the SPI lines and post it here?

    Q2: Where did you obtain the L01+ modules? Could you post a picture of your two modules?

     

    Best regards,

    Håkon

Related