Last byte of SPI transfer sometimes ends up in next transfer

Currently I'm working on the implementation of a driver for a SPI sensor using nRF Connect SDK v2.4.0.

The sensor uses an interrupt line to signal a measurement is ready at about 1 KHz. At this frequency 4 registers of the sensors are read and printed over a UART using printk using the work queue. Each transfer is 4 bytes. I noticed the sensor would stop asserting the interrupt line after a while. After some investication I found some issue in the SPI transfer. It sometimes seems to repeat the last byte of the previous transfer in the new transfer and than clocks out 3 of the 4 bytes of the new transfer. This issue appears within seconds or 10s of seconds after resetting the mcu.

Next to SPI3 the following peripherals are used:

 - USB - Input/Output for the Zephyr Shell
 - BLE (just advertising, not connected)
 - UART0 - Output for printk - baudrate 921600
 - I2C0
 - GPIO

The clock frequency is 5 MHz.

A logic analyzer shows the following:

*Note* I used to clock out 0x00 after the first byte, but for testing purposes I changed the bytes to: {[register address], 0x23, 0x34, 0x45}

As a test I rewrote the code to be triggered by a timer and the routine runs in a custom work queue (with a priority of 5 and a stack size of 1024 bytes). The timeout is set to an interval of 1 ms. I moved to a development kit of panasonic (with a nRF52840) with the MISO and MOSI connected together. I added a few lines of code to the sensor_read_reg routine so it compares the output with the input..

int sensor_read_reg(const struct device *spi, struct spi_config *config, uint8_t reg, uint16_t *data)
{

	int err;

    uint8_t tx_data[4] = {0x00, 0x23, 0x34, 0x45};
    uint8_t rx_data[4] = {0};


    tx_data[0] = 0x80 | reg;

	// Initialize tx-buffer
	struct spi_buf tx_buf  = 
	{		
		.buf = tx_data,
		.len = 4		
	};

	struct spi_buf_set tx = {
		.buffers = &tx_buf,
		.count = 1
	};

	// Initialize rx-buffer
	struct spi_buf rx_buf = 
	{		
		.buf = rx_data,
		.len = 4	
	};

	struct spi_buf_set rx = {
		.buffers = &rx_buf,
		.count = 1
	};

	err = spi_transceive(spi, config, &tx, &rx);

	if(err) 
	{
        printk("SPI: Transaction error [%d]\n", err);
		return err;
	}

	*data = ((uint16_t)rx_data[1]) << 8 | rx_data[2];

    // Compare tx/rx data buffers
	if(memcmp(tx_data, rx_data, 4) != 0)
	{
		printk("tx_data != rx_data\n");
	}

	return err;
	
}

The following routine is called in the custom work queue item which is placed in the queue when the timer expires.

void sensor_irq_worker(struct k_work *item)
{
	int16_t val_x, val_y, val_z;
	uint16_t status;
	
	sensor_read_reg(spi, &spi_cfg, 0x09, &val_x);
	sensor_read_reg(spi, &spi_cfg, 0x0A, &val_y);
	sensor_read_reg(spi, &spi_cfg, 0x0B, &val_z);
    sensor_read_reg(spi, &spi_cfg, 0x0E, &status);

	printk("X: %d;", val_x);
	printk("Y: %d;", val_y);
	printk("Z: %d; ", val_z);
	printk("Status: %04X\n", status);
}

SPI3 Configuration (based on nRF52840 dk dts file):

spi3: arduino_spi: spi@4002f000 {
    compatible = "nordic,nrf-spim";
    #address-cells = <1>;
    #size-cells = <0>;
    reg = <0x4002f000 0x1000>;
    interrupts = <47 NRF_DEFAULT_IRQ_PRIORITY>;
    max-frequency = <DT_FREQ_M(32)>;
    rx-delay-supported;
    rx-delay = <2>;
    status = "okay";
    cs-gpios = <&gpio0 26 GPIO_ACTIVE_LOW>;
    pinctrl-0 = <&spi3_default>;
    pinctrl-1 = <&spi3_sleep>;
    pinctrl-names = "default", "sleep";
    clock-frequency = <5000000>;
};

I ran this test and concluded that after a while it sometimes shifts the output by one byte as well. It regularly prints "tx_data != rx_data". The shift of one byte is also visible using a logic analyzer.

Other things I tested:

1) Run the code using nRF Connect SDK v2.2.0 - problem persists

2) Make the tx_data and rx_data buffers global or static - problems disappears (or at least does not trigger as fast).

To me it's unclear why the last byte of a previous transfer ends up in the next transfer.

Related