Any way to transfer image files between master and slave esp32 chips using SPI?

karunt
Posts: 101
Joined: Sat Apr 03, 2021 7:58 am

Re: Any way to transfer image files between master and slave esp32 chips using SPI?

Postby karunt » Fri Dec 06, 2024 6:23 pm

Thanks. I saw the example you posted a link to and adapted my approach to the following:
1. Transfer data from ESP32 (master) to ESP32-S3 (slave) over SPI using IRAM buffers
2. Transfer data from ESP32-S3 (slave) IRAM to PSRAM using DMA

Step 1 works fine. But step 2 doesn't - keeps rebooting because it says that memory isn't DMA aligned.

Here's my code and log. Appreciate if you could point out any issues you might notice:

Code:

Code: Select all

static IRAM_ATTR bool dmacpy_cb(async_memcpy_handle_t hdl, async_memcpy_event_t *event, void *args) {
    SemaphoreHandle_t sem = (SemaphoreHandle_t)args;
    BaseType_t task_wakeup = pdFALSE;
    xSemaphoreGiveFromISR(sem, &task_wakeup);
    return (task_wakeup==pdTRUE);
}

void spi_receive_start(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,
        .intr_type=GPIO_INTR_POSEDGE,
        .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[128]={0}; //IRAM buffer for spi transaction tx_buffer
    WORD_ALIGNED_ATTR char recvbuf[128]={0}; //IRAM buffer for spi transaction rx_buffer

//Define external memory to store data received by spi_slave_transaction_t.rx_buffer
    WORD_ALIGNED_ATTR static uint32_t *psram_mem;
    const uint32_t PSRAM_ALIGN = cache_hal_get_cache_line_size(CACHE_TYPE_DATA); //why this vs hard coding to 16, 32 or 64
    const uint32_t INTRAM_ALIGN = PSRAM_ALIGN;
    const uint32t length = 2048000;
    psram_mem = heap_caps_aligned_alloc(PSRAM_ALIGN, length, MALLOC_CAP_SPIRAM);
    assert(psram_mem != NULL);

//Define async_memcpy_config
    const async_memcpy_config_t cfg = {
        .backlog = (length+4091)/4092, //don't understand the rationale behind this formula
        .sram_trans_align = INTRAM_ALIGN,
        .psram_trans_align = PSRAM_ALIGN,
        .flags = 0
    };

//Install GDMA controller
    async_memcpy_handle_t async_memcpy_handle = NULL;
    ESP_ERROR_CHECK(esp_async_memcpy_install_gdma_ahb(&cfg, &async_memcpy_handle));

//Prepare for SPI transfer between master and slave
    spi_slave_transaction_t t;
    t.length=128*8;
    memset(&t, 0, sizeof(t));
    SemaphoreHandle_t semphr = xSemaphoreCreateBinary();

    while(n<=10) {
        memset(recvbuf, 0, sizeof(recvbuf));
        sprintf(sendbuf, "Transaction %04d received", n);
        t.tx_buffer=sendbuf;
        t.rx_buffer=recvbuf;        
        ret=spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY);

        //Save content received by t.rx_buffer into psram_mem
        ESP_LOGI(TAG, "Starting DMA copy.");
        ESP_ERROR_CHECK(esp_async_memcpy(async_memcpy_handle, psram_mem, t.rx_buffer, sizeof(t.rx_buffer), &dmacpy_cb, semphr));
        xSemaphoreTake(semphr, portMAX_DELAY);
        n++;
    }

    esp_async_memcpy_uninstall(async_memcpy_handle);
}
Here's the SPI portion of my sdkconfig file:

Code: Select all

# ESP PSRAM
#
CONFIG_SPIRAM=y

#
# SPI RAM config
#
# CONFIG_SPIRAM_MODE_QUAD is not set
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_TYPE_AUTO=y
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
CONFIG_SPIRAM_CLK_IO=30
CONFIG_SPIRAM_CS_IO=26
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=y
CONFIG_SPIRAM_SPEED_80M=y
# CONFIG_SPIRAM_SPEED_40M is not set
CONFIG_SPIRAM_SPEED=80
# CONFIG_SPIRAM_ECC_ENABLE is not set
CONFIG_SPIRAM_BOOT_INIT=y
# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set
# CONFIG_SPIRAM_USE_MEMMAP is not set
CONFIG_SPIRAM_USE_CAPS_ALLOC=y
# CONFIG_SPIRAM_USE_MALLOC is not set
CONFIG_SPIRAM_MEMTEST=y
# CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP is not set
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
# end of SPI RAM config
# end of ESP PSRAM
Here's my log:

Code: Select all

Leaving...
Hard resetting via RTS pin...
Executing action: monitor
Running idf_monitor in directory 'C:\Users\Alpha\ESP-IDF Peripherals - SPI slave example\receiver'
Executing "c:\Users\Alpha\ESP\.espressif\python_env\idf5.2_py3.11_env\Scripts\python.exe C:\Users\Alpha\esp\esp-idf\tools/idf_monitor.py -p COM6 -b 115200 --toolchain-prefix xtensa-esp32s3-elf- --target esp32s3 --revision 0 'C:\Users\Alpha\ESP-IDF Peripherals - SPI slave example\receiver\build\spi_slave_receiver.elf' --force-color -m 'c:\Users\Alpha\ESP\.espressif\python_env\idf5.2_py3.11_env\Scripts\python.exe' 'C:\Users\Alpha\esp\esp-idf\tools\idf.py'"...
--- WARNING: GDB cannot open serial ports accessed as COMx
--- Using \\.\COM6 instead...
--- esp-idf-monitor 1.2.1 on \\.\COM6 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
I (100) boot:  2 factory          factory app      00 00 ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xa (SPI_FAST_FLASH_BOOT)
Saved PC:0x42007c3c
0x42007c3c: s_test_psram at C:/Users/Alpha/ESP/esp-idf/components/esp_psram/esp_psram.c:418

SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3810,len:0x171c
load:0x403c9700,len:0x4
load:0x403c9704,len:0xc20
load:0x403cc700,len:0x2f94
SHA-256 comparison failed:
Calculated: 69b7a3013875b94363c030717518c738ac90a249877a1ef67f5047554d9c611a
Expected: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Attempting to boot anyway...
entry 0x403c9910
I (45) boot: ESP-IDF v5.2-dev-2756-g2bc1f2f574-dirty 2nd stage bootloader
I (45) boot: compile time Nov  8 2024 14:43:18
I (46) boot: Multicore bootloader
I (50) boot: chip revision: v0.2
I (54) boot.esp32s3: Boot SPI Speed : 80MHz
I (59) boot.esp32s3: SPI Mode       : DIO
I (64) boot.esp32s3: SPI Flash Size : 16MB
I (69) boot: Enabling RNG early entropy source...
I (74) boot: Partition Table:
I (78) boot: ## Label            Usage          Type ST Offset   Length
I (85) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (92) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (100) boot:  2 factory          factory app      00 00 00010000 00300000
I (107) boot: End of partition table
I (112) esp_image: segment 0: paddr=00010020 vaddr=3c020020 size=0c8bch ( 51388) map
I (126) esp_image: segment 1: paddr=0001c8e4 vaddr=3fc92e00 size=02dd8h ( 11736) load
I (130) esp_image: segment 2: paddr=0001f6c4 vaddr=40374000 size=00954h (  2388) load
I (137) esp_image: segment 3: paddr=00020020 vaddr=42000020 size=1f274h (127604) map
I (160) esp_image: segment 4: paddr=0003f29c vaddr=40374954 size=0e3a4h ( 58276) load
I (175) boot: Loaded app from partition at offset 0x10000
I (176) boot: Disabling RNG early entropy source...
I (187) cpu_start: Multicore app
I (187) octal_psram: vendor id    : 0x0d (AP)
I (187) octal_psram: dev id       : 0x02 (generation 3)
I (190) octal_psram: density      : 0x03 (64 Mbit)
I (196) octal_psram: good-die     : 0x01 (Pass)
I (201) octal_psram: Latency      : 0x01 (Fixed)
I (206) octal_psram: VCC          : 0x01 (3V)
I (211) octal_psram: SRF          : 0x01 (Fast Refresh)
I (217) octal_psram: BurstType    : 0x01 (Hybrid Wrap)
I (223) octal_psram: BurstLen     : 0x01 (32 Byte)
I (229) octal_psram: Readlatency  : 0x02 (10 cycles@Fixed)
I (235) octal_psram: DriveStrength: 0x00 (1/1)
I (241) MSPI Timing: PSRAM timing tuning index: 4
I (245) esp_psram: Found 8MB PSRAM device
I (250) esp_psram: Speed: 80MHz
I (266) mmu_psram: Instructions copied and mapped to SPIRAM
I (272) mmu_psram: Read only data copied and mapped to SPIRAM
I (272) cpu_start: Pro cpu up.
I (272) cpu_start: Starting app cpu, entry point is 0x403753cc
0x403753cc: call_start_cpu1 at C:/Users/karun/ESP/esp-idf/components/esp_system/port/cpu_start.c:178

I (0) cpu_start: App cpu up.
I (553) esp_psram: SPI SRAM memory test OK
I (561) cpu_start: Pro cpu start user code
I (561) cpu_start: cpu freq: 240000000 Hz
I (562) cpu_start: Application information:
I (565) cpu_start: Project name:     spi_slave_receiver
I (570) cpu_start: App version:      1af3222-dirty
I (576) cpu_start: Compile time:     Dec  6 2024 12:04:08
I (582) cpu_start: ELF file SHA256:  6c52c8732...
I (587) cpu_start: ESP-IDF:          v5.2-dev-2756-g2bc1f2f574-dirty
I (594) cpu_start: Min chip rev:     v0.0
I (599) cpu_start: Max chip rev:     v0.99
I (604) cpu_start: Chip rev:         v0.2
I (609) heap_init: Initializing. RAM available for dynamic allocation:
I (616) heap_init: At 3FC964F8 len 00053218 (332 KiB): DRAM
I (622) heap_init: At 3FCE9710 len 00005724 (21 KiB): STACK/DRAM
I (629) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (635) heap_init: At 600FE010 len 00001FD8 (7 KiB): RTCRAM
I (641) esp_psram: Adding pool of 8000K of PSRAM memory to heap allocator
I (649) spi_flash: detected chip: gd
I (653) spi_flash: flash io: dio
I (657) sleep: Configure to isolate all GPIO pins in sleep state
I (664) sleep: Enable automatic switching of GPIO sleep configuration
I (671) app_start: Starting scheduler on CPU0
I (676) app_start: Starting scheduler on CPU1
I (676) main_task: Started on CPU0
I (686) main_task: Calling app_main()
I (690) gpio: GPIO[17]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:1
I (12548) peripheral_rx_Alpha: Starting DMA copy.

[b]E (12561) async_mcp.gdma: mcp_gdma_memcpy(363): buffer not aligned: 0x3fc99f40 -> 0x3c030a40, sz=4[/b]

ESP_ERROR_CHECK failed: esp_err_t 0x102 (ESP_ERR_INVALID_ARG) at 0x42008202
0x42008202: spi_receive_start at C:/Users/Alpha/ESP-IDF Peripherals - SPI slave example/receiver/main/app_main_receiver_Alpha.c:225 (discriminator 1)

file: "./main/app_main_receiver_Alpha.c" line 225
func: spi_receive_start
expression: esp_async_memcpy(async_memcpy_handle, new_mem, t.rx_buffer, sizeof(t.rx_buffer), &dmacpy_cb, semphr)

abort() was called at PC 0x4037ace7 on core 0


Backtrace: 0x40375ba6:0x3fc99e60 0x4037acf1:0x3fc99e80 0x40380a56:0x3fc99ea0 0x4037ace7:0x3fc99f10 0x42008202:0x3fc99f40 0x42007fa7:0x3fc9a0f0 0x4201e64f:0x3fc9a110
0x4037ace7: _esp_error_check_failed at C:/Users/Alpha/ESP/esp-idf/components/esp_system/esp_err.c:50

0x40375ba6: panic_abort at C:/Users/Alpha/ESP/esp-idf/components/esp_system/panic.c:452

0x4037acf1: esp_system_abort at C:/Users/Alpha/ESP/esp-idf/components/esp_system/port/esp_system_chip.c:93

0x40380a56: abort at C:/Users/Alpha/ESP/esp-idf/components/newlib/abort.c:38

0x4037ace7: _esp_error_check_failed at C:/Users/Alpha/ESP/esp-idf/components/esp_system/esp_err.c:50

0x42008202: spi_receive_start at C:/Users/Alpha/ESP-IDF Peripherals - SPI slave example/receiver/main/app_main_receiver_Alpha.c:225 (discriminator 1)

0x42007fa7: app_main at C:/Users/Alpha/ESP-IDF Peripherals - SPI slave example/receiver/main/main.c:4

0x4201e64f: main_task at C:/Users/Alpha/ESP/esp-idf/components/freertos/app_startup.c:208 (discriminator 13)

MicroController
Posts: 1821
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Any way to transfer image files between master and slave esp32 chips using SPI?

Postby MicroController » Fri Dec 06, 2024 7:38 pm

Code: Select all

E (12561) async_mcp.gdma: mcp_gdma_memcpy(363): buffer not aligned: 0x3fc99f40 -> 0x3c030a40, sz=4[/b]
The problem is the "size" of the transfer, which must also be aligned, but was given as "4"; this in turn is because of

Code: Select all

..., t.rx_buffer, sizeof(t.rx_buffer), &dmacpy_cb, ...
sizeof(t.rx_buffer) should be sizeof(recvbuf). (t.rx_buffer is only a pointer, hence its size is always 4 bytes.)

By the way: Due to the internal cache, using the CPU (memcpy,...) to push only a few (kilo)bytes to PSRAM at once may be faster than using DMA. (And it's definitely more convenient.) Setting up each DMA transfer and the additional context switches take time too.

Also,

Code: Select all

    spi_slave_transaction_t t;
    t.length=128*8;
    memset(&t, 0, sizeof(t));
is not what you want :)

As to the notes in your code:

Code: Select all

const uint32_t PSRAM_ALIGN = cache_hal_get_cache_line_size(CACHE_TYPE_DATA); //why this vs hard coding to 16, 32 or 64
Could hard code a value. You'd just have to know which one was selected in menuconfig.

Code: Select all

.backlog = (length+4091)/4092, //don't understand the rationale behind this formula
In previous versions of the IDF, the "backlog" determined the number of DMA descriptors the driver would allocate. One DMA descriptor is needed for every 4092 bytes of a single DMA transaction (see #define DMA_DESCRIPTOR_BUFFER_MAX_SIZE (4095) /*!< Maximum size of the buffer that can be attached to descriptor */ - 4095 bytes is only enough for 1023 words, which is 4092 bytes).
Since v5.2, I believe, the backlog is actually the depth of a queue for DMA transactions, so should be ok to use the default (by setting backlog to 0).

karunt
Posts: 101
Joined: Sat Apr 03, 2021 7:58 am

Re: Any way to transfer image files between master and slave esp32 chips using SPI?

Postby karunt » Tue Dec 10, 2024 7:39 pm

Thanks for the explanations of backlog and pointing out my mistake in esp_async_memcpy. You mentioned that there's a part of code that I don't want.

Code: Select all

spi_slave_transaction_t t;
t.length=128*8;
memset(&t, 0, sizeof(t));
I'm a bit confused about what part of this code I don't want (please bear in mind my lack of formal comp sci background and the fact that I'm still fairly new to embedded programming and ESP). Perhaps I should've set t.length after memset, although not sure I understand why that would make a difference.

You also mentioned that using the CPU instead of DMA should be more efficient for a few KB of data. What, in your opinion, is the appropriate threshold at which to consider using DMA instead? I'm anticipating transferring a jpeg or png file's worth of data, which could be a few hundred KB on the lower side.

MicroController
Posts: 1821
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Any way to transfer image files between master and slave esp32 chips using SPI?

Postby MicroController » Tue Dec 10, 2024 10:48 pm

karunt wrote:
Tue Dec 10, 2024 7:39 pm
Perhaps I should've set t.length after memset, although not sure I understand why that would make a difference.
The memset will overwrite everything in "t" with 0. So setting t.length before the memset does nothing; you'll end up with t.length == 0 :) Clear the structure via memset first, then set the values you want to configure.
You also mentioned that using the CPU instead of DMA should be more efficient for a few KB of data. What, in your opinion, is the appropriate threshold at which to consider using DMA instead? I'm anticipating transferring a jpeg or png file's worth of data, which could be a few hundred KB on the lower side.
I guess by itself the cache may or may not do much for you in this case. Worst case would be that the cache keeps filling up with the data you write to PSRAM until the cache is full, and only then starts writing pieces of data to PSRAM whenever you write more data, effectively slowing the CPU down to PSRAM speed.
On the S3, it is possible to tell the cache when to write data back to PSRAM, which I think would be very beneficial in your case. You receive a chunk of data, write it to "PSRAM" (cache), and when that's done, you tell the cache to write it back to PSRAM; the cache does the write-back in the background, so while the cache deals with PSRAM the CPU can do other things. (If you're interested, I do have some code for this level of cache control - a bit of a 'hack', bypassing about 3-4 layers of Espressif code for performance.)

However, you should probably first check if you actually do need any performance tricks here. As you can see at https://github.com/project-x51/esp32-s3-memorycopy, the PSRAM should be able to accept about 30MB/s - that's much faster than you can possibly receive via SPI.

karunt
Posts: 101
Joined: Sat Apr 03, 2021 7:58 am

Re: Any way to transfer image files between master and slave esp32 chips using SPI?

Postby karunt » Wed Dec 11, 2024 9:45 pm

As you can see at https://github.com/project-x51/esp32-s3-memorycopy, the PSRAM should be able to accept about 30MB/s - that's much faster than you can possibly receive via SPI.
But I still need SPI, UART or some other protocol to transfer data from the master (ESP32) to slave (ESP32-S3), don't I? If not, what's an alternative that wouldn't have required that involved of a set up?

Who is online

Users browsing this forum: forrest and 86 guests