Как проверить eeprom на работоспособность
Перейти к содержимому

Как проверить eeprom на работоспособность

  • автор:

Arduino.ru

Скетч для проверки работоспособности портов и EEPROM

  • Войдите на сайт для отправки комментариев

13 ответов [Последнее сообщение]
Пт, 18/09/2015 — 18:22
Зарегистрирован: 28.12.2013

Написал универсальный скетч для проверки работоспособности портов и EEPROM памяти. Ищутся добровольцы для проверки в железе и доработки тестовой программы. Мой код проверен на ATmega128A, ATmega8A при этом никаких правок в коде делать не нужно. Количество цифровых и аналоговых выводов определяется автоматически.

Что еще нужно сделать:

1. Проверка аппаратных прерываний, таймеров, прерываний по переполнению таймера.

2. Прошивка на асме для проверки RAM, Stack Pointer Register, Status Register, регистров R0-R25, X,Y,Z

Что уже было сделано: Мой код при запуске ничего не делает, он ждет команду по UART. В терминал нужно отправить одну английскую букву для запуска нужного теста. Когда какой то тест был запушен, ч тобы его прервать нажмите кнопку ресет. Список комманд :

v — Выводит версию программы и краткое описание доступных тестов.

a — АЦП тест . Выводит значения всех аналоговых входов. Для этого вы подключаете переменный резистор поочередно к каждому аналоговому входу и смотрите как меняются значения.
i — Inputs test . В этом тесте включена подтяжка всех входов (INPUT_PULLUP). Тест в ыводит значения всех входов с низким уровнем на них. Вы берете провод подключенный на землю и через резистор 1КОм по очереди качаетесь каждого входа, в терминале должна появиться только одна надпись LOW с номером вывода. Этот тест позволяет найти замкнутые между собой пины или дорожки с обрывом (а также выводы со сгоревшими внутренними PULLUP резисторами )
o — Outputs test . Устанавливает все порты как выход с 1 на них. Вы берете тестер или светодиод с резистором и проверяете наличие высокого уровня на каждом выходе.
b — Blink . Тест наплатного светодиода.
0 — ZEROFILL встроенного EEPROM (тоесть заполнение нулями 0x00 во все ячейки ). Тест закончится, когда будет выведено «Done» в консоль. После этого запустите комманду » e » для вывода содержимого EEPROM в консоль и проверьте нет ли бытых ячеек
1 — 0xFF заполнение встроенного EEPROM. Тест закончится, когда будет выведено «Done» в консоль. После этого запустите комманду » e » для вывода содержимого EEPROM в консоль и проверьте нет ли бытых ячеек
p — Тест ШИМ на наплатном светодиод е . Тест надо полностью переписать, текущая реализация мне не нравится.

e — Выводит все содержимое EEPROM в терминал.

Цифровые порты 0 и 1 не тестируются. Этот тест предполагает, что выводы 0 ( RX ) и передачи данных 1 ( TX ) данных в порядке , раз у вас получилось загрузить скетч .

Код состоит из двух файлов. Последняя версия всегда доступна по ссылке

#define FIRST_PIN 2 #define PIN_LED 13 #define DELAY_ITERATION 200 #define DELAY_BW_TESTS 2000 #define DELAY_WARN 5000 #define ADC_NUMSAMPLES 8 int samples[ADC_NUMSAMPLES]; #include void setup() < Serial.begin(9600); pinMode(PIN_LED, OUTPUT); >void loop() < digitalWrite(PIN_LED, LOW); if (Serial.available() >0) < switch (Serial.read()) < case 'v': Serial.println("Arduino hardware test v. 1.1b"); Serial.println("a) ADC MANUAL test. Prints values from all analog pins"); Serial.println("i) Inputs MANUAL test. Prints all inputs with LOW level on them"); Serial.println("o) Outputs MANUAL test. Sets all ports to HIGH"); Serial.println("b) Blink. Tests onboard led"); Serial.println("0) Zerofill of built-in EEPROM"); Serial.println("1) 0xFF fill of built-in EEPROM"); Serial.println("p) Test PWM on on-board LED"); Serial.println(""); break; case 'a': //ADC test Tests all analog pins (A0-A7) while(true) < Serial.println("ADC test"); readAnalogValue(A0); readAnalogValue(A1); readAnalogValue(A2); readAnalogValue(A3); readAnalogValue(A4); readAnalogValue(A5); #if(NUM_ANALOG_INPUTS == 8) readAnalogValue(A6); readAnalogValue(A7); #endif digitalWrite(PIN_LED, LOW); delay(DELAY_BW_TESTS); >break; case 'i': //Inputs test. Tests all inputs in digital mode while(true) < Serial.println("Inputs test. Pins in LOW state:"); for(byte pin = FIRST_PIN; pin > delay(DELAY_BW_TESTS); digitalWrite(PIN_LED, !digitalRead(PIN_LED)); //Toggle LED > break; case 'o': //Outputs test. Tests setting all ports to HIGH Serial.println("Outputs test. All set to HIGH"); for(byte pin = FIRST_PIN; pin break; case 'b': //Blink. Tests onboard led while(true) < Serial.println("Blink"); digitalWrite(PIN_LED, HIGH); delay(1000); digitalWrite(PIN_LED, LOW); delay(1000); >break; case 'e': e2reader(); break; case '0': //Zerofill of built-in EEPROM Serial.println("Zerofill of EEPROM in 5s"); delay(DELAY_WARN); Serial.println("Started"); for(int i=0; i Serial.println("Done"); break; case '1': //0xFF fill of built-in EEPROM Serial.println("0xFF Fill of EEPROM in 5s"); delay(DELAY_WARN); Serial.println("Started"); for(int i=0; i Serial.println("Done"); break; case 'p': //Test PWM on on-board LED while(true) < Serial.println("LED PWM control"); // fade in from min to max in increments of 5 points: for (int fadeValue = 0 ; fadeValue // fade out from max to min in increments of 5 points: for (int fadeValue = 255 ; fadeValue >= 0; fadeValue -= 5) < analogWrite(PIN_LED, fadeValue); delay(40); >> break; > > > void readAnalogValue(int adcPin) < int i; for (i=0; i// average all the samples out int average=0; for (i=0; i average /= ADC_NUMSAMPLES; Serial.print("ADC PIN: "); Serial.print(adcPin); Serial.print(" value: "); Serial.println(average); digitalWrite(PIN_LED, !digitalRead(PIN_LED)); //Toggle LED >
#include void e2reader() < char buffer[16]; char valuePrint[4]; byte value; unsigned int address; uint8_t trailingSpace = 2; Serial.print("Dumping "); Serial.print(E2END + 1); Serial.println(" bytes from EEPROM."); Serial.print("baseAddr "); for(int x = 0; x < 2; x++)< Serial.print(" "); for(int y = 0; y < 25; y++) Serial.print("="); >// E2END is a macro defined as the last EEPROM address // (1023 for ATMEGA328P) for(address = 0; address 0 && address % 16 == 0) printASCII(buffer); sprintf(buffer, "\n 0x%05X: ", address); Serial.print(buffer); //clear the buffer for the next data block memset (buffer, 32, 16); > // save the value in temporary storage buffer[address%16] = value; // print the formatted value sprintf(valuePrint, " %02X", value); Serial.print(valuePrint); > if(address % 16 > 0) < if(address % 16 < 9) trailingSpace += 2; trailingSpace += (16 - address % 16) * 3; >for(int i = trailingSpace; i > 0; i--) Serial.print(" "); //last line of data and a new line printASCII(buffer); Serial.println(); > void printASCII(char * buffer) < for(int i = 0; i < 16; i++)< if(i == 8) Serial.print(" "); if(buffer[i] >31 and buffer[i] < 127)< Serial.print(buffer[i]); >else < Serial.print("."); >> >

Некоторые аспекты замены микросхем памяти в современных телевизорах

Анализ статистических данных по ремонту бытовой электронной техники показывает, что нередко причиной потери работоспособности аппаратуры и, в частности, современных телевизионных приемников, является выход из строя микросхем памяти (EEPROM). На практике наблюдается полная или частичная потеря работоспособности EEPROM, а также искажение содержащейся в них информации. В зависимости от неисправности в ряде случаев эксплуатация телевизоров становится невозможной, но во многих случаях не происходит полного отказа аппаратуры, а всего лишь теряются некоторые функциональные возможности. В телевизорах модельного ряда начала 90-х годов прошлого века микропроцессорная система управления телевизором заменяла применявшиеся ранее аналоговые механические регуляторы, а в EEPROM запоминались только значения основных аналоговых параметров (значения яркости, контрастности, насыщенности, параметров аудио-канала, настройки программ и др.). В телевизорах же более поздних разработок (подавляющее большинство моделей, выпущенных за последние 5 лет) в микросхемах памяти содержится также информация о конфигурации конкретной модели телевизора и об индивидуальных настройках различных функциональных узлов. Если микросхема EEPROM вышла из строя, для восстановления работоспособности телевизора и его исходных параметров необходима запись во вновь устанавливаемую микросхему определенной информации. Она может быть произведена в общем случае с помощью программатора EEPROM. С его же помощью можно прочитать содержимое демонтированной микросхемы и записать в новую микросхему считанные данные, а также записать в нее базовую прошивку для данной модели телевизора из банка данных, которая не учитывает индивидуальные параметры конкретного экземпляра телевизора. Системы управления телевизоров многих производителей имеют в сервисном меню опцию записи в EEPROM базовой прошивки усредненных параметров из ПЗУ микропроцессора. Это упрощает процедуру замены микросхемы памяти. Для индивидуальной настройки параметров в любом случае необходимо проводить коррекцию содержимого памяти с использованием сервисного меню. При отсутствии программатора для многих моделей телевизоров возможна установка требуемого содержимого EEPROM путем записи конкретных значений для каждого байта банка памяти либо регулировкой конкретных параметров в сервисном режиме. Но для осуществления этой операции необходимо, по меньшей мере, знать процедуру входа в сервисный режим. Ниже приведено краткое описание методики замены микросхем памяти и коррекции их информации в некоторых моделях телевизоров.

Замена микросхем памяти в телевизорах ORION

Необходимо отметить, что у массовых недорогих моделей телевизоров в микросхемах памяти, как правило, хранились лишь значения аналоговых параметров настройки. И при выходе из строя EEPROM, обычно сопровождающимся невозможностью запоминания вновь настроенных каналов, достаточно заменить неисправную микросхему на новую и провести настройку телевизора согласно инструкции. Очень часто такая неисправность встречается у телевизоров ORION разных моделей, например, 20 MS и 14J. Для замены можно также использовать и микросхемы с большим объемом памяти. Например, в данном случае вместо микросхемы 24С01 можно применить 24С02. 24С08. В этом случае просто весь объем памяти не будет использован. Применим такой вариант замены и для других телевизоров. Критерием возможности такого ремонта, как правило, является наличие на панели кинескопа подстроечных резисторов (не менее 3-х) для регулировки его режимов. Это говорит о том, что основные режимы кинескопа и, возможно, всей видеосистемы, устанавливаются без использования микропроцессора и памяти.

Замена микросхем памяти в телевизорах JVC

  • нажатием кнопки 1 отображается страница предустановок предпочтительных настроек изображения BRIGHT, STANDART и SOFT(рис. 2);
  • нажатием кнопки 2 отображается страница регулировок изображения для каждой системы цветности отдельно (рис. 3);
  • нажатием кнопки 3 вызывается страница установки параметров тракта ПЧ и видеопроцессора (рис. 4);
  • одновременным нажатием кнопок DISPLAY и PICTURE MODE вызывается страница установки системных констант (рис. 5);
  • возврат из выбранной страницы на главную страницу меню, а также выход из сервисного режима производится кнопкой MUTE.

Рис. 2. Страница предустановок предпочтительных настроек изображения

2. Перед включением телевизора с установленной новой микросхемой необходимо уменьшить напряжение на ускоряющем электроде кинескопа до минимума для предотвращения срабатывания системы защиты.

Рис. 3. Страница регулировок изображения для каждой системы цветности отдельно

3. Включают телевизор и проводят установку параметров телевизора согласно считанным ранее данным. Значения всех параметров выводятся на экран в десятичной системе счисления. Выбор регулируемого параметра и изменение его значения производится кнопка ми PICTURE ADJUST. Следует помнить, что при изменении многих параметров происходит скачкообразное изменение яркости изображения до минимума, поэтому иногда потребуется увеличивать величину ускоряющего напряжения, чтобы производить визуальный контроль за изменением параметров. Если производить регулировкупараметров, выбирая страницы в последовательности 4-3-2, то при этом частая регулировка ускоряющего напряжения практически не потребуется.

Рис. 4. Страница установки параметров тракта ПЧ и видеопроцессора

4. Установить номинальное значение ускоряющего напряжения, визуально контролируя изображение.

Рис. 5. Страница установки системных констант

При отсутствии возможности считывания исходной прошивки EEPROM необходимо ввести в микросхему усредненные значения параметров, приведенные на рисунках для всех пунктов меню.

Для достижения нормальной работы телевизора после ввода (корректировки) указанных параметров, желательно произвести дополнительную регулировку «баланса белого» и других режимов, используя методику, описанную в [1].

Следует особо отметить, что вход в сервисный режим обеспечивается только с оригинальными ПДУ, входящими в комплект телевизоров. Практика показала, что однотипные пульты, имеющиеся в настоящее время в широкой продаже, при одновременном нажатии кнопок DISPLAY и PICTURE MODE не обеспечивают формирование кода команды перевода телевизора в сервисный режим (автор не утверждает, что это присуще всем продаваемым ПДУ).

Источники:
1. Вхождение в режим сервиса с помощью ПДУ. Сервисная регулировка и настройка зарубежных телевизоров цветного изображения. Книга 2, с. 2-15.
2. М. Рязанов. Как войти в сервисное меню телевизора. Радио, 1999, № 7, с. 12.

Зотов С. Опубликована: 2004 г. 0 0

Вознаградить Я собрал 0 0

Оценить статью

  • Техническая грамотность

Работа с параметрами в EEPROM, как не износить память

Доброго времени суток. Прошлая моя статья про параметры в 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
        • Мы не хотим сами руками задавать адрес, он должен высчитываться на этапе компиляции
        • Это также должно делаться автоматически, но уже в runtime, никаких дополнительных действий в пользовательском коде мы делать не хотим.
        • Обычно EEPROM подключается через I2C и SPI, передача данных по этим интерфейсам тоже отнимает время, поэтому лучше кэшировать параметры в ОЗУ, и возвращать сразу копию из кеша.
        • При инициализации мы должны найти самую последнюю запись, её считать и закешировать.
        • За алгоритм проверки целостности отвечает драйвер, если при чтении он обнаружил несоответствие он должен вернуть ошибку. В нашем случае, пусть в качестве алгоритма целостности будет простое хранение копии параметра. Сам драйвер описывать не буду, но приведу пример кода.

        Ну кажется это все наши хотелки. Как и в прошлой статье давайте прикинем дизайн класса, который будет описывать такой параметр и удовлетворять нашим требованиям:

        Класс AntiWearNvData будет похож на, CachedNvData из прошлой статьи, но с небольшими изменениям. При каждой записи в EEPROM, нам нужно постоянно сдвигать адрес записи, поэтому необходимо хранить индекс, который будет указывать на номер текущей записи. Этот индекс должен записываться в EEPROM вместе с параметром, чтобы после инициализации можно было найти запись с самым большим индексом — эта запись и будет самой актуальной. Индекс можно сделать uint32_t точно хватит на 30 лет — даже при 100 000 циклах записи.

        И вот наш класс:

        Посмотрим на то, как реализуются наши требования таким дизайном.

        Пользователь должен задать параметры EEPROM и время обновления параметр

        В отличии от CachedNvData Из предыдущей статьи здесь появился параметр updateTime . На основе этого параметра можно посчитать сколько записей необходимо для того, чтобы уложиться в ожидаемое время жизни 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 eepromLifeTime = 3600U * 24U * 365U * 10U;

        Вообще можно было бы обойтись и без updateTime . И для каждого параметра задавать необходимое количество самим. Но я решил, все переложить на компилятор, потому что самому считать лень. В итоге сам расчет необходимого количества записей, с учетом, что все они выравнены по границам страницы, будет примерно таким:

        template class AntiWearNvData < private: struct tAntiWear < T data = defaultValue; std::uint32_t index = 0U; >; inline static tAntiWear nvItem; public: // Умножил на 2 чтобы зарезервировать место под копию. // Но по хорошему надо это убрать в список или драйвер static constexpr auto recordSize = sizeof(nvItem) * 2U; // предполагаем, что параметр не занимает больше страницы и // все они выравнены по границам страницы, но ничто не запрещает // сделать более сложный расчет необходимого количества записей, // для параметров, занимающих больше страницы. Такие ограничения для упрощения. static_assert(eepromPageSize/recordSize != 0, "Too big parameter"); static constexpr size_t recordCounts = (eepromPageSize/recordSize) * eepromLifeTime / (eepromWriteCycles * updateTime);
        При каждой следующей записи, адрес параметра должен изменяться, так, чтобы данные не писались по одному и тому же адресу

        Еще одной особенностью нашего противоизносного параметра является тот факт, что кроме самого значения, мы должны хранить еще и его индекс. Индекс нужен нам для двух вещей:

        • По нему мы будет рассчитывать следующий адрес записи
        • Для того, чтобы после выключения/включения датчика найти последнюю запись, считать её и проинициализировать значением по адресу этой записи кеширумое значение в ОЗУ.

        Для этого заведена специальная структура tAntiWear . Её то мы и будем сохранять при вызове метода Set(. ) , который, кроме непосредственно записи, еще сдвигает индекс текущей записи на 1.

        template class AntiWearNvData < public: ReturnCode Set(const T& value) const < tAntiWear tempData = ; //На основе текущего индекса расчитывем текущий адрес записи в EEPROM const auto calculatedAddress = GetCalculatedAdress(nvItem.index); ReturnCode returnCode = nvDriver.Set(calculatedAddress, reinterpret_cast(&tempData), sizeof(tAntiWear)); //Если запись прошла успешно, то обновляем кэшируемую копию параметра, //а также смещаем индекс на 1, для следующей записи if (!returnCode) < nvItem.data = value; nvItem.index ++; >return returnCode; > . >;

        Давайте посмотрим как реализован метод расчета текущего адреса записи:

        template class AntiWearNvData < . private: static 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 static auto GetAddress() < return NvList::template GetAddress>(); > >;
        Мы не хотим постоянно лазить в EEPROM, когда пользователь хочет прочитать параметр

        Метод Get() — крайне простой, он просто возвращает копию из ОЗУ

        template class AntiWearNvData < public: T Get() const < return nvItem.data; >>;

        Теперь самое интересное, чтобы проинициализировать копию в ОЗУ правильным значением, необходимо при запуске устройства считать все записи нашего параметра и найти запись с самым большим индексом. Наверняка есть еще разные методы хранения данных, например, связанный список, но использование индекса, показалось мне ну прямо очень простым.

        template class AntiWearNvData < public: static ReturnCode Init() < const auto ind = FindLastRecordPosition(); constexpr auto startAddress = GetAddress(); const auto calculatedAddress = startAddress + recordSize * ind; return nvDriver.Get(calculatedAddress, reinterpret_cast(&nvItem), sizeof(tAntiWear)); > . private: static std::uint32_t FindLastRecordPosition() < // Метод поиска индекса приводить не буду, оставлю на откуп читателям // Здесь нужно считать все записи парамтера и найти параметр с самым // большим индексом, пока предположим, что запись с самым большим индексом // находится на позиции 0. return 0U; >>;

        В общем-то и все класс готов, полный код класса:

        Полный код класса

        template class AntiWearNvData < public: ReturnCode Set(const T& value) const < tAntiWear tempData = ; // К размеру типа прибавляем 4 байта индекса и умножаем на номер индекса записи. // Умножаем на 2, чтобы драйвер мог хранить копиию записи для проверки целостности const auto calculatedAddress = GetCalculatedAdress(nvItem.index); ReturnCode returnCode = nvDriver.Set(calculatedAddress, reinterpret_cast(&tempData), sizeof(tAntiWear)); // std::cout return returnCode; > static ReturnCode Init() < const auto ind = FindLastRecordPosition(); constexpr auto startAddress = GetAddress(); const auto calculatedAddress = startAddress + recordSize * ind; return nvDriver.Get(calculatedAddress, reinterpret_cast(&nvItem), sizeof(tAntiWear)); > T Get() const < return nvItem.data; >static ReturnCode SetToDefault() < ReturnCode returnCode = nvDriver.Set(GetCalculatedAdress(nvItem.index), reinterpret_cast(&defaultValue), sizeof(T)); return returnCode; > private: static 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; >static std::uint32_t FindLastRecordPosition() < // Здесь нужно считать все записи парамтера и найти параметр с самым большим индексом, пока предположим, // что запись с самым большим индексом находится на позиции 1 - Там записано число 15 с индексом 5. return 1U; >constexpr static auto GetAddress() < return NvList::template GetAddress>(); > struct tAntiWear < T data = defaultValue; std::uint32_t index = 0U; >; inline static tAntiWear nvItem; public: static constexpr auto recordSize = sizeof(nvItem) * 2U; static_assert(eepromPageSize/recordSize != 0, "Too big parameter"); static constexpr size_t recordCounts = (eepromPageSize/recordSize) * eepromLifeTime / (eepromWriteCycles * updateTime); >;

        По аналогии с CachedNvData из прошлой статьи, все параметры должны быть зарегистрированы в едином списке, причем, в этом списке мы можем регистрировать как и CachedNvData , так и наши AntiWearNvData параметры.

        Я немного переделал список, так как IAR компилятор все еще не понимает много фишек из С++17, и собственно теперь список принимает только типы, а не ссылки на параметры. Кроме того, теперь у него появились методы SetToDefault и Init . Первый нужен, например, чтобы сбросить все параметры в их начальное значение. А второй, чтобы проинициализировать кешируемые в ОЗУ копии.

        template struct NvVarListBase < static ReturnCode SetToDefault() < return ( . || TNvVars::SetToDefault()); >static ReturnCode Init() < return ( . || TNvVars::Init()); >template constexpr static size_t GetAddress() < return startAddress + GetAddressOffset(); > private: template constexpr static size_t GetAddressOffset() < auto result = 0; if constexpr (!std::is_same::value) < //можно дописать алгоритм, чтобы все параметры были выравенны по странице. result = T::recordSize * T::recordCounts + GetAddressOffset(); > return result; > >;

        Также в CachedNvData я добавил параметр recordSize и recordCounts = 1 . Чтобы расчет адреса параметра был унифицирован для разного типа параметров.

        Результат

        Собственно все, теперь мы можем регистрировать в списке любые параметры:

        struct NvVarList; constexpr NvDriver nvDriver; using tString6 = std::array; 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 myFloatData; constexpr CachedNvData myStrData; constexpr CachedNvData myUint32Data; constexpr AntiWearNvData myUint32AntiWearData; constexpr AntiWearNvData myFloatAntiWearData; struct SomeSubsystem < static constexpr auto test = CachedNvData < NvVarList, std::uint16_t, myUin16DeafultValue, nvDriver>(); >; //*** Register the Shadowed Nv param in the list ***************************** struct NvVarList : public NvVarListBase < >;

        Замечу, что пользователю параметров нужно только объявить параметр и список, а вся портянка с кодом, до этого, пишется один раз. Используются параметры точно также как и CachedNvData .

        int main() < NvVarList::SetToDefault(); ReturnCode returnCode = NvVarList::Init(); myFloatData.Set(37.2F); myStrData.Set(tString6); 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 и это значение и индекс будут записаны в кэшируемую копию нашего параметра.

        Вот и все, надеюсь в этой статье цель получилось пояснить более детально, спасибо за то, что прочитали до конца.

        Введение

        Внутренняя программа микроконтроллера AVR может читать и записывать любой байт EEPROM памяти. Однако при программировании EEPROM`a внешним программатором чтение и запись осуществляется постранично. В зависимости от типа микроконтроллера страницы EEPROM памяти имеют разный размер. Например, у микроконтроллера atmega16 размер страницы EEPROM памяти равен 4-ем байтам.
        Существует мнение, что заявленный производителем ресурс EEPROM памяти AVR микроконтроллеров, равный 100000 циклов запись/чтение, относится не к единичной ячейке памяти, а к целой странице. То есть если мы в один байт EEPROM`а atmega16 запишем 100000 раз, остальные три ячейки страницы памяти потеряют свой ресурс, будучи вроде ни разу не тронутыми.
        Мне стало интересно узнать, соответствует ли это действительности, и я провел небольшой тест EEPROM памяти atmega16. Понятно, что этот тест не является каким-то глубоким научным исследованием, но это все же лучше, чем ничего.

        Суть теста была предельно проста. В заданный байт EEPROM`a записывалось произвольное число, затем из этой же ячейки производилось чтение. Далее программа сравнивала записанное и считанное значение, и по результату сравнения увеличивался или счетчик удачных записей, или счетчик ошибок. Произвольное число генерировалось с помощью встроенной Си функции, а запись-чтение EEPROM`a производилось самописными функциями (чтобы можно было обращаться к EEPROM по конкретным адресам).
        Каждые 10000 удачных записей в терминал выводилось сообщение с адресом тестируемой ячейки, числом произведенных записей и последним записанным числом. Таким образом, я контролировал, что микроконтроллер тестирует память, и каждый раз записывает разные числа. Также в терминал выводилось сообщение при появлении ошибок, чтобы отследить момент первого сбоя и посмотреть, как они накапливаются.
        Ячейки тестировались в порядке возрастания адреса. Сначала «убивалась» 0-я ячейка, затем 1, 2 и так далее. То есть когда доходила очередь до последнего байта страницы (3 и 7 байты), предыдущие три были уже испорчены.

        Результаты теста EEPROM

        Всего я проверил 8 байтов EEPROM памяти atmega16, то есть две страницы. Да, это не так много, но вопреки моим ожиданиям ячейки оказались очень стойкими, и на тест одного байта уходило полдня. Полученные данные я свел в два графика.
        Первый график отображает число циклов запись/чтение до появления первой ошибки. Из графика видно, что число циклов перезаписи для каждой ячейки составило не меньше 3 миллионов. Довольно большое число, но вряд ли ему стоит удивляться. Во-первых, заявленный ресурс EEPROM`a (100 тысяч), естественно, берется с большим запасом. Во-вторых, он указывается для всего рабочего температурного диапазона микроконтроллера, а я проводил тест только для комнатной температуры.
        Как видите, ресурс одной ячейки никак не зависит от ресурса другой. Если бы от одной испорченной ячейки портилась вся страница, соседние ячейки EEPROM`a в лучшем случае не показали бы такой же результат, а в худшем начинали сбоить практически сразу. Этого не было. Например, 2-ой байт вообще побил все рекорды, выдержав 6 миллионов записей, а ведь он тестировался третьим по счету. Делайте выводы.

        тест ресурса eeprom памяти

        Второй график, составленный по результатам теста, отображает характер «деградации» ячейки памяти. Дело в том, что даже после появления сбоев, EEPROM памятью еще можно пользоваться. Некоторое время ошибки возникают достаточно редко, и только после превышения определенного порога, ячейка начинает сбоить практически постоянно. Это значит, что программные способы коррекции ошибок, реально могут продлить ресурс EEPROM памяти и их нужно применять.

        Выводы

        Заявленный фирмой Atmel ресурс EEPROM памяти микроконтроллеров AVR, равный 100 тысячам циклов запись/чтение, относится к ресурсу одного байта памяти, а не к целой странице.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *