nrf5340 - CS/SS pin does not toggle with SPI+PPI+GPIOTE

Hello,

We were trying to sample 16-bit ADC data through SPIM at a constant and accurate 4kHz sampling rate. For that, we implemented PPI with TIMER event and SPIM task. We triggered GPIOTE task to toggle CS/SS pin externally using gppi_fork.

I referred to this post to setup the code.

CODE:

// Configure SPIM_CS_PIN
nrfx_gpiote_out_config_t  config_spim_cs_out = NRFX_GPIOTE_CONFIG_OUT_TASK_TOGGLE(true); 
err = nrfx_gpiote_out_init(SPIM_CS_PIN, &config_spim_cs_out);
    
// GPPI endpoint setup
uint32_t timer_event_addr = nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0);
uint32_t cs_task_addr = nrfx_gpiote_out_task_addr_get(SPIM_CS_PIN);
uint32_t spim_start_task_addr = nrfx_spim_start_task_get(&spim);
uint32_t spim_end_event_addr = nrfx_spim_end_event_get(&spim);
uint32_t led_task_addr = nrfx_gpiote_out_task_addr_get(LED_PIN);

nrfx_gppi_channel_endpoints_setup(ppi_channel,
	timer_event_addr,
	cs_task_addr);

nrfx_gppi_fork_endpoint_setup(ppi_channel, 
	spim_start_task_addr);

nrfx_gppi_channel_endpoints_setup(ppi_channel_endSPI,
	spim_end_event_addr,
	cs_task_addr);

EXPECTED RESULT: CS(orange) toggle high right before SPIM START(blue), and CS toggle low once SPIM END. 


ACTUAL RESULT: CS(orange) toggle high right after SPIM START(blue), and CS did not toggle low on SPIM END. Seemed like CS toggle happened only on SPIM END. 

CODE2: I did an experiment where I did not toggle CS back on SPIM END. 


// NO CS TOGGLE on SPIM END. 
nrfx_gppi_channel_endpoints_setup(ppi_channel,
	timer_event_addr,
	cs_task_addr);

nrfx_gppi_fork_endpoint_setup(ppi_channel, 
	spim_start_task_addr);

//nrfx_gppi_channel_endpoints_setup(ppi_channel_endSPI,
//	spim_end_event_addr,
//	cs_task_addr);

ACTUAL RESULT 2: CS toggle high before SPIM START, and still CS did not toggle low on SPIM END. Seemed like CS toggle happened only on SPIM START. 

I checked with LED and confirmed SPIM END event happened. LED was blinking as expected. 

void spim_event_handler(nrfx_spim_evt_t const *p_event, void *p_context)
{
	if (p_event->type == NRFX_SPIM_EVENT_DONE) 
	{
		gpio_pin_toggle_dt(&led);
	}
}

Q. Does anybody know the reason for this and how to fix it?

Also, image below shows a happy case where MOSI(blue) gets triggered while CS(orange) gets triggered (4-wire mode ADC). 

 

but we found in our case, MOSI stayed low all the time.

According to the documentation, MOSI stays HIGH all the time with 3-wire mode ADC. 

Q. How do I know if we are using 3-wire mode or 4-wire mode ADC? How to configure which mode to use?

Q. Any reason why MOSI stays low all the time? 

Thank you in advance for your time and effort,

Sincerely,

Skim.

  • Hi,

     

    Would it make sense to try to use SPIM4 peripheral, which has a dedicated hardware controlled /CSN pin?

    https://infocenter.nordicsemi.com/topic/ps_nrf5340/spim.html?cp=4_0_0_6_29_6#topic

     

    Q. Does anybody know the reason for this and how to fix it?

    Based on the scope pictures, it looks like the CSN task is called only on the END event, not the start event. There seems to be code missing, for instance for enabling and allocating etc. Have you looked at other samples that uses the nrfx_gppi API?

    https://github.com/zephyrproject-rtos/hal_nordic/tree/nrfx-2.11.0/nrfx/samples/src/nrfx_gppi

    https://github.com/nrfconnect/sdk-zephyr/tree/v3.3.99-ncs1/samples/boards/nrf/nrfx

    but we found in our case, MOSI stayed low all the time.

    According to the documentation, MOSI stays HIGH all the time with 3-wire mode ADC. 

    Q. How do I know if we are using 3-wire mode or 4-wire mode ADC? How to configure which mode to use?

    MOSI is undefined when /CS is inactive.

    Which mode are you referring to here? The SPI mode? If yes, that is set when you setup your spi device in firmware: https://github.com/nrfconnect/sdk-zephyr/blob/v3.3.99-ncs1/include/zephyr/drivers/spi.h#L311

     

    Which goes directly to the hardware setup in the registers: https://infocenter.nordicsemi.com/topic/ps_nrf5340/spim.html?cp=4_0_0_6_29_6_36#register.CONFIG

     

    Kind regards,

    Håkon

  • Hello,

    Thank you for your response. 

    Based on the scope pictures, it looks like the CSN task is called only on the END event, not the start event. There seems to be code missing, for instance for enabling and allocating etc. Have you looked at other samples that uses the nrfx_gppi API?

    -- I compared two samples you mentioned with my code, and I didn't find a significant difference. I am attaching my code here.

    #include <zephyr/kernel.h>
    #include <zephyr/drivers/gpio.h>
    #include <helpers/nrfx_gppi.h>
    #if defined(DPPI_PRESENT)
    #include <nrfx_dppi.h>
    #else
    #include <nrfx_ppi.h>
    #endif
    
    #include <nrfx_saadc.h>
    #include <nrfx_timer.h>
    #include <HP_saadc.h>
    
    //main on SPI master
    #include "nrf.h"
    #include <nrfx_spim.h>
    #include <nrfx_gpiote.h>
    #include <drivers/nrfx_errors.h>
    #include <string.h>
    
    nrfx_timer_t timer1 		= NRFX_TIMER_INSTANCE(1);
    
    #define SPIM_CS_PIN 	NRF_GPIO_PIN_MAP(1, 7)
    #define SPIM_SCK_PIN 	NRF_GPIO_PIN_MAP(1, 5)
    #define SPIM_MISO_PIN 	NRF_GPIO_PIN_MAP(1, 6)
    #define SPIM_MOSI_PIN	NRF_GPIO_PIN_MAP(1, 4)
    #define LED_PIN 		NRF_GPIO_PIN_MAP(0, 28)
    
    #define SPIM_SAMPLE_RATE	250	//250us sampling desired
    #define SPIM_INSTANCE		4
    
    static const nrfx_spim_t spim = NRFX_SPIM_INSTANCE(SPIM_INSTANCE);
    
    static uint8_t m_rx_buf[2];
    nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_RX(m_rx_buf, sizeof(m_rx_buf));
    
    void spim_event_handler(nrfx_spim_evt_t const *p_event, void *p_context)
    {
    	if (p_event->type == NRFX_SPIM_EVENT_DONE) 
    	{
    		uint16_t ads_data = p_event->xfer_desc.p_rx_buffer[0] << 8 | p_event->xfer_desc.p_rx_buffer[1];
    		printk("SPI transfer finished, %d\n", ads_data);
    	}
    }
    
    void timer_evt_handler(nrf_timer_event_t event_type, void * p_context)
    {
    }
    
    void timer_init(void)
    {
    	nrfx_err_t 			err = NRFX_SUCCESS;
    	nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
    	
    	timer_cfg.frequency          = NRF_TIMER_FREQ_16MHz;
        timer_cfg.mode               = NRF_TIMER_MODE_TIMER;
        timer_cfg.bit_width          = NRF_TIMER_BIT_WIDTH_32;		
        timer_cfg.interrupt_priority = 4   ;//NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY;
    
    	err = nrfx_timer_init(&timer1, &timer_cfg, timer_evt_handler);
    	if(err != NRFX_SUCCESS)
    	{
    		printk("Error! Could not initialize TIMER1: %d", err);
    	}
    
    	uint32_t time_ticks;
    	time_ticks = nrfx_timer_us_to_ticks(&timer1, SPIM_SAMPLE_RATE);
    
    	nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    	printk("TIMER configured\n");
    }
    
    void timer_enable(void)
    {
    	nrfx_timer_clear(&timer1);
    	nrfx_timer_enable(&timer1);
    	printk("TIMER enabled\n");
    }
    
    void spim_init(void)
    {
    	nrfx_err_t 		   err = NRFX_SUCCESS;
    	nrfx_spim_config_t spim_config = NRFX_SPIM_DEFAULT_CONFIG(SPIM_SCK_PIN, SPIM_MOSI_PIN, SPIM_MISO_PIN, NRFX_SPIM_PIN_NOT_USED);
    	
    	spim_config.ss_active_high = true;
    
    	IRQ_CONNECT(DT_IRQN(DT_NODELABEL(spi4)),
    				DT_IRQ(DT_NODELABEL(spi4), priority),
    				nrfx_isr, nrfx_spim_4_irq_handler, 0);
    
    	err = nrfx_spim_init(&spim, &spim_config, spim_event_handler, NULL);
    	err = nrfx_spim_xfer(&spim, &xfer_desc, NRFX_SPIM_FLAG_HOLD_XFER);
    	printk("SPIM configured\n");
    }
    
    void gpiote_init(void)
    {
    	uint8_t 		   out_channel;
    	nrfx_err_t 		   err = NRFX_SUCCESS;
    	
        IRQ_DIRECT_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_GPIOTE), IRQ_PRIO_LOWEST, nrfx_gpiote_irq_handler,
                           0);
    	if(!nrfx_gpiote_is_init())
    	{
    		err = nrfx_gpiote_init(NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
    		if (err != NRFX_SUCCESS) {
    		printk("nrfx_gpiote_init error: %08x\n", err);
    		return;
    		}
    	}
    
    	err = nrfx_gpiote_channel_alloc(&out_channel);
    	if(err != NRFX_SUCCESS) 
    	{
    		printk("nrfx_gpiote_channel_alloc error\n");
    		return;
    	}
    
    	static const nrfx_gpiote_output_config_t output_config =
        {
            .drive = NRF_GPIO_PIN_S0S1,
            .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
            .pull = NRF_GPIO_PIN_NOPULL,
        };
    
        const nrfx_gpiote_task_config_t task_config =
        {
            .task_ch = out_channel,
            .polarity = NRF_GPIOTE_POLARITY_TOGGLE,
            .init_val = NRF_GPIOTE_INITIAL_VALUE_HIGH,
        };
        
    	err = nrfx_gpiote_output_configure(SPIM_CS_PIN, &output_config, &task_config);
    	if (err != NRFX_SUCCESS) {
    		printk("nrfx_gpiote_output_configure error\n");
    		return;
    	}
    
    	// nrfx_gpiote_out_config_t  config_spim_cs_out = NRFX_GPIOTE_CONFIG_OUT_TASK_TOGGLE(true); 
        // err = nrfx_gpiote_out_init(SPIM_CS_PIN, &config_spim_cs_out);
    
    	nrfx_gpiote_out_config_t config_led_out = NRFX_GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
    	err = nrfx_gpiote_out_init(LED_PIN, &config_led_out);
    	
    	nrfx_gpiote_out_task_enable(SPIM_CS_PIN);
    	nrfx_gpiote_out_task_enable(LED_PIN);
    }
    
    void ppi_init(void)
    {
    	nrfx_err_t err;
    
    	uint8_t    ppi_channel;
    	uint8_t    ppi_channel_endSPI;
    
    	// allocate (D)PPI channel
    	if (nrfx_gppi_channel_alloc(&ppi_channel) != NRFX_SUCCESS ||
    		nrfx_gppi_channel_alloc(&ppi_channel_endSPI) != NRFX_SUCCESS) 
    	{
    		printk("PPI channel allocation error\n");
    		return;
    	}
    
    	uint32_t timer_event_addr = nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0);
    	uint32_t cs_task_addr = nrfx_gpiote_out_task_addr_get(SPIM_CS_PIN);
    	uint32_t spim_start_task_addr = nrfx_spim_start_task_get(&spim);
    	uint32_t spim_end_event_addr = nrfx_spim_end_event_get(&spim);
    
    	nrfx_gppi_channel_endpoints_setup(ppi_channel,
    		timer_event_addr,
    		cs_task_addr);
    
    	nrfx_gppi_fork_endpoint_setup(ppi_channel, 
    		spim_start_task_addr);
    
    	nrfx_gppi_channel_endpoints_setup(ppi_channel_endSPI,
    		spim_end_event_addr,
    		cs_task_addr);
    
    	nrfx_gppi_channels_enable(BIT(ppi_channel) | 
    							  BIT(ppi_channel_endSPI));
    
    	if (nrfx_gppi_channel_check(ppi_channel) && 
    		nrfx_gppi_channel_check(ppi_channel_endSPI))
    		{
    			printk("Everything configured\n");
    		}
    	
    	printk("PPI configured\n");
    }
    
    void ppi_spim_main(void)
    {
    	printk("nrfx_saadc sample on %s\n", CONFIG_BOARD);
    
    	timer_init();
    	spim_init();
    	gpiote_init();
    	ppi_init();
    
    	timer_enable();
    
    	while(1)
    	{
    		k_msleep(1000);
    	}
    }

    Q. I saw in the nrfx_gppi sample, the way it initiated GPIOTE pin was by calling nrfx_gpiote_output_configure(). What is the difference between using nrfx_gpiote_output_configure() and using nrfx_gpiote_out_init()?

    MOSI is undefined when /CS is inactive.

    Which mode are you referring to here? The SPI mode? If yes, that is set when you setup your spi device in firmware: https://github.com/nrfconnect/sdk-zephyr/blob/v3.3.99-ncs1/include/zephyr/drivers/spi.h#L311

     

    Which goes directly to the hardware setup in the registers: https://infocenter.nordicsemi.com/topic/ps_nrf5340/spim.html?cp=4_0_0_6_29_6_36#register.CONFIG

    -- I was referring to 3-wire and 4-wire mode of ADC.

    -- I am attaching a datasheet for ads8866: ads8866_datasheet.pdf. Page 21 and 22 shows a timing diagram of 3-wire and 4-wire mode. Here it says DIN/MOSI pin stays high with 3-wire mode and DIN/MOSI pin gets toggled on every SPIM_START with 4-wire mode. 

    Q. How do I know if we are using 3-wire mode or 4-wire mode ADC? How to configure which mode to use?

    The problem I am getting is the Slave Select line not toggling correctly so we end up only getting every other sample. 

    Q. Even though we are getting every other sample, MOSI stays low all the time. Do you think this could be related to our SS-not-toggle problem?

    Thank you,

    Sincerely,

    Skim.

  • Hi Skim,

     

    Try to use SET/CLR on the events, here's a suggestion:

    void ppi_init(void)
    {
    	nrfx_err_t err;
    
    	uint8_t    ppi_channel;
    	uint8_t    ppi_channel_endSPI;
    
    	// allocate (D)PPI channel
    	if (nrfx_gppi_channel_alloc(&ppi_channel) != NRFX_SUCCESS ||
    		nrfx_gppi_channel_alloc(&ppi_channel_endSPI) != NRFX_SUCCESS) 
    	{
    		printk("PPI channel allocation error\n");
    		return;
    	}
    
    	uint32_t timer_event_addr = nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0);
    	uint32_t cs_task_addr = nrfx_gpiote_set_task_addr_get(SPIM_CS_PIN);
    	uint32_t spim_start_task_addr = nrfx_spim_start_task_get(&spim);
    	uint32_t spim_end_event_addr = nrfx_spim_end_event_get(&spim);
    
    	nrfx_gppi_channel_endpoints_setup(ppi_channel,
    		timer_event_addr,
    		cs_task_addr);
    
    	nrfx_gppi_fork_endpoint_setup(ppi_channel, 
    		spim_start_task_addr);
    
    	nrfx_gppi_channel_endpoints_setup(ppi_channel_endSPI,
    		spim_end_event_addr,
    		nrfx_gpiote_clr_task_addr_get(SPIM_CS_PIN));
    
    	nrfx_gppi_channels_enable(BIT(ppi_channel) | 
    							  BIT(ppi_channel_endSPI));
    
    	if (nrfx_gppi_channel_check(ppi_channel) && 
    		nrfx_gppi_channel_check(ppi_channel_endSPI))
    		{
    			printk("Everything configured\n");
    		}
    	
    	printk("PPI configured\n");
    }

     

    This will set/clear when SPIM starts and stops:

     

    Note, you can also use the hardware controlled CSN line for SPIM4.

    skim said:

    -- I am attaching a datasheet for ads8866: ads8866_datasheet.pdf. Page 21 and 22 shows a timing diagram of 3-wire and 4-wire mode. Here it says DIN/MOSI pin stays high with 3-wire mode and DIN/MOSI pin gets toggled on every SPIM_START with 4-wire mode. 

    Q. How do I know if we are using 3-wire mode or 4-wire mode ADC? How to configure which mode to use?

    The problem I am getting is the Slave Select line not toggling correctly so we end up only getting every other sample. 

    I am not familiar with this sensor. Have you tried asking the manufacturer about this?

     

    skim said:
    Q. Even though we are getting every other sample, MOSI stays low all the time. Do you think this could be related to our SS-not-toggle problem?

    You are not setting the TX buffer, only RX. if you set both to be equal (ie. send 2, receive 2) you can define the TX buffer as you'd like.

     

    Kind regards,

    Håkon

  • This has solved my problem! Thank you so much!

Related