EEPROM — электрически стираемое перепрограммируемое постоянное запоминающее устройство, которое позволяет записывать и перезаписывать данные, подобно тому как это происходит для обычных дисковых накопителей. Flash является разновидностью EEPROM и характеризуется тем, что запись производится блоками, а удаление осуществляется постранично. Перезаписать ячейку памяти в произвольное значение на Flash нельзя. Память типа EEPROM обычно позволяет перезаписывать большее количество раз по сравнению со Flash, зато запись на Flash осуществляется за более короткое время. Еще в пользу Flash можно добавить, что этот тип памяти дешевле в изготовлении, по сравнению с EEPROM, поэтому на данный момент преобладают устройства, в которых мало EEPROM и много (относительно) Flash памяти.
Для программиста взаимодействовать со flash памятью неудобно. Появляется необходимость в создании библиотеки, которая будет осуществлять:
- поиск данных по ключу за минимально возможное время
- поддержку целостности данных при сбоях во время записи
- использование минимума оперативной памяти для хранения вспомогательных структур
- циркуляцию использования страниц (wear-leveling)
- абстрагирование от конкретной задачи, ОС и устройства
Для осуществления первого требования необходимо решить как ссылаться на данные. Два простых способа:
ссылка по имени, ссылка по названию. Первый вариант, по типу файловых систем, приведет к излишним накладным расходам, необходимо будет ограничивать длину названия (как это делается в FAT), и скорее всего будет для многих встраиваемых систем ограничением на пути к использованию. Поэтому я выбрала решение на основе ссылки по идентификатору. В случае 16-битного значения, существует 65536 различных идентификаторов. Для многих задач такого количества достаточно, учитывая значительные ограничения по ресурсам во встраиваемых системах.
Для чтения данных с носителя, необходимо знать количество записанных данных. Поэтому за 2 байтным идентификатором и перед записанными данными, соответствующими идентификатору, следует размер данных.
Для удовлетворения условия целостности, после данных следует 16 битная контрольная сумма, вычисляемая по алгоритму longitudinal parity check от идентификатора, длины и данных.
Приходим к следующему формату хранения данных:
| ID (16 бит) | LENGTH (16 бит) | DATA (LENGTH байт) | CHECKSUM (16 бит) |
Равномерное использование flash страниц (wear leveling) является ключевым алгоритмом для многих производителей устройств хранения на основе flash памяти. Алгоритмы известны в общих чертах, хотя готовых реализаций я не нашла. Я выбрала самый простой вариант: использование алгоритма round robin. Помимо страничного закольцовывания, нужно хранить и поддерживать дополнительную информацию. Ввиду этого каждая страница имеет заголовок, состоящий, как минимум, из статуса страницы. Для целей реализации хватило трех типов статусов: VALID, EMPTY, RECEIVING.
Страницы типа VALID являются страницами, на которых располагаются данные и/или на которые можно записать
необходимую информацию в соответствии с вышеприведенным форматом данных. Для VALID страниц после статуса хранится их порядковый номер. Страницы типа EMPTY не содержат данных и могут быть аллоцированы. Страницы типа RECEIVING — страницы, которые находятся в состоянии перехода из статуса EMPTY в VALID.
В процессе обновления данных может происходить фрагментация страниц. То есть на странице может оказаться много места, которое уже не может быть занято данными и не может быть освобождено простым способом ввиду того, что flash память стирается блоками, обычно кратными странице. Поэтому необходимо регулярно проводить дефрагментацию. На данный момент используется некоторая простая эвристика определения степени замусоривания: если половина страницы уже не может быть использована, то страница перераспределяется.
Во время инициализации VIrtEEPROM производится присваивание переменным необходимых значений, а также восстановление системы после сбоя, проверка валидности хранимых значений. Все RECEIVING страницы стираются и переводятся в статус ERASE. Заполняется массив, задающий правильный логический порядок страниц, а также массив валидных и занятых страниц.
На страницах типа VALID могут быть обнаружены несколько валидных элементов, имеющих одни и те же идентификаторы. В этом случае рассматривается самое последнее вхождение как наиболее новое.
Выше были перечислены требования и основные нюансы реализации. Далее обсудим API взаимодействия с VirtEEPROM.
В самом начале необходимо создать в единственном экземпляре текущий статус виртуального EEPROM'a, что осуществляется вызовом функции veeprom_create_status(), которое возвращает указатель на структуру типа veeprom_status.
Далее нужно сообщить адрес начала отображаемой flash памяти c помощью функции
veeprom_vstatus_init(veeprom_status *vstatus, uint16_t *addr), где vstatus — ранее созданный указатель на структуру veeprom_status, addrs — указатель, на область памяти начала flash памяти.
После этого необходимо вызывать veeprom_init(veeprom_status *s), которая возвращает 0 (OK), в случае, если не произошло ошибок в процессе инициализации.
Для поиска поиска данных по идентификатору используется функция veeprom_read(uint16_t id, veeprom_status *vstatus), которой в качестве параметров необходимо передать проинициализированный указатель на структуру vtstatus. Функция возвращает либо NULL в случае, если данные не найдены, либо указатель на структуру vdata*, которая в поле p содержит указатель, где записаны данные в вышеописанным формате.
Для записи необходимо указать идентификатор через параметр id, передать указатель на данные через data, передать размер данных в параметре length, так же передать указатель на veeprom_status в функцию veeprom_write(uint16_t id, uint8_t *data, int length, veeprom_status *vstatus). В случае успешной записи, функция возвращает 0. Если данные с переданным идентификатором уже были записаны, то значение обновится новой информацией.
Для удаления используется функция veeprom_delete(uint16_t id, veeprom_status *vstatus), которой передаётся удаляемый идентификатор и указатель на veeprom_status. В случае успеха функция возвращает 0.
Код виртуального EEPROM (VirtEEPROM) находится на стадии концепта. Уже сейчас есть готовая и рабочая реализация, основанная на флеш эмуляторе. Для более детального рассмотрения исходников можно их скачать и запустить:
$ git clone https://github.com/ninaevseenko/virteeprom.git
$ cd virteeprom/verification/
$ make
$ ./examine
Помимо этого, у кого есть STM32F3-Discovery, то можно прошить и посмотреть код VirtEEPROM в действии. Все необходимое находится в virteeprom/verification/stm32f3discovery/. После запуска на UART1 печатаются различные отладочные сообщения, и в котором запускается shell со следующим набором команд:
- initflash — инициализирует необходимые структуры для работы с virteeprom
- wipeflash — стирает все, кроме кода и данных программы
- writeflash — записывает 100 однобайтовых значений
- readflash — считывает 100 однобайтовых значений
Из скриншота ниже видно, что до инициализации было 34184 байт свободной памяти, после инициализации и записи 100 значений — 30544. То есть накладные расходы на хранение каждого отдельного элемента, позволяющего осуществить быстрый поиск по идентификатору, составляет примерно 36 байт.
В дальнейших планах сделать код более абстрактным и независимым от ОС и устройств, адаптировать к различным целевым платформам.
Комментарии (25)
Sergey_datex
14.10.2015 13:04Вопрос автору статьи — а где коррекция ошибок? Это самое сложное в работе с «сырым» NAND без хардварного модуля ECC.
Основное отличие EEPROM от NAND — это необходимость (обязательная) использования коррекции ошибок. Глубина коррекции диктуется типом используемой памяти. В даташите на конкретную память почти всегда указано минимально допустимую корректирующую способность для обеспечения прогнозируемого ресурса работы. Минимально для SLC — 4 бита на страницу 528 байт. Типично для современной TLC — 60 бит на 1 килобайтовый диапазон.
Ну и поправьте немного в статье — NAND это тоже подкласс EEPROManvoebugz
14.10.2015 13:40Сейчас нету коррекции. Либо записали, либо не записали.
«NAND Flash является разновидностью EEPROM» — как раз то, что вы имеете ввиду.alabram
14.10.2015 13:57Сейчас нету коррекции. Либо записали, либо не записали.
Боюсь, что реализовать коррекцию битовых ошибок нужно первым делом. Потому как вероятность варианта «не записали» или «записали, а завтра не прочитали» очень велика.
Да и вообще вы себе задачу выбрали неблагодарную. Я не специалист, но мне кажется лучше использовать SPI flash, чем пытаться имитировать ее с помощью NAND.anvoebugz
14.10.2015 14:21Есть 256 кбайт встроенной флешки. Хочется использовать имеющийся ресурс без дополнительных затрат.
Sergey_datex
14.10.2015 14:48Вы что-то путаете. Встроенная флешка не имеет отношения к NAND. Мне кажется вы запутались в терминах и называете NANDом то, что является внутренней NOR памятью контроллера.
MrYuran
14.10.2015 13:14+1В МК вообще и конкретно STM32 набортная флешь разбита на страницы по 1 или 2кБ, оттестирована и полностью готова к употреблению. Никаких спецполей не предусмотрено, контроль целостности и исправности полностью на плечах программиста. Вообще, если надо хранить много данных, обычно ставят сбоку обычную SPI-флешь, EEPROM или FRAM, у которой практически бесконечный ресурс.
VT100
14.10.2015 14:14Судя по статье — автор использует внешний чип NAND-Flash. Или нет?
anvoebugz
14.10.2015 14:19+2Я использую встроенный флеш микроконтроллера.
Sergey_datex
14.10.2015 14:50+2Тогда редактируйте статью, и заменяйте все NAND на NOR. Это совершенно другой тип памяти, с другой организацией и целевым назначением.
anvoebugz
14.10.2015 14:17SPI-флеш — может потребавать SPI шины, которая уже используется под другие нужды.
Чтение/запись в EEPROM осуществляется за несколько мс, для флешки — за несколько мкс.
Про FRAM не слышала. Киньте, пожалуйста, ссылку.MrYuran
14.10.2015 14:37+1Производит Ramtron, вот пример.
Также, TI встраивают FRAM в некоторые свои МК вместо флеши
JerleShannara
14.10.2015 14:40Если отбросить варианты 8-ми битных МК, то как правило линий CS у контроллера хватает с избытком. Другое дело, если вылезает ситуация, что свободных линий просто нет, но в этом случае можно поиграть с мультиплексором. Впрочем ваше решение имеет свою нишу — миниатюрность и энергопотребление тут будут намного вкуснее чем с внешними чипами.
boeing777
15.10.2015 10:26Как правило, внешние чипы EEPROM/FLASH с SPI не требуют высокой частоты интерфейса, поэтому вполне можно использовать программный SPI, реализованный на GPIO. В нашей промышленной разработке так и произошло — при переходе с stm32l (у которого был EEPROM на борту) к stm32f стали использовать внешний чип. Связано это, главным образом, с максимальным количеством циклов перезаписи: у встроенного flash stm32f гарантируется всего 10к циклов. Нам этого мало :)
anvoebugz
15.10.2015 14:37Понятно. Тем не менее, есть ресурс, который можно использовать для чего-то.
Sergey_datex
14.10.2015 14:56Разобрались таки. Автор описывает использование встроенной Flash памяти для организации виртуального EEPROM. Такое решение кстати используется производителями жестких дисков, например Toshiba (бывшая Hitachi), Samsung.
kacang
15.10.2015 05:30+1А вот заводская реализиция для PIC-ов, если кому интересно
anvoebugz
15.10.2015 14:31Спасибо за ссылку, очень интересно.
Посмотрела.
Они себе задачу упростили тем, что записывают данные фиксированного размера (пишут int'ы):
unsigned char DataEEWrite (unsigned int data, unsigned int addr);
Еще довольно странно, что не проверяется осуществилась ли запись:
DataEEWrite(DEEdata,DEEaddr1);
value1 = DataEERead(DEEaddr1);
Nop();
VT100
ToDo:
1. Учитываете-ли заводскую информацию о битых страницах (если брать современные NAND со страницей в 2112 байт, то это записано в последних 64 байтах)?
2. Планируете-ли вписывать ECC в эти 64 байта на живых страницах?
Sergey_datex
Уточню
1) Современные имеют размер страницы 16к.
2) Обычно плохой блок маркирован нулем в первом байте Spare области.
3) Современная память TLC часто имеет неисправные столбцы, список которых можно запросить у самого чипа.
Столбцы нужно вырезать на этапе чтения страницы в буфер.
anvoebugz
1. Сейчас размер страниц не имеет значения.
2.-3. Понятно, это нужно учесть.
anvoebugz
1. Это обязательно надо будет учитывать, при наличии такой информации
2. Не очень понятен вопрос. По идее ECC полезно при записи во флешку с выставленным LowLatency,
чтобы реже получать ошибки. То есть, да, можно в реализацию flash_write добавить коррекцию
для устройств, которые имеют контроллер, в котором есть ECC.
VT100
У Вас «чистый» чип NAND или со встроенным контроллером? Если «чистый», то обязанность учёта битых блоков и использования корректирующих кодов — на Вас.
anvoebugz
Встроенный.
VT100
В таком случае — Вы кардинально заблуждаетесь, встроенная память программ МК — всегда NOR. И статья требует переписывания (что-бы не вводить в заблуждение читателей и что-бы указать отличия предложенного метода от AN2594, AN4061 и AN4056).
anvoebugz
Да, это правда, встроенная — NOR. NAND на usb флешках.
Я поправила данное заблуждение в статье.
Я напишу новую статью про отличия от метода, который по ссылке находится.