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,