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

  • Hi

    We are severely understaffed this week because of the Christmas holidays, and Kenneth will have to get back to you on this next week. Sorry for the inconvenience.

    Best regards
    Torbjørn

Reply Children
No Data
Related