NRF5340DK High frequency SPIM communication failure / spi_transceive() delay problem

Hello,

I am trying to exchange data with an external slave sensor via SPIM communication using the nRF5340DK.

I am currently facing two main issues:

1.The spi_transceive() function takes too long to execute.

This is significantly reducing the effective data transfer rate.

I have also reviewed a Q&A from someone who pointed out a similar delay with spi_transceive().

Time gap between each spi_transceive )

However, the solution suggested in that Q&A, which involved modifying spi configuration in the main.c, doesn't seem to be applicable to the nRF5340 as it resulted in an error

To clearly explain my situation, I have attached my main.c, overlay, and configuration files. 

main.c

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/gpio.h>
#include <stdio.h>

/* Get SPI device from devicetree */
#define SPI_DEVICE_NODE DT_ALIAS(spi4_basic)
static const struct device *spi_dev = DEVICE_DT_GET(SPI_DEVICE_NODE);

static const struct device *uart_for_data_tx = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));

#define SW0_NODE     DT_ALIAS(sw0)
static const struct gpio_dt_spec cs = GPIO_DT_SPEC_GET(SW0_NODE, gpios);


static struct spi_config spi_cfg = {
    .frequency = 1000000,           // SPI frequency.
    .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB, // SPI Mode 0
    .slave = 0,                     // Master mode
    .cs = NULL,
    //.cs = &spi_cs,                // Manage CS pin with cs-gpios in Devicetree
};


/* RHD2000 command definitions (16-bit) */
#define RHD_CMD_CONVERT(channel) (uint16_t)((((uint16_t)(channel) & 0x3F) << 8)) 
// Input: 00RR RRRR 0000 0000 
// Output: DDDD DDDD DDDD DDDD
#define RHD_CMD_WRITE(reg, data) (uint16_t)(0x8000 | (((uint16_t)(reg) & 0x3F) << 8) | ((uint16_t)(data) & 0xFF)) 
// Input: 10RR RRRR DDDD DDDD
// Output: 1111 1111 DDDD DDDD , D: same with input's D (for confirm)
#define RHD_CMD_READ(reg)        (uint16_t)(0xC000 | (((uint16_t)(reg) & 0x3F) << 8))
// Input: 11RR RRRR 0000 0000
#define RHD_CMD_CALIBRATE        (uint16_t)(0x5500) // 0101 0101 0000 0000 
#define RHD_CMD_DUMMY_READ       RHD_CMD_READ(63) //


static int rhd_spi_transfer(uint16_t tx_command, uint8_t *rx_buffer) {
    uint8_t tx_buffer[2];

    // Convert 16-bit command to a Big-Endian byte array
    tx_buffer[0] = (uint8_t)(tx_command >> 8);  // MSB
    tx_buffer[1] = (uint8_t)(tx_command & 0xFF); // LSB

    const struct spi_buf tx_spi_buf = {
        .buf = tx_buffer,
        .len = sizeof(tx_buffer)
    };
    const struct spi_buf_set tx_spi_bufs = {
        .buffers = &tx_spi_buf,
        .count = 1
    };

    // Directly store received data in the rx_buffer passed from main
    struct spi_buf rx_spi_buf = {
        .buf = rx_buffer,
        .len = 2
    };
    const struct spi_buf_set rx_spi_bufs = {
        .buffers = &rx_spi_buf,
        .count = 1
    };

    while (1) {

        gpio_pin_set_dt(&cs, 1); // CS -> 0

        int err = spi_transceive(spi_dev, &spi_cfg,
            &tx_spi_bufs, &rx_spi_bufs); 

        gpio_pin_set_dt(&cs, 0);

        return 0;
    }
}

int main(void)
{
    int ret;
    int err;
    ret = gpio_pin_configure_dt(&cs, GPIO_OUTPUT_INACTIVE);
    gpio_pin_set_dt(&cs, 0); // I still don't understand why the output is 1 when 0 is passed to the set function.

    if (!device_is_ready(spi_dev)) {
        printk("SPI device %s is not ready!\n", spi_dev->name);
        return 0;
    }

    if (!device_is_ready(uart_for_data_tx)) {
        printk("UART device for data TX (%s) is not ready!\n", uart_for_data_tx->name);
        return 0;
    }

    printk("nRF5340 SPIM example started.\n");
    printk("Reading MISO pin (connected to DC voltage) and sending to PC via UART every second.\n");


    uint8_t rx_main_buffer[2];

    err = rhd_spi_transfer(RHD_CMD_WRITE(0, 0xDE), rx_main_buffer); // ADC configuration, disable fast settle
    if (err) return err;


    while (1) {
        /* SPI communication (transceive) - send dummy bytes via MOSI to read MISO value */
        rhd_spi_transfer(RHD_CMD_READ(0) , rx_main_buffer);
    }
    return 0;
};

app.overlay

/ {

    chosen {
        zephyr,console = &uart0;    
        zephyr,shell-uart = &uart0; 
    };

    aliases {
        spi4-basic = &spi4;
    };

};




&uart0 {
    status = "okay";             
    current-speed = <1000000>;    
};


&pinctrl {
    spi4_custom_pins: spi4_custom_pins { 
        group1 {
            psels = <NRF_PSEL(SPIM_SCK,  1, 15)>,   
                    <NRF_PSEL(SPIM_MOSI, 1, 14)>,  
                    <NRF_PSEL(SPIM_MISO, 1, 13)>;  
        };
    };


    spi4_custom_pins_sleep: spi4_custom_pins_sleep {
         group1 {
            psels = <NRF_PSEL(SPIM_SCK,  1, 15)>,
                    <NRF_PSEL(SPIM_MOSI, 1, 14)>,
                    <NRF_PSEL(SPIM_MISO, 1, 13)>;
            low-power-enable; 
        };
    };
};


&spi4 {
    compatible = "nordic,nrf-spim";
    status = "okay"; 
    pinctrl-0 = <&spi4_custom_pins>; 
    pinctrl-1 = <&spi4_custom_pins_sleep>; 
    pinctrl-names = "default", "sleep";   
    cs-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
    //max-frequency = <560000>;
    //easydma-maxcnt-bits = <32>;
};

prj.conf

# enable console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y

CONFIG_SERIAL=y
CONFIG_PRINTK=y


CONFIG_SPI=y

CONFIG_GPIO=y 

In main.c, when I configure the SPI structure, I set the frequency to 1MHz.

I have confirmed with an oscilloscope that the actual clock frequency is indeed 1MHz.

Also, I'm controlling the CS pin directly via GPIO

2. Although the nRF5340 specifications state that it supports SPI frequencies up to 8MHz (and even up to 32MHz for spi4),

the quality of the Clock (CLK) signal degrades significantly at frequencies higher than 4MHz.

Thank you.

Best regards, 

gwan0624

  • Dear gwan0624, sorry for the delay. 

    Did you try adding CONFIG_SENSORS=yes? Did that solve the issue? 

    Here is the missing image from the Devcademy lesson you mentioned. Is the page working now?


    Best regards, 

    Håkon

  • I apologize for the delayed feedback on Turbo J's response. I was on summer vacation.

    Did you try adding CONFIG_SENSORS=yes? Did that solve the issue? 

    First, the issue was not resolved even after using CONFIG_SENSOR=yes.

    I have already tried using spi_transceive_dt(), but I encountered an error when calling SPI_DT_SPEC_GET. As a result, I am using a struct spi_config instead.


    However, as you initially suggested, the problem might be that I am not controlling the SPI properly through the device tree. (Currently, I am unable to load the custom pins from the overlay file using DEVICE_DT_GET, so I am using the built-in LED or Button instead). Therefore, I plan to re-examine the device tree configuration.

    Additionally, the following post recommends using the IFTIMING.CSNDUR register, which I also plan to try: How to reduce spi chipselect to sclk delay 

    Here is the missing image from the Devcademy lesson you mentioned. Is the page working now?

    Lastly, There are still several missing images on 'Exercise1-Interfacing with a sensor over SPI' at Lesson5.

    Missing image's caption: 

    BME280 sensor
    Sensor breakout board
    BME280 register memory map
    BME280 compensation parameters' register addresses
    P0.28 to P0.31 are used as SPI pins for nRF5340 DK overlay
    P0.05, P0.06, P0.25 & P0.26 are used as SPI pins for nRF7002 DK overlay.

    Best regards, 

    gwan0624 

  • Hi gwan0624, 

    Sorry for the long delay, I will try to get back to you next week. Please let me know if you have any significant new findings.

    gwan0624 said:
    Lastly, There are still several missing images on 'Exercise1-Interfacing with a sensor over SPI' at Lesson5.

    Please see the attached PDF for this lesson. 

    Zephyr SPI API - Nordic Developer Academy.pdf

    Best regards,

    Håkon

  • Dear Håkon,


    Thank you for your response again.

    As you mentioned, there have been new discoveries over the past month, but I was in a difficult situation as I was still experiencing the problem.


    Currently, I have succeeded in controlling the CS pin through the Devicetree instead of the existing GPIO-based CS pin control. (Simply performing a pristine build enabled the use of the existing Devicetree's SPI_DT_SPEC_GET related functions.)

    However, the long delay between the CS pin's ON/OFF and the CLK signal was not resolved even with Devicetree-based control.


    However, I found a post that dramatically reduced this delay when using the lower-level DPPI instead of the SPI Driver! 
    NRF5340 SPIM turnaround time 

    Here, there is almost no delay between the CLK and the CS pin! This was what I wanted.

    (In this post, the guy asking about a more advanced level of optimization than what I need, and it seems  haven't received an answer yet. Could you possibly help this person as well?)


    Therefore, I have rewritten the entire code to shorten the timing of interrupt events instead of using the SPI driver. However, since I have almost no experience writing code this way, I've managed to put together a rough version with the help of an LLM (Gemini 2.5).

    #include <zephyr/kernel.h>
    #include <stdio.h>
    
    #include <zephyr/sys/printk.h>
    #include <zephyr/device.h>
    
    #include <zephyr/drivers/uart.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/spi.h>
    
    #include <nrfx_spim.h>
    
    
    struct spi_dt_spec spispec = SPI_DT_SPEC_GET(DT_NODELABEL(bme280), SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 0);
    
    static const nrfx_spim_t spim_inst = NRFX_SPIM_INSTANCE(4);
    static uint8_t rx_buf0[2];
    static uint8_t rx_buf1[2];
    static uint8_t tx_cmd_buf[2];
    K_SEM_DEFINE(sem_data_ready, 0, 1);
    static volatile uint8_t *latest_rx_data_ptr;
    
    
    void spim_event_handler(nrfx_spim_evt_t const *p_event, void *p_context)
    {
        if (p_event->type == NRFX_SPIM_EVENT_DONE) {
            // 1. main() loop can process the completed buffer
            latest_rx_data_ptr = p_event->xfer_desc.p_rx_buffer;
            k_sem_give(&sem_data_ready);
    
            // 2. Immediately start the next transfer (IRQ-based Ping-Pong).
            // The fix is to declare and initialize the struct in one step.
            if (latest_rx_data_ptr == rx_buf0) {
                nrfx_spim_xfer_desc_t next_xfer_desc = NRFX_SPIM_XFER_TRX(tx_cmd_buf, sizeof(tx_cmd_buf), rx_buf1, sizeof(rx_buf1));
                nrfx_spim_xfer(&spim_inst, &next_xfer_desc, 0);
            } else {
                nrfx_spim_xfer_desc_t next_xfer_desc = NRFX_SPIM_XFER_TRX(tx_cmd_buf, sizeof(tx_cmd_buf), rx_buf0, sizeof(rx_buf0));
                nrfx_spim_xfer(&spim_inst, &next_xfer_desc, 0);
            }
            // Note: The call to nrfx_spim_xfer is also moved inside the if/else blocks
            // to respect the scope of next_xfer_desc. We can no longer check its return
            // value easily here, but in an IRQ handler, this is often acceptable.
        }
    }
    
    static int rhd_nrfx_spi_transfer_blocking(uint16_t tx_command, uint8_t *rx_buffer)
    {
        uint8_t tx_b[2];
        tx_b[0] = (uint8_t)(tx_command >> 8);
        tx_b[1] = (uint8_t)(tx_command & 0xFF);
        nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_TRX(tx_b, sizeof(tx_b), rx_buffer, 2);
        nrfx_err_t result = nrfx_spim_xfer(&spim_inst, &xfer, 0);
        return (result == NRFX_SUCCESS) ? 0 : -EIO;
    }
    
    
    
    
    int main(void)
    {
        int err;
        uint8_t rx_main_buffer[2];
        
        printk("nRF5340 SPIM high-speed example started.\n");
        
        
        nrfx_spim_config_t spim_config = {
            .sck_pin      = 47, // P1.15
            .mosi_pin     = 46, // P1.14
            .miso_pin     = 45, // P1.13
            .ss_pin       = 44, // P1.12
            .ss_active_high = false,
            .irq_priority = NRFX_SPIM_DEFAULT_CONFIG_IRQ_PRIORITY,
            .orc          = 0xFF,
            .frequency    = 16000000UL,
            .mode         = NRF_SPIM_MODE_0,
            .bit_order    = NRF_SPIM_BIT_ORDER_MSB_FIRST,
            .use_hw_ss    = true, 
            .ss_duration  = 2, 
       
        };
        
        
        err = nrfx_spim_init(&spim_inst, &spim_config, spim_event_handler, NULL);
        //for non blocking mode 
        
        
        if (err != NRFX_SUCCESS) {
            printk("NRFx SPIM non-blocking re-init failed: %d\n", err);
            return 0;
        }
        
        
        uint16_t read_cmd = RHD_CMD_READ(0);
        tx_cmd_buf[0] = (uint8_t)(read_cmd >> 8);
        tx_cmd_buf[1] = (uint8_t)(read_cmd & 0xFF);
    
        nrf_spim_shorts_enable(spim_inst.p_reg, NRF_SPIM_SHORT_END_START_MASK);
        //END/START short enable
        
        printk("High-speed data acquisition started.\n");
        
        nrfx_spim_xfer_desc_t first_xfer_desc = NRFX_SPIM_XFER_TRX(tx_cmd_buf, sizeof(tx_cmd_buf), rx_buf0, sizeof(rx_buf0));
        //IRQ based ping-pong chain
    
    
        err = nrfx_spim_xfer(&spim_inst, &first_xfer_desc, 0);
        
    	
    	if (err != NRFX_SUCCESS) {
            printk("Failed to start first xfer: %d\n", err);
            return 0;
        }
        
        
            while (1) {
    
            k_sem_take(&sem_data_ready, K_FOREVER);
    
        }
        
        return 0;
    }

    The code builds and flashes well, but there is no output from any of the CS, MOSI, MISO, and CLK pins.

    If you have a moment, would you be able to help me?


    Best regards,

    gwan0624

  • Dear gwan0624,

    Just letting you know I have received some input on a possible strategy regarding how to shorten the delay. I will provide you with more details on this next week.

    Regardig the quality of the signal: You could try to lower the signal voltage. This way the signal will have time to change from high to low and vice versa. It will also help to shorten the wires. 

    Best regards, 

    Håkon

Related