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

SPI handler getting triggered when spi is not running?

Hi,

I've built a project out of the USBD_ACM_CDC example in which I have added a SPI master driver. This project has a timer interrupt that will shoot off every ~500us and trigger a 520 bit SPI transfer. The received packets undergo *very* minor processing and are spit out over USB to my host computer.

So it seems like I'm getting all the packets I expect, only I'm also getting 6 extra invalid packets with every valid packet. Here's a screenshot of my terminal from when I'm just transferring a hex counter:

The counter is incrementing so those bits are all fine. As you can see there are consistently 6 extra invalid packets (the FFs). If I slow down how often I shoot off SPI transfers, these invalid packets will disappear, but that doesn't make too much sense. Right now it seems like the SPI handler is being called erroneously. Does anyone have insight into where these are coming from? I looked at the SPI wires with an oscope and couldn't see any issues.

Parents
  • If you are using Nordic library drivers the following should not apply as the issue is (should be) handled in the drivers; if using low-level driver note the following where a handler will be invoked twice instead of once. How do I know this? Because the counter is incremented twice instead of once for every SPI transaction if the workaround is not applied.

    Option 1:

    // Example using SPI0
    static volatile uint32_t mSpiInterruptCounter = 0UL;
    static NRF_SPIM_Type* pSPIM = NRF_SPIM0;
    static const IRQn_Type mSPIM_IRQn = SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn;
    /**
     * @brief SPI interrupt handler.
     * @param event
     */
    void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
    {
      blahblah(); // Processing (if any)
      // Clearing ->EVENTS_END MUST be first or add void read else get double interrupt ..
      pSPIM->EVENTS_END = 0;
      mSpiPacketTransferComplete = true;
      mSpiInterruptCounter++;
    }

    Option 2:

    // Example using SPI0
    static volatile uint32_t mSpiInterruptCounter = 0UL;
    static NRF_SPIM_Type* pSPIM = NRF_SPIM0;
    static const IRQn_Type mSPIM_IRQn = SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn;
    /**
     * @brief SPI interrupt handler.
     * @param event
     */
    void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
    {
      blahblah(); // Processing (if any)
      mAccPacketTransferComplete = true;
      mSpiInterruptCounter++;
      pSPIM->EVENTS_END = 0;
      // Data Synchronization Barrier: completes when all explicit memory accesses before this instruction complete
      __DSB();
    }

    Nordic library code mostly reads back the register to enforce the instruction completing, but that can be unsafe depending on compiler and optimisation levels.

Reply
  • If you are using Nordic library drivers the following should not apply as the issue is (should be) handled in the drivers; if using low-level driver note the following where a handler will be invoked twice instead of once. How do I know this? Because the counter is incremented twice instead of once for every SPI transaction if the workaround is not applied.

    Option 1:

    // Example using SPI0
    static volatile uint32_t mSpiInterruptCounter = 0UL;
    static NRF_SPIM_Type* pSPIM = NRF_SPIM0;
    static const IRQn_Type mSPIM_IRQn = SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn;
    /**
     * @brief SPI interrupt handler.
     * @param event
     */
    void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
    {
      blahblah(); // Processing (if any)
      // Clearing ->EVENTS_END MUST be first or add void read else get double interrupt ..
      pSPIM->EVENTS_END = 0;
      mSpiPacketTransferComplete = true;
      mSpiInterruptCounter++;
    }

    Option 2:

    // Example using SPI0
    static volatile uint32_t mSpiInterruptCounter = 0UL;
    static NRF_SPIM_Type* pSPIM = NRF_SPIM0;
    static const IRQn_Type mSPIM_IRQn = SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn;
    /**
     * @brief SPI interrupt handler.
     * @param event
     */
    void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
    {
      blahblah(); // Processing (if any)
      mAccPacketTransferComplete = true;
      mSpiInterruptCounter++;
      pSPIM->EVENTS_END = 0;
      // Data Synchronization Barrier: completes when all explicit memory accesses before this instruction complete
      __DSB();
    }

    Nordic library code mostly reads back the register to enforce the instruction completing, but that can be unsafe depending on compiler and optimisation levels.

Children
  • Firstly, thank you!!

    This is very interesting. I'm currently using the Nordic provided spi library but I'll try adding the following lines to my spi handler to see if it helps:

      SPI->EVENTS_END = 0;
      mSpiPacketTransferComplete = true;
      mSpiInterruptCounter++;

    Are there any special considerations for how frequently I can use the SPI? I'm currently just using SPI0 (with easyDMA enabled), which from my understanding should be able to run fast enough, but maybe I misunderstood the sdk. 

  • If using the library code it won't help adding the lines I don't think, but no harm in trying; regarding the SPI throughput provided a transfer has completed before starting the next transfer there is no limitation other than the slightly verbose library code which traverses multiple functions. I would advise using the maximum clock rate the SPI device you are connecting to can support, at least 8MHz. At the higher clock speeds you have to increase the SCK, MOSI and CS drive levels to H0H1 after setting the SPI init.

  • Where can I look for these drive options? Are they all in nrf_drv_spi.h or in some gpio file?

    Update: I see that I can use 

    NRF_GPIO->PIN_CNF[pin_number] = (GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos);

    so if I'm using p0.29 (29) for MOSI, I could just use: 

    NRF_GPIO->PIN_CNF[29] = (GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos);

    I don't know what CNF stands for. I will continue to google! This seems like a long shot, but I'll take anything. Seriously, thank you. I'm a little new to this so I appreciate the help.

  • They are not driver options, just commands to override the default settings of Standard Drive output port pins for '0' (low-level) and '1' (high-level):

    // Set to High-Drive on the SPI output pins (assuming nRF52832 is the Master)
     nrf_gpio_cfg(SPI0_CS_PIN,   NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
     nrf_gpio_cfg(SPI0_MOSI_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
     nrf_gpio_cfg(SPI0_SCK_PIN,  NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);

    This must be done after the SPI init, just once. Edit: Yes, you can modify the driver code as you suggest, less safe as that is hidden; even less safe as the other bits in the snippet you post are now not handled correctly; CNF has many bit fields.

    Here is an example showing all the CNF fields for a typical SPI0 (sorry, source code not available):

    // P0.19  SPI0-SCK CNF: 0x00000303 [Sense: Off] [Drive: High 0&1] [Pull: None] [Input: Disconnect] [Dir: Output] (now 0) (SPI0 SCK)
    // P0.20 SPI0-MOSI CNF: 0x00000303 [Sense: Off] [Drive: High 0&1] [Pull: None] [Input: Disconnect] [Dir: Output] (now 1) (SPI0 MOSI)
    // P0.22 SPI0-nCS0 CNF: 0x00000303 [Sense: Off] [Drive: High 0&1] [Pull: None] [Input: Disconnect] [Dir: Output] (now 1) (SPI0 CSN)

  • Amazing! I'll try this out right now. Thank you.

    Edit: So I bumped up the SPI_CLK to 8MHz and confirmed that via an oscope & single SPI transactions. Unfortunately, when I try sending SPI transactions every 500 ms, I get the same issue as before. 

    That being said! I started printing what is received by the slave and am seeing no issues (through UART logging). On the master side, I'm outputting everything via USB to COM port on my PC. Is it possible that the data is getting corrupted through the USB transmission? Are there any considerations for frequently triggering USB transfers. The USB should be able to hit 1 Mb/s easily in theory, but I'm just using the example code. Are there any configs I should change?  

Related