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

    I'm sorry, but I'm not sure what you're asking how to handle here. Is the READY handshake interrupting your transfers when you're not expecting it to, or what exactly is the issue here? There could be buffer that reaches its limit for example, but please share some more information here on what the issue here is.

    Best regards,

    Simon

  • nuts,,,, wrong button here.. let me see if I can clear the idea.

    Think of RS232 XON/XOFF functionality.  In this case we're using an outside signal to hold off the transmission of bytes.  When it asserts, I need to not send the next byte (while keeping the select signal asserted).  Once it de-asserts, I can resume sending bytes via SPI

  • Yes, there is a similar XON/XOFF functionality in the SPIM, use PPI to trigger hardware SPIM TASKS_SUSPEND and TASKS_RESUME from the active and inactive edges of the external hold signal.

    "A transaction can be suspended and resumed using the SUSPEND and RESUME tasks. When the SUSPEND task is triggered, the SPI master will complete transmitting and receiving the current ongoing byte before it is suspended"

  • Very interesting indeed.... sounds like that's the ticket for sure.  I don't suppose someone has some example code of how to do it?

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

Reply
  • 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.

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

  • 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.
Related