
Когда устройству нужна относительно большая энергонезависимая память с возможностью быстрой замены, SD или microSD-карта — один из самых удобных и доступных вариантов.
Компактность, простое подключение, поддержка микроконтроллерами и низкая цена при большом объёме делают SD-карту стандартом для логирования, хранения конфигураций и обновления прошивок.
Многие считают работу с SD-картой чем-то сложным и низкоуровневым. На практике, если использовать SDIO + FatFS, бо́льшая часть сложности уже спрятана библиотеками и аппаратными модулями микроконтроллера.
В этой статье я покажу, как добавить поддержку SD-карт в проекты на STM32: что лежит в основе, и как она настраивается.
Цель статьи — не исчерпывающее руководство, а готовый понятный прототип, который можно взять за основу в реальных проектах и для своих экспериментов.
Полный исходный код примера из статьи доступен в моём GitHub репозитории
Как микроконтроллер работает с SD-картой
Микроконтроллер для работы с SD-картой может использовать один из двух физических интерфейсов: SDIO или SPI.
Перед началом работы с SD-картой её необходимо проинициализировать. Для нативного режима (SDIO) часть микроконтроллеров STM32 имеет встроенные аппаратные блоки, которые берут часть рутины по инициализации на себя.
Для режима SPI всю последовательность команд, контроль линий CS и разбор байтовых ответов программисту приходится прописывать вручную или использовать библиотеки.
Если не рассматривать ранние версии стандарта, SD-карты являются блочными устройствами. В них используется LBA-адресация, как у жёстких и оптических дисках.
SD-картой можно работать и без файловой системы, но это скорее частные случаи. Карту можно разбить на несколько разделов и поставить различные файловые системы. Но обычно на карте или один раздел с файловой системой FAT16, FAT32, exFAT в зависимости от размера карты.
Для работы с FAT использование библиотеки FatFS, можно сказать, является стандартом. Особенностью FatFS является то, что код библиотека не зависит от физического устройства, на котором располагается файловая система. Нужно написать несколько функций, которые определяют, как считать информацию с устройства, как записать и ещё несколько других важных операций.
Подключение SD-карты
Я использовал отладочную плату WeAct Studio на базе микроконтроллера STM32H750VBT6. У платы распаян microSD-слот, и все необходимые пины подтянуты к питанию, а микроконтроллер поддерживает аппаратный SDIO/SDMMC.
Если у микроконтроллера есть SDIO/SDMMC и вам не критично количество пинов — используйте SDIO. Это действительно почти так же просто, как работать с USB или Ethernet в STM32.
SPI имеет смысл использовать, когда:
нет свободного SDIO,
нужно использовать произвольные пины,
хотите сэкономить выводы,
делаете очень простой/дешёвый проект.
Если у вас какая-то другая плата, и вы будете использовать внешний модуль SD-карты, нужно убедиться, что разведены все пины для SDIO интерфейса и подтянуты к питанию пины (существуют модули microSD, которые можно использовать только в режиме SPI или без подтягивающих резисторов).
Мне известны следующие модули для microSD-карт:
Hw-203,
Hw-125,
Adafruit 4682,
Micro SD(TF) Storage Board.
Работа в режиме SDIO возможна только с последними двумя.
Для большинства SD-карт и логические уровни 3,3 В. Это необходимо помнить, чтобы не повредить карту.
Фактически карты поддерживают диапазон сигнальных напряжений от 2,7 В до 3,6. Также в современных высокоскоростных стандартах используется более низкое напряжение.
SD-Карты могут быть разного форм фактора: SD, MicroSD, MiniSD.
Иметь разную адресацию, побайтовую и LBA.
Разные классы скорости и производительности.
Также существуют различные слоты для SD/microSD карт: с механическим контактом для определения наличия карты и без, подпружиненные и нет.
В статье рассматривается работа с самой распространённой картой и которая была у меня — microSD SDHC с классом скорости Class 10. В качестве слота использовался тот, что на моей отладочной плате.
Настройка STM32CubeMX
Настроить и сгенерировать шаблонный код для работы с SD-картой удобно в STM32CubeMX.
Не все микроконтроллеры STM32 поддерживают аппаратный SDIO. Читая спецификацию конкретного микроконтроллера, можно узнать, поддерживает он или нет. В более современных версиях микроконтроллеров она называется SDMMC.
Запускаем STM32CubeMX.
Выбираем наш микроконтроллер (STM32H750VBT6). Нажимаем
Start Project.Соглашаемся в появившемся окне с вопросом о предконфигурировании MPU.
-
Включаем SDMMC1, выбрав в выпадающем списке
ModeзначениеSD 4 bits Wide busилиSD 1 bit. Это значение определяет, сколько линий интерфейса будет использоваться для обмена данными. Для моей платы можно выбрать любое из этих двух. При использовании одной линии обмен будет медленнее.
-
На вкладке
Parameter SettingsвключаемSDMMC hardware flow controlи устанавливаем значение дляSDMMC Clock Divide Factorравным2. Если это не сделать, карта на моей плате не инициализировалась.
-
Подключаем FatFS. Ставим флажок
SD Card
-
На вкладке
Platform Settingsнам предлагается указать пин, который будет использоваться для детектирования того, что SD-карта вставлена в слот:
У меня на отладочной плате была не закорочена перемычка
SW2, отвечающая за подключение пина слота SD-карты к микроконтроллеру. Поэтому я соединил контакты перемычки припоем.-
Кликаем на пине PD4 и выбираем GPIO_Input.

-
Для FatFs на вкладке
Advanced SettingsвыбираемPD4дляDetect_SDIO.
-
Настраиваем диагностический вывод (я использовал Semihosting, так как он в режиме отладки будет работать везде). Выбираем
Debug. Для поляDebugвыбираемSerial Wire.
-
Переходим на вкладку
Clock Configurationи соглашаемся на разрешения конфликтов для тактовых частот.
-
На вкладке
Project Managerвводим имя проекта в полеProject Nameи выбираемCMakeв полеToolchain/IDE
-
На вкладке
Code Generatorустанавливаем флажокGenerate periferal initialization as a pair of '.c/.h' per peripheral
Генерируем проект.
Структура проекта следующая:
. ├── cmake │ ├── gcc-arm-none-eabi.cmake │ ├── starm-clang.cmake │ └── stm32cubemx │ └── CMakeLists.txt ├── CMakeLists.txt ├── CMakePresets.json ├── Core │ ├── Inc │ │ ├── gpio.h │ │ ├── main.h │ │ ├── sdmmc.h │ │ ├── stm32h7xx_hal_conf.h │ │ └── stm32h7xx_it.h │ └── Src │ ├── gpio.c │ ├── main.c │ ├── sdmmc.c │ ├── stm32h7xx_hal_msp.c │ ├── stm32h7xx_it.c │ ├── syscalls.c │ ├── sysmem.c │ └── system_stm32h7xx.c ├── Drivers │ ├── CMSIS │ │ ├── Core │ │ ├── Core_A │ │ ├── DAP │ │ ├── Device │ │ ├── DSP │ │ ├── Include │ │ ├── LICENSE.txt │ │ ├── NN │ │ ├── RTOS │ │ ├── RTOS2 │ │ └── st_readme.txt │ └── STM32H7xx_HAL_Driver │ ├── Inc │ ├── LICENSE.txt │ └── Src ├── FATFS │ ├── App │ │ ├── fatfs.c │ │ └── fatfs.h │ └── Target │ ├── bsp_driver_sd.c │ ├── bsp_driver_sd.h │ ├── fatfs_platform.c │ ├── fatfs_platform.h │ ├── ffconf.h │ ├── sd_diskio.c │ └── sd_diskio.h ├── Middlewares │ └── Third_Party │ └── FatFs ├── startup_stm32h750xx.s ├── STM32H750xx_FLASH.ld ├── stm32-sdio-sample.ioc └── User ├── utils.c └── utils.h
В директории Core находится основной сгенерированный код. Drivers содержит CMSIS и HAL библиотеки для конкретных микроконтроллеров STM32, в нашем случае STM32H7xx. В директории FATFS находится сгенерированный код, который позволяет работать FatFs с SD-картой. Сама библиотека FatFs находится здесь Midleware/Third_Party/FatFs.
Файл stm32-sdio-sample.ioc содержит настройки, на основании которых STM32CubeMX генерирует код проекта на C.
Код, который я написал сам, я помещаю в директорию User. Чтобы этот код был виден в проекте, директорию и .c файлы необходимо подключить в файле 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 )
Код начальной загрузки (startup_stm32h750xx.s), скрипт для компоновщика (STM32H750xx_FLASH.ld), наверное, интересны для изучения, но не в этой статье.
Работу с SD-картой можно сделать, если настроить DMA. Но из-за того, что я использовал STMH7 и STM32CubeMX генерирует немного некорректный код, в этой статье DMA рассматривать не будем.
Сгенерированный проект нужно импортировать в STM32CubeIDE и настроить semihosting.
Настройка semihosting
Настраиваем semihosting, который позволит нам увидеть вывод функции printf в консоли отладчика.
-
В файле
main.cдобавляем:/* USER CODE BEGIN PFP */ extern void initialise_monitor_handles(void); /* USER CODE END PFP */ int main(void) { /* USER CODE BEGIN 1 */ // Инициализация semihosting должна быть вызвана до любого ввода-вывода initialise_monitor_handles(); /* USER CODE END 1 */ -
В файле
CMakeLists.txt:Исключаем файл
Core/Src/syscalls.cиз компиляции:set_source_files_properties(Core/Src/syscalls.c PROPERTIES HEADER_FILE_ONLY TRUE)Настраиваем компоновщик:
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE --specs=rdimon.specs )# Add linked libraries target_link_libraries(${CMAKE_PROJECT_NAME} stm32cubemx # Add user defined libraries rdimon ) В конфигурации отладки для проекта в STM32CubeIDE не забываем включить semihosting.
Работа с FatFS
Бо́льшую часть работы по инициализации SD-карты выполняет аппаратный блок SDMMC микроконтроллера STM32. A STM32CubeMX генерирует немного кода, который инициализирует блок SDMMC.
void MX_SDMMC1_SD_Init(void) { /* USER CODE BEGIN SDMMC1_Init 0 */ /* USER CODE END SDMMC1_Init 0 */ /* USER CODE BEGIN SDMMC1_Init 1 */ /* USER CODE END SDMMC1_Init 1 */ hsd1.Instance = SDMMC1; hsd1.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING; hsd1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_ENABLE; hsd1.Init.BusWide = SDMMC_BUS_WIDE_4B; hsd1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE; hsd1.Init.ClockDiv = 2; if (HAL_SD_Init(&hsd1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN SDMMC1_Init 2 */ /* USER CODE END SDMMC1_Init 2 */ }
SD-карта может содержать разделы с файловыми системами, а может быть без таблицы разделов (супердискета). FatFs поддерживает оба случая. Библиотеку FatFS можно конфигурировать, например, включить поддержку длинных имён. Перед тем как работать с файлами, необходимо смонтировать файловую систему при помощи функции f_mount().
Чтобы проверить, что мы всё верно настроили для работы с SD-карты, напишем функцию Test_SD_Card, которая выводит содержимое карты, проверяет есть ли файл в корне с именем “hello.txt”, если нет, то создаёт файл с текстом “Hello World”, закрывает его, потом открывает его и печатает содержимое.
Функция показывает использование основных структур данных и функции, которые вам понадобятся при работе с файлами на SD-карте.
Структуры:
FATFS,
FIL,
DIR,
FILINFO,
FRESULT,
Функции:
f_mount(),
f_opendir(),
f_closedir(),
f_open(),
f_close(),
f_read(),
f_write(),
f_stat()
Поместим её определение в файл User/utils.с, а определение - в User/utils.h
Содержимое User/utils.с:
#include <stdio.h> #include <string.h> #include "ff.h" // Main FatFs header file FATFS fs; // File system object 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:
#ifndef UTILS_H #define UTILS_H extern void Test_SD_Card(void); #endif
Запустив прошивку на отладку со вставленной отформатированной и пустой SD-картой, в консоли отладчика мы увидим:
=== Starting SD Card Test === File system successfully mounted. --- Root Directory Contents: --- --- Checking for file: hello.txt --- File hello.txt not found. Creating... Successfully wrote 11 bytes. --- Reading file: hello.txt --- Read from file: "Hello World" (Total: 11 bytes) === Test finished, card unmounted ===
Заключение
Почти все функции библиотеки FatFs являются интуитивно понятными.
Иногда из-за настроек, функция f_mount() и другие могут возвращать ошибку. В большинстве случаев помогает снижение частоты клока для SD-карты.
Также ошибки работы с SD-картой возникают из-за отсутствующих или низкого номинала подтягивающих резисторов, длинных проводников (если слот у вас не распаян на плате).
Я намеренно не усложнял статью использованием DMA и RTOS, чтобы было ощущение простоты реализации работы с SD-картой. Теперь у вас есть базис, позволяющий, кроме всего прочего, понять и сравнить как реализована в других микроконтроллерах, таких как ESP32 и RP2040.
Низкоуровневые осциллограммы иногда могут дать ответ о причинах сбоев в работе SD-карты, но если у вас нет осциллографа или логического анализатора, проверьте тактовую частоту SD-карты, длину проводников и наличие подтягивающих резисторов. В большинстве случаев это причина некорректной работы.
Я использовал semihosting для вывода отладочной информации, что требует запуска прошивки в режиме отладки, вы же можете использовать другие способы отладочной информации, изложенные в моей предыдущей статье (RTT, SWO, UART).
© 2026 ООО «МТ ФИНАНС»
Комментарии (7)

aabzel
07.10.2026 17:09Какой вам удалось достигнуть максимальной битовой скорости на запись и чтение в fatfs файл?

artyomsoft Автор
07.10.2026 17:09Как я понимаю, многоге зависит от самой карты памяти , установленного делителя частоты, и используется dma или нет. Я не ставил целью изучать скорость чтения записи в этой статье.

artyomsoft Автор
07.10.2026 17:09А для fatfs еще зависит от того, как работают с файлом, его размера и включен fast seek или нет.

aabzel
07.10.2026 17:09Замечал особенность. На около предельных битовых скоростях sdio fatfs write может отработать успешно, однако в файле будут записаны не те байты.
MaFrance351
Понравилось.
Когда-то прошёл через это же самое, начиная эксперименты с платой STM32-Discovery:
А про это пост будет?
artyomsoft Автор
Если будет спрос, будет и предложение)