Введение
В этом материале мы рассмотрим возможные проблемы при работе с EEPROM. Часть из них связана с аппаратным стороной микроконтроллера, такие как повреждение EEPROM при пониженном напряжении питания и ограниченный ресурс EEPROM, а часть с программным обеспечением, например, при использовании EEPROM в прерываниях. Также рассмотрим способы решения этих проблем и методы повышения надежности хранения данных в EEPROM.
Повреждение EEPROM при пониженном напряжении питания
Одна из старых проблем AVR - это повреждение EEPROM данных при пониженном питании микроконтроллера. Это может происходить в двух случаях:
- Если напряжение питания ниже определенной величины, запись в EEPROM будет выполняться некорректно.
- При пониженном напряжении питания микроконтроллер сам может выполнять команды некорректно.
Этих повреждений EEPROM данных можно избежать, соблюдая следующие рекомендации:
- Нужно удерживать микроконтроллер AVR в состоянии сброса, если напряжение питания находится ниже нормы. Для этого можно использовать внешние супервизоры питания или встроенный детектор пониженного питания - Brown-out Detector (BOD). Встроенный детектор управляется с помощью fuse битов микроконтроллера - BODEN и BODLEVEL. BODEN - разрешает/запрещает работу детектора, а BODLEVEL - определяет его уровень срабатывания.
Если сброс микроконтроллера происходит во время процесса записи в EEPROM, то операция записи будет завершена только при достаточном уровне напряжения.
- Также в многие разработчике рекомендуют не использовать 0-ую ячейку EEPROM`a, поскольку именно ее содержимое чаще всего повреждается при снижении питания микроконтроллера.
Проблемы с EEPROM из-за прерываний
Операция записи в EEPROM состоит из нескольких шагов. Вспомним эту последовательность:
1. Ожидаем готовности EEPROM, опрашивая бит EEWE регистра EECR.
2. Устанавливаем адрес в регистре EEAR.
3. Записываем байт данных в регистр EEDR.
4. Устанавливаем основной флаг разрешения записи EEMWE регистра EECE
5. Устанавливаем флаг разрешения записи EEWE регистра EECE
Бит EEWE должен быть установлен в течении 4-ех тактов после установки бита EEMWE. Если этого не произойдет по причине прерываний, то запись в EEPROM не будет произведена. Этого легко избежать, если запретить прерывания перед 4-м шагом, а после 5-го снова разрешить их.
Однако есть еще один подводный камень. Если прерывание возникло после 1-го, 2-го или 3-го шага, и в прерывании тоже используются операции с EEPROM (чтение или запись), то запись может не состояться, или запишутся не те данные и не туда, потому что содержимое регистров EEAR (адрес) и EEDR (данные) будет изменено.
Описанное выше касается и процедуры чтения EEPROM.
Лечить это можно следующими способами:
- Не использовать операции чтения и записи EEPROM в прерываниях.
Это особенно касается операции записи, потому что она медленная и выполняется с использованием внутреннего RC генератора. Например, для mega16 в даташите указано, что при записи в EEPROM используется внутренний RC генератор с частотой 1 МГц (независимо от установок fuse битов CKSEL) и время записи составляет 8.5 мс. Для прерывания это очень долго.
- Запрещать прерывания на время всей процедуры записи (чтения) EEPROM, то есть в самом начале.
- Сохранять в начале обработчика прерывания содержимое регистров EEAR (адрес) и EEDR (данные), а перед выходом восстанавливать их.
- Использовать флаги (семафоры) для сигнализации о выполнении работы с EEPROM.
Перед выполнением записи в основном цикле программы (или задаче, если используется ос) устанавливать программный флаг, а в прерывании (или другой задаче) проверять его.
Ресурс EEPROM
EEPROM имеет ограниченный ресурс. Atmel гарантирует, что количество циклов перезаписи EEPROM составляет не меньше 100000. Цифра довольно большая, однако и она может быть достигнута, если записывать в EEPROM часто и на протяжении долгого времени.
Есть два приема по "увеличению" ресурса EEPROM.
Первый - простой и состоит в том, чтобы записывать в EEPROM данные, только если они изменили свое значение.
__eeprom uint8_t data;
uint8_t newData;
...
if (newData != data) {
data = newData;
}
Второй- хитрый и состоит в том, чтобы хранить данные не в одной ячейки памяти (или группе ячеек, если речь идет о многобайтных переменных), а в нескольких, и записывать в них по очереди.
Допустим, нам нужно хранить в EEPROM один байт. Выделяем под него 8 байтов и каждый раз записываем в следующую ячейку, когда доходим до последней ячейки, то записываем в первую. И так по кругу, как в кольцевом буфере. Если каждая ячейка EEPROM имеет ресурс 100000, то записывая в 8 ячеек по очереди, мы получаем ресурс перезаписи байта 800000.
EEPROM и оптимизация компилятора
Переменные, которые объявлены, но не используются, часто удаляются компилятором в процессе оптимизации. Если такие переменные нужны, перед ними следует добавлять ключевое слово volatile.
//для IAR AVR
volatile __eeprom char name[] = "prog 12.3";
Если используются свои функции для работы с EEPROM, то могут возникнуть проблемы при высоких уровнях оптимизации компилятора. Компилятор может объединить одинаковые (с его точки зрения) части кода в одну подпрограмму и нарушить логику работы вашей функции. Чтобы этого не происходило нужно или запрещать оптимизацию данной функции, или отключать перекрестную оптимизацию (cross call optimization) для функции или файла. Как это делается зависит от компилятора. Как правило, для этого существуют определенные ключи и прагмы.
Программные способы повышения надежности EEPROM
Один из простых способов повышения надежности хранения данных в EEPROM - это метод мажоритарного резервирования. Суть метода заключается в том, что для хранения данных выделяется нечетное количество ячеек памяти - N. При сохранении данных - запись производится во все выделенные ячейки. При чтении - читаются тоже все, но решение относительно содержимого принимается на основе равенства (N+1)/2 ячеек.
Рассмотрим пример тройного мажоритарного резервирования байта данных. Для сохранения байта используются три байта EEPROM, а решение о содержимом принимается на основании равенства 2 байтов. Код приведен для компилятора IAR AVR.
//функция сохранения
void EEPROM_SaveByte(uint8_t value, uint8_t __eeprom *buf)
{
buf[0] = value;
buf[1] = value;
buf[2] = value;
}
//функция считывания
uint8_t EEPROM_LoadByte(uint8_t *value, uint8_t __eeprom *buf)
{
uint8_t a = buf[0];
uint8_t b = buf[1];
uint8_t c = buf[2];
if ((a == b)||(a == c)){
*value = a;
return 0;
}
else {
if (b == c){
*value = b;
return 0;
}
}
return 1;
}
....
//пример использования
__eeprom uint8_t buffer[3];
uint8_t data;
EEPROM_SaveByte(125, buffer);
EEPROM_LoadByte(&data, buffer);
В случае повреждения одной из ячеек или неудачной записи в одну из них, такой метод поможет избежать ошибки. Но если неправильное значение запишется в две ячейки, то прочитанные данные будут неверными.
Другой метод повышения надежности хранимых данных состоит в использовании контрольных сумм, например CRC. Я не использовал этот метод в своей практике, поэтому о нем мало чего могу сказать.
На этом все...
Comments
Нет, не volatile - он тут не нужен.
В IAR для этого есть соответствующее ключевое слово:
__root __eeprom char name[] = "prog 12.3";
для AvrGcc вроде так:
Code:
#include <avr/eeprom.h>
__attribute__ ((used)) EEMEM char name[]="prog 12.3";
Может и подойдёт, но volatile не для того задумывался. Хоть в этой ситуации и выручает.
Off. У вас на сайте работа с переменными расположенными во flash под AVRGCC нигде не рассматривалась ?
2. Ну раз у нас мажорирование - почему бы не сделать последний штрих - если какая-то ячейка из трёх не совпадает - сразу и попытаться исправить ее на основе найденного правильного числа. Может, она не сбилась вовсе, а просто недописалась когда-то из-за внезапной потери питания. Зачем ждать пока вторая отвалится и мы останемся ни с чем?
3. Есть еще спорный и философический аспект. Некоторые гуру утверждают, что ЕEPROM - то же самое как и флеш. То есть пишутся страницами, просто это скрыто от нас контроллером ЕЕПРОМ. И что понятие максимального количества перезаписей 100000 относится не к каждому байту а к странице. На эту тему в эпоху расцвета AVR было много споров, но не в этом суть. Суть в том, что тогда мажорирование имеет смысл только когда мажорируемые ячейки расположены "далеко" друг от дружки, заведомо в разных "страницах". В-общем - дело хозяйское, но я, например, сам вручную разбрасываю адреса для мажорируемых ячеек подальше.
2. Попробую это добавить позже. И про __root тоже.
3. О таких тонкостях не слышал.
Атмеловская апнота (AVR101: High Endurance EEPROM Storage) тверждает, что используя круговой буфер, повысить ресурс eeprom можно. Про страницы там ничего не говорится. Почему им не верить?
P.S. В xmega eeprom действительно организована страницами.
Quote: Смысл мажорирования не в том, чтобы ресурс сберечь, а в том чтобы иметь возможность гарантированно восстановить значение переменной (или иметь признак того, что был апдейт переменной, во время которого произошёл сбой).
Quote: Сомнительное действие. Да и eeprom при включённом BOD-е и правильной схеме не слетает (как практика показывает).
Так что не так всё просто :)
Далее.
Я:"Суть в том, что тогда мажорирование имеет смысл только когда мажорируемые ячейки расположены "далеко" друг от дружки, заведомо в разных "страницах".
---
Вы: "Смысл мажорирования не в том, чтобы ресурс сберечь, а в том чтобы иметь возможность гарантированно восстановить"
....
А разве я об том, что мажорирование сберегает ресурс? Игде?)) Я о том, что мажорирование самО не должно зависеть от выносливости СТРАНИЦЫ EEPROM.
Далее.
Вы."Сомнительное действие. Да и eeprom при включённом BOD-е и правильной схеме не слетает (как практика показывает)".
Опять не туда! При чем тут слетание по питанию? Речь об зависимости коррупции ячеек от endurance страницы EEPROM! Правильная разводка и endurance КАК связаны? С BOD'ом тоже - как?
Вам, видимо, предстоит много "открытий"))
Тут ведь как посмотреть насчет "тратить на одну переменную по странице". С одной стороны страницы-то у ЕПРОМА маааленькие, по 8 байтиков. Наверно, специально сделаны такими. Типа - отошел на 8 адресов - это уже другая страница. Ну например давайте разместим 6 мажорированных байтовых констант. Это могут быть адреса ЕЕПРОМ: 1-6(первая копия всех 6-ти констант), 9-14(вторая копия- уже в другой странице!) ,17-22(третья копия - опять в следующей странице). То есть 6 мажорированных констант - всего-навсего 22 байта. Ерунда.
А теперь опять Вас напугаю.
То, что я показал - это для хранения калибровочных и прочих констант. А вот если например хранить счетчик количества записанных данных логгера (который например каждую минуту меняется) - это уже не пойдёт! Тут применяются другие технологии - например, Spread (размазывание, распрыскивание по ЕЕПРОМ чтобы повысить количество циклов перезаписи. Это когда старшая, редко меняющаяся часть счетчика стоит на фиксированном месте и хранит текущий индекс буфера для средней части счетчика (это небольшой, байта 64, кольцевой буфер, хранящий в свою очередь, текущий индекс младшего байта (это тоже кольцевой буфер ~256 или 512 или даже 1024 байта). Тут - да. Тут Вы правы. Тут много памяти надо.Зато повысится количество циклов до миллионофф.
Уфф! Ну, на этом хватит. Удачи!
"Атмеловский" стиль? Ну да, eeprom у них наверное не той системы.
Quoting Peter:
Вы приводите фразу, относящуюся к внешнему программированию. Изнутри доступ вполне может быть байтовым.
Например, из описания eeprom xmega(appnone AVR1315):
Quote: Стираются только те байты, в которые производилась запись (несмотря на то что страничный буфер общий).
Quoting Peter:
Видимо нужен эксперимент, показывающий есть или нет влияния страничной организации на ресурс eeprom.
Quoting Peter:
Правильное питание и BOD обеспечивают надёжную работу всего устройства и eeprom в часности.
Quoting Peter:
Да-да, просвященья дух мне их наверное заготовил множество... :)
Надеюсь, по английски читаете?
Читаем: AVR C Runtime Library - Ошибки: bug #22240, Add EEPROM Page Size to header.
"The factory has confirmed, via my FAE, that the listed Endurance figures are on a page bases, and not a byte bases".
(http://savannah.nongnu.org/bugs/?22240)
Далее - читаем Using the EEPROM memory in AVR-GCC. Dean Camera.
One important fact to note is that the AVR's internal EEPROM memory has a limited lifespan of 100,000 writes per EEPROM page...
(http://deans-avr-tutorials.googlecode.com/svn/trunk/EEPROM/Output/EEPROM.pdf)
Вполне. С пониманием прочитанного, правда сложнее...
Quoting Peter:
Одна бабка на базаре сказалаFAE утверждает, что ресурс относится к странице, документация утверждает противоположное. В общем вопрос спорный...Непонятно что нужно сделать чтобы внести ясность, запросить суппорт или затереть eeprom у 1-2 мег...Но что это докажет? Мы к тому же не знаем методики определения eeprom endurance.Quoting Peter:
Откуда он этот факт узнал?
Quoting Peter:
Ссылка битая - файл не качается.
Эх, дожили...
И ссылка не битая.
И отлично она качается.
И по приведенной фразе легко ищется в поисковиках.
И стыдно считать Dean Camera (AVR Applications Engineer) "одна бабка на базаре сказала".
В-общем, разговор принимает ненужную лиричность. Кому надо - спрашивайте по существу.
Верю атмеловским инженерам (они гоаорят про page, но не говорят про 100000/байт). И чтобы явно написано что 100000/байт - тоже не видел. Покажите.
Хорошо, а application notes верите? Их тоже атмеловские инженеры составляли.
Quoting Peter:
Это упущение с их стороны. Надо такие параметры указывать.
Quoting Peter:
Апнота для mega16 - повышают ресурс eeprom записью в 8-байтовый массив:
www.atmel.com/Images/AVR101.zip
www.atmel.com/Images/doc2526.pdf
Указывают, что endurance возрастает до 800000 циклов.
Для xmega приводил цитату где-то выше.
Даташиты что говорят? Дайте 100000/байт.
И во-вторых - дайте выдержки со ссылок -уважайте моё время! Там должно быть написано явно и ч0тко: выносливость - 100000/байт. - как привел - явно и ч0тко: PAGESIZE 8 bytes.
Апноты также являются документацией от производителя и дополняют даташит. Не надо их игнорировать.
Quoting Peter:
"явно и ч0тко" не получится - там вникать в алгоритм нужно.
Алгоритм простой - байт размазывается по 8-ми байтовому массиву(адреса 0-7). При этом endurance в итоге равна 800К. Что даёт 100К/байт.
Code:
/******************************************************/
/* Define the number of levels in the buffer, */
/* - eight levels will guarantee 800k writing of the parameter */
/******************************************************/
#define EE_PARAM_BUFFER_SIZE 8
#define EE_STATUS_BUFFER_SIZE EE_PARAM_BUFFER_SIZE
Не считаю. Потому как про Dean Camera и про Bob Paddock впервые слышу. (Темнота я бескультурная...:) )
Quoting Peter:
Тогда подробности не помешают. Какая фабрика (atmel их продал года 3-4 назад)? К каким конкретно avr-ам относится его утверждение? К Mega или xmega?
К тому же официальная документация утверждает противоположное .
Эти споры велись 10 лет назад если не больше.
Ну почему я должен Вам искать по этим вашим интернетам?
Ладно. Про страничную выносливость высказались даташЫты и уважаемые атмелом люди. А где подтверждения выносливости 100000/байт?
Возможно информация устарела - тех. процессы сменились, фабрики проданы...
Quoting Peter:
Про "страничную выносливость" в даташитах не говорится, так же как и про байтовую. Там пишется про "write/erase cycle".
Quote: Пишут что каждый байт может быть перезаписан и таких циклов - 100К.
"The EEPROM is organized in pages".
Что еще непонятно? ))
Да! Но только если также НЕ ПИСАТЬ В СОСЕДНИЙ АДРЕС ЭТОЙ ЖЕ СТРАНИЦЫ! ))
Они правы! Это так! 100000 в одну!
Зер гут!
Quoting Peter:
Это уже ваши домыслы. Там этого не написано.
Если принять ваше утверждение за правду, то реальные ресурс eeprom 100К/8, то есть 12,5К. В моих программах нет "размазывания" сохраняемых данных по разным страницам. И есть данные лежащие на одной странице и часто перезаписываемы е (конечно с учётом ресурса в 100К циклов на байт).
Думаю что за 10+ лет, что я работаю с АВР я бы уже не раз напоролся бы на отказ eeprom из-за ресурса в 12К. Но нет - не попадалось такое.
Потому - не верю. Разве что в суппорт написать запрос...но по буржуйски писать лениво...
Вот что в даташите:
"The EEPROM is organized in pages".
Что еще непонятно? ))
Не "записывается", не "программируетс я" - а изначально "Организована" как Страничная!
Верить не верить - вопрос свободы совести.
По мне - если есть несколько трактовок ситуации - применяй самую ограничивающую.
Хорошо, что это сходится с мнением инсайдеров - непосредственно из Атмела))
Мне такие сбои не попадались. Видимо эта рекомендация может считаться устаревшей и для современных мег не актуальной (у classic avr такая проблема вроде бы была). Или нет?
А вообще это не баг, а фича - типа если хотите уменьшить ток (у avr, ага :) или вам на не на eeprom, выключаете BOD и извращаетесь с культурой использования eeprom.
Правда, отвечают на свои же вопросы))
И всячески уворачиваются от вопроса:
В какой-то момент программы в Вашей EEPROM пусть так:
Ячейка с адресом 0 содержит FF.
Ячейка с адресом 1 содержит 00.
Записать в ячейку 0 число 00.
На сколько циклов уменьшится?
Вот это и интересТно!
RSS feed for comments to this post