How to do SPIM with handshakes

I'm using SDK15.3 on an nRF52840 with FreeRTOS and specifically non-preemptive scheduling.

I want to so some transfers on SPIM (it's configure with DMA but that's not a requirement)... I've got a part I'm talking to that has a READY handshake signal coming back from it that kicks in during long transfers... basically it's implementing XON/XOFF for the regular SPI transaction.

Got any hints at how to handle that?

  • Yah, I get the HITOLO and that thing... here's the question then:

    I've got one pin that the HITOLO would cause a suspend and the LOTOHI would cause the resume.  Setting this to check on TOGGLE makes sense since it's a single pin and both edges are significant (but different)

    How then would I work nrf_drv_ppi_channel_assign() since the pin is going to generate events on both edges and the nrf_drv_gpiote_in_event_addr_get(READY_PIN) wouldn't therefor distinguish which edge it's seeing.

    Does PPI just pingpong between the edges when you get both like this?

    Or do I need to assign an ISR to this nrfx_gpiote_in_init() and reset the association during that (i.e. a manual pingpong)?

  • You are right that there is only a single event for both edge. The PPI will not actually pingpong anything. It will simply trigger the PPI channel that is enabled.

    So, if both the suspend and resume channels are enabled, both tasks are triggered, which is obviously not what we want, and I have no idea what will happen in that case.

    And you are also completely right that you will need to use ISR.

    There are several options to do this:

    1. Manual "pingpong" like you mentioned. The simplest and fastest (shortest ISR time) way is to only enable one of the two PPI channels during initialization.
      The ISR handle will disable one channel and enable the other.
      1. A potential downside with this approach is a race condition if the next edge arrive before the ISR handling is done.
        How big a risk this is depends on the particularities of your system. There might be ways to work around it.

    2. The ISR handler controls the SPI suspending and resuming. It can do both tasks; or it can do one, and the other task is still done by PPI.
  • OK, this is more in line with my understanding of how this works.... I'll try to deal with it that direction.  Timing could be somewhat tight here but it helps that these parts are fast....

    Thanx for you folks help on this; you guys are great at it...

  • Don't need toggle. The trick is to know the a single pin can be input to several GPIOTE channels. This works, I tested it on some other stuff.

    #define PIN_SPIM_XON_XOFF  14
    #define PPI_SPIM_SUSPEND    0
    #define PPI_SPIM_RESUME     1
    #define GPIOTE_SPIM_SUSPEND 0
    #define GPIOTE_SPIM_RESUME  1
    
    static void TestPPI_SPIM_SuspendResume(void)
    {
        NRF_P0->OUTCLR = (1 << PIN_SPIM_XON_XOFF);
        // Configuration                       Direction    Input            Pullup         Drive Level      Sense Level
        // ================================    ==========   ==============   ============   ==============   =============
        NRF_P0->PIN_CNF[PIN_SPIM_XON_XOFF]  = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLNONE | PIN_DRIVE_H0H1 | PIN_SENSE_LOW);
    
        // Configure input for resume on rising edge with suspend on falling edge
        NRF_GPIOTE->CONFIG[GPIOTE_SPIM_RESUME]  = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                                  GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos |
                                                  PIN_SPIM_XON_XOFF             << GPIOTE_CONFIG_PSEL_Pos     |
                                                  GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos;
        NRF_GPIOTE->CONFIG[GPIOTE_SPIM_SUSPEND] = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                                  GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |
                                                  PIN_SPIM_XON_XOFF             << GPIOTE_CONFIG_PSEL_Pos     |
                                                  GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos;
    
        // Resume SPIM transfer on rising edge of pulse via PPI
        NRF_PPI->CH[PPI_SPIM_RESUME].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL_B];
        NRF_PPI->CH[PPI_SPIM_RESUME].TEP = (uint32_t)&NRF_SPIM0->TASKS_RESUME;
    
        // Suspend SPIM transfer on falling edge via PPI
        NRF_PPI->CH[PPI_SPIM_SUSPEND].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL_A]; // Event
        NRF_PPI->CH[PPI_SPIM_SUSPEND].TEP = (uint32_t)&NRF_SPIM0->TASKS_SUSPEND;                // Task #1
        // Let's add an EGU interrupt to play with
        NRF_PPI->FORK[PPI_SPIM_SUSPEND].TEP = (uint32_t)&NRF_EGU0->TASKS_TRIGGER[0];            // Task #2
    
        // Enable the Suspend/Resume PPI channels
        NRF_GPIOTE->EVENTS_PORT = 0;
        NRF_PPI->CHENSET = (1UL << PPI_SPIM_RESUME) | (1UL << PPI_SPIM_SUSPEND);
    
        blah_blah();
    }

Related