Timing of SPI when sharing common lines on independent timers

We are using two SPI masters (SPIM0 and SPMI1) inside two independently running timers. SPIM0 and SPIM1 share MISO, MOSI, and SCK. Only the chip selects are different between the two. My question is as follows:

What will happen if the timers synchronize at some point and both try to access SPIM0 and SPIM1 t the same time? Does the chip schedule the order or will there be a conflict?

Thank you

  • Since nordicator had some kind words, I wrote example code to allow multiple SPI peripherals to share the same pins. This is the basic functionality, the two SPI peripherals argue about who transmits but testing shows no corruption. Actual SPI devices are not required if loopback mode is used (Nordic documentation disapproves but it works fine): mInitSPI_Ports(true); the parameter enables (true) or disables (false) SPI loopback on a single pin.

    // Two or more SPI peripherals can share SCK, MOSI and MISO pins, assuming two SPI peripherals are
    // required by the application due to very different SPI settings or simple lack of spare io pins.
    // "The PSEL.SCK, PSEL.MOSI, and PSEL.MISO registers and their configurations are only used as long as
    // the SPI master/slave is enabled, and retained only as long as the device is in System ON mode".
    // TWIM is similar.
    // So the pins can be shared provided each SPI is disabled before using the other. However, they
    // cannot occupy the same timeslot; some form of software lock would be required to avoid being enabled
    // simultaneously. To avoid this, the two SPI peripherals would only be enabled in an interrupt of equal
    // priority, such that neither could interrupt the other, and the entire Enable-transfer-Disable
    // sequence would have to take place in that single interrupt context. That can be done by using
    // two SWI software interrupts of low and identical priority triggered from either a timer interrupt
    // handler (software) or PPI event (hardware). The matching low interrupt priority for both SWIs is
    // required to avoid distorting timers and stuff based on interrupts; stuff in main() will be blocked
    // until SPI transfers complete, which may affect sleep/power calculations. All other actions via
    // interrupts will safely interrupt both of these SWI handlers.
    
    // Event Generator Unit (EGUn) - A, use (say) SWI/EGU number 3
    static NRF_EGU_Type* pEGU_A = NRF_EGU3;
    #define SWI_EGU_IRQn_A        SWI3_EGU3_IRQn
    // Event Generator Unit (EGUn) - B, use (say) SWI/EGU number 4
    static NRF_EGU_Type* pEGU_B = NRF_EGU4;
    #define SWI_EGU_IRQn_B        SWI4_EGU4_IRQn
    // Timer for this module, not all timers have 6 CC registers
    static NRF_TIMER_Type *pTimer = NRF_TIMER3;
    #define PWM_TIMER_IRQn          TIMER3_IRQn
    static NRF_RTC_Type *pSpiRTC  = NRF_RTC2;
    IRQn_Type SpiRTC_IRQn         = RTC2_IRQn;
    volatile bool mRTC_Tick = true;
    
    static volatile uint32_t mTriggerTimer         = 0;
    static volatile uint32_t mSpiTransactionCountA = 0;
    static volatile uint32_t mSpiTransactionCountB = 0;
    
    #define EGU_COUNT_A 3   // Number of RTC ticks to trigger the SPI transfers
    #define EGU_COUNT_B 5
    
    #define CSPIN_A 10  // spi chip select A
    #define CSPIN_B 11  // spi chip select B
    #define SCKPIN  12  // spi clock
    #define MOSIPIN 13  // spi mosi
    #define MISOPIN 14  // spi miso
    #define LIS2DH12_WHO_AM_I  0x0F  // Read back as 0x33
    #define TYPICAL_INTERRUPT_PRIORITY  6
    
    uint8_t spiTXBufA[]                  = {0x80|LIS2DH12_WHO_AM_I, 0xFF, 0xFF};
    uint8_t spiRXBufA[sizeof(spiTXBufA)] = {0,0};
    static const uint8_t mRxTxBufLengthA = sizeof(spiTXBufA);
    static void mInitSPI_A(void);
    static void mTransferSPI_A(void);
    
    uint8_t spiTXBufB[]                  = {0x80|0x1F, 0xFF, 0xFF, 0xFF};
    uint8_t spiRXBufB[sizeof(spiTXBufB)] = {0,0,0};
    static const uint8_t mRxTxBufLengthB = sizeof(spiTXBufB);
    static void mInitSPI_B(void);
    static void mTransferSPI_B(void);
    
    static void TestShareMultipleSPI(void)
    {
       // Debug pin to observe wakeups, any spare unused pin
       nrf_gpio_cfg_output(X14_PIN);
       
       // Clear the A event ready to execute SPI transaction event A
       pEGU_A->EVENTS_TRIGGERED[0] = 0;
       pEGU_A->INTENSET = 1;
       // Set interrupt priority lower priority than timer and enable interrupt
       NVIC_SetPriority(SWI_EGU_IRQn_A, TYPICAL_INTERRUPT_PRIORITY+1);
       NVIC_ClearPendingIRQ(SWI_EGU_IRQn_A);
       NVIC_EnableIRQ(SWI_EGU_IRQn_A);
    
       // Clear the B event ready to execute SPI transaction event B
       pEGU_B->EVENTS_TRIGGERED[0] = 0;
       pEGU_B->INTENSET = 1;
       // Set interrupt priority lower priority than timer and enable interrupt
       NVIC_SetPriority(SWI_EGU_IRQn_B, TYPICAL_INTERRUPT_PRIORITY+1);
       NVIC_ClearPendingIRQ(SWI_EGU_IRQn_B);
       NVIC_EnableIRQ(SWI_EGU_IRQn_B);
    
       // Initialise the two SPI peripherals, using same pins other than /CS
       mInitSPI_A();
       mInitSPI_B();
    
       // Select 32kHz clock source, depends on whether external 32.768kHz crystal is fitted
       NRF_CLOCK->LFCLKSRC = CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos;
       //NRF_CLOCK->LFCLKSRC = CLOCK_LFCLKSRC_SRC_RC << CLOCK_LFCLKSRC_SRC_Pos;
       //NRF_CLOCK->LFCLKSRC = CLOCK_LFCLKSRC_SRC_Synth << CLOCK_LFCLKSRC_SRC_Pos;
       // Start 32kHz clock
       NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
       NRF_CLOCK->TASKS_LFCLKSTART = 1;
       while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) ;
       // Clear started event otherwise won't sleep
       NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
       // Start RTC, enable TICK event
       // Select tick: 12 bit prescaler for COUNTER frequency (32768/(PRESCALER+1)). Must be written when RTC is stopped
       pSpiRTC->PRESCALER = 7;
       pSpiRTC->EVTENSET = RTC_EVTENSET_TICK_Msk;
       // Set interrupt priority higher than SWI/EGU priority and enable interrupt
       NVIC_SetPriority(SpiRTC_IRQn, TYPICAL_INTERRUPT_PRIORITY);
       NVIC_ClearPendingIRQ(SpiRTC_IRQn);
       NVIC_EnableIRQ(SpiRTC_IRQn);
       // Enable tick interrupt
       pSpiRTC->INTENSET = 0x00001;
       pSpiRTC->TASKS_START = 1;
       // Send a load of conflicting SPI transfers
       while(1)
       {
          nrf_gpio_pin_set(X14_PIN);
          mTriggerTimer++;
          // Stop after a while, meanwhile trigger lots of transfers
          if (mTriggerTimer > 100000-1)
          {
             pSpiRTC->EVTENCLR    = 1;
             pSpiRTC->TASKS_STOP  = 1;
             pSpiRTC->TASKS_CLEAR = 1;
             // Clear any pending hardware register bus operations
             __DSB();
          }
          else
          {
             // Trigger the ready to execute SPI transaction event at arbitrary overlappng times
             if ((mTriggerTimer % EGU_COUNT_A) == 0) pEGU_A->TASKS_TRIGGER[0] = 1;
             if ((mTriggerTimer % EGU_COUNT_B) == 0) pEGU_B->TASKS_TRIGGER[0] = 1;
          }
          nrf_gpio_pin_clear(X14_PIN);
          // Go to sleep until next tick
          while(!mRTC_Tick)
          {
             // Clear the internal event register and wait for event - note an interrupt is required
             __WFE(); __SEV(); __WFE(); __NOP(); __NOP(); __NOP(); __NOP();
          }
          mRTC_Tick = false;
       }
    }

    These are the SWI/EGU (software interrupt/Event Generator Unit) and RTC interrupt handlers:

    void RTC2_IRQHandler(void)
    {
        if (pSpiRTC->EVENTS_TICK == 1)
        {
            pSpiRTC->EVENTS_TICK = 0;
            mRTC_Tick = true;
        }
        // Clear any pending hardware register bus operations
        __DSB();
    }
    
    // Event Generator Unit (EGUn) A
    void SWI3_EGU3_IRQHandler(void)
    {
        // Handle the ready to execute SPI transaction event
        if (pEGU_A->EVENTS_TRIGGERED[0])
        {
            // Clear the ready to execute SPI transaction event
            pEGU_A->EVENTS_TRIGGERED[0] = 0;
            // Clear the Rx buffer so easier to verify transfer
            for (uint32_t i=0; i<sizeof(spiRXBufA); i++) spiRXBufA[i] = '?';
            // Execute SPI transfer A, adjust the tx data a little
            spiTXBufA[1]++;
            mTransferSPI_A();
            mSpiTransactionCountA++;
        }
        // Clear any pending hardware register bus operations
        __DSB();
    }
    
    // Event Generator Unit (EGUn) B
    void SWI4_EGU4_IRQHandler(void)
    {
        // Handle the ready to execute SPI transaction event
        if (pEGU_B->EVENTS_TRIGGERED[0])
        {
            // Clear the ready to execute SPI transaction event
            pEGU_B->EVENTS_TRIGGERED[0] = 0;
            // Clear the Rx buffer so easier to verify transfer
            for (uint32_t i=0; i<sizeof(spiRXBufB); i++) spiRXBufB[i] = '?';
            // Execute SPI transfer B, adjust the tx data a little
            spiTXBufB[2]++;
            mTransferSPI_B();
            mSpiTransactionCountB++;
        }
        // Clear any pending hardware register bus operations
        __DSB();
    }
    

    SPI interrupt handlers:

    volatile uint32_t mSpiInterruptCounterA = 0UL;
    volatile uint32_t mSpiInterruptCounterB = 0UL;
    volatile bool mSPI_TransactionComplete_A = false;
    volatile bool mSPI_TransactionComplete_B = false;
    
    volatile NRF_SPIM_Type *pSPIM1 = NRF_SPIM1;
    volatile NRF_SPIM_Type *pSPIM2 = NRF_SPIM2;
    #define SPI_IRQn_A  SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQn
    #define SPI_IRQn_B  SPIM2_SPIS2_SPI2_IRQn
    
    void SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler(void)
    {
      if (pSPIM1->EVENTS_END) pSPIM1->EVENTS_END = 0;
      mSpiInterruptCounterA++;
      mSPI_TransactionComplete_A = true;
    }
    
    void SPIM2_SPIS2_SPI2_IRQHandler(void)
    {
      if (pSPIM2->EVENTS_END) pSPIM2->EVENTS_END = 0;
      mSpiInterruptCounterB++;
      mSPI_TransactionComplete_B = true;
    }

    Initialisation of ports and SPI peripherals:

    static void mInitSPI_Ports(const bool LoopBackSPI);
    
    static void mInitSPI_Ports(const bool LoopBackSPI)
    {
       NRF_GPIO->OUTSET  = 1 << CSPIN_A;      // deactivate by setting chip select high
       NRF_GPIO->OUTSET  = 1 << CSPIN_B;
       NRF_GPIO->OUTSET  = 1 << SCKPIN;       // default outputs all high
       NRF_GPIO->OUTSET  = 1 << MOSIPIN;
       NRF_GPIO->PIN_CNF[CSPIN_A] = 0x301;    // output, high drive high and low H0H1
       NRF_GPIO->PIN_CNF[CSPIN_B] = 0x301;    // output, high drive high and low H0H1
       if (LoopBackSPI)
       {
          NRF_GPIO->PIN_CNF[MOSIPIN] = 0x30D; // output, high drive high and low H0H1, input connected, pull-up
       }
       else
       {
          NRF_GPIO->PIN_CNF[MOSIPIN] = 0x301; // output, high drive high and low H0H1, input connected
       }
       NRF_GPIO->PIN_CNF[SCKPIN]  = 0x301;   // output, high drive high and low H0H1
       NRF_GPIO->PIN_CNF[MISOPIN] = 0x00C;   // input pin, input buffer connected, pull-up, S0S1, sense disabled
    }
    
    static void mInitSPI_A(void)
    {
       pSPIM1->ENABLE = 0;                   // Disable SPIM
       mInitSPI_Ports(true);                // Initialise ports with loopback mode true or false
       NRF_GPIO->OUTSET  = 1 << CSPIN_A;     // deactivate by setting chip select high
       // Note SPI psel pins are lost when released, must be reset each transaction
       pSPIM1->CONFIG    = 0;                // CPOL 0 -- clock polarity active high, CPHA 1 -- sample on trailing clock edge, send Msb first
       pSPIM1->FREQUENCY = 0x80000000UL;     // 8 Mbps - LIS2DH12 works up to 10MHz on SPI
       pSPIM1->ORC = 0;                      // Unused Tx bytes, set all low
       pSPIM1->EVENTS_END   = 0;
       pSPIM1->EVENTS_ENDTX = 0;
       pSPIM1->EVENTS_ENDRX = 0;
       // Disable all interrupts
       pSPIM1->INTENCLR = 0xFFFFFFFFUL;
       // Enable selected interrupts
       pSPIM1->INTENSET = 0x040;            // END
       // Set interrupt priority and enable interrupt
       NVIC_SetPriority(SPI_IRQn_A, TYPICAL_INTERRUPT_PRIORITY);
       NVIC_ClearPendingIRQ(SPI_IRQn_A);
       NVIC_EnableIRQ(SPI_IRQn_A);
    }
    
    static void mInitSPI_B(void)
    {
       pSPIM1->ENABLE = 0;                   // Disable SPIM
       mInitSPI_Ports(true);                // Initialise ports with loopback mode true or false
       NRF_GPIO->OUTSET  = 1 << CSPIN_B;     // deactivate by setting chip select high
       // Note SPI psel pins are lost when released, must be reset each transaction
       pSPIM2->CONFIG    = 0;                // CPOL 0 -- clock polarity active high, CPHA 1 -- sample on trailing clock edge, send Msb first
       pSPIM2->FREQUENCY = 0x80000000UL;     // 8 Mbps
       pSPIM2->ORC = 0;                      // Unused Tx bytes, set all low
       pSPIM2->EVENTS_END   = 0;
       pSPIM2->EVENTS_ENDTX = 0;
       pSPIM2->EVENTS_ENDRX = 0;
       // Disable all interrupts
       pSPIM2->INTENCLR = 0xFFFFFFFFUL;
       // Enable selected interrupts
       pSPIM2->INTENSET = 0x040;            // END
       // Set interrupt priority and enable interrupt
       NVIC_SetPriority(SPI_IRQn_B, TYPICAL_INTERRUPT_PRIORITY);
       NVIC_ClearPendingIRQ(SPI_IRQn_B);
       NVIC_EnableIRQ(SPI_IRQn_B);
    }

    SPI Transfer functions:

    static void mTransferSPI_A(void)
    {
       // Note SPI psel pins are lost when released, must be reset each transaction
       pSPIM1->PSEL.SCK  = SCKPIN;
       pSPIM1->PSEL.MOSI = MOSIPIN;
       pSPIM1->PSEL.MISO = MOSIPIN; //MISOPIN; // Loopback - setto MOSIPIN
       pSPIM1->ENABLE = 7;                     // Enable SPIM
       pSPIM1->TXD.PTR     = (uint32_t)spiTXBufA;
       pSPIM1->TXD.MAXCNT  = mRxTxBufLengthA;
       pSPIM1->RXD.PTR     = (uint32_t)spiRXBufA;
       pSPIM1->RXD.MAXCNT  = mRxTxBufLengthA;
       NRF_GPIO->OUTCLR    = 1 << CSPIN_A;   // drive cs low to initiate spi comm
       pSPIM1->TASKS_START = 1;
       while (!mSPI_TransactionComplete_A) ;
       mSPI_TransactionComplete_A = false;
       pSPIM1->TASKS_STOP  = 1;
       // Clear pending hardware register bus operations
       __DSB();
       while(!pSPIM1->EVENTS_STOPPED);
       NRF_GPIO->OUTSET    = 1 << CSPIN_A;
       __DSB();
       pSPIM1->ENABLE      = 0;             // Disable SPIM
    }
    
    static void mTransferSPI_B(void)
    {
       // Note SPI pins are lost when released, must be reset each transaction
       pSPIM2->PSEL.SCK  = SCKPIN;
       pSPIM2->PSEL.MOSI = MOSIPIN;
       pSPIM2->PSEL.MISO = MOSIPIN; //MISOPIN; // Loopback - setto MOSIPIN
       pSPIM2->ENABLE = 7;                     // Enable SPIM
       pSPIM2->TXD.PTR     = (uint32_t)spiTXBufB;
       pSPIM2->TXD.MAXCNT  = mRxTxBufLengthB;
       pSPIM2->RXD.PTR     = (uint32_t)spiRXBufB;
       pSPIM2->RXD.MAXCNT  = mRxTxBufLengthB;
       NRF_GPIO->OUTCLR    = 1 << CSPIN_B;   // drive cs low to initiate spi comm
       pSPIM2->TASKS_START = 1;
       while (!mSPI_TransactionComplete_B) ;
       mSPI_TransactionComplete_B = false;
       pSPIM2->TASKS_STOP  = 1;
       // Clear pending hardware register bus operations
       __DSB();
       while(!pSPIM2->EVENTS_STOPPED);
       NRF_GPIO->OUTSET    = 1 << CSPIN_B;
       __DSB();
       pSPIM2->ENABLE      = 0;             // Disable SPIM
    }

    Paste all the above in order given to test; make sure SPI1, SPI2 and RTC2 are not enabled in any sdk_config.h file to avoid vector multiple definitions. Invoke in SystemInit() before running the usual errata workarounds and before main() for a clean no-frills test.

Related