SPI Master Slave handshake with spi_device_queue_trans

rosenrot
Posts: 25
Joined: Wed Jan 01, 2020 9:28 pm

SPI Master Slave handshake with spi_device_queue_trans

Postby rosenrot » Thu Jan 30, 2020 8:13 am

Dear all,

The master-slave communication example form here https://github.com/espressif/esp-idf/tr ... /spi_slave is wonderful. It does exactly what it should.

However, I would like to use this example with

Code: Select all

spi_device_queue_trans
and

Code: Select all

esp_err_tspi_device_get_trans_result
I splitted both commands into an SPI_RX and SPI_TX task like this:

Code: Select all

static void SPI_TX (void *arg){
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length=SPI_BUF_SIZE_SMALL*8;
    t.tx_buffer=spi_tx_buf;
    t.rx_buffer=NULL;
    
    while(1){
        memset(&spi_tx_buf,0x78,sizeof(spi_tx_buf));
        xSemaphoreTake(rdySem, portMAX_DELAY); //Wait until slave is ready - handshake
        t1.tx_buffer = spi_tx_buf;
        esp_err_t ret = spi_device_queue_trans(handle, &t, portMAX_DELAY);
        if(ret!=ESP_OK){
            ESP_LOGW(SPI_TAG,"error master queue %d",ret);
        }
    }
}
and

Code: Select all

static void SPI_RX (void *arg){
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length=SPI_BUF_SIZE_SMALL*8;
    t.tx_buffer=NULL;
    t.rx_buffer=spi_rx_buf;
    
    while(1){
        esp_err_t ret = spi_device_get_trans_result(handle, &t, portMAX_DELAY);
        if(ret!=ESP_OK){
            ESP_LOGW(SPI_TAG,"error master queue");
        }
        for(int i=0;i<SPI_BUF_SIZE_SMALL;i++){
            printf("%d",spi_rx_buf[i]);
        }
        taskYIELD();
    }
}
Somehow like this it doesn't work anymore. I thought I need the

Code: Select all

spi_device_queue_trans
to wait for the handshake of the slave with

Code: Select all

xSemaphoreTake(rdySem, portMAX_DELAY);
However, it even works without.

Most likely I didn't understand the functions well enough. Could someone enlighten me?

I don't get any error messages, I just reveive garbage from the SPI_RX function.

NevynSelby
Posts: 34
Joined: Thu Dec 24, 2015 12:04 pm

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby NevynSelby » Thu Jan 30, 2020 9:00 am

Are you using DMA ? If so what is the value for SPI_BUF_SIZE_SMALL ?

From the SPI Slave documentation:

The ESP32 DMA hardware has a limit to the number of bytes sent by a Host and received by a Device. The transaction length must be longer than 8 bytes and a multiple of 4 bytes; otherwise, the SPI hardware might fail to receive the last 1 to 7 bytes.

Regards,
Mark

rosenrot
Posts: 25
Joined: Wed Jan 01, 2020 9:28 pm

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby rosenrot » Thu Jan 30, 2020 10:10 am

I'm using DMA exactly the same as in the original example.

Code: Select all

SPI_BUF_SIZE_SMALL = 32
so it should be fine.

Can someone tell me if I can run this two commands in different tasks?

Where do I get my received data back? From t_send or from t_recv?
Do I have to access the spi_rx_buf in the spi_device_get_trans_result task to get the received data?

Unfortunately, I never found an example grabbing data back.

Code: Select all

spi_transaction_t t_send;
memset(&t_send, 0, sizeof(t_send));
t_send.length=SPI_BUF_SIZE_SMALL*8;
t_send.tx_buffer=spi_tx_buf;
t_send.rx_buffer=spi_rx_buf;
esp_err_t ret = spi_device_queue_trans(handle, &t_send, portMAX_DELAY);

Code: Select all

spi_transaction_t t_recv;
memset(&t_recv, 0, sizeof(t_recv));
//t_recv.length=SPI_BUF_SIZE_SMALL*8;
//t_recv.tx_buffer=spi_tx_buf;
//t_recv.rx_buffer=spi_rx_buf;
esp_err_t ret = spi_device_get_trans_result(handle, &t_recv, portMAX_DELAY);

//here data access via spi_rx_buf?

rosenrot
Posts: 25
Joined: Wed Jan 01, 2020 9:28 pm

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby rosenrot » Thu Jan 30, 2020 11:10 pm

Ok, I was able to move a step furter.

Can someone tell me at which point I can grab received data?

option1:
esp_err_t ret = spi_device_queue_trans(handle, &t_send, portMAX_DELAY);
esp_err_t ret = spi_device_get_trans_result(handle, &t_recv, portMAX_DELAY);
HERE from t_send rx_buffer?

option2:
esp_err_t ret = spi_device_queue_trans(handle, &t_send, portMAX_DELAY);
esp_err_t ret = spi_device_get_trans_result(handle, &t_recv, portMAX_DELAY);
HERE from t_recv rx_buffer?

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

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby ESP_Sprite » Fri Jan 31, 2020 12:20 pm

The hardware is allowed to do the transmission/reception anywhere between when queue_trans is called and get_trans_result returns. Note that in both your examples, get_trans_result should set t_recv to be a pointer to t_send (unless you queued up other transactions earlier) so you can use both/either to get to the data.

rosenrot
Posts: 25
Joined: Wed Jan 01, 2020 9:28 pm

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby rosenrot » Mon Feb 03, 2020 12:23 am

This is what I understood as well. However, it doesn't seem to work.

Should it work if I feed the same &t_send into queue_trans or do I need to create an array of t_send? I ask because queue_trans might get called 4 times while get_trans_result returns only 2 times.

I found an example syncing both function calls by a queue. However, I don't need a synced information transfer.

If I use the same &t_send, it will get overwritten once queue_trans is called twice and get_trans_queue is called only once, right?

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

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby ESP_Sprite » Mon Feb 03, 2020 9:36 am

You cannot use the same t_send; by queuing the transfer, you effectively hand over ownership of that memory to the SPI subsystem (and as such, you shouldn't in any way) until it gets returned to you using get_trans_result.

rosenrot
Posts: 25
Joined: Wed Jan 01, 2020 9:28 pm

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby rosenrot » Mon Feb 03, 2020 10:44 am

I see.

So, what is the most efficient scenario of using queue_trans and get_trans_result ?

Do you have a better idea than what I show below (using queues to have a ringbuffer for transmissions)? Can I just create new t_send or trans for each transmission, will the memory be released once the get_trans_result returns it?

Scenario:
Doing this, sometimes the tx task is called more often than the rx task and therefor the tx task has to wait for the queue to get the entry from the rx task. This results in the Queue is full but shouldn't be message, is it a design failure or what do I not get correctly?
TX task

Code: Select all

static void spi_slave_tx_task(){
for(int i = 0; i < RX_N; i++){
        trans_desc[i].tx_buffer = send_buffer[i];
        trans_desc[i].rx_buffer = receive_buffer[i];
        trans_desc[i].length = SPI_BUF_SIZE_SMALL*8;
        //trans_desc[i].flags &= ~SPI_TRANS_USE_TXDATA;
        ((char*)trans_desc[i].rx_buffer)[0] = 0;

        spi_slave_transaction_t * ptr = &trans_desc[i];
        xQueueSendToBack(empty_buffer_queue, &ptr, portMAX_DELAY);
    }

spi_slave_transaction_t * trans = NULL;

while(1){
        if(xQueueReceive(empty_buffer_queue, &trans, 10)){
                memcpy(trans->tx_buffer,&data, sizeof(data)); //fill TX
                if(ESP_OK != spi_slave_queue_trans(RCV_HOST, trans, 1)){
                        ESP_LOGE(TAG, "Delay queueing transaction, shouldn't happen");
                        spi_slave_queue_trans(RCV_HOST, trans, portMAX_DELAY);
                }
                }else{
            if(spi_tx_error%10==0){
                ESP_LOGE(TAG, "Queue empty, nowhere to write incoming spi occured %d",spi_tx_error);
            }
            spi_tx_error++;
        }
        }
RX task

Code: Select all

static void spi_slave_rx_task(){
    data_t data;
    spi_slave_transaction_t * trans = NULL;
    while(1){
        if(ESP_OK != spi_slave_get_trans_result(RCV_HOST, &trans, portMAX_DELAY)){
            ESP_LOGW(SPI_TAG,"error slave queue");
        }
        memcpy(&data,trans->rx_buffer,sizeof(data_t));	//get RX

        if(data[4]>0){
            xQueueSend(queue_spi_input, &data, portMAX_DELAY);	//usage RX
        }
        if(!xQueueSendToBack(empty_buffer_queue, &trans, 1)){
            ESP_LOGE(TAG, "Queue is full but shouldn't be");
        }
    }
}

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

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby ESP_Sprite » Mon Feb 03, 2020 4:00 pm

You could in theory create t_send items dynamically, using malloc() and free(), but you'd probably still be limited by the fact that your SPI queue has a limited amount if items it can contain, so it won't really help here.

Can you expand a bit on why you have a separate Tx and Rx task? Does this code run as SPI slave or SPI master?

rosenrot
Posts: 25
Joined: Wed Jan 01, 2020 9:28 pm

Re: SPI Master Slave handshake with spi_device_queue_trans

Postby rosenrot » Mon Feb 03, 2020 5:37 pm

This code runs as SPI Slave.

Let me explain what I want to do. I want to establish a bidirectional SPI communication between two ESP32s. Therefore, I used the master/slave example from the idf examples (first post). This example uses spi_transmit in one task to do so, also a handshake line is used to tell the master when he should transmit data in order for the slave's data to get picked up.

My idea was to have two tasks to send and receive data. One for loading the data to be send via queue_trans and one to receive data by get_trans_result . Maybe this is already a design failure. However, otherwise I wouldn't see the reason why to use these two functions and not spi_transmit. Again, I might be wrong here already. If there are no data available, I send empty data, so that the receiver knows that this is just dummy data.

Currently, I'm trying to understand https://github.com/espressif/esp-idf/is ... -351595974 because I think it might help me.

Any idea how to make a bidirectional SPI communication work realiably is welcome.

Who is online

Users browsing this forum: Google [Bot] and 75 guests