Firstly, are the following SPI bus and interface, and handshake line configurations for master and slave correct (based on the attached schematics and pin layouts for the 2 boards)?
Master (ESP32-DevKit-C):
Code: Select all
spi_bus_config_t buscfg={
.mosi_io_num=23,
.miso_io_num=19,
.sclk_io_num=18,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_device_interface_config_t devcfg={
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.clock_speed_hz=5000000,
.duty_cycle_pos=128,
.mode=0,
.spics_io_num=5,
.cs_ena_posttrans=10,
.queue_size=3
};
gpio_config_t io_conf={
.intr_type=GPIO_INTR_POSEDGE,
.mode=GPIO_MODE_INPUT,
.pull_up_en=1,
.pin_bit_mask=(1<<16)
};
Slave (Qualia ESP32-S3):
Code: Select all
spi_bus_config_t buscfg={
.mosi_io_num=7,
.miso_io_num=6,
.sclk_io_num=5,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_slave_interface_config_t slvcfg={
.mode=0,
.spics_io_num=15,
.queue_size=3,
.flags=0,
.post_setup_cb=my_post_setup_cb,
.post_trans_cb=my_post_trans_cb
};
gpio_config_t io_conf={
.intr_type=GPIO_INTR_DISABLE,
.mode=GPIO_MODE_OUTPUT,
.pin_bit_mask=(1<<17)
};
Code: Select all
while(1) {
memset(recvbuf, 0xA5, 129);
sprintf(sendbuf, "This is the receiver, sending data for transmission number %04d.", n);
//Set up a transaction of 128 bytes to send/receive
t.length=128*8;
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
/* This call enables the SPI slave interface to send/receive to the sendbuf and recvbuf. The transaction is
initialized by the SPI master, however, so it will not actually happen until the master starts a hardware transaction
by pulling CS low and pulsing the clock etc. In this specific example, we use the handshake line, pulled up by the
.post_setup_cb callback that is called as soon as a transaction is ready, to let the master know it is free to transfer
data.
*/
ret=spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY); //RCV_HOST = SPI2_HOST
n++;
}
Here's what my complete code looks like for the sender (master) and receiver (sender) boards:
Sender
Code: Select all
#define GPIO_HANDSHAKE 16
#define GPIO_MOSI 23
#define GPIO_MISO 19
#define GPIO_SCLK 18
#define GPIO_CS 5
#define SENDER_HOST VSPI_HOST
//The semaphore indicating the slave is ready to receive stuff.
static QueueHandle_t rdySem;
/*
This ISR is called when the handshake line goes high.
*/
static void IRAM_ATTR gpio_handshake_isr_handler(void* arg)
{
//Sometimes due to interference or ringing or something, we get two irqs after eachother. This is solved by
//looking at the time between interrupts and refusing any interrupt too close to another one.
static uint32_t lasthandshaketime_us;
uint32_t currtime_us = esp_timer_get_time();
uint32_t diff = currtime_us - lasthandshaketime_us;
if (diff < 1000) {
return; //ignore everything <1ms after an earlier irq
}
lasthandshaketime_us = currtime_us;
//Give the semaphore.
BaseType_t mustYield = false;
xSemaphoreGiveFromISR(rdySem, &mustYield);
if (mustYield) {
portYIELD_FROM_ISR();
}
}
//Main application
void app_main(void)
{
esp_err_t ret;
spi_device_handle_t handle;
//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=GPIO_MISO,
.sclk_io_num=GPIO_SCLK,
.quadwp_io_num=-1,
.quadhd_io_num=-1
};
//Configuration for the SPI device on the other side of the bus
spi_device_interface_config_t devcfg={
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.clock_speed_hz=5000000,
.duty_cycle_pos=128, //50% duty cycle
.mode=0,
.spics_io_num=GPIO_CS,
.cs_ena_posttrans=10, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
.queue_size=3
};
//GPIO config for the handshake line.
gpio_config_t io_conf={
.intr_type=GPIO_INTR_POSEDGE,
.mode=GPIO_MODE_INPUT,
.pull_up_en=1,
.pin_bit_mask=(1<<GPIO_HANDSHAKE)
};
int n=0;
char sendbuf[128] = {0};
char recvbuf[128] = {0};
spi_transaction_t t;
memset(&t, 0, sizeof(t));
//Create the semaphore.
rdySem=xSemaphoreCreateBinary();
//Set up handshake line interrupt.
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_set_intr_type(GPIO_HANDSHAKE, GPIO_INTR_POSEDGE);
gpio_isr_handler_add(GPIO_HANDSHAKE, gpio_handshake_isr_handler, NULL);
//Initialize the SPI bus and add the device we want to send stuff to.
ret=spi_bus_initialize(SENDER_HOST, &buscfg, SPI_DMA_CH_AUTO);
assert(ret==ESP_OK);
ret=spi_bus_add_device(SENDER_HOST, &devcfg, &handle);
assert(ret==ESP_OK);
//Assume the slave is ready for the first transmission: if the slave started up before us, we will not detect
//positive edge on the handshake line.
xSemaphoreGive(rdySem);
while(1) {
int res = snprintf(sendbuf, sizeof(sendbuf),
"Sender, transmission no. %04i. Last time, I received: \"%s\"", n, recvbuf);
if (res >= sizeof(sendbuf)) {
printf("Data truncated\n");
}
t.length=sizeof(sendbuf)*8;
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
//Wait for slave to be ready for next byte before sending
xSemaphoreTake(rdySem, portMAX_DELAY); //Wait until slave is ready
ret=spi_device_transmit(handle, &t);
n++;
}
Code: Select all
#define GPIO_HANDSHAKE 17
#define GPIO_MOSI 7
#define GPIO_MISO 6
#define GPIO_SCLK 5
#define GPIO_CS 15
#define RCV_HOST SPI2_HOST
//Called after a transaction is queued and ready for pickup by master. We use this to set the handshake line high.
void my_post_setup_cb(spi_slave_transaction_t *trans) {
gpio_set_level(GPIO_HANDSHAKE, 1);
}
//Called after transaction is sent/received. We use this to set the handshake line low.
void my_post_trans_cb(spi_slave_transaction_t *trans) {
gpio_set_level(GPIO_HANDSHAKE, 0);
}
//Main application
void app_main(void)
{
int n=0;
esp_err_t ret;
//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=GPIO_MISO,
.sclk_io_num=GPIO_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
//Configuration for the SPI slave interface
spi_slave_interface_config_t slvcfg={
.mode=0,
.spics_io_num=GPIO_CS,
.queue_size=3,
.flags=0,
.post_setup_cb=my_post_setup_cb,
.post_trans_cb=my_post_trans_cb
};
//Configuration for the handshake line
gpio_config_t io_conf={
.intr_type=GPIO_INTR_DISABLE,
.mode=GPIO_MODE_OUTPUT,
.pin_bit_mask=(1<<GPIO_HANDSHAKE)
};
//Configure handshake line as output
gpio_config(&io_conf);
//Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);
//Initialize SPI slave interface
ret=spi_slave_initialize(RCV_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO);
assert(ret==ESP_OK);
WORD_ALIGNED_ATTR char sendbuf[129]="";
WORD_ALIGNED_ATTR char recvbuf[129]="";
memset(recvbuf, 0, 33);
spi_slave_transaction_t t;
memset(&t, 0, sizeof(t));
while(1) {
//Clear receive buffer, set send buffer to something sane
memset(recvbuf, 0xA5, 129);
sprintf(sendbuf, "This is the receiver, sending data for transmission number %04d.", n);
//Set up a transaction of 128 bytes to send/receive
t.length=128*8;
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
/* This call enables the SPI slave interface to send/receive to the sendbuf and recvbuf. The transaction is
initialized by the SPI master, however, so it will not actually happen until the master starts a hardware transaction
by pulling CS low and pulsing the clock etc. In this specific example, we use the handshake line, pulled up by the
.post_setup_cb callback that is called as soon as a transaction is ready, to let the master know it is free to transfer
data.
*/
ret=spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY);
//spi_slave_transmit does not return until the master has done a transmission, so by here we have sent our data and
//received data from the master. Print it.
ESP_LOGI(TAG, "Received: %s\n", recvbuf);
n++;
}