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

Clarification on SPI double buffer and EVENTS_READY

First: nRF52840 dongle or nRF52840-Preview_DK (doesn't matter which I get the same issue), SDK17.0.2, Segger, Windows 10.  I am trying to dump a FIFO buffer from a sensor as fast as possible.  I thought I had it working, but then I realized that my rx_buffer in the nrf52840 was all 0s.  Then I saw that I might need to use the EVENTS_READY flag in this post.  I have been trying to figure out how to get this to work using the datasheet so that I don't need to use the interrupt system (too slow), but the code I have doesn't quite do it.  I get 24 clocks, I see the correct data on the MOSI pin, and I'm pretty sure on the MISO pin as well, but I never see the CS go high.  I guess this tells me that somehow the last while (!EVENTS_READY) is hanging, but if I remove it I don't get the correct result either.

// First time through, fill the double buffer
NRF_SPI0->TXD = FIFO_DATA_OUT_L | 0x80; // TXD = FIFO, RXD-1 = 0xFF
NRF_SPI0->TXD = 0xFF;  // TXD = 0xFF, TXD+1 = FIFO, RXD = 0xFF, RXD-1 = Byte1

// Read garbage (0xFF) from address TX when Event is ready
while (!NRF_SPI0->EVENTS_READY);
NRF_SPI0->EVENTS_READY = 0;

(void)NRF_SPI0->RXD; // After this line: TXD = 0xFF, TXD+1 = FIFO, RXD = Byte1, RXD-1 = ?

// Now we can push the last TX into the double buffer
NRF_SPI0->TXD = 0xFF;

// Now read data when Event is ready
while (!NRF_SPI0->EVENTS_READY);
NRF_SPI0->EVENTS_READY = 0;

ble_buff[ble_idx++] = (uint8_t)NRF_SPI0->RXD;

// Now read data when Event is ready
// while (!NRF_SPI0->EVENTS_READY);
// NRF_SPI0->EVENTS_READY = 0;

ble_buff[ble_idx++] = (uint8_t)NRF_SPI0->RXD;

nrf_delay_us(3);
NRF_GPIO->OUTSET = 1 << SPIM_CS;

How can I get three bytes from SPI as fast as possible without using the interrupts?  (obviously(?) this will go into a loop to clear out the sensor FIFO)

Parents Reply Children
  • I have used the SPIM module, but it is very slow by comparison.  The biggest problem is that the sensor I use requires CS to go high and low between each set of three bytes to read the FIFO, the three bytes being a TX of the FIFO address, then a RX of two bytes of data.  Using the SPIM module, each transaction of three bytes takes something like 14us, which is then multiplied about 8000 times.  Using the registers, it looks like I can get that down to around 6us (if I can get them to read correctly), for a time savings of about 10ms - which in turn adds up to a huge power savings.

    As far as following the datasheet carefully goes.  You can see that the pattern I implement follows the datasheet (the link is in my original post) precisely, although the datasheet doesn't include an example with so few bytes of transfer.  Going by the datasheet, it would seem that you can only transfer a minimum of 6 bytes using the double buffer, but that doesn't seem right.  In my application, where I'm transferring 3 bytes, my first TX byte is simultaneously the first byte (TX = 0 in the example from the datasheet) and the TX = n-2 byte from the example in the datasheet.  As a result, the datasheet isn't very helpful.

    Now if you could point out how my code differs from what the datasheet says, that would be very helpful - in fact, it's why I posted this question asking for clarification on exactly that part of the datasheet.

  • Okay, in an effort to further troubleshoot, I have been through many permutations of possibilities. I am beginning to think either there is something wrong with the nRF52840 or there is a typo somewhere in the datasheet. I have the following code:

    while (ble_idx < (fifo_buffer_size * 2))
    {
        NRF_SPI0->TXD = FIFO_DATA_OUT_L | 0x80;
        while (!NRF_SPI0->EVENTS_READY);
        NRF_SPI0->EVENTS_READY = 0;
        tmp = NRF_SPI0->RXD;
        printf("Finished first read, tmp is %" PRIu8 ", ER is %" PRIu32 "\r\n", tmp, NRF_SPI0->EVENTS_READY);
    
        NRF_SPI0->TXD = 0xFF;
        //while (!NRF_SPI0->EVENTS_READY);
        //NRF_SPI0->EVENTS_READY = 0;
        tmp0 = (uint8_t)NRF_SPI0->RXD;
        printf("Finished second read, tmp0 is %" PRIu8 "\r\n", tmp0);
    
        NRF_SPI0->TXD = 0xFF;
        //while (!NRF_SPI0->EVENTS_READY);
        //NRF_SPI0->EVENTS_READY = 0;
        tmp1 = (uint8_t)NRF_SPI0->RXD;
        printf("Finished third read, tmp1 is %" PRIu8 "\r\n", tmp1);
    
        nrf_delay_us(3);
        NRF_GPIO->OUTSET = 1 << SPIM_CS;
        __NOP();
        __NOP();
        __NOP();
        __NOP();
        NRF_GPIO->OUTCLR = 1 << SPIM_CS;
    }

    When I put in the last two while (!NRF_SPI0->EVENTS_READY) lines, I only get the first print statement (which tells me that tmp = 0 and EVENTS_READY = 0) and I only see two bytes of clocks on the oscilloscope. This pretty clearly indicates that the while loop hangs forever, which the datasheet indicates is not correct behavior.

    When I comment out the last two while (!NRF_SPI0->EVENTS_READY) lines (as in the code sample above) I get the three transmissions on the oscilloscope, but tmp, tmp0, and tmp1 are all 0 even though the MISO line has non-zero data.  I also get a fourth transmit when the outer loop starts over, but then it hangs indefinitely - again indicating that the while (!NRF_SPI0->EVENTS_READY) line is hanging in contradiction to the datasheet.

    Also, for completeness I ran the equivalent code on several other Cortex M4F-based MCUs just to check, and sure enough, they all read it correctly. I know that Nordic products work differently than some other MCUs, but it seems odd.

    Edit: I get the same behavior on two different nRF52840 Dongles as well, which seems to indicate that it is a problem with the nRF52840.  Also, in looking over the old spi_master example from 2015, I fail to see the effective difference between that code and what I have here.  If anyone could point it out (since I guess that would be equivalent to telling me how my code contradicts the datasheet) it would be extremely helpful.

    Edit edit: I have a thought that somehow it could be the MISO pin.  I can't find anywhere that says I can't use 0.24 for MISO - but if not, then that might explain why I always see 0 instead of what I should see (at least I should be seeing 0xFF for the first read).  Then it might follow that since the RXD is never being read, the EVENTS_READY register might not ever update?

    Here is my pic config:

    #define SPI_CS                         NRF_GPIO_PIN_MAP(0, 17) // Connected to P0.17
    #define SPI_SCK                        NRF_GPIO_PIN_MAP(0, 20) // Connected to P0.20
    #define SPI_MOSI                       NRF_GPIO_PIN_MAP(0, 22) // Connected to P0.22
    #define SPI_MISO                       NRF_GPIO_PIN_MAP(0, 24) // Connected to P0.24

    I have verified that the jumpers on the DK are correct and the dongle doesn't require jumpers for pin 0.24.

  • The only thing I can think of is that you may miss EVENTS_READY here for some reason (e.g the while loop is interrupted), the EVENTS_READY will not stack. You should also make sure that the EVENTS_READY is cleared before your outer while loop, since if it's already pending that will become problematic.

    Best regards,
    Kenneth

  • Two things look suspicious, not that I have used the legacy SPI ever as the SPIM is so much better. The first is that READY is hex 0x04, not 0 or 1, so any logical test becomes compiler dependent. Safer to explicitly test for the actual value, ie == 0x04 or != 0x04. The second is that READY has to be cleared for every Rx byte as it is only set on transferring a byte into RXD after reading a prior byte in RXD if there is one; arguable that the byte doesn't even have to be read, just clear READY. The 3rd byte will never be signaled READY until 2 x READY=0 have been issued.

  • Good idea... I have NRF_SPI0->INTCLR = (1 << 2) earlier in the code to prevent hitting another SPI interrupt, but maybe that is wrong?  What is the proper way to disable SPI interrupts?  The datasheet doesn't give an examle, but the register list has the "A" ID in position 2 for both INTENSET and INTENCLR.  When I read INTENSET I get a 64 (0x40) instead of a 0x00.  So maybe I am doing that wrong?

Related