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.
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.
(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.
(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).
SPI transmission repetition rate
- luca.gamma
- Posts: 15
- Joined: Tue Apr 19, 2016 8:40 pm
- Location: Switzerland
-
- Posts: 9715
- Joined: Thu Nov 26, 2015 4:08 am
Re: SPI transmission repetition rate
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.
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.
Re: SPI transmission repetition rate
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,
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;
}
}
}
}
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;
}
If someone is interested, I can post full example later this week,
- luca.gamma
- Posts: 15
- Joined: Tue Apr 19, 2016 8:40 pm
- Location: Switzerland
Re: SPI transmission repetition rate
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.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.
(yellow: sync signal, green: chip-select, violet: serial clock, red: MOSI)
-
- Posts: 9715
- Joined: Thu Nov 26, 2015 4:08 am
Re: SPI transmission repetition rate
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?
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?
- luca.gamma
- Posts: 15
- Joined: Tue Apr 19, 2016 8:40 pm
- Location: Switzerland
Re: SPI transmission repetition rate
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 tooloboris 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,
- luca.gamma
- Posts: 15
- Joined: Tue Apr 19, 2016 8:40 pm
- Location: Switzerland
Re: SPI transmission repetition rate
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 choiceESP_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?
Re: SPI transmission repetition rate
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:
How do you bind these signals ?
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);
Who is online
Users browsing this forum: Google [Bot] and 268 guests