Главная » Микроконтроллеры » FAT в микроконтроллерах STM32 – поддержка SD карт

FAT в микроконтроллерах STM32 – поддержка SD карт

В настоящее время SD (Secure Digital) карты являются самыми популярными и универсальными. Стандарт включает в себя карты емкостью до 4 ГБ и их разновидности, т. е. SDHC (Secure Digital High Capacity), до 32 ГБ. Стандарт SD-карт был разработан тремя компаниями: Matsushita, SanDisk и Toshiba.

Первые носители данных такого типа появились в конце 2000 года. Первоначально доступ к документации стандарта SD был довольно сложным, но ситуация изменилась с приходом 2006 года. Тогда стала доступна информация, в том числе и на интерфейсе SDIO, который позволил реализовать в микроконтроллерах аппаратные драйверы для SD-карты. Представители самой продвинутой группы систем из семейства STM32 имеют такой встроенный контроллер.

Все имеющиеся на рынке SD-карты поддерживают два стандарта связи: SDBus и SPI. Как обычно, родной интерфейс (SDBus) предлагает большие возможности и высокую скорость работы, но за счет увеличения сложности интерфейса. По этой причине связь с SD-картами также можно осуществить с  гораздо более простой в использовании шины SPI, но при несколько ограниченных возможностях.

Если только приложение не требует чрезвычайно высокой скорости передачи данных, то реализовывать поддержку интерфейса SDBus нет смысла, достаточно работать с шиной SPI. Представленный выше подход значительно упрощает этот вопрос, поскольку подавляющее большинство доступных микроконтроллеров (включая, конечно, семейство STM32F) оснащены аппаратным контроллером SPI.

Рис. 1. Подключение SD-карты через шину SPI к микроконтроллеру в наборе STM3210B-EVAL

Команды, поддерживаемые SD-картами

Каждая команда для отправки на SD-карту состоит из шести байтов. Первый байт всегда является кодом команды, следующие четыре байта являются его аргументом. В конце отправляется байт контрольной суммы CRC.

В то время как сумма CRC проверяется в рабочем режиме с помощью интерфейса SDBus, контрольная сумма игнорируется картой при обмене данными через шину SPI. Только при отправке команды CMD0, переключающей режим работы с SDBus на SPI, требуется байт CRC. Его не нужно рассчитывать каким-либо образом, поскольку он является фиксированным значением и равен 0x95.

В табл. 1 указаны несколько команд при работе с шиной SPI с описанием аргументов. В дополнение к стандартным командам CMD карты SD также могут использовать так называемые прикладные команды (ACMD). Для отправки команды необходимо сначала отправить команду CMD55, информирующую SD-карту о том, что следующей командой будет ACMD.

Табл. 1. Команды, поддерживаемые SD-картами в режиме SPIf

команда описание
CMD0 сбрасывает карту, позволяет включить режим шины SPI
CMD12 принудительное завершение передачи множества блоков данных
CMD16 настройка длины блока данных для чтения / записи
CMD17 чтение блока памяти длины, указанной CMD16
CMD24 запись блока памяти с длиной, указанной CMD16
CMD32 адрес первого удаляемого блока передается в аргументе
CMD33 адрес последнего удаляемого блока передается в аргументе
CMD38 удаляет блоки, обозначенные CMD32 и CMD33

Файловая система FAT

С точки зрения файловой системы каждый носитель данных (жесткий диск, карта памяти) разделен на сектора и кластеры. Сектор — это наименьшее количество байтов, которое может быть записано или прочитано. Обычно размер сектора составляет 512 байт. Файлы сохраняются в пронумерованных кластерах.

Размер кластера зависит от файловой системы и носителя. Каждый кластер полностью выделен для данного файла. Это означает, что даже если файл намного меньше размера кластера, он все равно занимает столько же, сколько один кластер на диске.

Ключевым элементом файловой системы FAT (File Allocation Table) является, в соответствии с ее именем, таблица размещения файлов. Файловая система FAT представлена ​​четырьмя разновидностями, во встроенных системах обычно используется две, в зависимости от размера носителя и требований к приложениям, это будет FAT16 или FAT32.

Рис. 2. Разделение носителя информации в системе FAT

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

К этой информации относятся, в частности: тип и размер разделов, размер сектора и количество секторов в кластере. За зарезервированной областью находятся таблицы размещения файлов, которые являются основным источником информации о данных, сохраняемых на носителе. Обычно, помимо основной таблицы размещения, есть и ее копия. Четвертая область — это корневой каталог, который создается автоматически при создании файловой системы. Последний, пятый сектор — это область данных.

Модуль FatFs

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

Файловая система FAT настолько хорошо документирована и в то же время относительно проста, что было создано много бесплатных инструментов, которые очень хорошо справляются с администрированием содержимого носителя данных с помощью файловой системы FAT. Как правило, открытый характер кода позволяет вносить необходимые изменения и исправления, которые могут понадобиться для стабильности работы устройства.

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

Рис. 3. Расположение модуля FatFs в программном проекте

Рис. 4. Структура файлов FatFs

Сам модуль FatFs написан на языке C. Файлы, необходимые для правильной работы FatFs, показаны на рис. 4 в виде дерева, скопированного из проекта с использованием файловой системы FAT. Теоретически для правильной работы модуля FatFs требуется наличие часов реального времени (RTC) во встроенной системе. Это требование можно легко обойти, введя фиксированные значения вместо даты и времени.

Реализация FatFs в микроконтроллерах STM32 — физический уровень

Пример проекта, используемый на веб-сайте модуля FatFs, был использован для разработки аппаратного уровня. Все функции, задачей которых является управление периферийными устройствами микроконтроллера, а также запись и чтение данных с карты памяти, были помещены в один файл sd_stm32.c.

Пример 1. Функция rcvr_spi () отвечает за получение данных от контроллера шины SPI.

static
void SELECT (void) // низкий уровень CS
{
GPIO_ResetBits (GPIOC, GPIO_Pin_12);
}
static
void DESELECT (void) // CS в высоком состоянии
{
GPIO_SetBits (GPIOC, GPIO_Pin_12);
}
static
void xmit_spi (BYTE Data) // Отправка байта в SD
{
// Отправить байт
while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData (SPI1, Data);
}
static
BYTE rcvr_spi (void) // Получение байта с SD
{
u8 Data = 0;
// отправка 0xFF
while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData (SPI1, 0xFF);
// Получить байт
while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_RXNE) == RESET);
Data = SPI_I2S_ReceiveData (SPI1);
return Data;
}

Четыре функции взаимодействуют напрямую с оборудованием. Получение данных от контроллера шины SPI — это функция rcvr_spi (), которая была представлена ​​вместе с тремя другими в примере 1. Отправка байтов через SPI на карту памяти обрабатывается функцией xmit_spi (). Аппаратный интерфейс также отвечает за управление сигналом выбора системы CS, за который отвечают функции SELECT () и DESELECT ().

Иногда, когда микроконтроллер запрашивает доступ к ресурсам карты памяти, может оказаться, что последний в данный момент занят выполнением других операций. Тогда важно иметь возможность проверить занятость карты. Для этого была написана функция wait_ready () в примере 2.

Задача данной функции состоит в том, чтобы подождать максимальное время 500 мс, пока полученный байт не будет иметь значение 0xFF. Если байт 0xFF не получен в течение 500 мс, функция завершается возвратом последнего значения, считанного с контроллера SPI.

Пример 2. Функция wait_ready (), задачей которой является максимальное время ожидания 500 мс, пока полученный байт не получит значение 0xFF

static
BYTE wait_ready (void)
{
BYTE res;
Timer2  = 50; // ждем 500мс
rcvr_spi ();
do
res = rcvr_spi ();
while ((res! = 0xFF) && Timer2);
return res;
}

У нас уже есть все необходимые функции, связанные с аппаратным интерфейсом, но чтобы они вообще работали, само оборудование должно быть правильно настроено.

Функция power_on () отвечает за настройку контроллера SPI, портов ввода/вывода и их тактовых сигналов, пример 3. После определения переменных, используемых в дополнительном функциональном коде, включаются тактовые сигналы для контактов (портов GPIOA и GPIOC) и контроллера SPI. Затем вывод PC12 конфигурируется для управления выбором системы (CS), а выводы PA5, PA6, PA7 в качестве линий шины SPI.

Пример 3. Функция power_on () отвечает за настройку контроллера SPI, портов ввода/вывода и включение их тактовых сигналов.

static 
void power_on (void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
u8 i, cmd_arg [6];
u32 Count = 0xFFF;
// Конфигурация выводов и контроллера SPI:
// Включить тактовые сигналы для периферийных устройств
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC |
RCC_APB2Periph_SPI1 | RCC_APB2Periph_AFIO, ENABLE);
// PA4 as CS
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed ​​= GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init (GPIOC, & GPIO_InitStructure);
// SCK, MISO и MOSI
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed ​​= GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init (GPIOA, & GPIO_InitStructure);
// Конфигурация SPI
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init (SPI1, & SPI_InitStructure);
// Включить SPI
SPI_Cmd (SPI1, ENABLE);
// Инициализация карты и переход в режим SPI:
DESELECT(); // CS = 1
for (i = 0; i <10; i ++)
xmit_spi (0xFF); // отправляем 0xFF 10 раз = 80 тактов
// (требуется не менее 74 циклов)
SELECT(); ; // CS = 0
// Готовим кадр инициализации для отправки
cmd_arg [0] = (CMD0 | 0x40);
cmd_arg [1] = 0; // Командный аргумент
cmd_arg [2] = 0; // даже когда команды нет
cmd_arg [3] = 0; // должны быть отправлены как нули
cmd_arg [4] = 0;
cmd_arg [5] = 0x95; // CRC = 0x95
for (i = 0; i <6; i ++) // Отправить кадр
xmit_spi (cmd_arg [I]);
while ((rcvr_spi ()! = 0x01) && Count) // Ожидание 0x01
Count--;
DESELECT(); // CS = 1
xmit_spi (0xFF); // Отправляем 0xFF
PowerFlag = 1;
}

Контроллер SPI установлен в качестве master для полнодуплексной работы. Кадр данных будет 8 бит, и состояние линии будет зафиксировано на переднем фронте тактового сигнала. В неактивном состоянии линия SCK будет высокой. Предварительный масштабирующий сигнал для тактовых импульсов контроллера SPI был установлен на 4, что означает, что данные будут отправляться со значительной скоростью 18 Мбит/с. После установки всех параметров SPI контроллер включается вызовом функции SPI_Cmd ().

Теперь микроконтроллер настроен должным образом, а SD-карта по умолчанию после включения питания работает в выделенном стандартном режиме поддержки SDBus. Чтобы связь (прием команд) была вообще возможна, вы должны сначала отправить как минимум 74 такта для инициализации карты. Затем, чтобы переключиться в режим SPI, отправьте команду CMD0. Если инициализация карты для работы в режиме SPI выполнена правильно, карта вернет подтверждающий байт 0x01.

Модуль FatFs требует тактовый сигнал для работы, который каждые 10 мс будет вызывать функцию disk_timerproc (), которая используется в дальнейшем для измерения времени. Для циклического вызова функции, упомянутой выше, использован 24-битный таймер SysTick. Его конфигурация была представлена в примере 4.

Пример 4. Процедура, отвечающая за настройку таймера SysTick

void SysTick_Conf (void)
{
// SysTick будет работать с частотой f = 72 МГц / 8 = 9 МГц
SysTick_CLKSourceConfig (SysTick_CLKSource_HCLK_Div8);
// Прерывание должно быть каждые 10 мс, f = 9 МГц, то есть оно отсчитывается от 90 000
SysTick_SetReload (90000);
// Разблокировать прерывание от таймера SysTick
SysTick_ITConfig (ENABLE);
// Активировать таймер
SysTick_CounterCmd (SysTick_Counter_Enable);
}

По умолчанию основные системные часы после дублирования с помощью цикла PLL составляют 72 МГц, и SysTick по умолчанию синхронизируется на этой частоте. Чтобы получать прерывание каждые 10 мс, использовался прескалер, деливший сигнал 72 МГц на 8, что привело к 9 МГц.

Если мы хотим, чтобы функция прерывания SysTick (SysTickHandler ()) вызывалась с частотой 100 Гц, то счетчик счетчиков должен составлять 90 000. Таймер SysTick обсуждается более подробно в главе 5, и в этом случае функция прерывания выглядит следующим образом следующим образом:

void SysTickHandler (void)
{
disk_timerproc ();
}

Обсуждаемые функции являются единственными аппаратно-зависимыми фрагментами кода в модуле FatFs, поэтому теперь мы будем иметь дело с самым высоким его уровнем, позволяющим выполнять операции с файлами и каталогами.

Основные операции над файлами и каталогами

Когда микроконтроллер уже может обмениваться данными с SD-картой и файловая система поддерживается должным образом, следующий шаг — это правильные операции с файлами и каталогами. Все функции, предлагаемые модулем FatFs, обсуждаются на его веб-сайте разработчика, а их список находится в таблице 2. Здесь мы рассмотрим примеры приложений, которые выполняют необходимые задачи для файлов и каталогов.

Табл. 2. Функции, предоставляемые модулем FatFs

функция  описание
f_mount «Монтирует» (регистрирует) логический диск в системе
f_open открытие и / или создание файла
F_CLOSE закрытие файла
f_read чтение содержимого файла
f_write сохранение файла
f_lseek перемещает индикатор чтения / записи файла
f_truncate сокращает длину файла
f_sync похож на f_close, за исключением того, что файл остается открытым, так что вы все еще можете работать с ним
f_opendir открывает каталог
f_readdir читает содержимое каталога
f_getfree позволяет читать количество свободных кластеров
F_State читает информацию о файле / каталоге
f_mkdir создает каталог
f_unlink удаляет каталог или файл
f_chmod изменяет атрибуты файла или каталога
f_utime изменяет дату и время для определенного файла или каталога
f_rename переименование или перемещение файла / каталога
f_mkfs создает файловую систему на носителе
f_forward читает данные с носителя и пересылает их напрямую

Пример программы, задачей которой является выполнение основных операций с файлами и каталогами (пример 5). Представленный код сначала монтирует логический диск с помощью функции f_mount (), чтобы модуль FatFs мог выполнять дисковые операции. В аргументах мы отправляем номер диска (здесь 0) и, по ссылке, основную переменную файловой системы FATFS.

Пример 5. Пример программы, которая выполняет основные операции с файлами и каталогами

int main (void)
{
FRESULT fresult;
FIL file;
WORD save_bytes;
RCC_Conf ();
GPIO_Conf ();
SysTick_Conf ();
fresult = f_mount (0, & g_sFatFs);
// Создать файл
fresult = f_open (& file, "file.txt", FA_CREATE_ALWAYS);
fresult = f_close (& file);
// Создать каталог
fresult = f_mkdir ("directory1");
// Сохранить файл
fresult = f_open (& file, "file.txt", FA_WRITE);
fresult = f_write (& file, "file content", 15, & save_bytes);
fresult = f_close (& file);
// Удалить файл
while(1);
}

После установки диска вы можете свободно управлять содержимым карты памяти. В этой программе сначала создается новый текстовый файл в корневом каталоге карты. Для этого использовалась функция f_open (), которая (название), по-видимому, не имеет ничего общего с действиями по созданию файлов. Хотя имя функции не указывает на это, оно также используется для создания новых файлов.

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

Второй аргумент — это строка, которая будет именем файла, в примере это будет текстовый файл с файлом file.txt. В качестве третьего аргумента мы отправляем запрос на выполнение действия. Для функции f_open () все возможные значения последнего аргумента показаны в табл. 3. В рассмотренном примере 5 вы можете видеть, что программа создает новый файл независимо от того, существовал ли файл с именем file.txt ранее или не существовал на карте памяти. 

Таб. 3. Возможные значения последнего аргумента функции f_open ()

Путь открытия описание
FA_READ файл открыт для чтения
FA_WRITE файл открыт для записи
FA_OPEN_EXISTING открыть файл, если файл не существует, появится сообщение об ошибке
FA_OPEN_ALWAYS открыть файл, если он не существует, будет создан новый файл
FA_CREATE_NEW создание нового файла, если файл существует, будет сообщено об ошибке
FA_CREATE_ALWAYS создание нового файла, если он уже существует, перезапишет старый файл

Каждая функция из модуля FatFs возвращает значение FRESULT. В общем, если возвращаемое значение равно 0, то все прошло правильно, в противном случае произошли ошибки.

Помимо создания файлов, у вас также должен быть механизм, позволяющий создавать каталоги. В модуле FatFs такого механизма предусмотрена функция f_mkdir (). В аргументе функции мы передаем имя каталога. Если каталог не должен быть создан в каком-либо другом каталоге, достаточно указать его имя, и, если вы хотите создать папку в качестве подкаталога, введите полный путь к ней. Последний случай представлен в следующей строке кода:

fresult = f_mkdir ("directory1 / directory2");

Если вы можете создавать файлы и каталоги, вы также можете удалить их. Это обеспечивается вызовом функция f_unlink (). Аргумент должен быть именем файла или каталога, который нужно удалить. Запись в файл после открытия его для записи выполняется путем вызова функции f_write ().

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

Администрирование содержимого карты памяти иногда может потребовать сокращения длины файла, другими словами, уменьшения его размера. Это можно сделать с помощью функции f_truncate () (truncate — shortten), вызов которой может выглядеть примерно так:

fresult = f_truncate (& file);

Как видите, единственным аргументом, который необходимо передать, является адрес дескриптора файла, то есть переменная (структура), содержащая всю информацию о файле, необходимую для его правильного существования в файловой системе.

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

Рис. 5. Механизм уменьшения размера файла

Просмотр содержимого карты памяти, информации о файлах и каталогах

Информацию о файлах в модуле FatFs можно получить, вызвав функцию f_stat (), все что вам нужно сделать, это вставить в программу следующую строку кода:

fresult = f_stat ("file.txt", & fileInfo);

Первым аргументом должна быть строка, содержащая имя файла, точнее указатель на начало таблицы с именем файла.

Информация сохраняется в структуре FILINFO, структура которой показана в примере 6, а ее адрес передается в качестве второго аргумента при вызове функции f_stat (). Данные о файле, который мы получаем: размер, дата последней модификации, время модификации, атрибуты файла, имя в массиве из 13 элементов (формат 8 + 3).

Пример 6. Построение структуры FILINFO

/ * Структура статуса файла * /
typedef struct _FILINFO {
DWORD fsize; / * Размер * /
WORD fdate; / * Дата * /
WORD ftime; / * Время * /
char fname [13]; / * Имя (формат 8.3) * /
} FILINFO;

Вы можете просмотреть содержимое всего каталога, используя функцию f_readdir (). Пример программы, которая собирает информацию о том, что находится в папке directory1, представлен в примере 7 .

Пример 7. Тестовая программа, которая собирает информацию о содержимом папки directory1

fresult = f_opendir (& Dir, "directory1");
if (fresult! = FR_OK)
return (fresult);
for (;;)
{
fresult = f_readdir (& Dir, & fileInfo);
if (fresult! = FR_OK)
return(fresult);
if(! plikInfo.fname [0])
break;
// p - команда для элементов массива FILINFO
* p ++ = информационный файл;
}

После открытия каталога с помощью функции f_opendir () в бесконечном цикле информация о последующих файлах и папках считывается путем копирования структуры типа, описанного выше (FILINFO). Последующие вызовы функции f_readdir () автоматически читают информацию о последовательных элементах каталога. Если поле имени элемента пустое, это означает, что в данной папке больше нет элементов, и цикл останавливается.

Оставить комментарий

Ваш email нигде не будет показан. Обязательные для заполнения поля помечены *

*