STM32 Работа с библиотекой FatFs

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

Существуют свободно распространяемые библиотеки для работы с FAT. Одна из них – FatFs. Больше информации о ней на русском языке можно найти здесь http://microsin.net/programming/ARM/fatfs-file-system.html 

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

Библиотеку можно использовать для 8-ми битных контроллеров, а для 32-х разрядных и подавно. Имеется возможность работы с длинными именами файлов, в кодировке Unicode.

Есть смысл познакомиться с ней поближе.

Для начала я скачал по указанной ссылке пример проекта с использованием FatFs, адаптированный под микроконтроллеры STM32. В этом примере работа с картой ведется через SPI. Имеется два варианта – без использования ДМА  и с использованием оного.

Я пока остановился на первом варианте с перспективой перехода на второй. Правда мне пришлось немного изменить его, т.к. в оригинале используется SPI1, а у меня карта подключена к модулю SPI2.

Сделаю небольшой обзор необходимых файлов:

  • ff.c – именно здесь содержатся функции для работы с FAT;
  • ff.h – содержит объявления функций для работы с FAT, описание различных структур, позволяет задавать режимы работы (например, разрешить/запретить использование длинных имен, максимальную длину этих имен) а также содержит некоторые макроопределения;
  • fattime.c и fattime.h – самые простые файлы. Нужны для возврата текущего времени, нужного при создании файлов. В принципе, можно возвращать не текущее время, а какое-то фиксированное;
  • integer.c – для определения размеров целочисленных типов;
  • diskio.c и diskio.h – для связи с картой памяти. Именно diskio.c необходимо корректировать для конкретного контроллера, вся остальная часть является платформенно независимой.

Есть еще файлы, которые позволяют работать с кодировкой Unicode, но я его пока не включал в проект, так как для первого ознакомления он не особо нужен, а памяти занимает много. Чтобы компилятор не ругался на отсутствие этого файла, пришлось закоментировать строки 1146 и 1186 в файле ff.c

Файл diskio.c я подкорректировал, так как у меня используется второй SPI

Помимо указанных выше файлов в проект нужно включить следующие файлы из библиотеки StdPeriph_Lib:

  • stm32f10x_gpio.h и stm32f10x_gpio.с;
  • stm32f10x_rcc.h и stm32f10x_rcc.с;
  • stm32f10x_spi.h и stm32f10x_spi.h;

Эти файлы нужны для работы с соответствующей периферии микроконтроллера.

Если кому интересно, указанные файлы можно скачать здесь:  скачать

Краткий обзор некоторых  функций

Для работы с картой памяти файлах diskio.c и diskio.h  имеются следующие функции:

  • DSTATUS disk_initialize (BYTE);
  • DSTATUS disk_status (BYTE);
  • DRESULT disk_read (BYTE, BYTE*, DWORD, BYTE);
  • DRESULT disk_write(BYTE, const BYTE*, DWORD, BYTE);
  • DRESULT disk_ioctl (BYTE, BYTE, void*);

DSTATUS disk_initialize (BYTE drv) – используется для инициализации диска. Она определяет тип карты, присутствует ли она вообще, включена ли защита от записи и т.д. Если ты только подключил карту и хочешь проверить результат своего творчества, то нужно использовать именно эту функцию. В качестве аргумента передаем ноль.

В качестве результата возвращается состояние диска. Результат также сохраняется в глобальной переменной Stat. В ней задействованы три младших разряда, которые являются отдельными флагами. В файле diskio.h для этого имеются следующие макроопределения:

  • STA_NOINIT        0×01    — диск не инициализирован;
  • STA_NODISK       0×02    — нет карты памяти;
  • STA_PROTECT     0×04    — включена защита от записи

Анализируя результат функции disk_initialize можем судить о состоянии диска (читай карты памяти). Если результат равен нулю, значит все окей, если установлен один из трех младших разрядов или их комбинация – значит есть какая-то бяка. Анализируя каждый разряд поймем в чем проблема.

DSTATUS disk_status (BYTE drv) – позволяет в любое время прочитать значение переменной Stat, указывающей на состояние диска. Аргумент – номер диска(0).

DRESULT disk_read (BYTE drv, BYTE* buff, DWORD sector, BYTE count) – позволяет прочитать нужное количество секторов (count = 1..128) начиная с указанного сектора (sector) в указанный буфер (buff). С аргументом drv мы уже знакомы.

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

Возвращаемое значение имеет тип DRESULT, который представляет собой перечисленный тип, который описан в diskio.h:

1
2
3
4
5
6
7
8
typedef enum 
{
  RES_OK = 0,    //операция выполнена нормально
  RES_ERROR,     //какая-то аппаратная ошибка (проблема с картой и т.д.)
  RES_WRPRT,     //включена защита от записи (используется при записи секторов)
  RES_NOTRDY,    //диск не был инициализирован
  RES_PARERR     //передали неверный параметр (например count = 0)
} DRESULT;

DRESULT disk_write(BYTE drv , const BYTE* buff, DWORD sector, BYTE count) – позволяет записать нужное количество секторов на карту, начиная с указанного сектора. Передаваемые аргументы и возвращаемый результат такие же как и в предыдущей функции.

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

 

Чуть не забыл самое главное. Чтобы перечисленные выше ф-ии нормально работали, необходимо настроить один из таймеров контроллера на прерывание через каждые 10мс, а в каждом прерывании необходимо вызывать функцию disk_timerproc. Это нужно для всяких таймаутов. Короче, нужно.

Описание функций библиотеки FatFs приведу в следующей статье. А пока подключаем карту памяти, выполняем инициализацию таймера, в прерывании которого обращаемся к  disk_timerproc. Затем в функции main  вызываем  disk_initialize. Если она возвращает ноль – радостно хлопаем в ладоши, иначе – чешим затылок и думаем: “а чё оно не работает”

Комментарии (10) на “STM32 Работа с библиотекой FatFs”

  • Apparatchik:

    Что конкретно нужно изменить в файле diskio.c чтоб использовать SPI1 ?

    • kontroller:

      Ниже приведена ссылка сайта, где содержаться проекты под FatFs для разных контроллеров. В примере для STM32 используется SPI1. Я использовал его для своего проекта, переделав под SPI2.
      Можете взять его для STM32
      http://www.siwawi.arubi.uni-kl.de/avr_projects/arm_projects/arm_memcards/index.html

    • Dmitry:

      Парочка power_on() для SPI1 (PB4, PB5, PB6, PB7) и SPI1 remap (PB3, PB4, PB5, PA15)

      /*————————————————————————*/
      /* Power Control and interface-initialization (Platform dependent) */
      /*————————————————————————*/
      // remapped SPI init for PB3-PB5 and PA15
      static
      void power_on(void)
      {
      RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;//включить тактирование альтернативных функций
      RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//включить тактирование порта А
      RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;//включить тактирование порта b

      GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); // отключаем JTAG
      GPIO_PinRemapConfig(GPIO_Remap_SPI1, ENABLE); // включаем ремап SPI1

      //вывод управления SS: выход двухтактный, общего назначения,50MHz
      GPIOA->CRH |= GPIO_CRH_MODE15; //
      GPIOA->CRH &= ~GPIO_CRH_CNF15; //
      GPIOA->BSRR = GPIO_BSRR_BS15; //

      //вывод SCK: выход двухтактный, альтернативная функция, 50MHz
      GPIOB->CRL |= GPIO_CRL_MODE3; //
      GPIOB->CRL &= ~GPIO_CRL_CNF3; //
      GPIOB->CRL |= GPIO_CRL_CNF3_1; //

      //вывод MISO: вход цифровой с подтягивающим резистором, подтяжка к плюсу
      GPIOB->CRL &= ~GPIO_CRL_MODE4; //
      GPIOB->CRL &= ~GPIO_CRL_CNF4; //
      GPIOB->CRL |= GPIO_CRL_CNF4_1; //
      GPIOB->BSRR = GPIO_BSRR_BS4; //

      //вывод MOSI: выход двухтактный, альтернативная функция, 50MHz
      GPIOB->CRL |= GPIO_CRL_MODE5; //
      GPIOB->CRL &= ~GPIO_CRL_CNF5; //
      GPIOB->CRL |= GPIO_CRL_CNF5_1; //

      //настроить модуль SPI
      RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //подать тактирование
      SPI1->CR2 = 0×0000; //
      SPI1->CR1 = SPI_CR1_MSTR; //контроллер должен быть мастером,конечно
      SPI1->CR1 |= SPI_CR1_BR; //для начала зададим маленькую скорость
      SPI1->CR1 |= SPI_CR1_SSI;
      SPI1->CR1 |= SPI_CR1_SSM;
      SPI1->CR1 |= SPI_CR1_SPE; //разрешить работу модуля SPI

      }

      // SPI1 init for PA4-PA7
      static void power_on2(void){

      RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;//включить тактирование альтернативных функций
      RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//включить тактирование порта А

      //вывод управления SS: выход двухтактный, общего назначения,50MHz
      GPIOA->CRL |= GPIO_CRL_MODE4; //
      GPIOA->CRL &= ~GPIO_CRL_CNF4; //
      GPIOA->BSRR = GPIO_BSRR_BS4; //

      //вывод SCK: выход двухтактный, альтернативная функция, 50MHz
      GPIOA->CRL |= GPIO_CRL_MODE5; //
      GPIOA->CRL &= ~GPIO_CRL_CNF5; //
      GPIOA->CRL |= GPIO_CRL_CNF5_1; //

      //вывод MISO: вход цифровой с подтягивающим резистором, подтяжка к плюсу
      GPIOA->CRL &= ~GPIO_CRL_MODE6; //
      GPIOA->CRL &= ~GPIO_CRL_CNF6; //
      GPIOA->CRL |= GPIO_CRL_CNF6_1; //
      GPIOA->BSRR = GPIO_BSRR_BS6; //

      //вывод MOSI: выход двухтактный, альтернативная функция, 50MHz
      GPIOA->CRL |= GPIO_CRL_MODE7; //
      GPIOA->CRL &= ~GPIO_CRL_CNF7; //
      GPIOA->CRL |= GPIO_CRL_CNF7_1; //

      //настроить модуль SPI
      RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //подать тактирование
      SPI1->CR2 = 0×0000; //
      SPI1->CR1 = SPI_CR1_MSTR; //контроллер должен быть мастером,конечно
      SPI1->CR1 |= SPI_CR1_BR; //для начала зададим маленькую скорость
      SPI1->CR1 |= SPI_CR1_SSI;
      SPI1->CR1 |= SPI_CR1_SSM;
      SPI1->CR1 |= SPI_CR1_SPE; //разрешить работу модуля SPI
      }

      stm32vl-discovery, stm32f100

  • scynet:

    Интересно это все конечно, но самое главное не сказано, какой тип памяти используется и если это NAND, то где оптимизационный алгоритм ?

  • Jura:

    Подскажи пожалуйсто почему при использовании твоей библиотеки в IAR выдаёт ошибки:
    Building configuration: dataflash — Debug
    Updating build tree…
    diskio.c
    Error[Pe144]: a value of type «void *» cannot be used to initialize an entity of type «BYTE *» D:\DATAFLASH\Libraries\ff9a\src\diskio.c 656
    Error while running C/C++ Compiler
    ff.c
    Warning[Pe111]: statement is unreachable D:\DATAFLASH\Libraries\ff9a\src\ff.c 1141
    Error[Pe144]: a value of type «void *» cannot be used to initialize an entity of type «BYTE *» D:\DATAFLASH\Libraries\ff9a\src\ff.c 1728
    Error[Pe144]: a value of type «void const *» cannot be used to initialize an entity of type «BYTE const *» D:\DATAFLASH\Libraries\ff9a\src\ff.c 1815
    Error while running C/C++ Compiler
    main.cpp

    Total number of errors: 3
    Total number of warnings: 1

  • Dmitry:

    Подскажи, пожалуйста, я правильно понимаю, что в твоём случае с FatFs имел место быть отказ от интеграции предыдущих наработок вроде SD_init, SD_ReadSector в пользу готовых power_on, rcvr_datablock и xmit_datablock из примера уважаемого ChaNа?
    А то я попробовал интегрировать SD_init, SD_ReadSector, SD_WriteSector (которые успешно работают для посекторной работы) в версию 0.09 и что-то не пошло. Завал случился на f_open, который возвращает 1. Типы возвращаемых результатов не совпадают?
    А в примере Чана очень суровая инициализация и у меня всё виснет. Там 103 чип, а у меня 100, может поэтому? Подозрения вызывает строка с комментом 72/4

    • kontroller:

      Я использовал пример Чана и чип 100. Работает нормально.
      Если возвращает 1, то насколько я помню это код ошибки «диск не инициализирован»
      Прежде чем читать диск нужно выполнить монтирование диска. Делается это так:
      FATFS fs;
      f_mount(0, &fs);

      • Dmitry:

        Представляешь, я заменил содержимое функции power_on на spi_init() (http://electronics-archive.ru/stm32-sd-card-%D0%B2%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5.html):

        RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;//включить тактирование альтернативных функций
        RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//включить тактирование порта А

        //вывод управления SS: выход двухтактный, общего назначения,50MHz
        GPIOA->CRL |= GPIO_CRL_MODE4; //
        GPIOA->CRL &= ~GPIO_CRL_CNF4; //
        GPIOA->BSRR = GPIO_BSRR_BS4; //

        //вывод SCK: выход двухтактный, альтернативная функция, 50MHz
        GPIOA->CRL |= GPIO_CRL_MODE5; //
        GPIOA->CRL &= ~GPIO_CRL_CNF5; //
        GPIOA->CRL |= GPIO_CRL_CNF5_1; //

        //вывод MISO: вход цифровой с подтягивающим резистором, подтяжка к плюсу
        GPIOA->CRL &= ~GPIO_CRL_MODE6; //
        GPIOA->CRL &= ~GPIO_CRL_CNF6; //
        GPIOA->CRL |= GPIO_CRL_CNF6_1; //
        GPIOA->BSRR = GPIO_BSRR_BS6; //

        //вывод MOSI: выход двухтактный, альтернативная функция, 50MHz
        GPIOA->CRL |= GPIO_CRL_MODE7; //
        GPIOA->CRL &= ~GPIO_CRL_CNF7; //
        GPIOA->CRL |= GPIO_CRL_CNF7_1; //

        //настроить модуль SPI
        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //подать тактирование
        SPI1->CR2 = 0×0000; //
        SPI1->CR1 = SPI_CR1_MSTR; //контроллер должен быть мастером,конечно
        SPI1->CR1 |= SPI_CR1_BR; //для начала зададим маленькую скорость
        SPI1->CR1 |= SPI_CR1_SSI;
        SPI1->CR1 |= SPI_CR1_SSM;
        SPI1->CR1 |= SPI_CR1_SPE; //разрешить работу модуля SPI

        и файл записался.

    • Dmitry:

      Проблема с невозможностью запустить пример Томаса, похоже, в строке:
      for (Timer1 = 250; Timer1; ); /* Wait for 250ms */
      решилась заменой этой строки на набор из слегка подрежактированой пары кусков отсюда http://mycontroller.ru/stm32-timer-funktsiya-zaderzhki/

      Вот то, что я посставил в функции power_on() из примера Томаса, вместо вышеприведёной строки:
      RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //подать тактирование на TIM2
      TIM2->PSC = 8000-1; //настроить делитель для формирования миллисекунд
      TIM2->CR1 = TIM_CR1_OPM; //режим одного импульса
      TIM2->ARR = 250; //загрузить значение задержки
      TIM2->CNT = 0;
      TIM2->CR1 = TIM_CR1_CEN; //запустить таймер
      while((TIM2->SR & TIM_SR_UIF)==0){} //дождаться конца задержки
      TIM2->SR &= ~TIM_SR_UIF; //сбросить флаг

      файл записался на SPI1
      попробую для ремапа SPI1 ещё

      • kontroller:

        А Вы не работали с русской кодировкой в именах файлов?
        А то у меня с этим небольшой затык.
        Проблема возникает с русскими буквами в коротком имени файла.

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

Spam Protection by WP-SpamFree