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.

Parents
  • For (mostly) adjacent registers I would recommend a single block SPI transaction to read all registers at once instead of multiple single-byte transactions even if that requires a couple of extra bytes as the status register is not adjacent to xyz; this reduces significantly the software overhead. Running SPIM3 at 32MHz is always a challenge, as SPIM3 has the lowest priority of all the peripheral devices (I kid you not) and latencies begin to be significant. You might find my comments towards the end of this post helpful: spim3-peripheral-not-reliable

Reply
  • For (mostly) adjacent registers I would recommend a single block SPI transaction to read all registers at once instead of multiple single-byte transactions even if that requires a couple of extra bytes as the status register is not adjacent to xyz; this reduces significantly the software overhead. Running SPIM3 at 32MHz is always a challenge, as SPIM3 has the lowest priority of all the peripheral devices (I kid you not) and latencies begin to be significant. You might find my comments towards the end of this post helpful: spim3-peripheral-not-reliable

Children
Related