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)

  • 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?

  • I'm not sure I understand why the SPIM master is better if it is so much slower, therefore leaving SPI enabled for longer - eating up the very tiny battery I have to work with.  Maybe you know how to use it more effectively than the SDK example?  The best I could do was 14us between transactions (I have to have CS pin cycle every three bytes).

    I have used both while (!NRF_SPI0->EVENTS_READY) and while (NRF_SPI0->EVENTS_READY == 0), both to no avail. 

  • 0x40 is the EVENTS_END interrupt bit mask. One source of confusion is often that the SPI/SPIM/TWI/TWIM peripheral is a general purpose peripheral that supports all 4 modes, which means bits have meaning even if not expected. Side-effects are supposed to be benign. EVENTS_READY is EVENTS_RXREADY in TWI but has no listing in SPIM.

    I also mistyped earlier; 0x04 is the interrupt enable/disable mask, READY is probably just 0 or 1 as you are using, it is not defined. I would disable all interrupts though, with 0xFFFFFFFF.

    SPIM using hardware registers is no slower than SPI, and the cpu is sleeping during transfer although of course DMA is running; unless I am missing something? SPIM3 works at 32MHz with cpu asleep; SPIM0 16MHz. 3-byte transfers are supported, but there is a bug with 1-byte transfers.

    Edit: also I would suggest allowing for the bus issues:

        NRF_GPIO->OUTSET = 1 << SPIM_CS;
        // Data Synchronization Barrier: completes when all explicit memory accesses before this instruction complete
        __DSB();
        __NOP();
        __NOP();
        __NOP();
        __NOP();
        NRF_GPIO->OUTCLR = 1 << SPIM_CS;
        // Data Synchronization Barrier: completes when all explicit memory accesses before this instruction complete
        __DSB();

  • Try this, SPIM not using interrupts - not tested:

    uint8_t txBuf[1] = FIFO_DATA_OUT_L | 0x80;
    static NRF_SPIM_Type* pSPIM = NRF_SPIM0;
    
      // Clear all events
      pSPIM->EVENTS_STARTED = 0;
      pSPIM->EVENTS_STOPPED = 0;
      pSPIM->EVENTS_ENDTX = 0;
      pSPIM->EVENTS_ENDRX = 0;
      pSPIM->EVENTS_END = 0;
    
      // Enable SPI and set up buffers
      pSPIM->ENABLE     = 7;
      pSPIM->TXD.PTR    = (uint32_t)txBuf;
      pSPIM->TXD.MAXCNT = 1;
      pSPIM->ORC        = 0xFF;    // Unused Tx bytes 2nd and 3rd, set to 0xFF
      pSPIM->RXD.PTR    = (uint32_t)ble_buff;
      pSPIM->RXD.MAXCNT = 3;
      // Disable all interrupts
      pSPIM->INTENCLR = 0xFFFFFFFFUL;
    Loop
    {
      pSPIM->TASKS_START = 1;
      while(!pSPIM->EVENTS_END) ;
      pSPIM->EVENTS_END = 0;
    //either:
      //ble_idx += 3;
      //pSPIM->RXD.PTR = (uint32_t)&ble_buff[ble_idx];
    //or, faster:
      pSPIM->RXD.PTR += 3;
    // CS stuff ..
    }

  • I need to give this a try still, but great idea bumping the rxd.ptr while keeping the rxd.maxcnt at 3.  I'm not sure why, but I didn't think you could do that - so last time I tried using SPIM, I was copying the contents to the buffer every time, which took way too long.

    I believe I owe you a beer.  Well played.

Related