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

Comms Between NRF52 Dev Kits using NRF24L01+ modules

Hello, this is a continuation from a different question I previously had answered: https://devzone.nordicsemi.com/f/nordic-q-a/40452/nrf24l01-spi-communication-with-nrf52-dk

I am using two NRF52 Dev Kits and two NRF24L01+ modules to try and communicate wirelessly without needing to use the NRF52832's radio as I would like to leave open the option of also using a soft device and BLE in the future when working with these products. I am using the Nordic SDK 14.2.0 and have started from some base code written by a Nordic engineer from the previous question: link . My end goal is to have two NRF52832s writing to/ reading from NRF24L01+ modules to communicate with each other custom 5B addresses with auto acknowledgement, 2B CRC, 2Mbps data rate, and ideally also dynamic payload length but for now static is fine as well.

I can read and write from the modules over SPI seemingly successfully: I have compared against the datasheet and everything is set as expected. All the functions from the example set up given to me appear to work correctly as I can set any specific register to whatever values I need it to be easily in my NRF24L_init. I have taken out most of the settings I plan on using in the future (RF channels, data rates, custom addresses, etc.) and have left the init mostly empty to leave the modules on as default the settings as I can get them since I keep reading that they should be pretty good to go from just powering up. Here are the settings for my two units set in their init functions:

//PRX
#define PAYLOAD_WIDTH 32    //1-32B

#define SPI_INSTANCE  0     //enabled in config & using EASY DMA, 6 IRQ priority, 8MHz
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);    //SPI instance

l01_instance_t NRF24L;
l01_config_t NRF24L_config = L01_MODULE_CONFIG(SPI_INSTANCE, CE_PIN, CSN_PIN, SPI_SCK_PIN,
                                               SPI_MOSI_PIN, SPI_MISO_PIN, IRQ_PIN);

void NRF24L_init(void)
{
    NRF24L.spi_instance = spi;
    NRF24L.pin_ce = CE_PIN;
    NRF24L.pin_irq = IRQ_PIN;   //GPIOTE 6 IRQ priority
    NRF24L.pin_csn = CSN_PIN;

    hal_nrf_nrf52_init(&NRF24L, &NRF24L_config, NRF24L_irq_handler); //Hardware prepared

    nrf_delay_ms(100);                        //delay 100ms for coming online after power loss
    hal_nrf_set_power_mode(HAL_NRF_PWR_DOWN); //power down internals, entering power down mode
    nrf_delay_us(1500);                       //wait 1.5ms to settle
    hal_nrf_set_power_mode(HAL_NRF_PWR_UP);   //power up internals, entering standby mode I
    nrf_delay_us(1500);                       //wait 1.5ms to ensure in standby mode I

    hal_nrf_clear_irq_flags_get_status(); //clear all IRQ flags in case any are set
    hal_nrf_flush_rx(); //empty out everything in RX FIFO in case they contain data
    hal_nrf_flush_tx(); //empty out everything in TX FIFO in case they contain data

    for(uint8_t i = 0; i < 6; i++){
        hal_nrf_set_rx_payload_width(i, PAYLOAD_WIDTH); //sets payload witdh for all rx pipes (0-5)
    }

    hal_nrf_set_operation_mode(HAL_NRF_PRX);  //Set as receiver
    hal_nrf_chip_enable(CE_ENABLE); //Enter Rx mode
    nrf_delay_ms(10);
}
//PTX
#define PAYLOAD_WIDTH 32    //1-32B

#define SPI_INSTANCE  0     //enabled in config & using EASY DMA, 6 IRQ priority, 8MHz
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);    //SPI instance

l01_instance_t NRF24L;
l01_config_t NRF24L_config = L01_MODULE_CONFIG(SPI_INSTANCE, CE_PIN, CSN_PIN, SPI_SCK_PIN,
                                               SPI_MOSI_PIN, SPI_MISO_PIN, IRQ_PIN);

void NRF24L_init(void)
{
    NRF24L.spi_instance = spi;
    NRF24L.pin_ce = CE_PIN;
    NRF24L.pin_irq = IRQ_PIN;   //GPIOTE 6 IRQ priority
    NRF24L.pin_csn = CSN_PIN;

    hal_nrf_nrf52_init(&NRF24L, &NRF24L_config, NRF24L_irq_handler); //Hardware prepared

    nrf_delay_ms(100);                        //delay 100ms for coming online after power loss
    hal_nrf_set_power_mode(HAL_NRF_PWR_DOWN); //power down internals, entering power down mode
    nrf_delay_us(1500);                       //wait 1.5ms to settle
    hal_nrf_set_power_mode(HAL_NRF_PWR_UP);   //power up internals, entering standby mode I
    nrf_delay_us(1500);                       //wait 1.5ms to ensure in standby mode I

    hal_nrf_clear_irq_flags_get_status(); //clear all IRQ flags in case any are set
    hal_nrf_flush_rx(); //empty out everything in RX FIFO in case they contain data
    hal_nrf_flush_tx(); //empty out everything in TX FIFO in case they contain data

    for(uint8_t i = 0; i < 6; i++){
        hal_nrf_set_rx_payload_width(i, PAYLOAD_WIDTH); //sets payload witdh for all rx pipes (0-5)
    }

    hal_nrf_set_operation_mode(HAL_NRF_PTX);  //Set as receiver

    nrf_delay_ms(10);
}

My previous setup with this works when I replace the transmitter NRF24L01+ module with an NRF52 Dev Kit running a slightly modified ESB Tx example from the proprietary RF folder, including custom addresses, address widths, payload widths, RF channels, data rates, transmitting powers, 2B CRC, auto ACKs, basically everything EXCEPT dynamic payload lengths, which was breaking it for some reason.

I've been experiencing some anomalies when leaving the transmitter and receiver both running for several minutes where in the PTX will detect a successful transmission (TX data sent IRQ read from status register in the interrupt handler) in a sea of failures to transmit (max retransmits hit without an acknowledge). When debugging the receiver the PRX will trigger a data received interrupt properly (RX data received IRQ) and most of the data is correct, but in seemingly random spots the values will change randomly. I could not find any rhyme or reason to the change; I tried repeatedly sending a 32B array with value 0 in the first index incrementing up to 31 in the final index as constants and additionally incrementing index 1 every time. I have checked on the transmitter side and the data being sent is as expected:

Packet No. | 0 | 1 | 2 | 3 | ... | 31
     0     | 0 | 1 | 2 | 3 | ... | 31
     1     | 0 | 2 | 2 | 3 | ... | 31
     2     | 0 | 3 | 2 | 3 | ... | 31
     etc.

I've tried changing settings one at a time with this setup and thus far I've found is RF channels need to be the same for this to occur (obvious) and enabling dynamic payload length or disabling auto acknowledges break it. The only significant change I noticed was when changing the CRC, but this only confused me further since my results are contradictory to what I understand about CRC.

  • No CRC [ hal_nrf_set_crc_mode(HAL_NRF_CRC_OFF); ] => no successful packets are sent
  • 1B CRC [ hal_nrf_set_crc_mode(HAL_NRF_CRC_8BIT); ] => periodic "successful" packets sent, one every couple of seconds
  • 2B CRC [ hal_nrf_set_crc_mode(HAL_NRF_CRC_16bit); ] => rarely "successful" packets sent, one maybe every 30 seconds to a minute

At this point I'm fairly confident I do not have any errors with my wiring (SPI & IRQ seem to work correct for both reading & writing + interrupt triggers properly) or power supply since I often read that adding capacitance by the modules helps stabilize them: both modules have a 0.1uF ceramic cap & 100uF electrolytic cap nearby and there doesn't appear to be much ripple on the voltage seen on an oscilloscope. I've also tested with multiple modules to ensure that the units I had weren't damaged and even fresh ones I hadn't touched before behave in the same manner.

My understanding of how these modules works is as follows:

Set PTX's TX_ADDR address to match whatever receiving pipe is used on PRX as that is the address the PRX will transmit to for sending an ack
Match the PTX's pipe 0 address to match the TX_ADDR before sending a packet, this will be scanned after transmitting for any incoming ack
If no ack is received after a set delay reattempt this process for the set number of retransmit attempts until either receive an ack or hit the max limit
Generate an interrupt appropriate for whether data was sent proper or not on PTX & generate a data received interrupt on PRX when a non-repeat packet is received

All the documentation, threads, and articles I've read on this seem to be pretty clear on how things must be set up (though it's unfortunate a lot of Arduino related stuff refer to "reading pipes" & "writing pipes" when most nothing else I found does) and I believe that I am correctly setting things up, especially since I am able to use an NRF24L01+ as a receiver when using my NRF52 Dev Kit running ESB as the transmitter. The settings for everything should be basically identical on both the PTX and PRX modules except for the PRIM_RX bit in the configuration register (Reg 0x00) as that controls whether it is acting as a PTX or PRX. To send data, use the W_TX_PAYLOAD SPI command then send the data bytes over SPI continuously to write a packet into the TX FIFO and then pulse the CE pin high for at least 10us to begin transmitting following the flow chart from page 33. For receiving, set the device as a PRX & set the CE pin high to enter receiving mode where it will continuously check the air for incoming packets following the flow chart from page 35.

If I have something basic wrong in here, PLEASE let me know. We have quite a few of these modules on hand at the moment and we would like to use these in various projects but just can't get the darn things to work!

  • Hi Andy

    I helped you with the last case, and will handle this one as well ;)

    One thing we should check first is a known issue that can occur of you have a bit error in the length field of the packet that makes the packet look larger than 32 bytes. 

    To avoid this issue it is necessary to check the length of the incoming packet before reading it out, and flush the RX FIFO if the problem is detected. 

    1. Wait for the RX interrupt to occur

    2. Read the length of the next packet in the RX FIFO using the hal_nrf_read_rx_payload_width() function.

    3. If the payload length is longer than the max payload length that you expect, flush the RX fifo

    4. If the payload length is as expected, proceed to read out the packet as normal. 

    One thing worth noting is that I am a bit unsure if the implementation of the hal_nrf_read_rx_payload_width() function is correct. 

    Please give me a try and let me know what it returns. Tomorrow I will set up a small test myself to verify if it is correctly implemented. 

    Best regards
    Torbjørn

  • Hi again Ovrebekk,

    Here is the code I have on the PRX side that's reading the payload and printing it out now. An oddity I'm seeing is the early data in it (B31 & B0 to ~B9) seem to never be corrupted, at least in the hundreds of "successes" I've checked through so far, and the late data (~B24 - B30) seem to near always be corrupted in some manner.

    void NRF24L_irq_handler(void)
    {
        irq_hit++;
        test_LED_toggle();  //toggles pin 20, connected to Dev Kit LED4
    
        uint8_t temp_flags = hal_nrf_get_clear_irq_flags();
        if(temp_flags & (1 << HAL_NRF_MAX_RT)){
            printf("\tirq = max retransmit\n");
            flag_max_rt = true;
        }
        if(temp_flags & (1 << HAL_NRF_TX_DS)){
            printf("\tirq = tx data sent\n");
            flag_tx_ds = true;
        }
        if(temp_flags & (1 << HAL_NRF_RX_DR)){
            printf("\tirq = rx data received\n");
            flag_rx_dr = true;
            
            uint8_t temp_len = hal_nrf_read_rx_payload_width();
            if(temp_len == PAYLOAD_WIDTH){
                printf("packet size: %d\n", temp_len);   //check packet size
    
                hal_nrf_read_rx_payload(rx_data); //reads 1 packet from RX FIFO
    
                for(uint8_t i = 0; i < PAYLOAD_WIDTH; i++){
                    printf("\ti%d", i); //prints out index count to make columns with
                }
                printf("\n");
    
                NRF24L_rx_last_byte_swap(rx_data);  //undoes LSB First
    
                for(uint8_t i = 0; i < PAYLOAD_WIDTH; i++){
                    printf("\t%d", rx_data[i]); //prints out each byte of data
                }
                printf("\n");
    
                printf("\t1: %d", rx_data[1]);  //singles out the byte incrementing on PTX side
    
                for(uint8_t i = 2; i < PAYLOAD_WIDTH; i++){
                    if(rx_data[i] != i){
                        testing_flag = true;
                    }
                }
               if(rx_data[0] != 0){
                    testing_flag = true;
                }
    
                if(testing_flag){
                    printf("\tData mismatch!!\n");  //all data doesn't match expected values
                }
                else{
                    printf("\t Data matches\n");    //all data does match expected values
                }
    
                for(uint8_t i = 0; i < PAYLOAD_WIDTH; i++){
                    rx_data[i] = 0;  //rewrites old data
                }
    
                irq_hit_total += irq_hit;
                printf("irqs-%d (%d)\n", irq_hit_total, (uint8_t)irq_hit_total);    //# of IRQs hit
                irq_hit = 0;
            }
            else{
                printf("Wrong width!\n");
            }
        }
    }

    The payload width doesn't seem to be a problem here, my setup is still only receiving packets randomly every couple of seconds and all of them are printing out that they are the correct width (32B). I'm going to keep experimenting with different ideas in the mean time, and I hope your test comes up with something useful!

    edit: Some of the testing I've done so far has just been really weird and I'm reconsidering if I have my SPI working 100% correctly. By default I've been transmitting on the PTX to address 0xE7E7E7E7E7 & expecting packets on its pipe 0 from there which corresponds with the PRX's pipe 0. I started to check the rx data source and payload length values that are returned from the function hal_nrf_read_rx_payload and I was getting pipe 0 and a length of 32B as expected. I changed the PTX addresses to be 0xC2C2C2C2C2, which should correspond to the default address of pipe 1 on the PRX but it the value returned said it received from pipe 2. I set the PTX addresses to 0xC2C2C2C2C3 to match the PRX's default for pipe 2 and I didn't get a single packet across. I'm not sure what might be causing this at all.

    edit's edit: I'm a goofball, I didn't check to see that all the rx data pipes were being enabled properly when doing that testing. By default only pipes 0 and 1 are enabled so after calling "hal_nrf_open_pipe(HAL_NRF_ALL, true);" in my PRX init I was able to receive data (still intermittently every few seconds and corrupted further and further into the payload) on all six channels as expected. Targeting address 0 claimed the RX source was pipe 0, address 1 pipe 1, address 2 pipe 4, etc. which looks like it's just an oddity in how the hal_nrf_read_rx_payload function returns the rx source and payload length. It returns a 16 bit value with the payload length in the low byte and rx source pipe in the high byte. Shifting and ANDing easily separates the values and payload length can be read by just typecasting as 8 bit or ANDing 0x00FF to remove the high byte. Shifting or using an 8 bit pointer to separate the pipe number takes a little more effort though as the returned value is actually just the status register's value received by calling a NOP masked with 0x0E to remove any bits not related to the pipe source. This leave 0b 0000 XXX0 though, which is shifted left once and hence all the numbers are doubled. At no point was I actually receiving from the wrong pipe thankfully. Back to testing.

    last edit I swear: In testing this I believe I'm seeing an issue similar to what you described with incorrect payload widths being registered as Rx successes. When I transmit to pipes 0, 1, 2, and 3 I read the correct pipe and correct payload length of 32B but when transmitting to pipes 4 and 5 I still get the correct pipe number returned but the payload is length 0 and the data remains the same from the last reception that had data. Part of my testing I had actually commented out the code that overwrites the RX data at the end of the IRQ and I forgot to add it back in, hence why I overlooked it earlier. Power cycling the PRX side and/ or re-adding the old data clearing results in nothing being written to the data array. I do not know why this would only be affecting pipes 4 and 5 since I am trying to set everything up to be the same for every pipe apart from default addresses.

  • Hi Ovrebekk,

    I think I found something in testing that could point to some kind of collision or error in timing for retransmits or acknowledges occurring. I've tested with changing the rate at which I'm sending packets; that's changing a compare value checked in a timer that's just continuously running. Here's the code on my PTX side where I transmit the packets, the TIMER_#MS is a define I've been changing to change the transmit period

    void timer_handler(nrf_timer_event_t event_type, void* p_context)
    {
        global_timer++;
        timer_event++;
        if(timer_event == TIMER_500MS){ //occurs every 100ms
            timer_event = 0;
            timer_seconds++;
    
            hal_nrf_flush_tx();
            test_LED_on();  //clears pin 20, turns Dev Kit LED4 on
    
            hal_nrf_write_tx_payload(tx_data, PAYLOAD_WIDTH); //write data to TX FIFO
    
            hal_nrf_chip_enable(CE_PULSE);  //set CE high for at least 10us to begin transmitting data
    
            while(flag_max_rt == false && flag_tx_ds == false && flag_rx_dr == false){} //blocking until IRQ occurs
    
            test_LED_off();  //sets pin 20, turns Dev Kit LED4 off
    
            irq_hit_total += irq_hit;
            printf("\ttotal-%d (%d) success-%d\n\n", irq_hit_total, (uint8_t)irq_hit_total, irq_success_total);
            irq_hit = 0;
    
            flag_max_rt = flag_tx_ds = flag_rx_dr = false;
        }
    }

    Increasing the frequency of transmissions seems to significantly increase the amount of RX interrupts that get triggered without really having any impact on how corrupt the data becomes.

    I also messed around with changing the payload width on both sides to see if anything weird would occur. With the receiver set to 32B and the transmitter set to 10B I was still able to receive corrupted packets where the early data was correct but deeper into the payload (started closer to ~8B this time) the data would become more and more corrupted. Setting the PTX to 25B instead of 10B also had false positives on the PRX with the data corrupting a bit before the 25th data byte, and these RX interrupts trigger more frequently the larger the PTX payload width.

    Decreasing the payload width on the PRX side seems to decrease how often false positives occur. At a payload width of 20B packets are fairly rare occurrences (~1 ever 20 seconds at best) and when brought down to 10B I saw a single false positive when left running for several minutes (3+ min). The data at ~20B also appeared a bit less corrupted often starting to change from 0 later and not by much, though this is a very subjective take that might be placebo, I'll keep testing on how the data changes late into my packets.

    Something very bizarre I found in testing is when I changed the data I was transmitting from an incrementing array to all 0s. The early parts of data were all 0 as expected would corrupt late into the payload as usual but generally staying at/ around the values 170 and 85 in decimal, or 0xAA and 0x55 in hex. That's the same as the preamble for incoming packets that the receiver first looks for in PRX mode; I feel this is very relevant to what's going wrong with my setup in some way.

  • Hi Andy

    Are you able to share your code projects with me?

    Then I can run your code on some regular nRF24L01+ dongles and see if I can reproduce the issue. 

    Best regards
    Torbjørn

Related