10 000 циклов записи

Добрый день, Дорогие Читатели! ![]()
В сегодняшней статье я бы хотел затронуть тему надежности флэш-накопителей.
В моем видеокурсе “Киберсант-Оборона” этому посвящен небольшой урок в третьем разделе курса. Можно сказать, что эта статья является дополнением к этому уроку.
Итак, каковы главные темы, о которых нужно говорить, когда стоит вопрос надежности хранения данных на USB-Flash Drive?
Во-первых, это количество циклов записи/стирания. Современными стандартами количество циклов перезаписи определяется на уровне от 10 000 до 100 000 раз.
На первый взгляд, даже 10 000 – это много. Да, для многих – это более чем достаточно. Однако, если вам приходится интенсивно использовать флэш накопитель каждый день, то это не так уж и много…
Стоит так же учесть и то, что не все USB-Flash Drive отрабатывают честно весь предначертанный им ресурс. Это касается прежде всего дешевых “флэшек”, USB-Flash Drive от малоизвестных фирм, а то и вовсе неизвестного происхождения. Бывает и так, что и среди именитых брендов встречаются подделки.
Чем грозит исчерпание ресурса?
Банальной потерей информации на носителе… Полностью или частично.
Если, например, при повреждении жесткого диска (при образовании испорченного сектора, скажем) обычно можно извлечь информацию с него (при соблюдении некоторых правил и при помощи специальных программ), то с USB-Flash Drive совсем другое.
По конструктивным особенностям, очень мало шансов на успешное восстановление данных с выработавшего свой ресурс накопителя. Природа запоминания информации на USB-Flash Drive несколько иная. Обычно, это микросхема энергонезависимой памяти, которая хранит информацию в ячейках.
Записать/стереть информацию из ячейки можно ограниченное число раз (см. выше), поэтому, когда ресурс накопителя заканчивается, записываемая информация записывается с ошибками в какие-либо ячейки, или же когда-то записанная информация считывается с ошибкой.
И в том и в другом случае весьма сложно провести восстановление и извлечение нужной информации. Поэтому, если Вам нужен как можно более надежный накопитель, или же Вы покупаете дорогой USB-Flash Drive большой емкости (быстрый выход которого из строя из-за интенсивного использования уж точно никого не обрадует), убедитесь, чтобы накопитель отвечал следующим требованиям:
1) Фирма-производитель.
Только известные бренды: Kingston. Transcend, Corsair, A-Data, Apacer. Без комментариев. Вряд ли разумно доверять ценные данные неизвестной продукции, где крайне трудно будет спронозировать реальный срок службы.
2) Тип памяти.
Ознакомьтесь с упаковкой или инструкцией. Там должно быть указано, что используется память типа NAND (современный стандарт) и рядом указан ресурс: 10 или 100 тыс. циклов перезаписи. Именно NAND тип может обеспечить 100 000 циклов записи/стирания информации.
Иногда указывают просто число циклов, а иногда указывают только тип. Типа ячеек всего два (среди “бытовых” USB-Flash Drive):
*SLC – каждая ячейка хранит один бит. Обеспечивается ресурс примерно в 100 000 циклов перезаписи.
*MLC – каждая ячейка хранит два бита. Обеспечивается ресурс в 10 000 циклов перезаписи. На данный момент USB-Flash Drive типа MLC – большинство.
Итак:
В любом случае, неважно, на 10 или 100 тыс. циклов рассчитан накопитель – помните, что если его ресурс подходит к концу, то информацию потом будет трудно восстанавливать. И что бы вообще не задумываться об этом, просто регулярно делайте дубликат рабочей информации с флэшки, с которой вам приходится часто работать, на какой-либо другой носитель.
Все-таки, дело даже не в ресурсе…
USB-Flash Drive мы носим с собой, подключаем к самым разным компьютерам. Потеряться или испортиться информация может даже в результате случайного (или намеренного) физического отключения USB-Flash Drive от порта. Если в этот момент система обращалась к накопителю (производилась запись или чтение), то часть информации (или она вся) может повредиться.
Я уж не говорю о возможности проникновения вирусов на накопитель сразу после подключения к порту и прочих угрозах…
Источник
Вопрос о том, сколько может прослужить флешка, волнует многих пользователей. Чтобы ответить на него, важно знать, сколько раз можно записывать данные на флеш-память. Блогер Джош Брессерс изучил вопрос и протестировал живучесть флешки. Результаты оказались неожиданными – девайс оказался крепче, чем ожидалось.
Разные накопители ведут себя по-разному, при этом результат зависит от внешних и внутренних факторов. Испытаниям подверглась флешка Sony Microvault USB на 1 Гб. Мы не советуем повторять нижеописанные действия, так как они могут полностью вывести флешку из строя.
Испытание на выносливость флешки
Для теста Джош Брессерс создал утилиту на базе признака O_DIRECT, который позволяет файловой системе обращаться напрямую к файлу, без кэширования данных в ОЗУ. Сама процедура кэширования удобна, но для испытания флешки она не подходит – нам нужно иметь доступ к накопителю, а не временной памяти компьютера.
Для использования O_DIRECT накопитель пришлось отформатировать в файловую систему ext3. На заводских настройках флешка по умолчанию работала в файловой оболочке vfat, которая не позволяла корректно работать с элементом O_DIRECT. Файловая оболочка была создана – на этом подготовительный этап эксперимента завершён, можно приступать к испытаниям.

Тестирование
Накопитель был полностью заполнен информацией – свободным оставался лишь один блок. Планировалось записывать в этот блок всё новые данные до того момента, пока флешка не даст сбой. С помощью консоли Linux был сделать тестовый файл. Автоматически диск заполнялся при помощи специальной команды.
Дальше было проведено непосредственно тестирование, которое проводилось с помощью программы, которая многократно перезаписывала файл до того момента, пока накопитель не отказал. Как в итоге оказалось, одного блока для проверки флешки мало. Блогер не знает, какова причина такого явления. Скорее всего, это специфика файловой системы.
Итак, для тестирования понадобилось три пустых блока в памяти. Брессерс ожидал, что флеш-накопитель выдержит не более 30 000 циклов перезаписи информации – по 10 000 перезаписываний данных в каждый из блоков. После этого, как ожидал блогер, устройство должно выйти из строя.

Итоги теста
Флешка дала сбой после 90 593 103 циклов перезаписи информации. Выносливость устройства намного превзошла ожидания испытателя. На каждый цикл записи информации потрачено примерно полторы тысячи микросекунд (1/1000000 секунды). Однако не все циклы длились такой промежуток времени. Были и длинные циклы записи продолжительностью около 10 000 микросекунд.
Наверное, флеш-накопитель каким-то образом определил, что скоро не справится со столь напряжённой работой, и стал замедлять скорость выполнения каждого цикла. Вместе с тем, скорость чтения данных осталась на прежнем уровне. Флешка дала отказ во время процесса записи, а не чтения. Терминальная консоль выдала сообщение о сбое в виде текстового сообщения.
После этого компьютер продолжал видеть флеш-память и давал возможность считывать с неё информацию, отказываясь при этом записывать на неё новые данные. На основании проведённого эксперимента Джош Брессерс сделал выводы:
- Флешка сохранила способность к чтению с неё файлов после того, как она стала бесполезной для записи новой информации.
- Вероятность уничтожения данных флешки ниже, чем утраты способности к дальнейшей записи.
- Все вышеизложенные факты являются лишь вероятностью и не могут быть стопроцентно подтверждены.
Результаты теста во многом предопределены конкретной моделью флешки. Возможно, другие модели внешней флеш-памяти покажут совсем непохожие результаты. Большинство же экспертов придерживаются мнения, что обычная флешка способна выдержать около одного миллионов циклов записи или удаления информации.

Как выбрать флешку с большим циклом перезаписи
Если вы ежедневно используете флешку, регулярно перезаписывая большие объёмы информации, для вас будет иметь значение количество циклов перезаписи данных. При выборе девайса работает привычное правило – чем дороже модель, тем она более качественная. Однако не всегда ресурс накопителя отвечает заявленному производителем.
Что учесть при выборе:
- Производитель. Предпочтение отдавайте только известным фирмам. Это Kingston, Transcend, Corsair, Silicon Power, DanDisc. Лучше не покупать изделие от неизвестных китайских компаний – скорей всего они имеют ограниченный ресурс в плане перезаписи информации.
- Тип памяти. Указывается обычно на упаковке. Современные модели поддерживают стандарт NAND. Он позволяет перезаписывать данные от 10 000 до 100 000 раз. Иногда производитель указывает только число перезаписей, иногда только тип памяти. Флешки для бытового использования имеют два типа ячеек: SLC – каждая ячейка хранит 1 бит и перезаписывается до 100 000 раз; MLS – каждая ячейка хранит 2 бита и перезаписывается до 10 000 раз. Большинство флешек имеют тип памяти MLC.
Итак, флешки имеют ограниченный ресурс на перезапись информации. К счастью, до того как устройство выйдет из строя, вы сможете перезаписывать файлы десятки тысяч раз. Это число зависит от фирмы-изготовителя, конкретной модели и типа памяти. Выбирая девайс надёжной компании о ресурсе записи/перезаписи можно совсем не задумываться, так как он очень большой.
Источник
Каждый день что-то удаляю, что то закачиваю на плеер с флеш памятью. Сокращает ли это срок его службы, или никак не влияет? Флеш-память имеет ограниченное число циклов записи, но неограниченное число циклов чтения. Теоретически первые проблемы у флешки могут начаться примерно после 10-20 тысяч циклов записи одних и тех же секторов памяти, а особо качественные флешки гарантируют до 50-70 тысяч циклов записи. Надо ещё учесть, что ячейки памяти при каждой записи меняются, так что на деле срок службы флешки получится очень долгим. Разумеется, частая запись сокращает срок службы флеш-памяти, но на этот счёт не стоит заморачиваться, до того момента когда флешка исчерпает свой ресурс она скорей всего уже будет не нужна. модератор выбрал этот ответ лучшим КорнетОболенский [106K] более года назад У каждого твердотельного накопителя имеется определенное количество циклов записи. Поэтому совершенно естественно, что с каждой последующей записью срок службы накопителя сокращается. Но самое главное в конечном итоге – это не общее количество записей/перезаписей, сколько качество материала, из которого изготовлен накопитель. Есть флэшки, которые теряют ёмкость практически сразу, в основном китайского производства, а есть такие, которые служат годами и ничего с ними не случается. Лучшие флэшки способны выдерживать десятки тысяч циклов перезаписи. И это не предел. Слышал информацию, что вскоре должны появится флэшки, способные выдерживать несколько сотен тысяч циклов перезаписи. Sensor [232K] 6 лет назад Не думаю, что частое стирание, форматирование и запись могут как-то влиять на работу флешки.Во всяком случае, я с таким не сталкивался. У меня работа такая, что вынужден ежедневно таскать материал на флешке из дома на работу и обратно. Ежедневно стираю и записываю. И ничего. Новые флешки покупаю только потому, что иногда теряю старые. А “крякнуть” флешка может из-за того, если Вы, например, вынете ее из компа во время операции. Ну разумеется сокращает. Количество циклом перезаписи флеш-карт бывает от десяти тысяч до ста тысяч раз. Некоторые флеш-карты “обучены” правильно распределять данные для продления дней свой жизни. Но так или иначе, в среднем флешка вполне сносно себя чувствует до десяти тысяч циклов полной перезаписи и форматирования. Николай60 [2.4K] 6 лет назад Конечно сокращает, т.к. срок ее службы зависит от количества циклов записи – стирания. Обычно он исчисляется десятками тысяч раз. Его Вам хватит надолго. Эл Лепсоид [138K] более года назад Ответ на поставленный вопрос однозначен, поскольку существует такое понятие, как предельное количество циклов записи – это то число, сколько раз в ячейку памяти допустимо что-то записать. Причем, перезапись осуществляется, как при добавлении новой, так и при удалении старой информации. Чтобы при частом использовании данные все время не попадали в одни и те же ячейки, производители разработали кучу алгоритмов записи для более-менее равномерного распределения записываемой информации по всему диску. Именно это, конечно, помогает продлить срок службы флешек, но все-равно частая перезапись снижает их срок службы. Хотя чаще причиной выхода флешек из строя является не разрушение ячеек памяти, а выход из строя контроллера или элементарное физическое разрушение (кто-то наступил, например). Sergeich91 [146K] более года назад В общем-то на срок службы флешки это конечно оказывает своё негативное влияние, но не столь заметное, вернее даже тут будет говорить о том, что почти совсем не повлияет, потому что это в буквально смысле как слону дробина. Ведь флешка вообще рассчитана на очень значительное количество циклов записи и стирания файлов, там счёт идёт на миллионы и при всё желании вы так много раз этого сделать вряд ли сумеете. Поэтому есть смысл даже говорить о том, что всё эти манипуляции подобного плана с вашей стороны в отношении флешки никак на её работоспособность и срок службы не влияют, а если и влияют, то негативное влияние будет мизерным. Поэтому не стоит на счёт этого чересчур заморачиваться и переживать, записывайте и стирайте столько, сколько вам нужно, флешка от этого не испортится. Dilyara K [4.9K] 3 года назад Еще неизвестно от чего ваша флешка сдохнет – от частого перезаписывания или из-за плохого качества самой флешки. Часто заказываю разные карточки памяти в Китае, встречаются качественные, которые работают долго, бывают плохие, которые не выдерживают и 10 записей. Если флешка хорошая, она должна выдержать 10-15 тысяч циклов записи (память поделена на ячейки, имеется ввиду циклы записи ячейки, а не всей памяти). Это довольно большое число, и в идеале вам должно хватить такого количества надолго при умеренном использовании флешки. Если конечно она не сдохнет раньше от своей бракованной сущности владсандрович [654K] более года назад Да, именно стирание и записывание на флешку новой информации, как считается, сокращает срок ее службы. Все потому что циклов таких, то есть когда вы стерли и записали на нее информацию снова, может быть в своей совокупности как считается по науке максимум 20 тысяч, а потом ваша флешка после этого, приходит в негодность. Красное облако [225K] более года назад Сокращается конечно, но тот самый цикл, это цифра с пятью нулями, то есть быстрей устройство в которое вставляется флешка выйдет из строя чем сама флешка. У меня флешки года три я постоянно что-то записываю и стираю, всё нормально работает, нет с ней проблем. Знаете ответ? |
Источник
Введение
Доброго времени суток. Прошлая моя статья про параметры в EEPROM была, мягко говоря, немного недопонята. Видимо, я как-то криво описал цель и задачу которая решалась. Постараюсь в этот раз исправиться, описать более подробно суть решаемой проблемы и в этот раз расширим границы задачи.
А именно поговорим о том, как хранить параметры, которые необходимо писать в EEPROM постоянно.
Многим может показаться, что это очень специфическая проблема, но на самом деле множество устройств именно этим и занимаются – постоянно пишут в EEPROM. Счетчик воды, тепловычислитель, одометр, всяческие журналы действий пользователя и журналы, хранящие историю измерений, да просто любое устройство, которое хранит время своей работы.
Особенность таких параметров заключается в том, что их нельзя писать просто так в одно и то же место EEPROM, вы просто израсходуете все циклы записи EEPROM. Например, если, необходимо писать время работы один раз в 1 минуту, то нетрудно посчитать, что с EEPROM в 1 000 000 циклов записей, вы загубите его меньше чем за 2 года. А что такое 2 года, если обычное измерительное устройство имеет время поверки 3 и даже 5 лет.
Кроме того, не все EEPROM имеют 1 000 000 циклов записей, многие дешевые EEPROM все еще производятся по старым технологиям с количеством записей 100 000. А если учесть, что 1 000 000 циклов указывается только при идеальных условиях, а скажем при высоких температурах это число может снизиться вдвое, то ваша EEPROM способно оказаться самым ненадежным элементом уже в первый год работы устройства.
Поэтому давайте попробуем решить эту проблему, и сделать так, чтобы обращение к параметрам было столь же простым как в прошлой статье, но при этом EEPROM хватало бы на 30 лет, ну или на 100 (чисто теоретически).
Итак, в прошлой статье, я с трудом показал, как сделать, так, чтобы с параметрами в EEPROM можно было работать интуитивно понятно, не задумываясь, где они лежат и как осуществляется доступ к ним
Напомню:
ReturnCode returnCode = NvVarList::Init(); //инициализируем все наши параметры из EEPROM returnCode = myStrData.Set(tString6{ “Hello” }); //Записываем Hello в EEPOM myStrData. auto test = myStrData.Get(); //Считываем текущее значение параметра myFloatData.Set(37.2F); //Записываем 37.2 в EEPROM. myUint32Data.Set(0x30313233);
Для начала проясню, для чего вообще нужно обращаться по отдельности к каждому параметру, этот момент был упущен в прошлой статье. Спасибо товарищам @Andy_Big и @HiSER за замечания.
Все очень просто, существует огромный пласт измерительных устройств, которые используют полевые протоколы такие как HART, FF или PF, где пользовательские команды очень атомарные. Например, в HART протоколе есть отдельные команды – запись единиц изменения, запись верхнего диапазона, запись времени демпфирования, калибровка нуля, запись адрес опроса и т.д. Каждая такая команда должна записать один параметр, при этом успеть подготовить ответ и ответить. Таких параметров может быть до 500 – 600, а в небольших устройствах их около 200.
Если использовать способ, который предложил пользователь @HiSER- это будет означать, что для перезаписи одного параметра размером в 1 byte, я должен буду переписать всю EEPROM. А если алгоритм контроля целостности подразумевает хранение копии параметров, то для 200 параметров со средней длиной в 4 байта, мне нужно будет переписать 1600 байт EEPROM, а если параметров 500, то и все 4000.
Малопотребляющие устройства или устройства, питающиеся от от токовой петли 4-20мА должны потреблять, ну скажем 3 мА, и при этом они должны иметь еще достаточно энергии для питания модема полевого интерфейса, графического индикатора, да еще и BLE в придачу. Запись в EEPROM очень энергозатратная операция. В таких устройствах писать нужно мало и быстро, чтобы средний ток потребления был не высоким.
Очевидно, что необходимо, сделать так, чтобы микроконтроллер ел как можно меньше. Самый простой способ, это уменьшить частоту тактирования, скажем до 500 КГц, или 1 Мгц (Сразу оговорюсь, в надежных применениях использование режима низкого потребления запрещено, поэтому микроконтроллер все время должен работать на одной частоте). На такой частоте, простая передача 4000 байт по SPI займет около 70 мс, прибавим к этому задержку на сохранение данных в страницу (в среднем 7мс на страницу), обратное вычитывание, и вообще обработку запроса микроконтроллером и получим около 3 секунд, на то, чтобы записать один параметр.
Поэтому в таких устройствах лучше чтобы доступ к каждому параметру был отдельным, и обращение к ним должно быть индивидуальным. Их можно группировать в структуру по смыслу, или командам пользователя, но лучше, чтобы все они не занимали больше одной страницы, а их адреса были выравнены по границам страницы.
Но вернемся к нашей основной проблеме – мы хотим постоянно писать параметры.
Как работать с EEPROM, чтобы не износить её
Те кто в курсе, можете пропустить этот раздел. Для остальных краткое, чисто мое дилетантское пояснение.
Как я уже сказал, число записей в EEPROM ограничено. Это число варьируется, и может быть 100 000, а может и 1 000 000. Так как же быть, если я хочу записать параметр 10 000 000 раз? И здесь мы должны понять, как внутри EEPROM устроен доступ к ячейкам памяти.
Итак, в общем случае вся EEPROM разделена на страницы. Страницы изолированы друг от друга. Страницы могут быть разного размера, для небольших EEPROM это, скажем, 16, 32 или 64 байта. Каждый раз когда вы записываете данные по какому-то адресу, EEPROM копирует все содержимое страницы, в которой находятся эти данные, во внутренний буфер. Затем меняет данные, которые вы передали в этом буфере и записывает весь буфер обратно. Т.е. по факту, если вы поменяли 1 байт в странице, вы переписываете всю страницу. Но из-за того, что страницы изолированы друг от друга остальные страницы не трогаются.
Таким образом, если вы записали 1 000 000 раз в одну страницу, вы можете перейти на другую страницу и записать туда еще 1 000 000 раз, потом в другую и так далее. Т.е. весь алгоритм сводится к тому, чтобы писать параметр не в одну страницу, а каждый раз сдвигаться в следующую страницу. Можно закольцевать эти действия и после 10 раз, снова писать в исходную страницу. Таким образом, вы просто отводите под параметр 10 страниц, вместо 1.
Да придется пожертвовать память, но как сделать по другому, я пока не знаю. Если есть какие мысли – пишите в комментариях.
Анализ требований и дизайн
Итак, мы почти поняли что хотим. Но давайте немного формализуем это. Для начала, назовем наши параметры, которые нужно писать постоянно – AntiWearNvData (антиизносные данные). Мы хотим, чтобы обращение к ним было такое же простое и юзер френдли, как и к кешируемым параметрам из предыдущей статьи.
// читываем из EEPROM все параметры и инициализируем их копии в ОЗУ ReturnCode returnCode = NvVarList::Init(); returnCode = myStrData.Set(tString6{ “Hello” }); //Записываем Hello в EEPROM myStrData. auto test = myStrData.Get(); //Считываем текущее значение параметра myFloatData.Set(37.2F); //Записываем 37.2 в EEPROM. myUint32Data.Set(0x30313233); myFloatAntiWearData.Set(10.0F); //Записали в параметр 10.0F в EEPROM первый раз myFloatAntiWearData.Set(11.0F); myFloatAntiWearData.Set(12.0F); myFloatAntiWearData.Set(13.0F); … // Записываем этот же параметр в EEPROM 11 000 000 раз. myFloatAntiWearData.Set(11’000’000.0F); myUint32AntiWearData.Set(10U); // Тоже самое с int myStrAntiWearData.Set(tString6{ “Hello” }); // со строкой и так далее
Все требования можно сформулировать следующим образом:
Пользователь должен задать параметры EEPROM и время обновления параметра
На этапе компиляции нужно посчитать количество необходимых страниц (записей), чтобы уложиться в необходимое время работы EEPROM. Для этого нужно знать:
Количество циклов перезаписи
Размер страницы
Время обновления параметра
Время жизни устройства
Хотя конечно, можно было дать возможность пользователю самому задавать количество записей, но что-то я хочу, чтобы все считалось само на этапе компиляции.
Каждая наша переменная(параметр) должна иметь уникальный начальный адрес в EEPROM
Мы не хотим сами руками задавать адрес, он должен высчитываться на этапе компиляции
При каждой следующей записи, адрес параметра должен изменяться, так, чтобы данные не писались по одному и тому же адресу
Это также должно делаться автоматически, но уже в run, никаких дополнительных действий в пользовательском коде мы делать не хотим.
Мы не хотим постоянно лазить в EEPROM, когда пользователь хочет прочитать параметр
Обычно EEPROM подключается через I2C и SPI, передача данных по этим интерфейсам тоже отнимает время, поэтому лучше кэшировать параметры в ОЗУ, и возвращать сразу копию из кеша.
При инициализации мы должны найти самую последнюю запись, её считать и закешировать.
За целостность должен отвечать драйвер.
За алгоритм проверки целостности отвечает драйвер, если при чтении он обнаружил несоответствие он должен вернуть ошибку. В нашем случае, пусть в качестве алгоритма целостности будет простое хранение копии параметра. Сам драйвер описывать не буду, но приведу пример кода.
Ну кажется это все наши хотелки. Как и в прошлой статье давайте прикинем дизайн класса, который будет описывать такой параметр и удовлетворять нашим требованиям:
Класс AntiWearNvData будет похож на, CachedNvData из прошлой статьи, но с небольшими изменениям. При каждой записи в EEPROM, нам нужно постоянно сдвигать адрес записи, поэтому необходимо хранить индекс, который будет указывать на номер текущей записи. Этот индекс должен записываться в EEPROM вместе с параметром, чтобы после инициализации можно было найти запись с самым большим индексом – эта запись и будет самой актуальной. Индекс можно сделать uint32_t точно хватит на 30 лет – даже при 100 000 циклах записи.
И вот наш класс:
Посмотрим на то, как реализуются наши требования таким дизайном.
Пользователь должен задать параметры EEPROM и время обновления параметр
В отличии от CachedNvData Из предыдущей статьи здесь появился параметр up. На основе этого параметра можно посчитать сколько записей необходимо для того, чтобы уложиться в ожидаемое время жизни EEPROM. Сами параметры EEPROM можно задать в отдельном заголовочнике. Например, так:
using tSeconds = std::uint32_t; constexpr std::uint32_t eepromWriteCycles = 1’000’000U; constexpr std::uint32_t eepromPageSize = 32U; // Хотим чтобы EEPROM жила 10 лет constexpr tSeconds eepromLife = 3600U * 24U * 365U * 10U;
Вообще можно было бы обойтись и без up. И для каждого параметра задавать необходимое количество самим. Но я решил, все переложить на компилятор, потому что самому считать лень. В итоге сам расчет необходимого количества записей, с учетом, что все они выравнены по границам страницы, будет примерно таким:
template<typename NvList, typename T, const T& defaultValue, tSeconds up, auto& nvDriver> class AntiWearNvData { private: struct tAntiWear { T data = defaultValue; std::uint32_t index = 0U; }; inline ic tAntiWear nvItem; public: // Умножил на 2 чтобы зарезервировать место под копию. // Но по хорошему надо это убрать в список или драйвер ic constexpr auto recordSize = sizeof(nvItem) * 2U; // предполагаем, что параметр не занимает больше страницы и // все они выравнены по границам страницы, но ничто не запрещает // сделать более сложный расчет необходимого количества записей, // для параметров, занимающих больше страницы. Такие ограничения для упрощения. ic_assert(eepromPageSize/recordSize != 0, “Too big parameter”); ic constexpr size_t recordCounts = (eepromPageSize/recordSize) * eepromLife / (eepromWriteCycles * up);
При каждой следующей записи, адрес параметра должен изменяться, так, чтобы данные не писались по одному и тому же адресу
Еще одной особенностью нашего противоизносного параметра является тот факт, что кроме самого значения, мы должны хранить еще и его индекс. Индекс нужен нам для двух вещей:
По нему мы будет рассчитывать следующий адрес записи
Для того, чтобы после выключения/включения датчика найти последнюю запись, считать её и проинициализировать значением по адресу этой записи кеширумое значение в ОЗУ.
Для этого заведена специальная структура tAntiWear. Её то мы и будем сохранять при вызове метода Set(…), который, кроме непосредственно записи, еще сдвигает индекс текущей записи на 1.
template<typename NvList, typename T, const T& defaultValue, tSeconds up, auto& nvDriver> class AntiWearNvData { public: ReturnCode Set(const T& value) const { tAntiWear tempData = {.data = value, .index = nvItem.index}; //На основе текущего индекса расчитывем текущий адрес записи в EEPROM const auto calculatedAddress = GetCalculatedAdress(nvItem.index); ReturnCode returnCode = nvDriver.Set(calculatedAddress, reinterpret_cast<const tNvData*>(&tempData), sizeof(tAntiWear)); //Если запись прошла успешно, то обновляем кэшируемую копию параметра, //а также смещаем индекс на 1, для следующей записи if (!returnCode) { nvItem.data = value; nvItem.index ++; } return returnCode; } … };
Давайте посмотрим как реализован метод расчета текущего адреса записи:
template<typename NvList, typename T, const T& defaultValue, tSeconds up, auto& nvDriver> class AntiWearNvData { … private: ic size_t GetCalculatedAdress(std::uint32_t ind) { constexpr auto startAddress = GetAddress(); //собственно весь алгоритм расчета сводится к прибавленипю к стартовому адресу //смещения текущей записи, которое находится по текущему индексу //как только индекс будет кратен расчитанному количеству, необходимо начать писать //с начального адреса – такой кольцевой буфер в EEPROM. size_t result = startAddress + recordSize * ((ind % recordCounts)); assert(result < std::size(EEPROM)); return result; } constexpr ic auto GetAddress() { return NvList::template GetAddress<const AntiWearNvData<NvList, T, defaultValue, up, nvDriver>>(); } };
Мы не хотим постоянно лазить в EEPROM, когда пользователь хочет прочитать параметр
Метод Get() – крайне простой, он просто возвращает копию из ОЗУ
template<typename NvList, typename T, const T& defaultValue, tSeconds up, auto& nvDriver> class AntiWearNvData { public: T Get() const { return nvItem.data; } };
Теперь самое интересное, чтобы проинициализировать копию в ОЗУ правильным значением, необходимо при запуске устройства считать все записи нашего параметра и найти запись с самым большим индексом. Наверняка есть еще разные методы хранения данных, например, связанный список, но использование индекса, показалось мне ну прямо очень простым.
template<typename NvList, typename T, const T& defaultValue, tSeconds up, auto& nvDriver> class AntiWearNvData { public: ic ReturnCode Init() { const auto ind = FindLastRecordPosition(); constexpr auto startAddress = GetAddress(); const auto calculatedAddress = startAddress + recordSize * ind; return nvDriver.Get(calculatedAddress, reinterpret_cast<tNvData*>(&nvItem), sizeof(tAntiWear)); } … private: ic std::uint32_t FindLastRecordPosition() { // Метод поиска индекса приводить не буду, оставлю на откуп читателям // Здесь нужно считать все записи парамтера и найти параметр с самым // большим индексом, пока предположим, что запись с самым большим индексом // находится на позиции 0. return 0U; } };
В общем-то и все класс готов, полный код класса:
Полный код класса template<typename NvList, typename T, const T& defaultValue, tSeconds up, auto& nvDriver> class AntiWearNvData { public: ReturnCode Set(const T& value) const { tAntiWear tempData = {.data = value, .index = nvItem.index}; // К размеру типа прибавляем 4 байта индекса и умножаем на номер индекса записи. // Умножаем на 2, чтобы драйвер мог хранить копиию записи для проверки целостности const auto calculatedAddress = GetCalculatedAdress(nvItem.index); ReturnCode returnCode = nvDriver.Set(calculatedAddress, reinterpret_cast<const tNvData*>(&tempData), sizeof(tAntiWear)); // std::cout << “Write at address: ” << calculatedAddress << std::endl; //Если запись прошла успешно, то обновляем кэшируемую копию параметра, а также смещаем индекс на 1, для следующей записи if (!returnCode) { nvItem.data = value; //если колчиство записей превысило нужное количество, обнуляем индекс, начинаем писать с начального адреса nvItem.index ++; } return returnCode; } ic ReturnCode Init() { const auto ind = FindLastRecordPosition(); constexpr auto startAddress = GetAddress(); const auto calculatedAddress = startAddress + recordSize * ind; return nvDriver.Get(calculatedAddress, reinterpret_cast<tNvData*>(&nvItem), sizeof(tAntiWear)); } T Get() const { return nvItem.data; } ic ReturnCode SetToDefault() { ReturnCode returnCode = nvDriver.Set(GetCalculatedAdress(nvItem.index), reinterpret_cast<const tNvData*>(&defaultValue), sizeof(T)); return returnCode; } private: ic size_t GetCalculatedAdress(std::uint32_t ind) { constexpr auto startAddress = GetAddress(); size_t result = startAddress + recordSize * ((ind % recordCounts)); assert(result < std::size(EEPROM)); return result; } ic std::uint32_t FindLastRecordPosition() { // Здесь нужно считать все записи парамтера и найти параметр с самым большим индексом, пока предположим, // что запись с самым большим индексом находится на позиции 1 – Там записано число 15 с индексом 5. return 1U; } constexpr ic auto GetAddress() { return NvList::template GetAddress<const AntiWearNvData<NvList, T, defaultValue, up, nvDriver>>(); } struct tAntiWear { T data = defaultValue; std::uint32_t index = 0U; }; inline ic tAntiWear nvItem; public: ic constexpr auto recordSize = sizeof(nvItem) * 2U; ic_assert(eepromPageSize/recordSize != 0, “Too big parameter”); ic constexpr size_t recordCounts = (eepromPageSize/recordSize) * eepromLife / (eepromWriteCycles * up); };
По аналогии с CachedNvData из прошлой статьи, все параметры должны быть зарегистрированы в едином списке, причем, в этом списке мы можем регистрировать как и CachedNvData, так и наши AntiWearNvData параметры.
Я немного переделал список, так как IAR компилятор все еще не понимает много фишек из С++17, и собственно теперь список принимает только типы, а не ссылки на параметры. Кроме того, теперь у него появились методы SetToDefault и Init. Первый нужен, например, чтобы сбросить все параметры в их начальное значение. А второй, чтобы проинициализировать кешируемые в ОЗУ копии.
template<const tNvAddress startAddress, typename …TNvVars> struct NvVarListBase { ic ReturnCode SetToDefault() { return ( … || TNvVars::SetToDefault()); } ic ReturnCode Init() { return ( … || TNvVars::Init()); } template<typename T> constexpr ic size_t GetAddress() { return startAddress + GetAddressOffset<T, TNvVars…>(); } private: template <typename QueriedType, typename T, typename …Ts> constexpr ic size_t GetAddressOffset() { auto result = 0; if constexpr (!std::is_same<T, QueriedType>::value) { //можно дописать алгоритм, чтобы все параметры были выравенны по странице. result = T::recordSize * T::recordCounts + GetAddressOffset<QueriedType, Ts…>(); } return result; } };
Также в CachedNvData я добавил параметр recordSize и recordCounts = 1. Чтобы расчет адреса параметра был унифицирован для разного типа параметров.
Результат
Собственно все, теперь мы можем регистрировать в списке любые параметры:
struct NvVarList; constexpr NvDriver nvDriver; using tString6 = std::array<char, 6U>; inline constexpr float myFloatDataDefaultValue = 10.0f; inline constexpr tString6 myStrDefaultValue = { “Popit” }; inline constexpr std::uint32_t myUint32DefaultValue = 0x30313233; inline constexpr std::uint16_t myUin16DeafultValue = 0xDEAD; constexpr CachedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData; constexpr CachedNvData<NvVarList, tString6, myStrDefaultValue, nvDriver> myStrData; constexpr CachedNvData<NvVarList, std::uint32_t, myUint32DefaultValue, nvDriver> myUint32Data; constexpr AntiWearNvData<NvVarList, std::uint32_t, myUint32DefaultValue, 60U, nvDriver> myUint32AntiWearData; constexpr AntiWearNvData<NvVarList, float, myFloatDataDefaultValue, 60U, nvDriver> myFloatAntiWearData; struct SomeSubsystem { ic constexpr auto test = CachedNvData < NvVarList, std::uint16_t, myUin16DeafultValue, nvDriver>(); }; //*** Register the Shadowed Nv param in the list ***************************** struct NvVarList : public NvVarListBase<0, decltype(myStrData), decltype(myFloatData), decltype(SomeSubsystem::test), decltype(myUint32Data), decltype(myFloatAntiWearData), decltype(myUint32AntiWearData) > { };
Замечу, что пользователю параметров нужно только объявить параметр и список, а вся портянка с кодом, до этого, пишется один раз. Используются параметры точно также как и CachedNvData.
int main() { NvVarList::SetToDefault(); ReturnCode returnCode = NvVarList::Init(); myFloatData.Set(37.2F); myStrData.Set(tString6{“Hello”}); myFloatAntiWearData.Set(10.0F); myFloatAntiWearData.Set(11.0F); myFloatAntiWearData.Set(12.0F); myFloatAntiWearData.Set(13.0F); myFloatAntiWearData.Set(14.0F); myUint32AntiWearData.Set(10U); myUint32AntiWearData.Set(11U); myUint32AntiWearData.Set(12U); myUint32AntiWearData.Set(13U); myUint32AntiWearData.Set(14U); myUint32AntiWearData.Set(15U); return 1; }
Что произойдет в этом примере, когда мы будем писать 10,11,12…15 в наш параметр. Каждый раз при записи, адрес параметра будет смещаться на размер параметра + размер индекса + размер копии параметра и индекса. Как только количество записей превысит максимальное количество, параметр начнет писаться с начального адреса.
На картинке снизу как раз видно, что число 15 с индексом 5 записалось с начального адреса, а 10 теперь нет вообще.
В данном случае после сброса питания, при инициализации, будет найдена запись с индексом 5 и значением 15 и это значение и индекс будут записаны в кэшируемую копию нашего параметра.
Вот и все, надеюсь в этой статье цель получилось пояснить более детально, спасибо за то, что прочитали до конца.
Как обычно код с примером.
Источник