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?

Parents Reply Children
  • I got involved just because I saw FreeRTOS here but the code that you seek does not necessarily depend on FreeRTOS.

    After you initialize the SPI using nrf_drv_spim_init, I think you can use something like below code as template to have the connections you see through PPI to the tasks.

    static nrf_ppi_channel_t ppi_channel_suspend;
    static nrf_ppi_channel_t ppi_channel_resume;
    
    void gpio_ppi_init(void)
    {
        APP_ERROR_CHECK(nrf_drv_gpiote_init());
    
        // Set up the READY_PIN as an input that can detect both active and inactive edges.
        // This will let us sense when the external device signals us to pause or resume.
        nrf_drv_gpiote_in_config_t ready_pin_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);
        ready_pin_config.pull = NRF_GPIO_PIN_NOPULL; // adjust if your circuit requires it.
        APP_ERROR_CHECK(nrf_drv_gpiote_in_init(READY_PIN, &ready_pin_config, NULL));
    
        // Reserve two PPI channels: one for pausing SPI (SUSPEND) and one for resuming it (RESUME).
        APP_ERROR_CHECK(nrf_drv_ppi_channel_alloc(&ppi_channel_suspend));
        APP_ERROR_CHECK(nrf_drv_ppi_channel_alloc(&ppi_channel_resume));
    
        // Link the READY_PIN's "active edge" event to the SPIM's SUSPEND task.
        // This ensures the SPI pauses when the device signals us to stop.
        APP_ERROR_CHECK(nrf_drv_ppi_channel_assign(
            ppi_channel_suspend,
            nrf_drv_gpiote_in_event_addr_get(READY_PIN),
            (uint32_t)&NRF_SPIM0->TASKS_SUSPEND
        ));
    
        // Link the READY_PIN's "inactive edge" event to the SPIM's RESUME task.
        // This ensures the SPI resumes when the device signals it's ready to continue.
        APP_ERROR_CHECK(nrf_drv_ppi_channel_enable(ppi_channel_suspend));
        APP_ERROR_CHECK(nrf_drv_ppi_channel_enable(ppi_channel_resume));
    }

    This is uncompiled code trying to show you the logic. Please use that as template.

  • How does this code know which edge is active and which is not? I'm looking for a down edge as suspend and an up edge as resume..

  • Hi Randy,

    The key lies in the configuration of the GPIOTE input:

        nrf_drv_gpiote_in_config_t ready_pin_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);

    Here, a preset configuration was used. The preset configurations can be found in modules/nrfx/drivers/include/nrfx_gpiote.h.

    /**
     * @brief Macro for configuring a pin to use a GPIO IN or PORT EVENT to detect low-to-high transition.
     * @details Set hi_accu to true to use IN_EVENT.
     */
    #define NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(hi_accu) \
    {                                                   \
        .sense = NRF_GPIOTE_POLARITY_LOTOHI,            \
        .pull = NRF_GPIO_PIN_NOPULL,                    \
        .is_watcher = false,                            \
        .hi_accuracy = hi_accu,                         \
        .skip_gpio_setup = false,                       \
    }
    
    /**
     * @brief Macro for configuring a pin to use a GPIO IN or PORT EVENT to detect high-to-low transition.
     * @details Set hi_accu to true to use IN_EVENT.
     */
    #define NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(hi_accu) \
    {                                                   \
        .sense = NRF_GPIOTE_POLARITY_HITOLO,            \
        .pull = NRF_GPIO_PIN_NOPULL,                    \
        .is_watcher = false,                            \
        .hi_accuracy = hi_accu,                         \
        .skip_gpio_setup = false,                       \
    }
    
    /**
     * @brief Macro for configuring a pin to use a GPIO IN or PORT EVENT to detect any change on the pin.
     * @details Set hi_accu to true to use IN_EVENT.
     */
    #define NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(hi_accu) \
    {                                                   \
        .sense = NRF_GPIOTE_POLARITY_TOGGLE,            \
        .pull = NRF_GPIO_PIN_NOPULL,                    \
        .is_watcher = false,                            \
        .hi_accuracy = hi_accu,                         \
        .skip_gpio_setup = false,                       \
    }

    You might notice the symbols are named a little different. That's because they are given aliases in integration/nrfx/legacy/nrf_drv_gpiote.h.

    /** @brief Macro for forwarding the new implementation. */
    #define GPIOTE_CONFIG_IN_SENSE_LOTOHI     NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI
    /** @brief Macro for forwarding the new implementation. */
    #define GPIOTE_CONFIG_IN_SENSE_HITOLO     NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO
    /** @brief Macro for forwarding the new implementation. */
    #define GPIOTE_CONFIG_IN_SENSE_TOGGLE     NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE

    These aliases are to adapt usage of the old nrf_drv APIs to new nrfx APIs.

    Hieu

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

Related