Fat32 на stm32

Использование SD-карт в малых встраиваемых системах. Часть 2

Рассматривается библиотека FatFs, позволяющая работать с файловой системой FAT на SD-карте

Для того чтобы записанные на SD-карту данные можно было прочесть на компьютере, они должны быть упорядочены в соответствии со структурой файловой системы FAT (File Allocation Table или таблица размещения файлов). Для облегчения труда разработчиков малых встраиваемых систем при реализации таблицы FAT программистом под псевдонимом ChaN была написана библиотека FatFs [1], которая быстро приобрела популярность благодаря тому, что ее функциональность позволяет выполнять действия, характерные для серьезных операционных систем при работе с файлами и папками. На данный момент доступна версия R0.10, которая вышла в середине октября 2013 года.

Эта библиотека написана на ANSI C и состоит из нескольких файлов. Она представляет собой связующее программное обеспечение (middleware) между приложением и аппаратным интерфейсным модулем (Рисунок 3). Это значит, что библиотека не зависит от аппаратной части и может быть использована с любым микроконтроллером. Портирование, то есть написание кода, связывающего аппаратную часть с FatFs, должен выполнить сам разработчик встраиваемой системы.

Рисунок 3. Место библиотеки FatFs в программной структуре малой встраиваемой системы.

В библиотеку входят следующие файлы:

  • ff.c и ff.h – являются основой FatFs, поскольку определяют функции, необходимые для работы с файловой системой.
  • diskio.c и diskio.h – отвечают за низкоуровневый интерфейс, эти файлы должны быть изменены разработчиком в соответствии с используемым способом связи с носителем.
  • integer.h – в этом файле можно задать используемые в библиотеке ключевые слова, определяющие тип данных, например, typedef int INT. Следует учитывать возможности этого файла при переходе с 8 разрядной системы на 32 разрядную и наоборот.
  • ffconf.h – здесь определяются настройки библиотеки, например, #define _FS_READONLY 0 разрешает чтение и запись на носитель, при выборе 1 запись была бы запрещена.

На Рисунке 4 показана взаимосвязь файлов библиотеки FatFs с приложением и отвечающими за работу модуля SDIO файлами, описанными в предыдущей части.

Рисунок 4. Взаимосвязь файлов.

Для того чтобы модуль SDIO понимал команды FatFs, нужно определить содержимое функций файла diskio.c. Первым делом организуется функция disk_initialize(drv). Она инициализирует физический диск и подготавливает его к чтению и записи данных. Аргумент drv определяет номер диска. В этой функции обязательно должна вызываться функция SD_Init(), которая непосредственно принимает на себя обязанности по инициализации SD-карты. Также следует учитывать, что функция disk_initialize не должна вызываться из приложения, поскольку она является служебной функцией в составе файла ff.c. Разработчик FatFs предупреждает, что в противном случае файловая структура носителя может быть испорчена. Вместо этого в приложении для монтирования диска нужно вызывать функцию f_mount, содержащую в себе disk_initialize. Функция f_mount будет рассмотрена ниже.

Следующей необходимой для правильной работы библиотеки функцией является disk_status(drv). Она возвращает текущее состояние диска. В ней обязателен вызов функции SD_GetCardInfo, передающей информацию о SD-карте. При получении от SD_GetCardInfo флага SD_OK функция disk_status должна возвращать 0, в противном случае она должна вернуть флаг STA_NOINIT, это будет являться свидетельством того, что диск не был проинициализирован.

Для чтения секторов имеется функция disk_read, которая в качестве аргументов принимает четыре параметра: drv (номер диска), *buff (указатель на буфер чтения данных), sector (номер начального сектора), count (количество читаемых секторов). В этой функции нужно организовать цикл, в котором функция SD_ReadBlock будет считывать блоки данных в указанном диапазоне (от sector до count). Чтобы прочитать данные, нужно в функции disk_write, принимающей в качестве аргументов те же четыре параметра, что и disk_read, организовать подобный цикл, но уже с функцией SD_WriteBlock, позволяющей считывать информацию с SD-карты. Обе функции (disk_read и disk_write) обязательно должны предусматривать возвращение флага RES_OK в случае успешной операции, либо флага RES_ERROR при возникновении аппаратной ошибки чтения/записи. Также библиотека FatFs предусматривает возврат от этих функций флага RES_PARERR в случае приема неверного параметра и флага RES_NOTRDY, если диск не был инициализирован.

Также для организации дополнительной функциональности, не включающей в себя операции чтения и записи, существует функция disk_ioctl. В качестве аргументов она принимает drv (номер привода), ctrl (команду управления) и *buff (указатель на буфер данных). Функция возвращает те же флаги, что и disk_read или disk_write. Аргумент ctrl может принимать следующие значения: CTRL_SYNC (позволяет завершить операции, ожидающие окончания процесса записи), GET_SECTOR_SIZE (возвращает размер сектора привода в переменную, на которую указывает buff), GET_SECTOR_COUNT (возвращает количество доступных секторов в переменную, на которую указывает buff), GET_BLOCK_SIZE (возвращает размер блока для очистки в переменную, на которую указывает buff), CTRL_ERASE_SECTOR (очищает область памяти, определяемую массивом, первым элементом которого является начальный адрес этой области памяти, а последним элементом – конечный; на сам массив должен указывать buff).

Наконец, в файле diskio.c нужно организовать работу еще двух зависящих от времени функций – disk_timerproc и get_fattime. Первая обеспечивает таймауты для надежного функционирования библиотеки. Ее нужно вызывать каждые 10 мс. Вторая возвращает текущее время выполнения определенной операции. Для ее правильной работы нужно включить и настроить часы реального времени. Если это не нужно, то можно организовать возврат какого-либо определенного значения или нуля. Подробный пример правильной инициализации всех вышеприведенных функций можно найти в [2]. Данная программа предназначена для микроконтроллеров STM32F407xxx/42xxxx/43xxxx и основана на работе модуля SDIO и драйвера stm324xg_eval_sdio_sd.

Теперь перейдем непосредственно к рассмотрению работы библиотеки FatFs в рамках приложения. Как уже было сказано выше, для монтирования и регистрации диска используется функция f_mount с тремя аргументами fatfs, path и opt. Аргумент fatfs является указателем на объект файловой системы, который должен быть зарегистрирован, path указывает на строку, которая в случае единственного привода должна быть пустой, opt определяет опцию инициализации и может принимать два значения: 0 (не монтировать привод, он будет смонтирован позже) и 1 (смонтировать привод сейчас, чтобы проверить его доступность). Функции библиотеки FatFs могут возвращать флаги, перечисленные в Таблице 2. f_mount возвращает FR_OK, FR_INVALID_DRIVE, FR_DISK_ERR, FR_NOT_READY и FR_NO_FILESYSTEM.

Fat32 на stm32

Возможности FatFS

  • Поддержка файловой системы, совместимой с FAT Windows.
  • Не зависит от платформы. Легко портируется на любую архитектуру.
  • Очень малые затраты на код и рабочую область.
  • Многочисленные опции конфигурации:
    • Поддержка нескольких томов (Multiple volumes) — физических дисков и их разделов.
    • Поддержка нескольких кодовых страниц ANSI/OEM, включая DBCS.
    • Поддержка длинных имен (Long file name, LFN) в кодировке ANSI/OEM или Unicode.
    • Поддержка со стороны операционных систем реального времени (RTOS, например FreeRTOS) и популярных библиотек (LUFA) .
    • Поддержка нескольких размеров сектора.
    • Поддержка Read-only (только чтение), минимизация API, настройка буфера I/O и т. д. .
  • Низкоуровневый код доступа к карте поддерживает как карты стандарта SD, так и SDHC (причем одновременно). Правда, это не совсем относится к библиотеке FatFS, так как уровни доступа к носителю памяти разделены, но все равно упомянуть о поддержке SDHC необходимо, так как это очень полезная и востребованная возможность.

Интерфейс приложения (Application Interface)

Модуль FatFs предоставляет набор функций для приложения (API). Другими словами, в этом списке показано, как через модуль FatFs можно получить доступ к томам FAT.

  • f_mount — регистрация/дерегистрация рабочей области
  • f_open — открытие или создание нового файла (Open/Create)
  • f_close — закрытие файла (Close)
  • f_read — чтение файла (Read)
  • f_write — запись в файл (Write)
  • f_lseek — перемещение по файлу позиции чтения/записи, увеличение размера файла (Expand file size)
  • f_truncate — обрезка файла (Truncate file size)
  • f_sync — сброс кэшированных данных на физический носитель (Flush cached data)
  • f_opendir — открыть директорию/папку (Open a directory)
  • f_readdir — прочитать информацию об объекте директории
  • f_getfree — получить количество свободных кластеров
  • f_stat — получить информацию о состоянии файла (Get file status)
  • f_mkdir — создать директорию/папку (Create a directory)
  • f_unlink — удалить файл или директорию
  • f_chmod — поменять атрибуты
  • f_utime — поменять время/дату (Change timestamp)
  • f_rename — переименовать/переместить файл или директорию (Rename/Move a file or directory)
  • f_chdir — поменять текущую директорию (Change current directory)
  • f_chdrive — поменять текущий диск (Change current drive)
  • f_getcwd — запросить текущую директорию
  • f_forward — перенаправить данные файла напрямую в поток
  • f_mkfs — создать файловую систему на диске
  • f_fdisk — поделить физический диск на разделы (логические диски)
  • f_gets — прочитать строку
  • f_putc — записать символ
  • f_puts — записать строку
  • f_printf — записать форматированную строку
  • f_tell — получить текущее значение позиции чтения/записи файла
  • f_eof — проверить на окончание данных файла (т. е. дошла ли позиция чтения до конца файла EOF, end-of-file )
  • f_size — получение размера файла
  • f_error — проверка — были ли ошибки при работе с файлом

Низкоуровневый интерфейс ввода/вывода диска (Disk I/O Interface)

Поскольку модуль FatFs спроектирован так, что полностью отделен от ввода/вывода на физический носитель, то для работы он нуждается в нескольких функциях для доступа к физической памяти, где хранятся данные файловой системы FAT (physical media). Если разрешена опция, связанная с операционной системой, то дополнительно нужны функции для работы с процессами/памятью (process/memory functions). Модуль низкоуровневого ввода/вывода не является частью модуля FatFs, и он должен быть предоставлен пользователем (в этом собственно и заключается основной процесс портирования на целевую систему). Примеры таких низкоуровневых модулей/драйверов можно найти в разделе Ссылки. Список функций низкоуровневого ввода/вывода:

  • disk_initialize — инициализация диска (disk drive)
  • disk_status — получение состояния диска (disk status)
  • disk_read — прочитать сектор (секторы)
  • disk_write — записать сектор (секторы)
  • disk_ioctl — управление опциями, зависящими от устройства
  • get_fattime — получение текущего времени

Ссылки

Модуль FatFs является свободным программным обеспечением (free software), открытым для целей образования, исследования и разработки. Вы можете его использовать, модифицировать и/или распространять для персональных проектов или коммерческих продуктов под Вашей личной ответственностью и без каких-либо ограничений. Для подробностей обращайтесь к указаниям по применению (application note).

FAT32 на STM32

В данной статье хочу поделиться своим опытом ковыряния файловой системы FAT32, на которые я потратил почти месяц свободного от работы времени.

В один прекрасный день, начитавшись в сети статей с громкими заявлениями вроде «STM32 — это просто», я решил попробовать освоить данный камень. Небольшой опыт программирования AVR у меня был, поэтому я приобрёл десяток stm32F103c8t6 и начал с ними разбираться. Поморгал светодиодом, потом подключил дисплей HD44780, написал на него драйвер. В общем, стало скучно, и я решил сделать что-то более сложное. Мой выбор пал на mp3 плеер, тем более, я давно хотел поработать с кодеками от VLSI. И пока я ждал заказанные vs1053, закипела работа над программой. Начал я с файловой системы. Выбор пал на FAT32, конечно, намного проще было взять готовый драйвер FatFs или, хотя бы разобраться с FAT16. Но! первый вариант отпал, потому что: 1 мне было очень сложно разбираться с чужими функциями, 2 для моей задачи мне не было понятно, как находить файлы с произвольным названием и расширением «mp3», 3 необходимость при чтении указывать количество байт чтения файла, а откуда мне знать сколько? И т.д. Второй вариант отпал по той причине, что FAT16 устарела, да и современную карту SDHC форматировать в FAT16 — глупо, если вообще возможно. А самое главное — хотелось поработать головой и разобраться.

Итак, приступим. Сначала нам надо разобраться со структурой файловой системы. Не будем углубляться в подробности. Первое, что надо знать — весь массив данных на носителе разбит на сектора (стандарт — 512 байт), т.е. минимальное количество информации, которое мы можем прочитать/записать. А сектора в своё время объединены в кластеры (кластерами хранится массив данных файлов). Сама FAT32 состоит из двух основных частей — системной области и области данных. Системная часть состоит из загрузочного сектора, зарезервированной области и таблиц FAT, далее начинается область данных. Область данных начинается с корневого каталога (т.е. то что у нас храниться в корне носителя), а за ним идут массивы данных. Необходимо упомянуть, что корневой каталог может начаться не сразу после последней копии фат-таблицы (в том случае, если будут битые сектора).

Читайте также  Комбинированный регулятор мощности

Теперь по порядку. Загрузочный сектор (512 байт) хранит очень много нужной для нас информации. Для примера приведу скриншот дампа SDHC карты 8 Гб (сделан бесплатной версией программы WINHEX).

Я перечислю только то, что нам пригодится (подробнее можно найти в сети, я рассчитываю, что моя статья пригодится тем, кто уже владеет азами). Не забываем, что байты расположены в непривычном для понимания обратном порядке (от младшего к старшему, т.е., например, ячейки F2 03 00 00 означают число 0x000003F2).

  • 1 байт – количество секторов в кластере (смещение 0x0D байт)
  • 1 байт – количество таблиц FAT (обычно 2, смещение 0x0C)
  • 4 байта — размер таблицы FAT (смещение 0x24-0x27)
  • 2 байта — адрес начала первой таблицы FAT (0x0E-0x0F)
  • 4 байта – номер кластера корневого каталога (смещение 0x2C-0x2F)

Чем нам поможет данная информация? Самое главное — мы сможем найти адреса начала таблиц FAT, их объём, а так же адрес начала корневого каталога. И размер кластера — тоже очень важный параметр (на картинке в ячейке 0x0D — значение 40h — что составляет 64 в десятичной системе). Давайте разбираться по порядку. Для начала нам необходимо знать, где начинается корневой каталог — по сути, это основная наша задача. Из загрузочного сектора (ячейки 0x2C-0x2F) мы узнали, что корень нашего носителя хранится в кластере №2 — число 00000002h (кстати, область данных начинается со второго кластера, кластеры 0 и 1 не существуют) — это уже что-то. Значит, наш корневой каталог будет находится впритык за последней копией фат. Из ячеек 0x0E-0x0F мы узнаём адрес начала таблиц фат, из ячеек 0x24-0x27 их размер, а из ячейки 0x0C — их количество. Дело за малым — рассчитать объём в секторах фат таблиц и добавить его к начальному адресу первой таблицы:

Объём FAT = количество FAT*объём одной таблицы

Адрес кластера №2 = адрес начала FAT+объём FAT

Вроде бы всё просто. Рассчитываем все адреса и начинаем работу — но нет. Здесь возникает первый подводный камень. Загрузочный сектор находится в нулевом логическом секторе. А, как оказалось, логический сектор — это далеко не физический сектор. И по нулевому адресу лежит совсем не то. Так вот, в нулевом физическом секторе находится главная загрузочная запись, она же MBR. В ней, кроме всего, хранятся записи о разделах диска (всего четыре записи). Так как у наc SD карта — то скорее всего там будет один раздел, адрес его начала (загрузочного сектора) находится в ячейках по адресу 0x1C6-0x1C7 (я это понял опытным путём). Теперь нам осталось к нашему адресу начала таблицы фат добавить адрес начала нашего загрузочного сектора (физический его адрес):

Физический адрес начала FAT = адрес начала FAT + физический адрес загрузочного сектора

Напишем программу инициализации файловой системы:

Объявим глобальные переменные

С адресами и объёмами определились. Теперь разберёмся немного с таблицами FAT. Все копии таблиц — идентичны (на тот случай, если одна будет повреждена, можно перейти к другой). Для примера приведу скриншот таблицы:

Вся таблица состоит из четырёхбайтных записей о каждом кластере носителя. Все записи идут по порядку. Я уже упомянул ранее, что кластеров №0 и №1 физически нет, но записи о них в таблице есть (0FFFFFF8h). Немного о значениях записей:

0FFFFFFFh — означает, что кластер данного файла последний

00000003h-0FFFFFF7h — номер кластера, в котором находится продолжение файла

0FFFFFF8h — кластер повреждён

Для чего она вообще нужна? Дело в том, что файл может быть раскидан в области данных не в подряд расположенных кластерах, а разбросан в разных местах. Поэтому, чтобы прочитать файл — необходимо считать первый кластер файла, затем перейти в запись об этом кластере в фат и узнать либо номер следующего кластера, либо, что это был последний кластер файла. Подробнее об этом расскажу далее.

И наконец-то самое интересное — корневой каталог. Он представляет собой набор 32 байтных записей о файлах, находящихся в корне носителя (в том числе и о подкаталогах, то есть папках, которые представляют из себя те же файлы, только помечены специальной меткой вместо расширения файла, структуру папок разбирать не будем). В каждой записи о файле есть: имя файла (первые 8 ячеек), расширение (следующие 3 ячейки), потом ещё куча ячеек с датами, временем создания файла и др, и самое главное — номер первого кластера файла (смещение от начала 32 байтной записи 0x14, 0x15, 0x1A, 0x1B). Но не всё так просто и очевидно. Как я писал выше, имя файла занимает только 8 байт (это стандарт короткого имени файла 8.3 — восемь символов имени и 3 символа расширения файла), а у нас файлы могут быть с ооочень длинными именами, поэтому в FAT32 используются структуры LFN, это тоже 32 байтные записи с продолжением имени (они должны предшествовать записи о самом файле). С LFN я сам не разобрался, да мне для моего проекта и не надо было. Чтобы не быть голословным — скриншот корневого каталога (на карте один MP3 файл):

Как видим, не все записи в корне несут информацию о файлах, в первой — у нас название флешки, далее скрытые системные папки, потом LFN структуры и только в конце — наш mp3 файл. Кто-то скажет, что выше тоже есть надпись mp3! Но, давайте вспомним структуру имени в формате 8.3. Второе, что бросается в глаза — это кракозябры в названии файла. Дело в том, что имена — в кодировке ASCII, а мой файл с русскими буквами, поэтому имя закодировано в LFN в другой кодировке (если я не ошибаюсь — в юникод). И ещё один нюанс: если имя файла начинается с E5h, значит этот файл был удалён, хотя в массиве данных он ещё есть (так восстанавливают удалённые данные). Давайте посмотрим, в каком кластере начинается наш файл — это кластер №6 (ячейка 4000DAh — т.е. смещение от начала 32 байтной записи 0x1A — это младший байт номера первого кластера, в остальных трёх старших байтах — нули). Теперь давайте вернёмся к скриншоту с таблицей фат и посмотрим, что у нас находится в шестом кластере. А там у нас — число 7, то есть продолжение файла в кластере №7 и так далее, пока не будет 0FFFFFFFh.

В общем и целом с описанием основных структур всё. Приступим к практике. Пример чтения файла я покажу на своём проекте (mp3 плеера). Там нет функций открытия/закрытия файла. Выбор и чтение происходит в основном цикле программы.

Выше я писал про функцию определения размера файла, ниже я приведу её текст и ещё некоторых служебных функций.

PS: я не описывал реализацию работы с SD картой на низком уровне (необходимы функции инициализации и чтения блока). Для работы драйвера необходимо три массива в 512 байт (в один читается загрузочный сектор, второй массив для чтения корневого каталога и третий для чтения данных файла), плюс несколько переменных (некоторые по 32 байта), следовательно нужно окало 1.6 — 1.8 килобайта ОЗУ микроконтроллера.

Введение

В этой части мы рассмотрим библиотеку для работы с дисками, имеющими файловую систему FAT. Это всем известная библиотека Elm Chan`a — Petit FatFs . Она представляет собой облегченную версия библиотеки FatFs и предназначена для микроконтроллеров с небольшим объемом оперативной памяти. Конечно, функционал Petit FatFs сильно ограничен, но имеет смысл начать знакомство с нее, потому что в ней проще разобраться.

В этой части изложена информация справочного характера, которую я почерпнул из документации на библиотеку, а в следующей части будет уже практический материал.

Состав библиотеки

Библиотека Petit FatFs состоит из 5 файлов. Изначально она не заточена под какой-т о конкретный носитель памяти с файловой системой FAT и может использоваться с любым из них. Для этого к библиотеке нужно добавить реализацию трех низкоуровневых функций для работы с диском — функцию инициализации, записи и чтения.

integer.h — здесь объявлены целочисленные типы данных, используемые в библиотеке

diskio.h — описаны прототипы низкоуровневых функций для работы с диском и статусные коды, возвращаемые функциями.

diskio.c — файл реализации низкоуровневых функций для работы с диском. Изначально содержит «пустышки».

pff.h — здесь задается конфигурация библиотеки, объявлены типы данных и прототипы функций для взаимодействия с файловой системой диска

pff.c — файл реализации функций для взаимодействия с файловой системой диска

Низкоуровневая работа с SD картой

Как вы понимаете из описания выше, для работы с SD картой библиотеку Petit FatFs нужно «допиливать». А именно, писать реализацию низкоуровневых функций в файле diskio.c. На сайте автора есть архив с примерами использования библиотеки Petit FatFs с различными дисками и микроконтроллерами. Есть там и пример использования SD карт с AVR. В принципе, можно не разбираться с низкоуровневой работой с SD картой, а взять уже готовый код, что я и сделаю в следующий части.

Подключение библиотеки и настройка

1. Копируем файлы библиотеки Petit FatFs в папку проекта

2. Подключаем сишные файлы (diskio.c, pff.c)к проекту внутри среды разработки

3. Задаем конфигурацию библиотеки Petit FatFs в файле pff.h

4. Включаем (инклюдим) заголовочные файлы библиотеки (integer.h, pff.h и diskio.h) в сишный файл, где будут использоваться ее функции.

5. Реализуем низкоуровневые функции для работы с диском или подставляем вместо diskio.c какой-нибудь «готовый» файл, например из примеров Elm Chan`a.

Ну а дальше используем функции библиотеки.

Функции библиотеки Petit FatFs

FRESULT pf_mount (FATFS* fs) — смонтировать/демонтировать диск. Эта функция должна вызываться перед началом работы с диском. Она получает данные о структуре файловой системы диска и позволяет продолжить работу с его содержимым. Также функция демонтирует или виртуально отключает диск, если ее вызывать с нулевым указателем. Когда диск демонтирован, все остальные функции библиотеки возвращают FR_NOT_ENABLED (диск не смонтирован). Функция pf_mount() доступна всегда.

FATFS *fs — указатель на объект типа FATFS. Это объект библиотеки, в котором описана структура файловой системы. На него можно взглянуть в файле pff.h. Ну а по сути, это просто переменная определенного типа, которая должна быть предварительно объявлена.

FR_OK — успешное завершение функции
FR_NOT_READY — не удалось инициализировать диск
FR_DISK_ERR — ошибка диска
FR_NO_FILESYSTEM — на диске нет правильного FAT раздела

FRESULT pf_open (const char* path) — открывает существующий файл. Функция должна вызываться перед тем, как выполняется любая работа с файлом — чтение, запись, изменение указателя. С открытым файлом можно работать до тех пор, пока не будет открыт другой файл. Функция pf_open() доступна всегда.

const char *path — указатель на строку, показывающую путь к файлу. Строка должна заканчиваться нулевым символом. Путь нужно указывать полностью, разделяя подкаталоги символом слэша.

Читайте также  Использование строительного фена при ремонте

«test.txt» — путь к файлу test.txt, лежащему в корне диска
«Folder/code.txt» — путь к файлу сode.txt, лежащему в папке Folder

FR_OK — успешное завершение функции
FR_NO_FILE — не удалось найти файл
FR_NO_PATH — не удалось найти путь
FR_DISK_ERR — ошибка диска
FR_NOT_ENABLED — не смонтирован диск

FRESULT pf_read(void* buff, WORD btr, WORD* br) — прочитать данные из файла. Функция читает заданное количество байт из открытого файла и записывает их в буфер пользователя. Также она подсчитывает количество прочитанных байт. Если заданное количество байт и прочитанное не совпадают, значит при чтении был достигнут конец файла. Функция доступна когда параметр _USE_READ равен 1.

void* buff — указатель на буфер, в котором будут сохраняться прочитанные данные. Если передать нулевой указатель, то прочитанные данные будут перенаправлены в другой поток, некую функцию, которую нужно описывать самостоятельно.

WORD btr — количество байт, которые нужно прочитать.

WORD* br — указатель на переменную, в которой функция pf_open сохранит количество прочитанных байтов.

FR_OK — успешное завершение функции
FR_DISK_ERR — ошибка диска
FR_NOT_OPENED — файл не был открыт
FR_NOT_ENABLED — не смонтирован диск

FRESULT pf_write(const void* buff, WORD btw, WORD* bw) — эта функция записывает данные в предварительно открытый файл. Она доступна только тогда, когда параметр _USE_WRITE равен 1. Из-за того, что библиотека Petit FatFs рассчитана на микроконтроллеры с маленьким объемом памяти, функцию записи имеет ряд ограничений:

— нельзя создать новый файл,
— нельзя увеличить размер файла,
— нельзя обновить временную метку файла,
— файловый атрибут «только для чтения» не может запретить запись,
— операцию записи можно запустить/остановить только на границе сектора диска.

Последний пункт, пожалуй, требует пояснения. SD карта разбита на сектора по 512 байт. Функция pf_write может выполнять запись только с начала какого-либо сектора. При этом, если записывается меньше 512 байт, остаток заполняется нулями. Например, мы порциями записываем 612 байтов полезной информации — первый сектор будет заполнен полностью, а во втором будет 100 байтов полезной информации и 412 нулевых байтов.

Функция pf_lseek(..), о которой рассказано ниже, позволяет перемещать указатель чтения/записи данных. При использовании функции записи указатель нужно устанавливать на начало сектора. Если установить указатель в середину, то он будет округлен к нижней границе сектора и запись все равно будет выполняться с его начала.

Таким образом, операция записи выполняется в следующей последовательности:

1. Устанавливаем указатель записи на какой-нибудь сектор.
2. Вызываем функцию записи.
3. Если записаны не все данные и конец файла не достигнут повторяем 2-й шаг.
4. Финализируем запись, вызвав функцию записи с нулевым указателем.

const void* buff — указатель на буфер содержащий данные для записи. Нулевое значение завершает текущую операцию записи.

WORD btw — количество байт, которые нужно записать в файл.

WORD* bw — указатель на 16-и разрядную переменную, в которой сохранится количество байт, которые удалось записать. По значению этой переменной можно определять, когда достигнут конец файла.

FR_OK — успешное завершение функции
FR_DISK_ERR — ошибка диска
FR_NOT_OPENED — файл не был открыт
FR_NOT_ENABLED — не смонтирован диск

FRESULT pf_lseek(DWORD offset) — смещает указатель чтения/записи открытого файла. Эта функцию используется чтобы указать с какого байта выполнять чтение, или с какого сектора диска выполнять запись. Можно выполнять абсолютное смещение указателя, передавая функции число, а можно выполнять смещение относительно текущей позиции, передавая значение указателя fs.fptr и величину смещения (смотри пример). Функция доступна, когда параметр _USE_LSEEK равен 1.

DWORD offset — количество байт, на которые нужно сместить указатель.

FR_OK — успешное завершение функции
FR_DISK_ERR — ошибка диска
FR_NOT_OPENED — файл не был открыт

FRESULT pf_opendir(DIR* dp, const char * path) — эта функцию открывает существующую директорию и инициализирует переменную типа DIR. В дальнейшем эта переменная может использоваться для получения списка файлов открытой директории. Функцию доступна, когда параметр _USE_DIR равен 1.

DIR *dp — указатель на переменную типа DIR. Она должна быть предварительно объявлена.

const char* path — указатель на строку-путь к директории. Строка должна заканчиваться нулевым символом. Путь нужно указывать полностью, разделяя подкаталоги символом слэша. Написание пути к директории подчиняется тем же правилам, что и написание пути к файлу (смотри функцию pf_open).

FR_OK — успешное завершение функции
FR_NO_PATH — не удалось найти путь
FR_NOT_READY — не удалось инициализировать диск
FR_DISK_ERR — ошибка диска
FR_NOT_ENABLED — не смонтирован диск

FRESULT pf_readdir(DIR* dp, FILINFO* fno) — эта функцию позволяет прочитать содержимое директории. Для этого ее нужно вызывать несколько раз, пока функция не возвратит нулевую строку в одном из членов переменной fno — fno.fname[]. Функция доступна, когда параметр _USE_DIR равен 1. Читаемая директория должна быть предварительно открыта с помощью функции pf_opendir(..).

DIR *dp — указатель на переменную типа DIR. Переменная должна быть предварительно объявлена.

FILINFO *fno — указатель на переменную типа FILINFO. Переменная должна быть предварительно объявлена.

FR_OK — успешное завершение функции
FR_DISK_ERR — ошибка диска
FR_NOT_OPENED — не открыта директория

Драйвер для MICROSD (SPI) + FAT32 для STM32

Собственно пишу драйвер для Micro SD карточек и файловую систему FAT32.

Тестируем драйвер MICRO_SD
Реализованы:
SD, SDSC, SDHC, MMC карты

Чтение и запись сектора.
Мульти режим не реализован пока.

Описалово в MICROSD.h
По умолчанию настроено для STM32F051RB

Помощь в написании контрольных, курсовых и дипломных работ здесь.

Драйвер (не VCP) для встроенного USB STM32
Всем привет ! На сайте STM нашел только VCP драйвер ( STSW-STM32102 STM32 Virtual COM Port Dryver.

Проблема microSD spi
Здравствуйте, столкнулся с такой проблемой: имеется устройство на мк stm32f103, работающее с.

SPI или SDIO и microSD
Доброго времени, гуру. Подскажите, что не так, плиз. Купил плату для microSD, чтобы по SPI.

Stm32f4 + spi + microsd еще разок D:
Вежливо попросив исходные файлы связывающие уже существующий Fatfs-SPI-MSD, я запнулся и в.

Все отбой. Проблема решена. Выяснилось что начало логического диска находились на разных секторах на разных флешках.

Почему то на одном диске PBR начинается с 135 сектора, а на другом с 63 сектора? Как такое может быть?

PBR — это что? Если это boot sector логического диска, то надо всегда читать MBR (Master Boot Record, вот он всегда сидит в секторе 0), а оттуда уже вычитывайте/вычисляйте всё остальное ..

Почитайте любой древний мануал про структуры жёсткого диска для MSDOS. Они уже лет 30 не менялись.

А цифра 63 пошла от достаточно древних винтов середины-конца 90-х годов и связано это с особенностями ихними контроллеров тех лет. И особенностями BIOS тех же лет.
Сейчас уже и контроллеры другие, и BIOS-ы другие и SD-карта, строго говоря, это не винт. Поэтому никогда не равняйтесь на эту цифру, хоть она справедлива в 99% случаев и по сей день.

Все отбой. Проблема решена. Выяснилось что начало логического диска находились на разных секторах на разных флешках.

Почему то на одном диске PBR начинается с 135 сектора, а на другом с 63 сектора? Как такое может быть?

А эту проблему тоже решили (как)? Чтение физического сектора никак не связано с логической структурой диска. Где бы ни были расположены PBR, данные, и т.п., — для чтения произвольного сектора это не важно.

И другой вопрос, по поводу SD_v2. Вроде бы под эту спецификацию попадают только SDHC (ну или хотя бы только новых выпусков карты, достаточно большого объёма). Вы уверены, что карта 128 Мегабайт — это SD_v2 ?? Первый раз такое слышу.

Ну не знаю, может вы и правы. На тех картах и флэшках, что у меня, везде есть нoрмальный MBR .
Может это зависит ещё и от того, где карта отформатирована — в компе или в каком-то девайсе.

Но по-любому это всё надо закладывать в проверки, а не надеяться, что в секторе 63 будет сидеть boot-сектор.

Все отбой. Проблема решена. Выяснилось что начало логического диска находились на разных секторах на разных флешках.

Почему то на одном диске PBR начинается с 135 сектора, а на другом с 63 сектора? Как такое может быть?

А Вы что, не используете в своей программе формулы для расчёта номера сектора PBR, номера сектора FAT, номера сектора ROOT, номера сектора начала данных. 8-0
Вот так прямо «63» или «135» в программу забиваете константой?

Использую конечно все, просто драйвер пишу сейчас, у меня три флешки в наличии это 1G, 2G, 128mb.

Так вот такая ситуация произошла:

1G флешка распознается как 1 версия и работает всего от двух команд CMD0, CMD1.
2G SANDYSK я купил новую она 2 версии стандартной емкости.
и 128 тоже 2 версии стандартной емкости.

Так вот у меня 1G считывала с 63 сектора 0 сектор PBR, думал что на тех так же должно быть, не проверил сей факт из-за не знания. Позже я уже выяснил что оказывается разное начало сектора PBR. Вот и был глюк. Читалось че попало нули. думал не работает, а на самом деле работает просто адрес не тот указывал ))).

FAT у меня на компьютере реализован на Delphi отлаживал, там у меня всегда 0 сектор был как PBR. ))

MBR закладывать буду конечно, считывание логических дисков не делал поэтому столкнулся с такой бедой.

Такой вопрос еще: я форматировал под Wymdows MBR почему то не трогает, я туда писал каракули и она не трогает. Кто форматирует MBR? И почему адреса логических дисков разные?

stm32plus: FAT32 & FAT16 drivers

Posted on Mar 24, 2012 in stm32, stm32plus

The code presented in this article requires a minimum of version 2.0.0 of my stm32plus library.

Filesystem drivers

stm32plus comes with a full featured, object-oriented FAT32 and FAT16 driver written by myself from scratch based on a close adherence to the official Microsoft specification. The interfaces exposed by the file system driver are designed to dovetail seamlessly with other parts of the stm32plus library such as the streams library.

Features

The driver supports all common filesystem operations such as opening and closing files, reading and writing files, iterating directories, creating and deleting directories. Long filenames are supported natively for reading and writing.

The long filename generation algorithm is known to be patented by Microsoft. If you are planning to use these drivers in a commercial application then you can avoid the issue by ensuring that your filenames conform to the short 8.3 upper case format. The long filename generation algorithm is contained in a separate code module and by only using 8.3 names you will ensure that the contentious algorithm is never called by your code.

Filesystems on partitions as well as whole-device ‘super-floppy’ file systems are supported. The type of the file system (FAT32 or FAT16) is auto-detected and abstracted away from the caller via a high-level interface. Formatters for FAT16 and FAT32 file systems are included.

The file system driver is written to be independent of the underlying device. It only needs something that implements a block device interface on to the underlying hardware, such as an SDIO flash card or a memory IC.

Читайте также  Проводка от столба к дому

Documentation by example

The best way to understand how to use library code is to see some relevant examples with supporting documentation, so here I go.

Initialising the file system

Before you can get on the with the business of interacting with files you need to get an instance of a FileSystem object. For an SD card, you do it like this:

Let’s get a bit of an explanation about what’s going on up there. A FileSystem object can interact with anything that implements the BlockDevice interface. In this case we are going to use the SdioDmaSdCard implementation to read a file system on the SD card using the SDIO interface controlled by its DMA channel.

To speed things up we use a wrapper class, CachedBlockDevice. This wrapper provides caching for a regular block device. The cache is a write-through MRU cache. FAT file systems tend to hit the FAT table quite hard during operation so caching of a few blocks is highly recommended.

FileSystem::getInstance is the static method that analyses the structure on the block device and returns an implementation of the FileSystem abstract class to you. The actual type of the file system, FAT16 or FAT32 is abstracted away from you.

FileSystem implementations require an implementation of the TimeProvider abstract class to use when you create files and directories and write to files so that it can stamp the metadata with the correct time and date. The NullTimeProvider object is a dummy implementation that will use zero as the date and time values. I also provide an RtcTimeProvider that uses the STM32 real time clock to obtain time and date values.

Opening and closing files

Given a filesystem object it is easy to open and close files at will.

The pathname separator is the Unix-style, forward-slash. MSDOS-style backslashes are not supported. Long filenames are supported seamlessly and the pathnames are case-independent.

Opened files have the file pointer set to zero (the start) and are available for reading and writing.

Note that you cannot open directories using the openFile method.

Reading and writing files

Given an open file, you can read and write to it like this:

Reading from a file returns the actual amount of data read. If you hit the end of the file then this value will be less than amount requested.

You can seek to a random position within the file using the seek method. This method takes an offset and an enum value to offset from. Valid values are:

You can seek to a positive offset from the start, a negative offset from the end and a positive or negative offset from the current position.

Writing to a file takes place at the current file pointer. If the file pointer is not at the end of the file then the data at the current file pointer is overwritten. If the end-of-file is hit and there is still more data to be written then the file size is extended to accommodate the new data.

After a write operation the last-modified time of the file is updated using the current time supplied by the TimeProvider instance that you created when you initialised the FileSystem object (see above).

Both read and write operations move the file pointer to the position just after where they last read from or wrote to.

Reading and writing to and from files is more efficient if it can be done in multiples of 512 bytes starting from an offset that is also a multiple of 512 bytes.

Enumerating a directory

DirectoryIterator objects are provided for the purpose of enumerating the files and subdirectories in a directory. They are used like this.

getDirectoryIterator() is the method that will get you an iterator on to any directory in the file system. The example above iterates files in the root directory.

The iterator provides you a reference to the information about each file or directory that it encounters through a FileInformation object. This object allows you to see the usual metadata associated with a file: the name, attributes, various times and the 32-bit length.

getFilename() returns the filename and extension only. It does not include the full pathname prefix.

getAttributes() returns a bitmask of the following attributes:

The various time and date methods return a unix time_t. The underlying ugliness of the MSDOS dates and times are hidden from you.

Getting file information directly

You don’t have to create a directory iterator to get file or directory information if you know the full pathname of the object:

Unlike the FileInformation object recycled over and over again by the DirectoryIterator, in this case you own the object returned by getFileInformation and you must delete the object when you’re finished.

Create a new file

Creating a new file is straightforward:

createFile requires that the directory containing the file must already exist on the file system. The newly created file will have zero length and will be tagged with a creation time taken from your TimeProvider implementation.

createFile does not open the file for you so you’ll need to call openFile (see above) before you can start reading and writing to it.

Create a new directory

Much like createFile really:

In the sample above “/dir” must already exist. This method will not automatically create all parent directories for you.

Delete a file

Deleting a file is a basic operation:

If there are any open File objects on the file then this method will still succeed and those File objects are now in an indeterminate state and must never be used again. Basically, close your files before deleting them.

Delete a directory

Deleting a directory is a basic operation:

The subdirectory must be empty of all files and directories except the two “special” files named “.” and “..” that you find on FAT file systems.

The same warnings about objects open that reference the directory apply. Make sure you’ve finished accessing data in the directory before you delete it.

Get the device free space

We can obtain the free space on the device like this:

This method returns the number of free bytes on the filesystem split into two values, which when multiplied together give the total free bytes.

The reason for the split into two values is that FAT32 filesystems can exceed 4Gb in size. 4Gb is the maximum value that can be held in a 32-bit data type so we have to provide the total as two 32-bit values.

Formatting a device

I support formatting devices with either FAT16 or FAT32 file systems. If other formats become supported in the future then I will document them here.

Microsoft imposes minimum and maximum size limits on FAT16 and FAT32 file systems. My implementation respects these limits and will not allow the creation of file systems outside them. The limits are as follows:

File system Lower limit Upper limit
FAT16 4.1Mb 2Gb
FAT32 32.5Mb 127.5Gb

The following example shows how to format using FAT32. To use FAT16 replace Fat32FileSystemFormatter.h with Fat16FileSystemFormatter.h and Fat32FileSystemFormatter with Fat16FileSystemFormatter.

Fat32FileSystemFormatter is the object that does the formatting. It takes a block device, the first block number to format, the total blocks to include in the file system and the 8+3 MSDOS name for the new volume.

The constructor itself does the formatting operation and you can check for the status using the ErrorProvider object when it’s completed.

The above example shows the slightly more involved operation of formatting an SD card. Older SD cards are formatted as ‘super-floppy’ disks. They have no partition table and are literally just a huge sequence of blocks all devoted to the one filesystem on the device.

Newer SD cards are formatted like hard disks. They have a Master Boot Record that describes where up to four partitions are located and each partition contains a file system.

SD cards are normally created with one partition that fills all the available space so the above example shows how we first detect that an MBR is present and if it is then we format the first partition on the device. If it’s not present then the SD card is a ‘super floppy’ and we format all the blocks as one filesystem.

Streams

Streams are a common metaphor in programming languages used to present any data source that will be accessed serially such as a USART or an SPI channel. Files are random access so stm32plus provides a stream class that wraps a file and allows it to be used by any method that expects a stream, for example the drawBitmap method in the GraphicsLibrary class.

And of course there’s an output stream for operations that will write to the file serially starting at the current file pointer.

Streams deserve an article of their own, and in due course that’s exactly what they’ll get.

Error Handling

The general policy for error handling in stm32plus is that all methods return true to indicate success and false to indicate failure. If the method is a constructor and hence has no return value then the error provider getLast() method can be used to check for a non-zero status to detect an error.

The ErrorProvider class holds the state of the last error that occurred and there is a singleton named errorProvider declared in ErrorProvider.h. An error consists of a 16-bit provider code and a 16-bit provider-specific code. The provider codes are enumerated in the ErrorProvider class and the specific codes are enumerated in the provider class itself.

For example, in the stm32plus FAT file code there are the following lines in the read method:

Here we can see that in the event of a read attempt on a zero length file an error will be raised with a provider code of ErrorProvider::ERROR_PROVIDER_FILE and a specific code of File::E_END_OF_FILE.

In your code you might handle this situation as follows:

The possibilities are endless and your error handling strategy is your affair, stm32plus merely gives you the ability to tell what went wrong and where it went wrong.

Future

FAT12 support is very much a possibility. This would allow us to create file systems on small devices opening up the possibility of a SRAM disk, or small FLASH disk. Dealing with the 12-bit FAT entries looks like a pain in the rear but it’s not too hard.

A formatter for FAT16, and FAT12 if I write it. Support for RAM disks means that a formatter is essential.