SPIS Multi-Byte Read/Write (Burst-Mode)

Hello,

I am using a nrf52840-dk and communicate via SPI with an external board (NUCLEO-L476RG from ST) during development.
For the development itself I am using the nRF Connect SDK v1.9.1. The communication over SPI is actually working quiet well.

My goal is to run the nrf52840 as a slave, which has multi-byte read/write capabilities with auto-address increment. So something like a burst write/read.
That means: The master wants to write data e.g. into several registers. So it initiates the transaction with:

  1. Toggle CS-Line
  2. Transmit 1 Byte as start register address
  3. Transfer n Bytes as long as desired (I don't care about a rollover of the address at the moment)
  4. Toggle CS-Line

The logical sequence of the slave would look somewhat like this:

uint8_t rx_buffer[1] = {};
struct spi_buf rx_buf = {.buf = rx_buffer, .len = sizeof(rx_buffer)};
struct spi_buf_set rx = {.buffers = &rx_buf, .count = 1};

uint8_t tx_buffer[1] = {};
struct spi_buf tx_buf = {.buf = tx_buffer, .len = sizeof(tx_buffer)};
struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};

struct k_poll_signal signal;
k_poll_signal_init(&signal);

struct k_poll_event event;
k_poll_event_init(&event, K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &signal);

k_poll_signal_reset(&signal);

while(1)
{
	// Read Data
	if(read_action)
	{
		spi_read_async(spi_dev, &spi_cfg, &rx, &signal);
	}
	else
	{
		spi_write_async(spi_dev, &spi_cfg, &tx, &signal);
	}
	
	rc = k_poll(&event,1,K_USEC(10));
	if(rc != 0)
	{
		// Something is wrong
		// Reset signal
		event.signal->signaled = 0;
		event.state = K_POLL_STATE_NOT_READY;
		k_poll_signal_reset(event.signal);
		
		// Reset state
		state = READ_REG_ADDR;
		
		if(rc == -EAGAIN)
		{
			printk("Communication timed out.\n");
		}
		continue;
	}
	k_poll_signal_check(event.signal, &signaled, &signal_result);
	if(rc == 0)
	{
		if(signaled != 0) // signal is given as long as its value is non-zero
		{
			state_machine(rx);
		}
	}
	
	// Reset signal
	event.signal->signaled = 0;
	event.state = K_POLL_STATE_NOT_READY;
	k_poll_signal_reset(event.signal);
}

I use a simple state machine to check the content of the read/write buffer. Thereby rx/tx is also changed sometimes, e.g.

switch(state)
{
	case READ_REG_ADDR:
		auto addr = rx_buffer[0];
		auto read_action = (addr & 0x80u) == 0x80u; // msb set, so client wants to read from address
		addr &= 0x7fu; // actual register address (7-bit)
		if(read_action)
		{
			state = WRITE_DATA; 
			tx_buf.buf = registers[addr]->data;
			tx_buf.len = registers[addr]->size;
		}
		else
		{
			state = READ_DATA;
			rx_buf.buf = registers[addr]->data;
			rx_buf.len = registers[addr]->size;
		}
		break;
	case WRITE_DATA:
		// do stuff like address increment
		// switch state
		break;
	case READ_DATA:
		// do stuff like address increment
		// switch state
		break;
}

Unfortunately, this does not work. From the behavior of the functions spi_read_async/spi_write_async it's like they expect the CS-Line to toggle first. However, that's exactly what I don't want.

Is there any way to achieve my goal?

Parents
  • Hi,

     

    I'm a bit uncertain in the feature that you're after here, please explain a bit more in-detail if I've misunderstood anything.

    Writing to the tx_buf / rx_buf structs does not automatically trigger a transaction. The nRF52840 does not have a generic DMA channel, if that is the functionality you're after.

    Let us define a transaction as CSN going active, x bytes clock in, and CSN goes inactive.

    The SPIS cannot dynamically change the content based on the first byte within the same transaction. Each transaction must be decided upon prior to when CSN goes active.

    The SPIS peripheral works as described in the PS:

    https://infocenter.nordicsemi.com/topic/ps_nrf52840/spis.html?cp=4_0_0_5_25

      

    After writing your data to the respective struct, you'll have to trigger a transfer function (which sets up the easydma buffers and starts the transfer), as shown in this SPIM example:

    https://github.com/sigurdnev/ncs-playground/blob/master/samples/spi_test/src/main.c

     

    (there's also spis samples in the above repo)

     

    Kind regards,

    Håkon

  • Thank you very much for the answer.

    That is relatively clear to me. What I want to achieve is the following:
    In the nRF52840 I manage a so-called register of n bytes. For demonstration purposes, let's just define 64 bytes for this.
    I want to make this register available via SPI. So a master should be able to write and read data here arbitrarily.

    Classically each byte is addressed by a register address. This is usually done by the first byte sent by the master.
    The highest bit of this byte corresponds to a read/write flag. If this bit is set (0b10000000) then the master wants to read from the specified address. Otherwise he wants to write data to the address.


    If the master now writes 0x00 as the first byte, as a slave I know: Okay, the master wants to write into the register with the address 0x00.
    So the next byte sent by the master is the data.
    The same is true for all further addresses up to 0x3F.

    Now I know of many devices that implement a protocol to write or read multiple bytes in one burst. For this the device performs an internal auto-increment of the address with every further received or written byte.
    So instead of the master having to establish a new communication and send the address every time like:

    tx_buffer[0] = 0x00u; // Register-Address 0x00
    tx_buffer[1] = 0xAAu; // Data for Register-Address 0x00
    spi_write(spi_dev, &spi_cfg, &tx);
    
    tx_buffer[0] = 0x01u; // Register-Address 0x01
    tx_buffer[1] = 0x42u; // Data for Register-Address 0x01
    spi_write(spi_dev, &spi_cfg, &tx);

    Instead, I would like to do this in one go:

    tx_buffer[0] = 0x00u; // Register-Address 0x00
    tx_buffer[1] = 0xAAu; // Data for Register-Address 0x00
    tx_buffer[2] = 0x42u; // Data for Register-Address 0x01
    spi_write(spi_dev, &spi_cfg, &tx);

    So as in my slave example code in my previous post, I would expect something like this to work:

    spi_read(spi_dev, &spi_cfg, &rx); // Read one byte as start-address
    current_address = rx_buffer[0];
    do
    {
    	spi_read(spi_dev, &spi_cfg, &rx); // Read next data byte 
    	my_registers[current_address++] = rx_buffer[0]; // write data into current register address and increment address itself
    }while(no timeout and other conditions);

    To my understanding, as long as the master sends the next 8 clock pulses, the slave should receive the next byte. As long as it does not take too long (hence the timeout).

    Another example would be this description (taken from ISM330DLC Datasheet)

    https://imgur.com/a/8v3R1Nb

  • Hi,

     

    Talisca said:
    To my understanding, as long as the master sends the next 8 clock pulses, the slave should receive the next byte. As long as it does not take too long (hence the timeout).

    The problem is that once the CSN is latched, your buffers are locked. You shall not change the content of the memory at this point.

    CSN input is controlled by the hardware, so there's not many ways around this, other than to send and receive in two separate SPI transactions. See this chapter specifically: https://infocenter.nordicsemi.com/topic/ps_nrf52840/spis.html?cp=4_0_0_5_25_2#concept_abk_lbf_wr

     

    Kind regards,

    Håkon

Reply Children
No Data
Related