Multiple tasks sending commands to the same SPI device

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Multiple tasks sending commands to the same SPI device

Postby zazas321 » Mon Dec 06, 2021 9:53 am

Hello. I am learning more about the ESP32, freertos and the SPI. I would like to understand the behaviour of polling SPI transacations.

I know that the following may not be the ideal way to implement SPI communications where multiple different tasks are trying to send commands to the same SPI device but this is for learning purposes.

I have an SPI eeprom connected to my ESP32 device.

It is initialised as following:

Code: Select all

void SPI_init_eeprom(){
    esp_err_t ret;
    //spi_device_handle_t spi;
    spi_bus_config_t buscfg={
        .mosi_io_num=PIN_NUM_MOSI2,
        .miso_io_num=PIN_NUM_MISO2,
        .sclk_io_num=PIN_NUM_CLK2,
        .quadwp_io_num=-1,
        .quadhd_io_num=-1,
        .max_transfer_sz=PARALLEL_LINES2*320*2+8
    };


    spi_device_interface_config_t devcfg={

        
        .command_bits = 8,
        .mode=0, 
        .clock_speed_hz=10*100*100,           //Clock out at 10 MHz                              //SPI mode 0
        .spics_io_num=PIN_NUM_CS2,               //CS pin
        .flags = SPI_DEVICE_HALFDUPLEX,
        .queue_size=7,                          //We want to be able to queue 7 transactions at a time
    };

    //Initialize the SPI bus
    ret=spi_bus_initialize(EEPROM_HOST, &buscfg, DMA_CHAN);
    ESP_ERROR_CHECK(ret);
    //Attach the LCD to the SPI bus
    ret=spi_bus_add_device(EEPROM_HOST, &devcfg, &external_eeprom);
    ESP_ERROR_CHECK(ret);


}
I have 2 freertos tasks running:

Code: Select all

  void TEST_TASK1(void* param){

    for (;;)
    {
        printf("reading task 1 \n");
        uint8_t data_buf[10];
        EEPROM_read_multiple_bytes(external_eeprom,0,10,data_buf);
        vTaskDelay(500/portTICK_PERIOD_MS);
    }

}
 


 void TEST_TASK2(void* param){

    for (;;)
    {
        printf("reading task 2 \n");
        uint8_t data_buf[10];
        EEPROM_read_multiple_bytes(external_eeprom,0,10,data_buf);
        vTaskDelay(500/portTICK_PERIOD_MS);
    }

}
 
Both tasks are same priority and they will try to read 10 bytes from the EEPROM memory.

Code: Select all

void EEPROM_read_multiple_bytes(spi_device_handle_t spi,uint32_t start, uint32_t number,uint8_t* data_bufer){
    if( (start + number) > 256000 ){
        printf("EEPROM out of bounds \n");
        return;
    }
    while(EEPROM_check_if_busy(spi) != true){ // return true if flash is not busy
        vTaskDelay(10/portTICK_PERIOD_MS);
        printf("waiting for busy flag to clear \n");
    }
    uint32_t length = number;
    spi_transaction_ext_t trans = {
       .base = {
            .flags = SPI_TRANS_VARIABLE_ADDR,
            .cmd = 0b00000011,
            .addr = (uint32_t)start,
            .rxlength = length*8,
            .rx_buffer = data_bufer,
       },
       .address_bits = 24,
   };
   spi_device_polling_transmit(spi, (spi_transaction_t*)&trans);
   data_bufer = (uint8_t*)trans.base.rx_buffer;
}

I thought that this implementation will work since I have an additional status register check to ensure that if the eeprom chip is busy, the transaction will not start:

Code: Select all

    while(EEPROM_check_if_busy(spi) != true){ // return true if flash is not busy
        vTaskDelay(10/portTICK_PERIOD_MS);
        printf("waiting for busy flag to clear \n");
    }
   

Code: Select all

bool EEPROM_check_if_busy(spi_device_handle_t spi){
    if( (EEPROM_read_status_register(spi) & 0x01) == 1){
        printf("eeprom is busy");
        return false;
    }
    else{
        return true;
    }
}

The EEPROM_check_if_busy function will read the status register and if the busy bit is detected, that means the eeprom chip is busy and new transaction should not be started (I have added a simple 10tick delay).



Once my program starts, both tasks are started and after a few seconds of the program running the CPU crashses with the following report:

Code: Select all

reading task 2 
status = 0 
reading task 1
status = 0
reading task 1 
status = 0 
reading task 2
status = 0
reading task 2 
status = 0 
reading task 1
E (8278) spi_master: spi_device_polling_start(929): Cannot send polling transaction while the previous polling transaction is not terminated.
assertion "ret==ESP_OK" failed: file "../components/M95M04/M95M04.cpp", line 88, function: uint8_t EEPROM_read_status_register(spi_device_handle_t)

abort() was called at PC 0x4013c167 on core 0
0x4013c167: __assert_func at /builds/idf/crosstool-NG/.build/HOST-x86_64-w64-mingw32/xtensa-esp32-elf/src/newlib/newlib/libc/stdlib/assert.c:62 (discriminator 8)


Backtrace:0x4009074b:0x3ffd8c10 0x40090f49:0x3ffd8c30 0x4009569e:0x3ffd8c50 0x4013c167:0x3ffd8cc0 0x400dde39:0x3ffd8cf0 0x400dde6d:0x3ffd8d40 0x400ddf25:0x3ffd8d60 0x400da01e:0x3ffd8db0 0x40091359:0x3ffd8de0


What is the correct way to handle this ( I know the correct way would probably be that only 1 task is handling all SPI communcations to the device but I would like to know how to handle the situation if multiple tasks need to communicate with the same SPI device)

fevang
Posts: 6
Joined: Thu Jun 04, 2020 7:11 pm

Re: Multiple tasks sending commands to the same SPI device

Postby fevang » Mon Dec 06, 2021 9:00 pm

Hi there. There may be a more elegant solution, but I would replace EEPROM_check_if_busy() with a semaphore. Any task that wishes to perform a spi transaction must lock on the semaphore, and unlock it after the tranaction is complete.

Additionally,

Code: Select all

EEPROM_read_multiple_bytes()
will not pass any data back through to your data_buf[10].

The line

Code: Select all

data_bufer = (uint8_t*)trans.base.rx_buffer;
does not copy the data into your data buffer. Instead, you should perform a memcpy() inside EEPROM_read_multiple_bytes() to fill the data buffer with data.

Who is online

Users browsing this forum: No registered users and 89 guests