This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

How to properly configure periodic SPI transfers with PPI and TIMER?

I'm working on a BLE application based on the Zephyr RTOS. I need to read a SPI device at fixed time intervals and, as already suggested me here at point 3, I'm trying to implement a PPI channel that connects the TIMER0 event to the SPI task. I wrote a simple code just to test this feature but it' doesn't work, sure cause I lost something:

#include <zephyr.h>
#include <logging/log.h>

#define LOG_MODULE_NAME main
LOG_MODULE_REGISTER( LOG_MODULE_NAME );

#define SCK_PIN                    0x04
#define MISO_PIN                   0x1D
#define MOSI_PIN                   0x1C
#define CS_PIN                     0x03

static const nrfx_timer_t timerInst = NRFX_TIMER_INSTANCE( 0 );
static const nrfx_spim_t spiInst = NRFX_SPIM_INSTANCE( 0 );
static const nrfx_spim_config_t spiConfig = NRFX_SPIM_DEFAULT_CONFIG( SCK_PIN, MOSI_PIN, MISO_PIN, CS_PIN );
nrf_ppi_channel_t readingPpiChan;
static uint32_t readingTaskAddr;
static uint32_t readingEventAddr;
static uint8_t tempBuff[ 2 ];
static uint8_t tempReg;

void Timer_Handler( nrf_timer_event_t event_type, void *p_context )
{
    
}

nrfx_err_t Spi_Timer_Ppi_Init( void )
{
    nrfx_err_t err; 

    err = nrfx_spim_init( &spiInst, &spiConfig, NULL, NULL );

    if ( err != NRFX_SUCCESS )
    {
        LOG_ERR( "nrfx_spim_init error: %08X", err );
        return err;
    }

    tempReg = 0x80;
    nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_TRX( &tempReg, 1, tempBuff, 2 );
    uint32_t flags = NRFX_SPIM_FLAG_HOLD_XFER | NRFX_SPIM_FLAG_RX_POSTINC | NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER | NRFX_SPIM_FLAG_REPEATED_XFER;
    err = nrfx_spim_xfer( &spiInst, &xfer, flags );

    if ( err == NRFX_SUCCESS )
    {
        readingTaskAddr = nrfx_spim_start_task_get( &spiInst );
    }

    nrfx_timer_config_t timerConfig = NRFX_TIMER_DEFAULT_CONFIG;
    timerConfig.bit_width = NRF_TIMER_BIT_WIDTH_32;
    err = nrfx_timer_init( &timerInst, &timerConfig, Timer_Handler );

    if ( err != NRFX_SUCCESS )
    {
        LOG_ERR( "nrfx_timer_init error: %08X", err );
        return err;
    }

    nrfx_timer_compare( &timerInst, NRF_TIMER_CC_CHANNEL0, nrfx_timer_ms_to_ticks( &timerInst, 1000 ), false );
    nrfx_timer_enable( &timerInst );

    err = nrfx_ppi_channel_alloc( &readingPpiChan );

    if ( err != NRFX_SUCCESS )
    {
        LOG_ERR( "nrfx_ppi_channel_alloc error: %08X", err );
        return err;
    }

    readingEventAddr = nrfx_timer_compare_event_address_get( &timerInst, readingPpiChan );
    err = nrfx_ppi_channel_assign( readingPpiChan, readingEventAddr, readingTaskAddr );

    if ( err != NRFX_SUCCESS )
    {
        LOG_ERR( "nrfx_ppi_channel_assign error: %08X", err );
        return err;
    }

    err = nrfx_ppi_channel_enable( readingPpiChan );

    if ( err != NRFX_SUCCESS )
    {
        LOG_ERR( "nrfx_ppi_channel_enable error: %08X", err );
        return;
    }

    return NRFX_SUCCESS;
}

void main( void )
{
    Spi_Timer_Ppi_Init();

    while ( 1 )
    {
        
    }
}

I have some doubts:

1) Here it describes that if I wanto to use the NRFX_SPIM_FLAG_HOLD_XFER flag I must to set the chip select pin as NRFX_SPIM_PIN_NOT_USED and manage it outside the driver. How I should do it if the tranfers are supposed to be autonomous?

2) To generate the TIMER0 event in the PPI can I set false the enable_int parameter in the nrfx_timer_compare function or I have to set it as true? Because if I enable the interrupt, the Zephyr OS crash and restart the chip.

What am I doing wrong? Can you help me?

  • Hi,

    The second argument to nrfx_timer_compare_event_address_get() should be the TIMER COMPARE channel, not the PPI channel (e.g. 0 for COMPARE0). Most likely, the variable readingEventAddr does not contain the desired address after the function call (0x40008140), and the PPI channel will connect to something else.

    1) Here it describes that if I wanto to use the NRFX_SPIM_FLAG_HOLD_XFER flag I must to set the chip select pin as NRFX_SPIM_PIN_NOT_USED and manage it outside the driver. How I should do it if the tranfers are supposed to be autonomous?

    If you have multiple SPI devices on the same bus, or need to control the CS line for power reasons, you can setup multiple COMPARE events to trigger after each other in the same timer, and use a second PPI channel together with GPIOTE to toggle the CS line before starting the SPI transfer. Similarly, you can toggle the GPIOTE channel when SPI END event is generated.

    2) To generate the TIMER0 event in the PPI can I set false the enable_int parameter in the nrfx_timer_compare function or I have to set it as true? Because if I enable the interrupt, the Zephyr OS crash and restart the chip.

    Yes, interrupt is not needed when using PPI. This is only used when you need the event to trigger an interrupt in SW.

    Best regards,
    Jørgen

  • The second argument to nrfx_timer_compare_event_address_get() should be the TIMER COMPARE channel, not the PPI channel (e.g. 0 for COMPARE0). Most likely, the variable readingEventAddr does not contain the desired address after the function call (0x40008140), and the PPI channel will connect to something else.

    Yes, you are right. Thanks.

    After that I wrote a semi-working code with some modifications. And I found out:

    1) With the TIMER0 the code didn't work. I read somewhere that the TIMER0 is supposed to be used by the radio module. I shifted to the TIMER1 and now it works. Is it correct that the TIMER0 belongs to the radio module?

    2) SPI with PPI doesn't support anyway the managening of chip select signal. As explained here if the transfer is configured as NRFX_SPIM_FLAG_HOLD_XFER the chip select needs to be managed necessarily outside the driver. Am I right? For this reason I created other two PPI channels to toggle the chip select using the SPI START and END events.

    My code now is:

    #define LOG_MODULE_NAME main
    LOG_MODULE_REGISTER( LOG_MODULE_NAME );
    
    #define SCK_PIN                    0x04
    #define MISO_PIN                   0x1D
    #define MOSI_PIN                   0x1C
    #define CS_PIN                     0x03
    
    static const nrfx_timer_t readingTimerInst = NRFX_TIMER_INSTANCE( 1 );
    static const nrfx_spim_t spiInst = NRFX_SPIM_INSTANCE( 0 );
    static const nrfx_spim_config_t spiConfig = NRFX_SPIM_DEFAULT_CONFIG( SCK_PIN, MISO_PIN, MOSI_PIN, NRFX_SPIM_PIN_NOT_USED );
    nrf_ppi_channel_t readingPpiChan;
    nrf_ppi_channel_t csPpiChan;
    nrf_ppi_channel_t spiEndPpiChan;
    static uint32_t readingTaskAddr;
    static uint32_t csTaskAddr;
    static uint32_t readingEventAddr;
    static uint32_t spiEndEventAddr;
    static uint8_t tempBuff[ 2 ];
    static uint8_t tempReg;
    
    void Timer_Handler( nrf_timer_event_t event_type, void *p_context )
    {
        LOG_INF("Timer completed.");
    }
    
    void Spi_Handler( nrfx_spim_evt_t const *p_event, void *p_context )
    {
        switch ( p_event->type )
        {
            case NRFX_SPIM_EVENT_DONE:
            {
                LOG_INF("Spi transfer completed. Received %02X", tempBuff[ 1 ] );
                break;
            }
    
            default:
            {
                break;
            }
        }
    }
    
    nrfx_err_t Spi_Timer_Ppi_Init( void )
    {
        nrfx_err_t err;
    
        /********************************************************************
         *     SPI
         * *****************************************************************/
        IRQ_CONNECT( DT_IRQN( DT_NODELABEL( spi0 ) ), DT_IRQ( DT_NODELABEL( spi0 ), priority ), nrfx_isr, nrfx_spim_0_irq_handler, 0 );
    
        err = nrfx_spim_init( &spiInst, &spiConfig, Spi_Handler, tempBuff );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_spim_init error: %08X", err );
            return err;
        }
    
        tempReg = 0x80;
        nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_TRX( &tempReg, 1, tempBuff, 2 );
        uint32_t flags = NRFX_SPIM_FLAG_HOLD_XFER | NRFX_SPIM_FLAG_RX_POSTINC | NRFX_SPIM_FLAG_REPEATED_XFER;
    
        err = nrfx_spim_xfer( &spiInst, &xfer, flags );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_spim_xfer error: %08X", err );
            return err;
        }
    
        readingTaskAddr = nrfx_spim_start_task_get( &spiInst );
        spiEndEventAddr = nrfx_spim_end_event_get( &spiInst );
    
        /********************************************************************
         *     TIMER
         * *****************************************************************/
        nrfx_timer_config_t timerConfig = NRFX_TIMER_DEFAULT_CONFIG;
        timerConfig.bit_width = NRF_TIMER_BIT_WIDTH_32;
    
        err = nrfx_timer_init( &readingTimerInst, &timerConfig, Timer_Handler );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_timer_init error: %08X", err );
            return err;
        }
    
        uint32_t ticks = nrfx_timer_ms_to_ticks( &readingTimerInst, 1000 );
        nrfx_timer_extended_compare( &readingTimerInst, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false );
        readingEventAddr = nrfx_timer_compare_event_address_get( &readingTimerInst, NRF_TIMER_CC_CHANNEL0 );
        nrfx_timer_enable( &readingTimerInst );
    
        /********************************************************************
         *     CS
         * *****************************************************************/
        nrfx_gpiote_out_config_t const out_config = {
            .action = NRF_GPIOTE_POLARITY_TOGGLE,
            .init_state = 1,
            .task_pin = true,
        };
    
        err = nrfx_gpiote_out_init( CS_PIN, &out_config );
        if (err != NRFX_SUCCESS)
        {
            LOG_ERR("nrfx_gpiote_out_init error: %08x", err );
            return err;
        }
    
        nrfx_gpiote_out_task_enable( CS_PIN );
        csTaskAddr = nrfx_gpiote_out_task_addr_get( CS_PIN );
    
    
        /********************************************************************
         *     PPI
         * *****************************************************************/
        err = nrfx_ppi_channel_alloc( &readingPpiChan );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_alloc error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_assign( readingPpiChan, readingEventAddr, readingTaskAddr );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_assign error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_enable( readingPpiChan );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_enable error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_alloc( &csPpiChan );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_alloc error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_assign( csPpiChan, readingEventAddr, csTaskAddr );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_assign error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_enable( csPpiChan );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_enable error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_alloc( &spiEndPpiChan );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_alloc error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_assign( spiEndPpiChan, spiEndEventAddr, csTaskAddr );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_assign error: %08X", err );
            return err;
        }
    
        err = nrfx_ppi_channel_enable( spiEndPpiChan );
        if ( err != NRFX_SUCCESS )
        {
            LOG_ERR( "nrfx_ppi_channel_enable error: %08X", err );
            return err;
        }
    
        return NRFX_SUCCESS;
    }
    
    void main( void )
    {
        Spi_Timer_Ppi_Init();
    
        while ( 1 )
        {
            
        }
    }

    But now I have a little issue again. If I run the code above on the nRF52833 the sistem reboot every fixed time (about 25 seconds) without errors in the nRF terminal. Why?

    And last: how can I configure just one PPI channel for the timer compare event since it triggers two tasks, the SPI transfer and the chip select toggle?

  • But now I have a little issue again. If I run the code above on the nRF52833 the sistem reboot every fixed time (about 25 seconds) without errors in the nRF terminal. Why?

    I found the problem: NRFX_SPIM_FLAG_RX_POSTINC made an overflow in my size-defined rx buffer. I disabled it and now the code works.

  • Andrea Verdecchia said:
    I found the problem: NRFX_SPIM_FLAG_RX_POSTINC made an overflow in my size-defined rx buffer.

    Good that you found the problem, that makes sense!

    Andrea Verdecchia said:
    1) With the TIMER0 the code didn't work. I read somewhere that the TIMER0 is supposed to be used by the radio module. I shifted to the TIMER1 and now it works. Is it correct that the TIMER0 belongs to the radio module?

    TIMER0 does not belong to the RADIO, but it is used by many radio protocols for short and precise timing interval, including the Multiprotocol Service Layer (MPSL), which is used by the Softdevice controller for BLE, OpenThread, and Zigbee protocols.

    Andrea Verdecchia said:
    2) SPI with PPI doesn't support anyway the managening of chip select signal. As explained here if the transfer is configured as NRFX_SPIM_FLAG_HOLD_XFER the chip select needs to be managed necessarily outside the driver. Am I right? For this reason I created other two PPI channels to toggle the chip select using the SPI START and END events.

    Yes, that is correct.

    Andrea Verdecchia said:
    And last: how can I configure just one PPI channel for the timer compare event since it triggers two tasks, the SPI transfer and the chip select toggle?

    You can use the FORK feature of the PPI peripheral to trigger two separate tasks from one event. See the nrfx_ppi_channel_fork_assign() API documentation.

Related