The Jerusalem PostIDF's next-generation artillery gets NIS 600 million training boost with Elbit dealUN NewsSix in seven journalist murders go unpunished, UNESCO reportsPunchNLC can negotiate alone, Ajaero replies TUC presidentBollywood HungamaBigg Boss 20: Panchayat fame Aasif Khan eliminated from Salman Khan-hosted showInquirerPITX logs nearly 1.5 million passengers during Undas weekDaily MaverickDIGITAL POLITICS OP-ED: The anti-foreigner movement squares up to the state in run-up to electionsWirtualna PolskaKryzys w TK. Ćwiąkalski: Kancelaria Premiera musiała być poinformowana7sur7Élections de mi-mandat aux États-Unis: Trump veut faire mentir les mauvais sondages dans les urnesIl Fatto QuotidianoPier Paolo Pasolini, la Procura di Roma riapre le indagini sull’omicidio: nuovo fascicolo dopo un espostoNU.nlVerstappen trots op F1-wereldkampioen Antonelli: 'Ongelooflijk goed gereden'La PresseLa revue de presse de Paul Arcand | Négociations Canada–États-Unis, les propos de Trump et élections de mi-mandatGMA NewsFore sweeps through homes in Barangay E. Rodriguez, QC
The Daily Newsstand · Free, Always
Monday, November 2, 2026

Как я подружил SD-карту с DMA на STM32H750

Translate

На Хабре уже есть статья о том, как работать с SD-картами в DMA режиме на микроконтроллерах STM32, поэтому я думал, что на STM32H7 будет также просто это реализовать.

На деле же оказалось, что такой мощный чип хранит в себе подводные камни, и реализация работы с SD-картой при помощи DMA требует лучшего понимания архитектуры STM32.

DMA

С периферийными устройствами можно работать тремя способами, используя polling, прерывания или DMA.

Это три основных способа организации обмена, но они не являются взаимоисключающими.

Например, в STM32 можно запустить передачу через DMA, а после её завершения получить прерывание. В этом случае DMA выполняет передачу данных, а прерывание сообщает процессору о её завершении.

В микроконтроллерах STM32H7 используется усовершенствованный периферийный контроллер SDMMC (в STM32F4 используется SDIO), предназначенный для работы с картами памяти SD и другими совместимыми устройствами. По сравнению с реализациями предыдущих поколений он поддерживает более новые версии стандартов, высокоскоростные режимы обмена и встроенный DMA-контроллер IDMA, позволяющий передавать данные между периферией и памятью без постоянного участия процессора.

Я не нашёл в интернете гайд, который рассказывает подробно об особенностях работы SDMMC DMA. Надеюсь, что эта статья поможет вам сэкономить время, когда вы столкнётесь с этими особенностями.

Конфигурирование микроконтроллера

Создадим новый проект в STM32CubeMX и выполним конфигурирование.

Настройка отладки

  1. Включаем 2-проводной интерфейс отладки и прошивки (SWD).

    Pinout & Configuration -> Trace and Debug -> DEBUG -> Debug -> Serial Wire

  2. Включаем 3-й модуль универсального синхронно-асинхронного приёмопередатчика (USART) в классическом асинхронном режиме (UART).

    Connectivity -> USART3 -> Mode -> Asynchronous

Настройка SDMMC

  1. Переводим SDMMC в режим работы по 4-битной шине данных.

    Connectivity -> SDMMC1 -> Mode -> SD 4 Bit Wide bus

  2. Для надёжности и защиты от потери данных включаем аппаратное управление потоком данных для SDMMC

    Connectivity -> Configuration -> Parameter Settings -> SDMMC parameters -> SDMMC hardware flow control -> The hardware control is enabled

  3. В STM32H7 используется отдельный внутренний DMA для интерфейса SDMMC, поэтому использовать каналы DMA, как для чипов STM32F4, не нужно. Включаем обработку прерываний на аппаратном уровне для SDMMC1 через контроллер прерываний NVIC (Nested Vectored Interrupt Controller).

    Connectivity -> Configuration -> NVIC Settings -> SDMMC1 global interrupt

  4. Указываем, что для работы с SD-картой использовать шаблон DMA (Direct Memory Access — прямого доступа к памяти).

    Middleware and Software Packs -> Configuration -> Advanced Settings -> SDIO/SDMMC -> Use dma template -> Enabled

Настройка FatFs

  1. Настраиваем один из пинов для детекта карты. Кликаем на PD4 и выбираем GPIO Input

  2. Подключаем библиотеку FatFs и связываем её с драйвером SD-карты.

    Middleware and Software Packs -> Mode -> FATFS -> SD Card

  3. Указываем библиотеке FatFs, к какой конкретно физической ножке микроконтроллера подключён контакт Card Detect (CD) слота SD-карты (в нашем случае — к пину PD4).

    Middleware and Software Packs -> Configuration -> Platform Settings -> Detect SDIO -> Found Solutions -> PD4

Настройка системы тактирования

  1. Выполняем Clock configuration. Соглашаемся в появившемся окне. Устанавливаем частоту клока для SDMMC 50 МГц.

    *SDMMC1,2 Clock Mux -> 50

Генерация проекта

  1. Вводим название проекта:

    Project Manager -> Project -> Project Settings -> Project Name -> stm32-sdmmc-dma-sample

  2. Указываем, что генерировать проект для CMake:

    Project Manager -> Project -> Project Settings -> Toolchain / IDE -> CMake

  3. Указываем, что нужно генерировать отдельные файлы для каждой периферии:

    *** Project Manager -> Code Generator -> Generated files -> Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral**

  4. Нажимаем кнопку Generate Code

Код приложения

  1. Добавляем в сгенерированный проект два файла User/utils.h и User/utils.c

    User/utils.c

    #include <stdio.h>
    #include <string.h>
    #include "ff.h" // Main FatFs header file
    
    
    #include <assert.h>
    // Declare global or static structures to avoid stack overflow
    FATFS fs;
    FIL file;           // File object
    DIR dir;            // Directory object
    FILINFO fno;        // File/folder information structure
    FRESULT res;        // Variable to check FatFs return codes
    
    void Test_SD_Card(void)
    {
        UINT bytes_written = 0;
        UINT bytes_read = 0;
        char read_buffer[64];
        const char* filename = "hello.txt";
        const char* text_to_write = "Hello World";
    
        printf("=== Starting SD Card Test ===\n");
    
        // 1. Mount the file system (0: is the default logical drive, 1 forces immediate mounting)
        res = f_mount(&fs, "0:", 1);
        if (res != FR_OK) {
            printf("f_mount error: %d. Check card connection or formatting.\n", res);
            return;
        }
        printf("File system successfully mounted.\n");
    
        // 2. List the contents of the root directory
        printf("\n--- Root Directory Contents: ---\n");
        res = f_opendir(&dir, "0:/");
        if (res == FR_OK) {
            while (1) {
                res = f_readdir(&dir, &fno);                   // Read next item
                if (res != FR_OK || fno.fname[0] == '\0') break; // End of list or error
    
                // Check if it is a directory or a file
                if (fno.fattrib & AM_DIR) {
                    printf(" [DIR]  %s\n", fno.fname);
                } else {
                    printf(" [FILE] %s (Size: %lu bytes)\n", fno.fname, fno.fsize);
                }
            }
            f_closedir(&dir);
        } else {
            printf("Failed to open root directory, error: %d\n", res);
        }
    
        // 3. Check if "hello.txt" exists using f_stat
        printf("\n--- Checking for file: %s ---\n", filename);
        res = f_stat(filename, &fno);
    
        if (res == FR_NO_FILE) {
            // File does not exist — create it
            printf("File %s not found. Creating...\n", filename);
    
            // Open file with flags to create a new file and allow writing
            res = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE);
            if (res == FR_OK) {
                // Write "Hello World" text to the file
                res = f_write(&file, text_to_write, strlen(text_to_write), &bytes_written);
                if (res == FR_OK) {
                    printf("Successfully wrote %u bytes.\n", bytes_written);
                } else {
                    printf("f_write error: %d\n", res);
                }
    
                // File must be closed to physically save data to the card
                f_close(&file);
            } else {
                printf("f_open error during creation: %d\n", res);
            }
        } else if (res == FR_OK) {
            printf("File %s already exists on the card.\n", filename);
        } else {
            printf("f_stat check error: %d\n", res);
        }
    
        // 4. Open the file for reading and print its content
        printf("\n--- Reading file: %s ---\n", filename);
        res = f_open(&file, filename, FA_READ);
        if (res == FR_OK) {
            // Clear the buffer before reading
            memset(read_buffer, 0, sizeof(read_buffer));
    
            // Read data from the file into the buffer
            res = f_read(&file, read_buffer, sizeof(read_buffer) - 1, &bytes_read);
            if (res == FR_OK) {
                printf("Read from file: \"%s\" (Total: %u bytes)\n", read_buffer, bytes_read);
            } else {
                printf("f_read error: %d\n", res);
            }
    
            // Close the file after reading
            f_close(&file);
        } else {
            printf("f_open error during read: %d\n", res);
        }
    
        // 5. Unmount the file system to release resources
        f_mount(NULL, "0:", 0);
        printf("\n=== Test finished, card unmounted ===\n");
    }
    
    User/utils.h
    
    ```cpp
    extern void Test_SD_Card(void);
    
  2. Модифицируем CMakeLists.txt чтобы добавленные файлы компилировались при сборке:

    # Add sources to executable
    target_sources(${CMAKE_PROJECT_NAME} PRIVATE
        # Add user sources here
            User/utils.c
    )
    
    # Add include paths
    target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
        # Add user defined include paths
            User
    )
    
  3. Добавляем определение функции _write() в main.c, чтобы printf() выводила информацию на UART.

    /* USER CODE BEGIN PV */
    int _write(int file, char *ptr, int len)
    {
    HAL_UART_Transmit(&huart3, (uint8_t *)ptr, len, HAL_MAX_DELAY);
    return len;
    }
    /* USER CODE END PV */
    
  4. Подключаем заголовочный файл utils.h в main.c

    /* USER CODE BEGIN Includes */
    #include "utils.h"
    /* USER CODE END Includes */
    
  5. Добавляем вызов нашей функции Test_SD_Card().

    /* USER CODE BEGIN 2 */
    Test_SD_Card();
    /* USER CODE END 2 */
    

Почему не работает DMA

Запускаем — и что мы видим? Микроконтроллер посылает DMA-запрос и не получает ответа, падает по таймауту.

Дело не в SD-карте, так как без DMA всё работало.

Поиск в интернете и чтение документации дают ответ на вопрос, почему такое происходит. У STM32H750 оперативная память разбита на регионы и домены. Внутренний SDMMC DMA не видит DTCMRAM.

Смотрим в файле STM32H750xx_FLASH.ld как распределяет память компоновщик.

Объявлены следующие области памяти:

/* Specify the memory areas */
MEMORY
{
DTCMRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
RAM (xrw)      : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw)      : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw)      : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw)      : ORIGIN = 0x00000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 128K
}

Данные и константы (секции .data и .bss) помещаются в DTCMRAM

  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
  } >DTCMRAM AT> FLASH

```cpp
  .bss (NOLOAD) : ALIGN(4)
  {
    *(.bss)
    *(.bss*)
    *(COMMON)

      . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
      PROVIDE( __bss_end = .);
  } >DTCMRAM

Вывод распределения памяти при сборке это подтверждает.

Решение проблемы с DMA

Нужно найти способ, как разместить буферы, которые используются DMA в другом домене.

Можно пойти радикально, и все данные разместить в RAM. Кстати, в LD- скриптах, поставляемых с Platform.io, так и делается.

Но работа с RAM медленнее, поэтому мы добавим ещё одну секцию в LD-скрипт, а в программе добавим атрибуты для переменных типа FIL и FATFS, указывающие, что эти переменные следует размещать в этой секции.

  .dma_buffer (NOLOAD) :
  {
    . = ALIGN(32);
    _sdma_buffer_start = .; 

    *(.dma_buffer)          
    *(.dma_buffer.*)

    . = ALIGN(32); 
    _sdma_buffer_end = .;
  } > RAM 
__attribute__((section(".dma_buffer"))) FATFS fs;
__attribute__((section(".dma_buffer"))) FIL file;           // File object

Не забываем пересобрать проект, после того как внесли изменение в .ld файл.

Проблема с кешем

Запускаем, всё вроде бы работает, но оказывается, что у STM32H7 инструкции и данные могут кэшироваться.

Включим D-Cache в STM32CubeMX.

Pinout & Configuration -> CORTEX_M7 -> Cortex Interface Settings -> CPU ICache -> Enabled

Pinout & Configuration -> CORTEX_M7 -> Cortex Interface Settings -> CPU DCache -> Enabled

А DMA этот кэш не видит. При простом чтении данных с SD-карты мы даже не увидим проблемы. Но, например, возьмём сценарий, когда обнулим буфер, а потом прочитаем в него данные с SD-карты. Процессор будет видеть те данные, которые он записал в буфер, а не то, что прочитал DMA с SD. То есть в буфере будут 0.

Решение проблемы с кешем

Как решается проблема с буфером? Документация по STM32H7 советует использовать функцию SCB_CleanDCache_by_Addr() и SCB_InvalidateDCache_by_Addr() при использовании DMA.

В заглушке, которую генерирует STM32CubeMX, есть закомментированный макрос ENABLE_SD_DMA_CACHE_MAINTENANCE.

/* USER CODE BEGIN enableSDDmaCacheMaintenance */
/* #define ENABLE_SD_DMA_CACHE_MAINTENANCE  1 */
/* USER CODE END enableSDDmaCacheMaintenance */

Макрос добавляет код для правильной работы с D-Cache.

Раскомментируем макрос. Собираем и запускаем. Всё работает.

Обратите внимание, что в заглушке, которую генерирует CubeMX, процессор ждёт в цикле, пока не считаются данные по DMA. Что в принципе практически сводит на нет все преимущества DMA. Поэтому, по-хорошему, нужно переписывать весь код самим на основе колбеков или использовать FreeRTOS, в котором вытесняющий планировщик отдаст управление другой задаче, пока первая будет ждать ответа по DMA.

Но это уже, как говорится, другая история. А сейчас у нас есть работающий через DMA SDMMC-интерфейс.

Заключение

Использовать DMA для работы с SD-картой на STM32H7 сложнее, чем на микроконтроллерах более простых серий.

В интернете практически нет информации по этому вопросу. В основном пользователи спрашивают, почему у них что-то не работает, но так и не получают чёткого ответа от сообщества.

Чтобы заставить DMA работать с SD-картой, мне пришлось:

  • разбираться, как работает компоновщик,

  • где находятся и что содержат LD-скрипты.

  • вспоминать, что такое выравнивание.

Код для работы с SD-картой, генерируемый CubeMX, не является оптимальным: без использования FreeRTOS работа через DMA почти не даёт никаких преимуществ.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.