Implementation of Periodic SPI Operations with nrfx

Good evening,

I would like to execute an SPI operation every 20ms using an interruption. I saw that it was recommended utilizing nrfx timer and PPI to achieve this, ensuring that the SPI writing process does not encounter timeouts. But I have difficulties implementing this solution, and don't manage to find a lot of documentation about it, and online samples seem to be a bit outdated.

Would you be able to help me implementing this, either with a small sample or some guidelines? Thank you very much and have a good day.

Parents
  • Hi,

    1) Set a timer in timer mode:

    #define TIMER1_INST_IDX 0
    #define TIMER_COMPARE_COUNT 320000  // 16000000 * 0.02s
    
    // Timer instance
    const nrfx_timer_t timer1_inst = NRFX_TIMER_INSTANCE(TIMER1_INST_IDX);
    
    void timer_init(void)
    {
        nrfx_err_t err;
        (void)err;
        
        // Configure Timer
        uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(timer1_inst.p_reg);
        nrfx_timer_config_t config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
        config.bit_width = NRF_TIMER_BIT_WIDTH_32;
        config.mode = NRF_TIMER_MODE_TIMER;
        config.p_context = &timer1_inst;
        err = nrfx_timer_init(&timer1_inst, &config, NULL);
        NRFX_ASSERT(err == NRFX_SUCCESS);
        
        #if defined(__ZEPHYR__)
        IRQ_DIRECT_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_TIMER_INST_GET(TIMER1_INST_IDX)), IRQ_PRIO_LOWEST,
        NRFX_TIMER_INST_HANDLER_GET(TIMER1_INST_IDX), 0);
        #endif
        
        /* Setting the timer (in timer mode) channel NRF_TIMER_CC_CHANNEL0 in the extended compare
         * mode to clear the timer and trigger an event if internal counter register is equal
         * to TIMER_COMPARE_COUNT.
         */
        nrfx_timer_extended_compare(&timer1_inst, NRF_TIMER_CC_CHANNEL0, TIMER_COMPARE_COUNT,
        NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    }

    2) Set SPIM:

    // SPIM instance to be used
    #define SPIM_INST_IDX 4
    
    // SPI instance
    nrfx_spim_t spim_inst = NRFX_SPIM_INSTANCE(SPIM_INST_IDX);
    
    // SPI Configuration
    void SPI_Init(void)
    {
        nrfx_spim_config_t spim_config = NRFX_SPIM_DEFAULT_CONFIG(SCK_PIN,
                                                                  MOSI_PIN,
                                                                  MISO_PIN,
                                                                  CS_PIN);
    
    	spim_config.ss_pin = CS_PIN;
    	spim_config.use_hw_ss = true;		// hardware SS (SPIM4 only)
    
        // Enable IRQ if necessary
    	// IRQ_CONNECT(DT_IRQN(DT_NODELABEL(spi4)),
    	// 			DT_IRQ(DT_NODELABEL(spi4), priority),
    	// 			nrfx_isr, nrfx_spim4_irq_handler, 0);
    
        err = nrfx_spim_init(&spim_inst, &spim_config, NULL, NULL);
        NRFX_ASSERT(err != NRFX_SUCCESS);
    }    

    3) Configure DPPI:

    uint8_t ppi_channel1;
    
    void ppi_init(void)
    {
    	nrfx_err_t err;
    
    	/* Allocate a (D)PPI channel. */
    	err = nrfx_gppi_channel_alloc(&ppi_channel1);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("nrfx_gppi_channel_alloc error: 0x%08X", err);
    		return;
    	}
    	
    	/* Configure endpoints of the channel so that the TIMER1, CCCH0 event is
    	 * connected with the SPIM task.
    	 */
    	nrfx_gppi_channel_endpoints_setup(ppi_channel1,
    			nrfx_timer_compare_event_address_get(&timer1_inst, NRF_TIMER_CC_CHANNEL0),
    			nrfx_spim_start_task_address_get(&spim_inst));
    	
    	// Enable the channel
    	nrfx_gppi_channels_enable(BIT(ppi_channel1));
    }

    4) Configure xfer and start timer:

        uint8_t tx_data[TX_DATA_SIZE]; // FILL with data to send
        uint8_t rx_data[RX_DATA_SIZE];
        
        // SPIM Descriptor
        nrfx_spim_xfer_desc_t spim_xfer_desc = NRFX_SPIM_XFER_TRX(tx_data, sizeof(tx_data), rx_data, sizeof(rx_data));
    
    	// Configure SPIM transfer, hold for an event, increment rx pointer after transfer
    	// Note: Should reset the pointer at some point
    	err = nrfx_spim_xfer(&spim_inst, &spim_xfer_desc, NRFX_SPIM_FLAG_HOLD_XFER|NRFX_SPIM_FLAG_REPEATED_XFER|NRFX_SPIM_FLAG_RX_POSTINC|NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER);
        NRFX_ASSERT(err != NRFX_SUCCESS);
    
    	
    	nrfx_timer_clear(&timer1_inst);
        nrfx_timer_enable(&timer1_inst);

    Enjoy!

  • Hello, thank you for your answers.

    In the end it appear that I can't use PPI for my application as I need to have fine control over SPI transactions...

    I tried to perform my SPI transactions in a dedicated thread that is triggered by a 50 Hz timer.
    Unfortunately, my SPI thread is not triggered every time that timer callback is called. Do you have nay idea of what be the cause of this issue ?

    I've tried to trigger the thread with either semaphore or thread management (with k_thread_resume()) but the observed behaviour is the same

    This is the test code I'm using:

    void t_test(void){
        while(1){
            //k_sem_take(&_sem, K_FOREVER);
            printk("Second Thread\n");
            k_thread_suspend(t_thread);
        }
    }
    
    
    
    void main(void)
    {
        //k_sem_init(&_sem, 0, 1);
    
        t_thread = k_thread_create(&t_k_thread, _stack, STACKSIZE, t_test, NULL, NULL, NULL, -2, 0, K_NO_WAIT);
        k_msleep(1000);
        printk("Starting timer\n");
        meas_timer_init();
    
        while (1) {
            printk("Main thread: still alive\n");
            k_msleep(1000);
        }
    }

    And this is the timer part :

    ISR_DIRECT_DECLARE(meas_timer_handler)
    {
    
        if (NRF_TIMER1->EVENTS_COMPARE[0]) {
            NRF_TIMER1->EVENTS_COMPARE[0] = 0;
            printk("Timer\n");
            //k_sem_give(&_sem);
            k_thread_resume(t_thread);
           
        }
        return 0;
    }
    
    void meas_timer_init(void)
    {
        IRQ_DIRECT_CONNECT(TIMER1_IRQn, 4, meas_timer_handler, 0);
        NRF_TIMER1->BITMODE = 32;
        NRF_TIMER1->PRESCALER = 8;
        NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;
        NRF_TIMER1->CC[0] = 1250;
        NRF_TIMER1->SHORTS = 0;
        NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
        NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
        NRF_TIMER1->TASKS_START = 1;
        irq_enable(TIMER1_IRQn);
    }

    Thank you,
    Best regards

Reply
  • Hello, thank you for your answers.

    In the end it appear that I can't use PPI for my application as I need to have fine control over SPI transactions...

    I tried to perform my SPI transactions in a dedicated thread that is triggered by a 50 Hz timer.
    Unfortunately, my SPI thread is not triggered every time that timer callback is called. Do you have nay idea of what be the cause of this issue ?

    I've tried to trigger the thread with either semaphore or thread management (with k_thread_resume()) but the observed behaviour is the same

    This is the test code I'm using:

    void t_test(void){
        while(1){
            //k_sem_take(&_sem, K_FOREVER);
            printk("Second Thread\n");
            k_thread_suspend(t_thread);
        }
    }
    
    
    
    void main(void)
    {
        //k_sem_init(&_sem, 0, 1);
    
        t_thread = k_thread_create(&t_k_thread, _stack, STACKSIZE, t_test, NULL, NULL, NULL, -2, 0, K_NO_WAIT);
        k_msleep(1000);
        printk("Starting timer\n");
        meas_timer_init();
    
        while (1) {
            printk("Main thread: still alive\n");
            k_msleep(1000);
        }
    }

    And this is the timer part :

    ISR_DIRECT_DECLARE(meas_timer_handler)
    {
    
        if (NRF_TIMER1->EVENTS_COMPARE[0]) {
            NRF_TIMER1->EVENTS_COMPARE[0] = 0;
            printk("Timer\n");
            //k_sem_give(&_sem);
            k_thread_resume(t_thread);
           
        }
        return 0;
    }
    
    void meas_timer_init(void)
    {
        IRQ_DIRECT_CONNECT(TIMER1_IRQn, 4, meas_timer_handler, 0);
        NRF_TIMER1->BITMODE = 32;
        NRF_TIMER1->PRESCALER = 8;
        NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;
        NRF_TIMER1->CC[0] = 1250;
        NRF_TIMER1->SHORTS = 0;
        NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
        NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
        NRF_TIMER1->TASKS_START = 1;
        irq_enable(TIMER1_IRQn);
    }

    Thank you,
    Best regards

Children
Related