Jump to content

    

Хранение данных на NOR flash (Кольцевой буфер)

Для хранение калибровочных констант + журнала аварий решил использовать внешнюю память, выбор пал на  A25L032 так как они уже были закуплены когда то давно.

Ну и так как памяти стало гораздо больше (раньше писал во flash процессора), то решил сделать лог работы программы, и записывать его во flash.
В общем получилось что надо записывать 3 вида данных (3 разных файла)  
1) Журнал аварий (он же отображается в устройстве)
2) Настройки программы (калибровка)
3) Лог работы аварии  (отображать буду через UDP)

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

Другая задача считывает данные из очереди, и в зависимости от имени файла применяет к нему соответственную структуру или сразу записывает как есть во внешную flash.

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

Нагуглил интересную статью на habr много нового почерпнул   https://habr.com/ru/post/479044/
Но вопросов стало еще больше.

Такой вариант хранения мне понравился (правда думаю между Magic number  и версией вставит ешё NameFile). Для журнала аварий данные буду структра (время ,тип аварий ,тек параметр и тд) а длина размер структуры, а для лога будут string + её длина.

7rsrfafqrc263dsu8zd5ptceqga.png

Вот с алгоритмом записи перезаписи, пока не все ясно.
После "форматирования" флешки, пробегаемся по всем страницам и записываем Magic number, это как бы флаг что если после Magic number нету данных, значит страница чистая и можно туда записывать без стирания сектора.
при записи страницы без Magic number пропускаются.

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

А вот при перезаписи(когда выделенная память для файла закончилась) совсем не понятно, что если новые данные скажем 99 гораздо больше по длине чем занимали данные 1 ? Да на странице может быть свободное место но не факт что его хватит.

И второй момент, где хранить указатель на текущую страницу ?  Можно в нулевом секторе, но как то жирно перетирать 4кб для сохранения пару байт. Можно сделать со смешением, но  как поиск  проще реализовать?

 

Edited by pokk

Share this post


Link to post
Share on other sites

автор на  habr, что-то мудрёное затеял, есть же готовые библиотеки для хранения (не помню где искать), с однобитными счетчиками и указателями, с подменой секторов. Но вам реально нужна простая микросхема? просто в sd/mmc картах это уже всё сделано на встроенном процессоре

Edited by gridinp

Share this post


Link to post
Share on other sites
2 часа назад, pokk сказал:

Вот с алгоритмом записи перезаписи, пока не все ясно.

У меня в одном проекте, где есть похожая задача, сделано так:

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

Т.е. - при старте системы хранения, сперва программа пробегается по всем секторам, в поисках полностью стёртого сектора (сектора все байты которого == 0), потом начиная от этого сектора ищет уже точное байтовое смещение начала головы. С этого текущего положения головы и пойдут уже новые записи в КОХ. Чтение данных тоже производится с этой позиции.

Данные перед записью в КОХ, сперва дополняются CRC, обрамляются маркерами начала и конца блока данных (не равными 0), затем кодируются по протоколу COBS и пишутся во флешь закодированными COBS. Таким образом исключается наличие в данных байтов == 0 (так как это маркер чистых страниц и маркер границ записей). Каждая новая запись в голову КОХ отделяется от предыдущих маркером границы записи (байт == 0). При чтении данных, начиная от головы ищется ближайший маркер конца записи (==0), данные от головы до маркера конца декодируются COBS и сверяется CRC, если всё ок - поиск заканчивается - это наши данные. Если нет (может данные были разрушены из-за внезапного сброса МК), ищется следующий маркер конца записи и декодируется следующая запись. И так далее, пока не будет найдена последняя корректная запись.

Также в каждом секторе для записи данных я использую только (РАЗМЕР_СЕКТОРА-4_БАЙТА) байт. А в 4 байтах (в конце сектора) храню маркер занятости сектора. Это для ускорения процедуры поиска стёртой дырки при старте системы хранения (чтобы уменьшать операции чтения). Т.е. - сначала я читаю эти 4 байта и по ним определю предварительное состояние стёртости сектора. И только когда по маркеру занятости сектора обнаружу границу перехода СТЁРТ/НЕ_СТЁРТ - только для такого стёртого сектора полностью его считываю и проверяю содержимое на равенство всех байт ==0. Также этот маркер я использую как маркер стёртого сектора. Процедура стирания сектора у меня 2-этапная: сперва в маркер занятости сектора (поверх старого значения занятости сектора) пишется спец. значение "сектор стирается", и только потом даётся команда на стирание сектора. Таким образом, если вдруг во время операции стирания (длительной) произойдёт сбой питания, то при следующем запуске системы хранения она обнаружит маркер стирания сектора и дотрёт его до конца.

Система хранения не требует никакого предварительного специального "форматирования" чипа и может начать работу с чипа заполненного произвольным мусором (если стёртая дырка не будет найдена при старте - считается что КОХ не инициализирована - тогда она инициализируется автоматически созданием этой самой дырки из одного сектора).

Длина записываемых блоков данных может быть любой какой угодно. И блоки могут быть разной длины - без разницы: при декодировании границы блока обнаруживаются по маркерам границ записи. Запись может занимать и несколько секторов.

Это примерный алгоритм. Есть ещё детали.

 

Таким образом я получил устойчивую к сбоям питания систему хранения данных в виде кольцевого буфера.

Share this post


Link to post
Share on other sites

Пока что-то совсем не въезжаю. При каждой новой записи блока данных, голова сдвигается на сектор неважно заполнилась она полностью или нет ?
т.е получаеться  как то так ?

sector_0{ 1 block }    sector_1{ 2 block }  sector_2{  0x00 }  sector_3{ head 3 block }  sector_4{0 block}        // Было так  + 1 новая запись    
sector_0{ 1 block }    sector_1{ 2 block }  sector_2{3 block}  sector_3{ 0x00 }          sector_4{ head 4 block}  // стало так sector_3 скопирован в сектор 2 + новая запись в сектор 4

Или наоборот новые данные в пустую область вставляются?  А после того как голова начала содержать максимальное блоков данных ? Походу как-то не так =(

2 hours ago, jcxz said:

если меньше одного сектора - стираем предыдущий сектор, а затем уже - пишем страницу.

Так в предыдущем секторе данные же уже хранятся.  

 

 

Share this post


Link to post
Share on other sites
1 час назад, pokk сказал:

Пока что-то совсем не въезжаю. При каждой новой записи блока данных, голова сдвигается на сектор неважно заполнилась она полностью или нет ?

Нет. Зачем? Записывается поток байт полученный после кодирования в COBS.

Share this post


Link to post
Share on other sites

Да я про сам алгоритм записи перезаписи.С кодирование как раз все понятно.

44 minutes ago, jcxz said:

Нет. Зачем?

Так тогда получается что в случае, если произошел сбой питания во время записи, нового блока данных, то слетели все блоки данных которые были в этом секторе. 

Share this post


Link to post
Share on other sites
3 минуты назад, pokk сказал:

Так тогда получается что в случае, если произошел сбой питания во время записи, нового блока данных, то слетели все блоки данных которые были в этом секторе. 

Нет. Сектор - элемент стирания. Страница - элемент записи. 1 сектор содержит много страниц. Данные пишутся страницами.

 

PS: Вы как-то невнимательно читаете написанное.....  :unknw:

Перечитайте моё исходное сообщение внимательнее. Там разжёвано всё предельно ясно.

Share this post


Link to post
Share on other sites

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

5 hours ago, jcxz said:

что перед головой в любой момент времени должен быть как минимум один полностью стёртый сектор

Как может быть больше 1 ?

3 minutes ago, jcxz said:

Нет. Сектор - элемент стирания. Страница - элемент записи.

И правда, а если запись идет по второй итерации в кольце?

 

Share this post


Link to post
Share on other sites
3 минуты назад, pokk сказал:

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

Не понимаю о чём Вы. Какое "смешение"? Кого с кем? :wacko2:   И что такое "нулевой сектор"? Первый в кольце что-ль?

3 минуты назад, pokk сказал:

Как может быть больше 1 ?

"Как минимум" один нужен для алгоритма поиска дырки из стёртых секторов. Может быть и больше - без разницы.

3 минуты назад, pokk сказал:

И правда, а если запись идет по второй итерации в кольце?

И что?

Share this post


Link to post
Share on other sites
шаг 1:
-1-  |  -2-   |  -3-   |       ;границы секторов
XXX00 00000000 00XXXXXX XXX    ;страницы (0-пустые(заполненные 0-и); X-не пустые)

-1-  |  -2-   |  -3-   |  -4-   |  -5-   |       ;границы страниц
00000 00000000 00000000 00000000 00XXXXXX XXX    ;байты (0-равные 0; X-не равные 0)

шаг 2:
Надо записать N1 байт данных. Кодируем их в COBS. Получем M1 байт. Отступаем 1 байт (межкадровый маркер), записываем, получаем:
-1-  |  -2-   |  -3-   |  -4-   |  -5-   |       ;границы страниц
00000 00000000 0000YYYY YYYYYYYY Y0XXXXXX XXX    ;байты (0-равные 0; X-байты старых записей; Y-байты новой записи длиной M1 байт)
  
шаг 3
Надо записать N2 байт данных. Кодируем их в COBS. Получем M2 байт. Отступаем 1 байт (межкадровый маркер), записываем, получаем:
-1-  |  -2-   |  -3-   |  -4-   |  -5-   |       ;границы страниц
00000 0000ZZZZ ZZZ0YYYY YYYYYYYY Y0XXXXXX XXX    ;байты (0-равные 0; X,Y-байты старых записей; Z-байты новой записи длиной M2 байт)

Если например на шаге3, когда пересекалась граница страницы, была попытка пересечения границы секторов1 и 2 (на границе 2й и 3й страниц), то перед операцией записи страницы2 стирается сектор1, и только затем пишется страница2. Таким образом, чтобы перед головой кольца записанных данных всегда был как минимум один стёртый сектор.

Вроде все очень просто.

И конечно перед записью данных в новый, стёртый сектор в него сначала прописывается маркер занятого сектора (последние 4 байта). При записи данных эти байты выкусываются из размера сектора. Перед стиранием сектора, в маркер сектора сначала записывается значение 0xFFFFFFFF (маркер стёртого сектора) и потом даётся команда стирания.

Share this post


Link to post
Share on other sites

Благодарю, более менее стало понятно.

А как происходит переход по кольцу? Скажем  если  КОХ содержит 100 блоков с данными (по всем секторам), и 1 блок это самая первая запись и 100 блок это последняя запись самая актуальная, то при добавления
101 записи как происходит? На место первого блока(а если длина нового блока больше чем была у первого?) так как он менее актуальный или как то по другому ?  

 

Если на шаге3, когда пересекалась граница страницы, была попытка пересечения границы секторов1 и 3 ?
Точнее голова переходит на 3 сектор ? Но перед этим второй сектор должен быть очищен? Но там же были данные, если кусок блока мал то, много данных. 
 

 

 

 

 

 

Edited by pokk

Share this post


Link to post
Share on other sites
2 часа назад, pokk сказал:

А как происходит переход по кольцу? Скажем  если  КОХ содержит 100 блоков с данными (по всем секторам), и 1 блок это самая первая запись и 100 блок это последняя запись самая актуальная, то при добавления

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

Цитата

Если на шаге3, когда пересекалась граница страницы, была попытка пересечения границы секторов1 и 3 ?
Точнее голова переходит на 3 сектор ? Но перед этим второй сектор должен быть очищен?

да, 2-й. Как только запись данных хочет занять хотя бы один байт из сектора 3, перед этим предварительно стирается сектор 2.

 

Цитата

Но там же были данные, если кусок блока мал то, много данных. 

Хвост трётся. В КОХ не 3 сектора, это на рисунке 3 (не понимайте буквально - не буду же я 100 секторов рисовать). Сколько выделите секторов под КОХ - столько и будет длина кольца. Чем больше секторов в КОХ - тем меньше их износ (ниже частота прохода по всему кольцу). Не хватает секторов для хранения данных - добавьте ещё.

шаг 1:
  -1-   |  -2-   |  -3-   |  -4-   |  -5-   |  -6-   | ... |  -K-      ;границы секторов
XXXXXXXX 00000000 00XXXXXX XXXXXXXX XXXXXXXX XXXXXXXX       XXXXXXXX   ;страницы (0-пустые(заполненные 0-и); X-не пустые)
     ^              ^
     |              |
     хвост          голова 

Здесь КОХ состоящая из K секторов. Начиная от головы до хвоста может находиться  множество записанных ранее записей (разделённых маркером 0). У меня читается всегда самая первая от головы (самая последняя записанная). Если первую прочитать нельзя (была разрушена во время внезапного откл. питания) - декодер идёт дальше по цепочке байт, читая и декодируя следующую запись (как будто это поток байт принимаемых с интерфейса связи), до тех пор пока не прочитает успешно первую запись. Количество таких попыток декодирований можно принудительно ограничить некотором числом.

Увеличивая число K например в 2 раза, мы уменьшаем износ секторов в КОХ в 2 раза.

Share this post


Link to post
Share on other sites

Всё это прекрасно, но что произойдет при плохом секторе ?

Share this post


Link to post
Share on other sites

Посиотрите piconomic library

у меня есть перепилка их журнала с блэкджэками

Share this post


Link to post
Share on other sites
1 hour ago, jcxz said:

Не хватает секторов для хранения данных - добавьте ещё.Э 

Наоборот, 2-3 сектора за глаза хватает, по этому и удивлялся что перетираем сразу 60% информации которая отображается, но хотя если взять десяток секторов то это 60% будет "100 летней" давности.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now