В предыдущей статье был рассмотрен вариант использования микроконтроллера STM32F103C8T6 как flash накопителя с внутренней файловой системой FAT12. Теперь можно рассмотреть, каким образом получить данные из нашей внутренней файловой системы. К примеру нам необходимо хранить некие настройки нашей программы.
Для хранения именованных настроек особого усложнения структур формата данных не потребуется. Примем следующий формат — одна настройка = один файл. К примеру, нам необходимо хранить скорость подключения по UART и допустим, задержку в миллисекундах мигания светодиода. Создадим (в любом текстовом редакторе) в нашей файловой системе файлы UART_SPD.SET и DELAY_BL.SET. Запишем в них значения: соответственно 115200 и 1000.
Чтобы получить эти данные, обратившись к нашей внутренней FAT12 потребуется либо взять стандартную библиотеку HAL FatFs и попытаться ее приспособить под внутреннюю flash. Или пойти более простым и удобным способом — написать свою функцию чтения файловых данных FAT12, что мы и сделаем:

uint8_t f12_read_data (
char *file_name,                // имя файла для поиска в формате 11 символов - "NAME    TXT"
char **file_data,               // здесь будут данные найденного файла
char *file_list,                // здесь будет список файлов
uint16_t file_list_size)  {     // размер для списка файлов
 // FAT12 - http://elm-chan.org/docs/fat_e.html
  uint16_t BPB_BytsPerSec;
  uint8_t  BPB_NumFATs;
  uint16_t BPB_RootEntCnt;
  uint16_t BPB_RsvdSecCnt;
  uint16_t BPB_FATSz16;
  uint8_t  BPB_SecPerClus;
  uint16_t BS_BootSign;
  uint8_t  found=0;
  uint16_t cluster_no;

  char     *fname;
  uint32_t SEEK_FAT12_NAMES_OFFSET;
  uint32_t SEEK_FAT12_NAMES_OFFSET_END;
  uint32_t SEEK_DATA_OFFSET;
  uint32_t fs;

  // Получим данные FAT12
  BPB_BytsPerSec=uint16_t_from_internal_flash(PAGE_ADDR+11);
  BPB_NumFATs=uint8_t_from_internal_flash(PAGE_ADDR+16);
  BPB_RootEntCnt=uint16_t_from_internal_flash(PAGE_ADDR+17);
  BPB_RsvdSecCnt=uint16_t_from_internal_flash(PAGE_ADDR+14);
  BPB_FATSz16=uint16_t_from_internal_flash(PAGE_ADDR+22);
  BPB_SecPerClus=uint8_t_from_internal_flash(PAGE_ADDR+13);
  BS_BootSign=uint16_t_from_internal_flash(PAGE_ADDR+510);

  if (BS_BootSign != 0xAA55) { // неверная сигнатура boot сектора FAT
      return FAT12_ERR_BAD_FAT;
  }

  SEEK_FAT12_NAMES_OFFSET = (BPB_RsvdSecCnt+(BPB_FATSz16 * BPB_NumFATs)) * BPB_BytsPerSec; // отступ на начало имен
  SEEK_FAT12_NAMES_OFFSET_END=SEEK_FAT12_NAMES_OFFSET +
                            (((32 * BPB_RootEntCnt + BPB_BytsPerSec - 1) / BPB_BytsPerSec) * BPB_BytsPerSec); // отступ на конец имен

  SEEK_DATA_OFFSET= BPB_BytsPerSec * BPB_RsvdSecCnt+ // Boot сектор + резерв
                     ((BPB_FATSz16 * BPB_NumFATs) * BPB_BytsPerSec) + // Размер FAT
                     BPB_RootEntCnt*32; // Размер имен и хар-к файлов

  if (file_list_size > 0) // обнулить строку списка файлов
             file_list[0]=0;
  while (SEEK_FAT12_NAMES_OFFSET < SEEK_FAT12_NAMES_OFFSET_END)  {
    fname = char_from_internal_flash(PAGE_ADDR+SEEK_FAT12_NAMES_OFFSET);
    cluster_no  = uint16_t_from_internal_flash(PAGE_ADDR + SEEK_FAT12_NAMES_OFFSET + 0x1A);
    fs = uint32_t_from_internal_flash(PAGE_ADDR + SEEK_FAT12_NAMES_OFFSET + 0x1C);
       if ((file_list_size > 0) && 
           (strlen(file_list)+20 < file_list_size) && 
           (cluster_no  > 0)) {
              sprintf(file_list,"%s%11s %06d bytes\r\n",
                      file_list,fname, fs); // дополним полный список файлов
       }
       for (int i=0;i<9;i++) { // упрощенный поиск по имени файла
        	  if (fname[i] != file_name[i]) {
			  found=0;
			  break;
        	  }
        	found=1;
       }

       if (found==1) 
       break;

       SEEK_FAT12_NAMES_OFFSET+=0x20;
  }
     if (found == 0) {
         return FAT12_ERR_FILE_NOT_FOUND;
     }
    if ( *file_data != 0 ) {
      *file_data=char_from_internal_flash(PAGE_ADDR+(cluster_no-2) *
              (BPB_SecPerClus * BPB_BytsPerSec) + // Размер кластера
              SEEK_DATA_OFFSET);
     }
      return 0;
 }

Теперь — получаем значение необходимой настройки:

f12_read_data("UART_SPDSET" /*имена без точек, дополнение по 11 символов - пробелами*/,&fdata, file_list, 200);


В массиве fdata будет находится строка «115200», которую можно перевести в целое используя функцию atoi():
Проект с исходниками можно взять здесь.

На этом — всё. Спасибо за внимание!

Комментарии (12)


  1. hardex
    05.03.2019 18:28

    Форматировать код вам религия не позволяет?


    1. VladimirKuzmin Автор
      05.03.2019 19:15

      Есть отличные инструменты, как пример:
      www.tutorialspoint.com/online_c_formatter.htm
      в т.ч. с различными настройками.
      К сожалению воспользоваться не получилось — пытался делать читабельным в HTML


  1. IgorPie
    06.03.2019 01:16

    Так понимаю, что к предыдущей статье и к текущей — один репозиторий на гитхабе и он немного оброс чтением однокластерных файлов fat12?


    1. VladimirKuzmin Автор
      06.03.2019 10:32

      Репозиторий один. Насчет кластеров — да. Пишем любые, читаем однокластерные.


  1. besitzeruf
    06.03.2019 02:55

    Простите, но почему выбор пал на FAT12 а не… например NTFS? <sarcasm />


    1. IgorPie
      06.03.2019 05:01

      Fat открытая спецификация и структура диска весьма примитивная. К тому же на все про все выдано 64 сектора по 512 байт, или вроде того. Это даже не дискета в 360 килобайт.


    1. VladimirKuzmin Автор
      06.03.2019 10:23

      Вообще — из-за простоты и доступности решения. Чтобы хранить данные на внутреннем флеше, собственно, вообще никакой файловой системы не требуется. Можно формировать и заливать образы диска 65К с компьютера. Но это не очень удобно пользователям. Файловая система упрощает работу редактирования настроек. Например это можно делать вообще со смартфона. А файловая система fat12 — упрощает работу программиста :)


  1. FGV
    06.03.2019 05:31

    хм. в "упрощенный поиск по имени файла"
    found=1;
    надо бы вынести перед циклом, иначе все время будет возвращать данные первого попавшегося файла.
    Ну и вот это:
    if (found==1) {
    found=1;
    break;
    }
    это зачем?


    1. VladimirKuzmin Автор
      06.03.2019 10:13

      Вы правы, found=1 перед break — остатки от рефакторинга :) Это можно убрать.
      А вот выносить перед циклом — не надо.


      1. FGV
        06.03.2019 12:09

        del


  1. IgorPie
    06.03.2019 15:55
    +1

    Собрал исходники из предыдущей статьи под CooCox'ом для stm32f103cbt6, работает.
    Спасибо за статью и сорцы, повеселился с флэшкой на 12 килобайт.

    Портировалось без проблем, разве что кокос (arm gcc) с разными видами оптимизаций выдает код значительно длиннее 24576 байт, пришлось подвинуть начало «флэшки» с 0x08006000 значительно дальше, иначе при форматировании затиралась сама рабочая программа. Даже при оптимизации «по объему», вышло 36 кило.
    Ну и вещи типа «for(int i=...» gcc не любит.


    1. VladimirKuzmin Автор
      06.03.2019 16:58

      Пожалуйста. Можно кстати вполне увеличить объем «флешки» — увеличив соответственно количество STORAGE_BLK_NBR c 0x80 до 0xB7. Получим флешку 92К.
      (131072(128К ) — 36864(36К) = 94208 = 184 (B7) сектора по 512 байт )