SPI transmission repetition rate

User avatar
luca.gamma
Posts: 15
Joined: Tue Apr 19, 2016 8:40 pm
Location: Switzerland

SPI transmission repetition rate

Postby luca.gamma » Tue Mar 07, 2017 2:17 pm

In my application I need to send a series of 16-bit wide SPI commands to some slave devices (A/D converters). Since the chip-select line is used to initiate ADC sampling I need to send command at a given repetition rate (i.e. every 1.5us) with a serial clock speed of 20MHz like in the diagram below.

Image

The only way to send commands with standard SPI pattern (chip select assertion, shift-out word bits and chip-select de-assertion) seems to load the command word in the SPI transaction structure each time. The SPI diver offers two transmission variants: synchronous and queued.

Using the synchronous transmit (i.e. a single queue/get) the command repetition rate is more than 40us (but constant) between two consecutive transmit.

Image
(yellow: sync signal, green: chip-select, violet: serial clock, red: MOSI)

Using the queue/get transmit (i.e. queue all transmit then get queue results) the command repetition rate varies between 10us and 19us (and so not constant) between two consecutive transmit.

Image
(yellow: sync signal, green: chip-select, violet: serial clock, red: MOSI)

It looks like a pity having the SPI clock reach high speed (i.e. up to 80MHz) but having the driver to send commands only at 100kHz (best case).



My question: Is it possible to implement a sort of burst transfer (i.e. from a buffer) to transmit command words (with chip-select assertion/de-assertion) at a higher repetition rate? (i.e. sending 140 command words at 4kHz)

Note: SPI clock speed is currently set to 10MHz.



Hint: Sending 16-bit word using the internal buffer of the SPI transaction structure it is required to load the low byte in tx_data[1] and the high byte in tx_data[0] to respect the correct bit order (e.g. MSB first).

ESP_Sprite
Posts: 9724
Joined: Thu Nov 26, 2015 4:08 am

Re: SPI transmission repetition rate

Postby ESP_Sprite » Wed Mar 08, 2017 1:59 am

I understand your question, but in hardware, this is just not possible. We support linked lists of DMA data regions, but they all need to be in one transaction, they can not be in multiple different ones.

Maybe a weird idea, but perhaps you can use the RMT peripheral for this? It supports precisely-defined signal shapes, so you can basically format the signal yourself. Decoding the result out may be slightly more complex, but at least you get the well-defined timing you need.

User avatar
loboris
Posts: 514
Joined: Wed Dec 21, 2016 7:40 pm

Re: SPI transmission repetition rate

Postby loboris » Wed Mar 08, 2017 7:59 am

Once the spi bus is initialized, the device added and the device initialized (you can call spi_device_transmit for that), you no longer need to use queued/DMA data transfer.
You can add the simple functions for non-queued/no-DMA data transfer to spi_master driver:

Code: Select all

static void IRAM_ATTR spi_transfer_start(spi_host_t *host, int bits) {
	// Load send buffer
	host->hw->user.usr_mosi_highpart=0;
	host->hw->mosi_dlen.usr_mosi_dbitlen=bits-1;
	host->hw->miso_dlen.usr_miso_dbitlen=0;
	host->hw->user.usr_mosi=1;
	host->hw->user.usr_miso=0;
	// Start transfer
	host->hw->cmd.usr=1;
}

void IRAM_ATTR disp_spi_transfer_data(spi_device_handle_t handle, uint8_t *data, uint8_t *indata, uint32_t wrlen, uint32_t rdlen) {
	spi_host_t *host=(spi_host_t*)handle->host;
	uint32_t bits;
	uint32_t wd;
	uint8_t bc;

	if ((data) && (wrlen > 0)) {
		uint8_t idx;
		uint32_t count;

		bits = 0;
		idx = 0;
		count = 0;
		// Wait for SPI bus ready
		while (host->hw->cmd.usr);

		while (count < wrlen) {
			wd = 0;
			for (bc=0;bc<32;bc+=8) {
				wd |= (uint32_t)data[count] << bc;
				count++;
				bits += 8;
				if (count == wrlen) break;
			}
			host->hw->data_buf[idx] = wd;
			idx++;
			if (idx == 16) {
				spi_transfer_start(host, bits);
				bits = 0;
				idx = 0;
				if (count < wrlen) {
					// Wait for SPI bus ready
					while (host->hw->cmd.usr);
				}
			}
		}
		if (bits > 0) {
			spi_transfer_start(host, bits);
		}
	}

	if (!indata) return;

	uint8_t rdidx;
	uint32_t rdcount = rdlen;
	uint32_t rd_read = 0;
    while (rdcount > 0) {
    	//read data
    	if (rdcount <= 64) bits = rdcount * 8;
    	else bits = 64 * 8;

    	// Wait for SPI bus ready
	while (host->hw->cmd.usr);

	// Load send buffer
	host->hw->user.usr_mosi_highpart=0;

	host->hw->mosi_dlen.usr_mosi_dbitlen=0;
	host->hw->miso_dlen.usr_miso_dbitlen=bits-1;
	host->hw->user.usr_mosi=0;
	host->hw->user.usr_miso=1;
	// Start transfer
	host->hw->cmd.usr=1;
	// Wait for SPI bus ready
	while (host->hw->cmd.usr);

	rdidx = 0;
    	while (bits > 0) {
			wd = host->hw->data_buf[rdidx];
			rdidx++;
			for (bc=0;bc<32;bc+=8) {
				indata[rd_read++] = (uint8_t)((wd >> bc) & 0xFF);
				rdcount--;
				bits -= 8;
				if (rdcount == 0) break;
			}
    	}
    }
}
This example is for non-duplex mode (send data first than receive data) and fixed 8-bit byte transfer, you can easily make the function for duplex mode and/or different bit length.
I've expanded the spi_master driver that way and tested with ILI9341 displays and it works fine.
Queued/DMA transactions and non-queued/non-DMA transfers can be mixed.

If using multiple devices, kind of select function can be defined to select and configure the right device. If using it, the initial spi_device_transmit is not needed, you can call spi_device_select instead.

Code: Select all

esp_err_t spi_device_select(spi_device_handle_t handle, int force)
{
	int i;
    SPI_CHECK(handle!=NULL, "invalid handle", ESP_ERR_INVALID_ARG);
    //These checks aren't exhaustive; another thread could sneak in a transaction inbetween. These are only here to
    //catch design errors and aren't meant to be triggered during normal operation.
    SPI_CHECK(uxQueueMessagesWaiting(handle->trans_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE);
    SPI_CHECK(uxQueueMessagesWaiting(handle->ret_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE);

	spi_host_t *host=(spi_host_t*)handle->host;

	for (i=0; i<NO_DEV; i++) {
		if (host->device[i] == handle) {
			break;
		}
	}
	SPI_CHECK(i != NO_DEV, "invalid dev handle", ESP_ERR_INVALID_ARG);

	//Reconfigure according to device settings, but only if the device changed or forced reconfig requested.
	if ((force) || (host->device[host->cur_device] != handle)) {
		//Assumes a hardcoded 80MHz Fapb for now. ToDo: figure out something better once we have
		//clock scaling working.
		int apbclk=APB_CLK_FREQ;
		spi_set_clock(host->hw, apbclk, handle->cfg.clock_speed_hz, handle->cfg.duty_cycle_pos);
		//Configure bit order
		host->hw->ctrl.rd_bit_order=(handle->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST)?1:0;
		host->hw->ctrl.wr_bit_order=(handle->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST)?1:0;
		
		//Configure polarity
		//SPI iface needs to be configured for a delay unless it is not routed through GPIO and clock is >=apb/2
		int nodelay=(host->no_gpio_matrix && handle->cfg.clock_speed_hz >= (apbclk/2));
		if (handle->cfg.mode==0) {
			host->hw->pin.ck_idle_edge=0;
			host->hw->user.ck_out_edge=0;
			host->hw->ctrl2.miso_delay_mode=nodelay?0:2;
		} else if (handle->cfg.mode==1) {
			host->hw->pin.ck_idle_edge=0;
			host->hw->user.ck_out_edge=1;
			host->hw->ctrl2.miso_delay_mode=nodelay?0:1;
		} else if (handle->cfg.mode==2) {
			host->hw->pin.ck_idle_edge=1;
			host->hw->user.ck_out_edge=1;
			host->hw->ctrl2.miso_delay_mode=nodelay?0:1;
		} else if (handle->cfg.mode==3) {
			host->hw->pin.ck_idle_edge=1;
			host->hw->user.ck_out_edge=0;
			host->hw->ctrl2.miso_delay_mode=nodelay?0:2;
		}

		//Configure bit sizes, load addr and command
		host->hw->user.usr_dummy=(handle->cfg.dummy_bits)?1:0;
		host->hw->user.usr_addr=(handle->cfg.address_bits)?1:0;
		host->hw->user.usr_command=(handle->cfg.command_bits)?1:0;
		host->hw->user1.usr_addr_bitlen=handle->cfg.address_bits-1;
		host->hw->user1.usr_dummy_cyclelen=handle->cfg.dummy_bits-1;
		host->hw->user2.usr_command_bitlen=handle->cfg.command_bits-1;
		//Configure misc stuff
		host->hw->user.doutdin=(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX)?0:1;
		host->hw->user.sio=(handle->cfg.flags & SPI_DEVICE_3WIRE)?1:0;

		host->hw->ctrl2.setup_time=handle->cfg.cs_ena_pretrans-1;
		host->hw->user.cs_setup=handle->cfg.cs_ena_pretrans?1:0;
		host->hw->ctrl2.hold_time=handle->cfg.cs_ena_posttrans-1;
		host->hw->user.cs_hold=(handle->cfg.cs_ena_posttrans)?1:0;

		//Configure CS pin
		host->hw->pin.cs0_dis=(i==0)?0:1;
		host->hw->pin.cs1_dis=(i==1)?0:1;
		host->hw->pin.cs2_dis=(i==2)?0:1;
		
		host->cur_device = i;
	}

	return ESP_OK;
}
The driver can also be expanded to use software CS activation/deactivation, inserting delays before/after transmission, use semaphore etc.
If someone is interested, I can post full example later this week,

User avatar
luca.gamma
Posts: 15
Joined: Tue Apr 19, 2016 8:40 pm
Location: Switzerland

Re: SPI transmission repetition rate

Postby luca.gamma » Fri Mar 10, 2017 9:35 am

ESP_Sprite wrote:I understand your question, but in hardware, this is just not possible. We support linked lists of DMA data regions, but they all need to be in one transaction, they can not be in multiple different ones.

Maybe a weird idea, but perhaps you can use the RMT peripheral for this? It supports precisely-defined signal shapes, so you can basically format the signal yourself. Decoding the result out may be slightly more complex, but at least you get the well-defined timing you need.
Thanks, this sounds quiet good but emulating SPI timing with RMT doesn't seems to solve the problem. After configuring waveforms I see it is not possible to start all the waveforms at the same time, even if rmt_write_items() returns immediately, but consecutive writes (i.e. chip-select first, then serial clock) are delayed by about 8.76us and so not usable for emulating an high-speed SPI bus and relative chip-select lines. Timings are respected and precise but sending the serial clock waveform (violet) also generates a lot of cross-talk on other RMT lines (chip-select, yellow, and MOSI, red) even if carriers are completely disabled.

Image
(yellow: sync signal, green: chip-select, violet: serial clock, red: MOSI)

ESP_Sprite
Posts: 9724
Joined: Thu Nov 26, 2015 4:08 am

Re: SPI transmission repetition rate

Postby ESP_Sprite » Fri Mar 10, 2017 9:39 am

Ahrg, I forgot that you can't start them at the same time. I had the same issue but fixed it by timing how far apart the channels started, and fixing that in the signal I sent them. Not the nicest way, but it worked...

Tbh, I doubt crosstalk is an issue of the ESP32... the RMT is using the GPIO matrix like everything else. Are you sure you don't have a different source of crosstalk?

User avatar
luca.gamma
Posts: 15
Joined: Tue Apr 19, 2016 8:40 pm
Location: Switzerland

Re: SPI transmission repetition rate

Postby luca.gamma » Fri Mar 10, 2017 9:43 am

loboris wrote:Once the spi bus is initialized, the device added and the device initialized (you can call spi_device_transmit for that), you no longer need to use queued/DMA data transfer.
You can add the simple functions for non-queued/no-DMA data transfer to spi_master driver:

This example is for non-duplex mode (send data first than receive data) and fixed 8-bit byte transfer, you can easily make the function for duplex mode and/or different bit length.
I've expanded the spi_master driver that way and tested with ILI9341 displays and it works fine.
Queued/DMA transactions and non-queued/non-DMA transfers can be mixed.

If using multiple devices, kind of select function can be defined to select and configure the right device. If using it, the initial spi_device_transmit is not needed, you can call spi_device_select instead.

The driver can also be expanded to use software CS activation/deactivation, inserting delays before/after transmission, use semaphore etc.
If someone is interested, I can post full example later this week,
Thanks for your suggestion, this seems a clean workaround and a first implementation give a considerable improvement on the repetition rate with 16-bit transfer. We are working on the chip-selects, since we are pretty new to FreeRTOS could be interesting to have a look at semaphore too ;)

User avatar
luca.gamma
Posts: 15
Joined: Tue Apr 19, 2016 8:40 pm
Location: Switzerland

Re: SPI transmission repetition rate

Postby luca.gamma » Fri Mar 10, 2017 9:50 am

ESP_Sprite wrote:Tbh, I doubt crosstalk is an issue of the ESP32... the RMT is using the GPIO matrix like everything else. Are you sure you don't have a different source of crosstalk?
Yes, could be generate from the test board, sometimes I forget that using such frequencies (20MHz) on a breadboard (just for testing anyway) is not the optimal choice :D

kopyrmen
Posts: 3
Joined: Wed May 30, 2018 11:47 am

Re: SPI transmission repetition rate

Postby kopyrmen » Wed May 30, 2018 11:56 am

Hi,
I have similar problem but i cannot find solution. I'd like to read fifo from accelerometr but the time between transaction make it impossible. I need to read 786 times two 8 - bits values from two adresses next to each other. I thought that I can generate my own CS but i cannot bind my CS with SCLK and MISO MOSI. My code:

Code: Select all

        pwmSetValue(LEDC_CHANNEL_0, 16);
        esp_err_t err = spi_device_transmit(acce_bus, &t);
        pwmSetValue(LEDC_CHANNEL_0, 0);
How do you bind these signals ?

Who is online

Users browsing this forum: Majestic-12 [Bot] and 241 guests