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

Рассмотрим вариант хранения в пожалуй самых распространенных микроконтроллерах STM серии F103. Способствовала распространенности также всем известная макетная плата Blue Pill

image
Имеющаяся в ней flash позволяет не только хранить и модифицировать настройки используя файловую систему FAT12 во внутреннем flash, но и организовать обновление прошивки.

Согласно документации в STM32F103C8T6 имеется 64К flash памяти. Однако практически во всех STM32F103C8T6 установлено 128К. Об этом также упоминается в разных источниках — обычно ставят на 64К больше. Такая «фича» позволяет использовать микроконтроллер как flash накопитель объемом 128К — 20К (системные нужды FAT12) — размер прошивки.

Многие энтузиасты, пытавшиеся использовать данный контроллер как накопитель flash, сталкивались с проблемой его использования в режиме файловой системы FAT12. Использовать для снятия/заливки образа диска получалось. А вот при работе как с файловым накопителем начинались проблемы.

Эта проблема заключается в разной последовательности доступа к секторам (блокам). При загрузке образа диска запись происходит последовательно, например:

-запись блока №1,
-запись блока №2,
-запись блока №3.

При записи данных FAT12 запись может происходить произвольно:

-запись блока №3,
-запись блока №1,
-запись блока №2.

И, так как для записи во flash требуется стирать всю страницу размером 1К, то при использовании в накопителе секторов размером 512 байт (а использовать другие размеры сектора не удается), если используется произвольный доступ — стирается информация в соседнем секторе. Чтобы этого не происходило, в приведенном примере используется массив 512 байт для хранения соседнего сектора. И запись должна происходить следующим образом:

— определяем адрес начала страницы,
— запоминаем соседний сектор,
— стираем страницу,
— пишем запомненный сектор,
— пишем данные.

Чтобы не углубляться в дебри железа без требуемой необходимости, проект я подготовил в CubeMX.

Приведу пример функции записи во flash через HAL (usbd_storage_if.c)

// функция записи во flash
void writeBuf (uint32_t page_addr, uint8_t *buf){
   uint32_t erase_addr=get_erase_addr(page_addr);
   uint32_t buf_erase_addr;
   uint32_t buf32;
   if (page_addr != erase_addr) { 
       buf_erase_addr=erase_addr;
   }   else    {
       buf_erase_addr=erase_addr+STORAGE_BLK_SIZ;
     }
    HAL_FLASH_Unlock();

// вызываем функцию сохранения соседнего сектора
   set_buf_before_erase(buf_erase_addr);

// заполняем структуру записи сектора и стираем
   FLASH_EraseInitTypeDef EraseInitStruct;
   uint32_t PAGEError = 0;
   EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
   EraseInitStruct.PageAddress = erase_addr;
   EraseInitStruct.NbPages     = 1;
   HAL_FLASHEx_Erase(&EraseInitStruct,&PAGEError);

   // запишем сохраненный буфер
   for (int i=0; i<STORAGE_BLK_SIZ/4;i++) {
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,buf_erase_addr,blk_buff[i]);
    buf_erase_addr+=4;
    }
   // запишем данные
   for (int i=0; i<STORAGE_BLK_SIZ/4;i++) {
    buf32=*(uint32_t *)&buf[i*4];
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, page_addr,buf32);
    page_addr+=4;
    }
    HAL_FLASH_Lock();
}

Размер бинарного файла у меня получился около 20К, поэтому страница памяти данных у меня определена с 0x08006000 (24K).

Компилируем (исходники примера можно взять здесь).

Подключаем:

[ 8193.499792] sd 4:0:0:0: Attached scsi generic sg2 type 0
[ 8193.502050] sd 4:0:0:0: [sdb] 128 512-byte logical blocks: (65.5 kB/64.0 KiB)
[ 8193.502719] sd 4:0:0:0: [sdb] Write Protect is off
[ 8193.502722] sd 4:0:0:0: [sdb] Mode Sense: 00 00 00 00
[ 8193.503439] sd 4:0:0:0: [sdb] Asking for cache data failed
[ 8193.503445] sd 4:0:0:0: [sdb] Assuming drive cache: write through
[ 8193.523812]  sdb:
[ 8193.526914] sd 4:0:0:0: [sdb] Attached SCSI removable disk

Диск определился, все отлично!

Приступим к формированию раздела и форматированию нашего диска.

В Linux это делать достаточно просто из командной строки:

sudo fdisk /dev/sdb



отформатируем в FAT12:

sudo mkfs.fat /dev/sdb -F 12

Копируем файл для теста:



Однако, не следует забывать, что по документации, число циклов перезаписи flash
гарантировано лишь в пределах 100000. К примеру, форматирование и запись одного файла 30К займет (по отладочному журналу данного примера):

00106 44 67 Write_FS blk_addr=003 0x08006600 

106 циклов перезаписи.

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

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


  1. u_235
    25.02.2019 17:28

    Но ведь 106 раз перезаписывается не в один блок?


    1. VladimirKuzmin Автор
      25.02.2019 17:35

      Да, конечно. В разные страницы.
      Но вообще вопрос какая страница будет переписываться чаще всего зависит от типа использования. Если плодить тысячи файлов-настроек (а что, удобно ведь! название настройки — один файл — одна настройка) то первая, где хранятся имена в FAT. И очевидно — та страница, где чаще всего меняются данные.


      1. u_235
        25.02.2019 18:20

        Вопрос ещё и в том, как часто записывать настройки. Самое простое — сразу после изменения, но это может привести к быстрому износу памяти. Можно использовать буфер и время от времени делать flush (), но так часть настроек может пропасть при сбое питания.


        1. VladimirKuzmin Автор
          25.02.2019 18:34

          Записывать настройки «изнутри» используя FAT — не очень простой вариант.
          «Изнутри» удобнее читать (надо будет продумать что-то лайт варианта поиска по FAT указателя на данные по имени файла) настройки, сделанные через компьютер.

          Если надо именно только читать-писать изнутри, по лучше использовать следующий алгоритм:
          — выделить переменную-счетчик циклов и хранить на странице флеш.
          — по достижении к примеру 100000, страницу помечать как использованную и переходить на следующую.
          так мы увеличим ресурс флеша в количество раз доступных страниц (если страница 1К — то 100К нашего флеша (если 28К прошивка), постранично выдержат 10 000 000 циклов)


          1. IgorPie
            25.02.2019 21:21

            Первой откажет страница флэша со счетчиком, не зря про wear-leveling — не мало копий сломано.

            Отличный пример. Вы осуществили мою небольшую мечту и протоптали дорожку. Спасибо большущее!


            1. VladimirKuzmin Автор
              25.02.2019 21:50

              … конечно, если мы счетчик будем хранить на одной странице. Но так делать не надо. Счетчик всегда переезжает на "свежую" страницу, оставляя предыдущую, использованную, исключительно "read-only"


              1. IgorPie
                25.02.2019 22:28

                Вполне можно и на одной, просто записывать в незанятые ячейки. А когда все (256 или 512, или сколько там вмещается на страницу) заполнятся, только тогда стирать страницу. Собственно, это часть стратегии level wearing


                1. DanilinS
                  26.02.2019 10:11

                  Не изобретайте велосипед. В телефонах, планшетах и др. устройствах со флешем уже давно используются алгоритмы хранения, когда активная зона записи «переползает» по поверхности флеша для более равномерного износа.
                  Посмотрите внимательно на «старые» модели. Тогда отсутствовала «живучая» флеш, а потому проблема износа флеша была актуальна. И система использовала хитрые алгоритмы для минимизации износа флеша.

                  Поищите еще по теме «Flash file system».


                  1. IgorPie
                    27.02.2019 13:18

                    Я и говорил, что это часть стратегии. Пример для одной 16 или 32 бит переменной и блока в 1КБ. Если блоков несколько, то раскидывается на несколько блоков. В начале в первом заполняется, второй и т.п. После заполнения последнего стирают все и начинают с начального блока.


          1. Sau
            26.02.2019 12:04

            А там нет EEPROM чтобы хранить счётчик? Обычно у него ресурс перезаписи больше.


            1. VladimirKuzmin Автор
              26.02.2019 12:35

              На рассматриваемом F103 — увы нет. Но есть Backup registers — это несколько десятков 16 битных регистров, сохраняющихся при отключении основного питания, если есть питание от VB — дополнительной батарейке (как примерно биос на материнке). У них неисчерпаемый ресурс перезаписи, пока жива батарейка.