При разработках устройств часто бывает необходимым хранить настройки вне рабочей программы. Еще лучше иметь возможность их модификации без использования специальных средств.
Рассмотрим вариант хранения в пожалуй самых распространенных микроконтроллерах STM серии F103. Способствовала распространенности также всем известная макетная плата Blue Pill
Имеющаяся в ней 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)
Размер бинарного файла у меня получился около 20К, поэтому страница памяти данных у меня определена с 0x08006000 (24K).
Компилируем (исходники примера можно взять здесь).
Подключаем:
Диск определился, все отлично!
Приступим к формированию раздела и форматированию нашего диска.
В Linux это делать достаточно просто из командной строки:
отформатируем в FAT12:
Копируем файл для теста:
Однако, не следует забывать, что по документации, число циклов перезаписи flash
гарантировано лишь в пределах 100000. К примеру, форматирование и запись одного файла 30К займет (по отладочному журналу данного примера):
106 циклов перезаписи.
На этом — все. Спасибо за внимание!
Рассмотрим вариант хранения в пожалуй самых распространенных микроконтроллерах STM серии F103. Способствовала распространенности также всем известная макетная плата Blue Pill
Имеющаяся в ней 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 циклов перезаписи.
На этом — все. Спасибо за внимание!
u_235
Но ведь 106 раз перезаписывается не в один блок?
VladimirKuzmin Автор
Да, конечно. В разные страницы.
Но вообще вопрос какая страница будет переписываться чаще всего зависит от типа использования. Если плодить тысячи файлов-настроек (а что, удобно ведь! название настройки — один файл — одна настройка) то первая, где хранятся имена в FAT. И очевидно — та страница, где чаще всего меняются данные.
u_235
Вопрос ещё и в том, как часто записывать настройки. Самое простое — сразу после изменения, но это может привести к быстрому износу памяти. Можно использовать буфер и время от времени делать flush (), но так часть настроек может пропасть при сбое питания.
VladimirKuzmin Автор
Записывать настройки «изнутри» используя FAT — не очень простой вариант.
«Изнутри» удобнее читать (надо будет продумать что-то лайт варианта поиска по FAT указателя на данные по имени файла) настройки, сделанные через компьютер.
Если надо именно только читать-писать изнутри, по лучше использовать следующий алгоритм:
— выделить переменную-счетчик циклов и хранить на странице флеш.
— по достижении к примеру 100000, страницу помечать как использованную и переходить на следующую.
так мы увеличим ресурс флеша в количество раз доступных страниц (если страница 1К — то 100К нашего флеша (если 28К прошивка), постранично выдержат 10 000 000 циклов)
IgorPie
Первой откажет страница флэша со счетчиком, не зря про wear-leveling — не мало копий сломано.
Отличный пример. Вы осуществили мою небольшую мечту и протоптали дорожку. Спасибо большущее!
VladimirKuzmin Автор
… конечно, если мы счетчик будем хранить на одной странице. Но так делать не надо. Счетчик всегда переезжает на "свежую" страницу, оставляя предыдущую, использованную, исключительно "read-only"
IgorPie
Вполне можно и на одной, просто записывать в незанятые ячейки. А когда все (256 или 512, или сколько там вмещается на страницу) заполнятся, только тогда стирать страницу. Собственно, это часть стратегии level wearing
DanilinS
Не изобретайте велосипед. В телефонах, планшетах и др. устройствах со флешем уже давно используются алгоритмы хранения, когда активная зона записи «переползает» по поверхности флеша для более равномерного износа.
Посмотрите внимательно на «старые» модели. Тогда отсутствовала «живучая» флеш, а потому проблема износа флеша была актуальна. И система использовала хитрые алгоритмы для минимизации износа флеша.
Поищите еще по теме «Flash file system».
IgorPie
Я и говорил, что это часть стратегии. Пример для одной 16 или 32 бит переменной и блока в 1КБ. Если блоков несколько, то раскидывается на несколько блоков. В начале в первом заполняется, второй и т.п. После заполнения последнего стирают все и начинают с начального блока.
Sau
А там нет EEPROM чтобы хранить счётчик? Обычно у него ресурс перезаписи больше.
VladimirKuzmin Автор
На рассматриваемом F103 — увы нет. Но есть Backup registers — это несколько десятков 16 битных регистров, сохраняющихся при отключении основного питания, если есть питание от VB — дополнительной батарейке (как примерно биос на материнке). У них неисчерпаемый ресурс перезаписи, пока жива батарейка.