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.
- We've tried at both low power and high power modes.
- We've tried different data rates.
- The units are about 1 m away from each other.
- The carrier detect register (RPD) is never 1. We've tried different channels.
- We've tried the default address (E7E7E7E7) for the TX and RX.
- We are using a power adapter with capacitors built-in to avoid power issues.
- 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