Shared SPI for the M5 stack

Description

On the M5Stack the screen and the sd card share the SPI bus. so when I try to write to the SD card after initializing the screen I get an error
spi_master: spi_bus_initialize(237): dma channel already in use
which makes sense. The question is what is the best way to share the SPI bus with little VGL?

What MCU/Processor/Board and compiler are you using?

ESP32

What do you want to achieve?

Use the M5Stack screen and SD Card at the same time

Hi,

I haven’t used the esp32 microcontroller for a lot of projects, but the display is allocating a fixed dma channel (dma channel 1), I think the solution would be to add the spi device that talks to the SD card using another dma channel. This is how we allocate the spi buffer with channel 1:

void disp_spi_init(void)
{

    esp_err_t ret;

    spi_bus_config_t buscfg={
            .miso_io_num=-1,
            .mosi_io_num=DISP_SPI_MOSI,
            .sclk_io_num=DISP_SPI_CLK,
            .quadwp_io_num=-1,
            .quadhd_io_num=-1,
#if CONFIG_LVGL_TFT_DISPLAY_CONTROLLER == TFT_CONTROLLER_ILI9341
            .max_transfer_sz = DISP_BUF_SIZE * 2,
#elif CONFIG_LVGL_TFT_DISPLAY_CONTROLLER == TFT_CONTROLLER_ST7789
            .max_transfer_sz = DISP_BUF_SIZE * 2,
#elif CONFIG_LVGL_TFT_DISPLAY_CONTROLLER == TFT_CONTROLLER_ILI9488
            .max_transfer_sz = DISP_BUF_SIZE * 3,
#elif CONFIG_LVGL_TFT_DISPLAY_CONTROLLER == TFT_CONTROLLER_HX8357
            .max_transfer_sz = DISP_BUF_SIZE * 2
#elif CONFIG_LVGL_TFT_DISPLAY_CONTROLLER == TFT_CONTROLLER_ILI9486
            .max_transfer_sz = DISP_BUF_SIZE * 2,
#endif
    };

    //Initialize the SPI bus
    ret=spi_bus_initialize(TFT_SPI_HOST, &buscfg, 1);
    assert(ret==ESP_OK);

    //Attach the LCD to the SPI bus
    disp_spi_add_device(TFT_SPI_HOST);
}

So you maybe try using the function disp_spi_add_device_config to add your sd card device configuration to the bus. what do you think?

Regards

@Carlos_Diaz. yip that worked :partying_face: you are awesome
I didn’t realize the SD card could use a different dma channel. Your suggestion put me on the right path.
Thank you!

Great to know it worked, I wasn’t sure about my suggestion hehe. Changing the dma channel for the sd card was all you needed?

Regards

Hi @Carlos_Diaz I thought i did work but unfortunately the screen froze and will no longer update. I can see from print statements that LittleVgl is updating the buttons but after calling the SD card code the screen no longer updates. any suggestions?

Hmmm, can you show me the printf statements you’re getting?
I have a lot of work to do, but maybe I can use the Wrover kit i have to try replicate it on the weekend.

Regards,
Carlos

Hi @Carlos_Diaz
It would be amazing if you could help me out of this.

try giving this a go

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_freertos_hooks.h"
#include "freertos/semphr.h"

#include "esp_system.h"
#include "driver/gpio.h"

#include <sys/unistd.h>
#include <sys/stat.h>
#include <sys/dirent.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "freertos/task.h"
#include "driver/sdmmc_host.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"

/* Littlevgl specific */
#include "lvgl/lvgl.h"
#include "lvgl_driver.h"
#include "lv_examples/lv_apps/demo/demo.h"

static const char *TAG = "SD-CARD";

#define SD_CARD_MOSI 23
#define SD_CARD_MISO 19
#define SD_CARD_CLK 18
#define SD_CARD_CS 4
#define DMA_CHANNEL 2
#define MAX_BUFSIZE (16 * 1024)

/*********************
 *      DEFINES
 *********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void IRAM_ATTR lv_tick_task(void *arg);
void guiTask();
void test_sd_card(void);

/**********************
 *   APPLICATION MAIN
 **********************/
void app_main()
{
    xTaskCreatePinnedToCore(guiTask, "gui", 4096 * 2, NULL, 0, NULL, 1);

    //give a delay to ensure little vgl is working
    printf("screen still working...\n");
    printf("writing to card in...\n");
    for (size_t i = 10; i > 0; i--)
    {
        printf("..%d\n",i);
        vTaskDelay(pdMS_TO_TICKS(1000));    
    }

    printf("writing to card\n");
    test_sd_card();
    printf("SD card routine complete\n");
    printf("Screen is now frozen\n");
}

void test_sd_card(void)
{

    sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
    slot_config.gpio_miso = SD_CARD_MISO;
    slot_config.gpio_mosi = SD_CARD_MOSI;
    slot_config.gpio_sck = SD_CARD_CLK;
    slot_config.gpio_cs = SD_CARD_CS;
    slot_config.dma_channel = DMA_CHANNEL;

    sdmmc_host_t host = SDSPI_HOST_DEFAULT();

    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = false,
        .max_files = 5,
        .allocation_unit_size = MAX_BUFSIZE};

    // Use settings defined above to initialize SD card and mount FAT filesystem.
    // Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function.
    // Please check its source code and implement error recovery when developing
    // production applications.
    sdmmc_card_t *card;
    esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);

    if (ret != ESP_OK)
    {
        if (ret == ESP_FAIL)
        {
            ESP_LOGE(TAG, "Failed to mount filesystem. "
                          "If you want the card to be formatted, set format_if_mount_failed = true.");
        }
        else
        {
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                          "Make sure SD card lines have pull-up resistors in place.",
                     esp_err_to_name(ret));
        }
        return;
    }

    // Card has been initialized, print its properties
    sdmmc_card_print_info(stdout, card);

    // Use POSIX and C standard library functions to work with files.
    // First create a file.
    ESP_LOGI(TAG, "Opening file");
    FILE *f = fopen("/sdcard/hello.txt", "w");
    if (f == NULL)
    {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    fprintf(f, "Hello %s!\n", card->cid.name);
    fclose(f);
    ESP_LOGI(TAG, "File written");

    // Check if destination file exists before renaming
    struct stat st;
    if (stat("/sdcard/foo.txt", &st) == 0)
    {
        // Delete it if it exists
        unlink("/sdcard/foo.txt");
    }

    // Rename original file
    ESP_LOGI(TAG, "Renaming file");
    if (rename("/sdcard/hello.txt", "/sdcard/foo.txt") != 0)
    {
        ESP_LOGE(TAG, "Rename failed");
        return;
    }

    // Open renamed file for reading
    ESP_LOGI(TAG, "Reading file");
    f = fopen("/sdcard/foo.txt", "r");
    if (f == NULL)
    {
        ESP_LOGE(TAG, "Failed to open file for reading");
        return;
    }
    char line[64];
    fgets(line, sizeof(line), f);
    fclose(f);
    // strip newline
    char *pos = strchr(line, '\n');
    if (pos)
    {
        *pos = '\0';
    }
    ESP_LOGI(TAG, "Read from file: '%s'", line);

    // All done, unmount partition and disable SDMMC or SPI peripheral
    esp_vfs_fat_sdmmc_unmount();
    ESP_LOGI(TAG, "Card unmounted");
}

static void IRAM_ATTR lv_tick_task(void *arg)
{
    (void)arg;

    lv_tick_inc(portTICK_RATE_MS);
}

void some_random_task()
{
    printf("vgl task still ticks but screen freezes \n");
}

SemaphoreHandle_t xGuiSemaphore;

void guiTask()
{
    xGuiSemaphore = xSemaphoreCreateMutex();

    lv_init();

    lvgl_driver_init();

    static lv_color_t buf1[DISP_BUF_SIZE];
    static lv_color_t buf2[DISP_BUF_SIZE];
    static lv_disp_buf_t disp_buf;
    lv_disp_buf_init(&disp_buf, buf1, buf2, DISP_BUF_SIZE);

    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.flush_cb = disp_driver_flush;
    disp_drv.buffer = &disp_buf;
    lv_disp_drv_register(&disp_drv);

#if CONFIG_LVGL_TOUCH_CONTROLLER != TOUCH_CONTROLLER_NONE
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.read_cb = touch_driver_read;
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    lv_indev_drv_register(&indev_drv);
#endif

    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        /* name is optional, but may help identify the timer when debugging */
        .name = "periodic_gui"};
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    //On ESP32 it's better to create a periodic task instead of esp_register_freertos_tick_hook
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 10 * 1000)); //10ms (expressed as microseconds)

    demo_create();
    lv_task_create(some_random_task, 2000, LV_TASK_PRIO_LOWEST, NULL);
    while (1)
    {
        vTaskDelay(1);
        //Try to lock the semaphore, if success, call lvgl stuff
        if (xSemaphoreTake(xGuiSemaphore, (TickType_t)10) == pdTRUE)
        {
            lv_task_handler();
            xSemaphoreGive(xGuiSemaphore);
        }
    }

    //A task should NEVER return
    vTaskDelete(NULL);
}

Hi @mair.swartz,

Just to let you know that I’m working on this, while doing some research i have found this Support SD-SPI bus sharing and reading through it i think it’s needed to improve a lot the tft drivers, the problem seems to be that SD SPI driver takes the spi bus sometimes for more than a spi transaction, so it can delay the lvgl task, also it seems like the spi bus frq must be configured to 20MHz because of the SD Card.
So what I was planning to do is to sample the spi bus using the lvgl task only and figure out how much time it have the spi bus working and then do the same for the SD SPI, and try to know the efect/delay caused by the SD SPI driver, I also have not an SD card SPI adapter, I will look in my work toolbox if we have one :smiley:, this is fun.

One question, is the SD card embedded on the M5Stack board or are you using it separately?

Regards

Thanks @Carlos_Diaz
The SD card on the M5 stack is embedded and tied to the same SPI lines :frowning: (different cs naturally) .
Here are some observations which might be useful

  1. using VSPI the screen freezes.
  2. Using HSPI I get an error
E (20449) spi: SPI2 already claimed by spi master.
E (20449) spi_master: spi_bus_initialize(231): host already in use
  1. in either case, if you write to the sd card first and then initialize Lilltlevgl it works but subsequent access to the SD card will have one of the 2 effects above.

If you need me to test anything, or think I can help in any way please let me know

1 Like

Hi,

Thanks for the information, I’m making a new repo to place your code so both of us can work on it.
I’ve done some changes on the code you uploaded, but i don’t have the hardware to test it, so once i cleanup the repo i will post it here so you can run some tests for me.

Is this a personal project you’re working on or part of your work? I ask because I don’t know how much time it will take to make it work.

Take care

@Carlos_Diaz, Its part of my work.
Sounds good. I’ll keep an eye out for any time you reach out to me

Here’s the repo: https://github.com/C47D/m5stack_sd_display_shared_spi

It doesn’t compile yet, will make it compile tonight…

@mair.swartz

Just found this: Can’t mount SD card and display at the same time when sharing the same SPI bus
on the comments there seems to be a workaround.

any idea how to implement this work around in c?

Haven’t took a deep dive into it, I will try to get some free time tonight to get this started, i think we need a Semaphore to lock the SPI Master peripheral, so the SD SPI code doesn’t try to use it when lvgl is using it, that’s what I think is happening here. Will try to replicate your code with my Wrover Kit v4.1.

Has any progress been made with this issue.

Not much, I have no M5Stack myself, I was testing sharing SPI bus on my Wrover kit but the display and sd card are not sharing the spi bus on it :frowning:
I setup a repo here https://github.com/C47D/m5stack_sd_display_shared_spi
@mair.swartz was helping me with their M5Stack board and had an issue.

I can work again on this if you have a M5Stack board and can test for me, please let me know.

In the micropython fourm
https://forum.micropython.org/viewtopic.php?f=18&t=8352

Mike Teachman said…

Are you using the Python or C based ILI9341 display driver? If you use the C driver I found a work around
I faced the same problem and found a workaround by modifying the C based ILI9341 display driver, as follows:

  • change the SPI bus config to full duplex

fstengel wrote…

I have used @Mike Teachman’s solution (and the derived code I found in that repo https://github.com/sci-bots/lv_binding_ … fed5fac8a2). It works.

but that link goes to… sci-bots/lv_binding_micropython

test(m5stack): [WIP] full-duplex SPI and no init
Try modifiying the pure/hybrid ili9341 driver to work with a pre-initialized SPI bus (e.g., interfaced to an SD card). These changes aim to replicate this workaround which resolves the issue when using the ILI9341 C module.

Which looks like a uPy attempt. [WIP] in title Work In Progress?
I built using using ili9341.py from test(ili9341.py-shared-SPI) branch.

mountrd SDcard ok

import tft
ILI9341 initialization completed
Enable backlight
Double buffer
E (43990) spi_master: check_trans_valid(801): txdata transfer > host maximum
…and dev board locks up.

This is the closest I have gotten over a month.
Hope this gives you some insight.

Hi all
I have a few M5 stacks and am happy to help with testing.
apart from the repo above,
is there a defined test scenario / .py script to reproduce the issue that I could start with ?