Инициализация eeprom что это
Перейти к содержимому

Инициализация eeprom что это

  • автор:

EEPROM

EEPROM от англ. Electrically Erasable Programmable Read-Only Memory — это электрически стираемое перепрограммируемое ПЗУ (ЭСППЗУ), один из видов энергонезависимой памяти таких, как PROM и EPROM. Память такого типа может стираться и заполняться данными до миллиона раз. Данные, записанные в эту память не исчезают при отключении питания.

Использование

Электроника большинства 3D принтеров поддерживает хранение некоторого объема настроек в своей внутренней памяти. Прошивка Marlin использует EEPROM для хранения настроек принтера и загружает при подаче питания на контроллер. Внимание! Изменение исходного кода и перепрошивка микропрограммы контроллера не изменяет содержимое EEPROM.

Включение / отключение EEPROM

В прошивке Marlin можно как включить использование EEPROM, так и отключить эту возможность. За включение использования EEPROM отвечает строка #define EEPROM_SETTINGS если эта строчка закомментирована, то использование EEPROM отключено. Для включения использования возможности EEPROM требуется расскоментировать строку #define EEPROM_SETTINGS. Данная строка находится в файле Configuration.h

На данный момент, по умолчанию, разработчики Repository версии Marlin отключили использования EEPROM. Это чтобы избежать «непредсказуемые действия / последствия» работы принтера.

Учтите, что если у вас EEPROM включена, то вы всегда можете сбросить на «Установки по умолчанию» (исходя из исходного кода), используя команды M502 с последующим M500.

EEPROM G-коды

  • M500 Сохранение текущих параметров в EEPROM.
  • M501 Чтение всех параметров из EEPROM.
  • M502 сбросить текущие настройки по умолчанию, как указано в Configurations.h (не забудьте потом сохранить изменения командой M500)
  • M503 Печать текущих настроек — Выводит на экран хранящиеся в EEPROM настройки.

Настройки в EEPROM

Когда вы делаете M503 команду она выводет отчет , как это, который будет отличаться в зависимости от конфигурации Marlin :

‘>>> M503 S0’ M92 X80.00 Y80.00 Z4000.00 E1258.14

M203 X500.00 Y500.00 Z2.25 E45.00 M201 X9000 Y9000 Z100 E300 M204 P1500.00 R1500.00 T3000.00 M205 S0.00 T0.00 B20000 X20.00 Z0.40 E5.00 M206 X0.00 Y0.00 Z0.00 M145 M0 H180 B70 F0 M145 M1 H240 B110 F0 M301 P20.83 I1.04 D104.71 C1.00

Если вы не изменили эти параметры, то они соответствуют содержимому EEPROM.

Очистка EEPROM

EEPROM clear.png

Ссылки

  1. Это авто перевод статьиhttps://github.com/MarlinFirmware/Marlin/wiki/EEPROM с частичными правками человека 🙂

Работа с библиотекой EEPROM на ESP32, ESP8266.

EEPROM (Electrically Erasable Programmable Read-Only Memory) — это технология энергонезависимой памяти, которая позволяет хранить данные даже после отключения питания. В ESP32 и ESP8266 флэш-память используется для эмуляции EEPROM, что позволяет сохранять данные даже при перезагрузке устройства.

В ESP32 и ESP8266 флэш-память используется для эмуляции EEPROM, что позволяет сохранять данные даже при перезагрузке устройства.

Применение флэш-памяти: Флэш-память на ESP32 и ESP8266 может быть использована для сохранения различных типов данных, таких как последнее состояние переменной, настройки устройства, количество активаций и многое другое. Это особенно полезно, когда необходимо сохранить данные на длительное время.

Ограничения флэш-памяти: Одним из ограничений флэш-памяти является ограниченное количество операций записи. Вы можете записать данные в нее ограниченное количество раз (обычно 100,000 — 1,000,000 операций записи). Однако вы можете считывать данные сколько угодно раз без ограничений.

Библиотека EEPROM: Для работы с флэш-памятью ESP32 и ESP8266 в Arduino IDE используется библиотека EEPROM. Эта библиотека позволяет вам легко записывать и считывать данные из флэш-памяти, подобно тому, как вы делали это с Arduino EEPROM.

Список всех функций библиотеки EEPROM для ESP32, ESP8266 с подробными описаниями:

1. EEPROM.begin(size):

Инициализирует эмулированную EEPROM с указанным размером size (в байтах). Обычно вызывается в функции setup() .

2. EEPROM.end():

Завершает работу с эмулированной EEPROM и освобождает зарезервированные ресурсы. Необходимо вызывать, когда вы закончили работу с EEPROM.

3. EEPROM.write(address, value):

Записывает значение value (байт) в указанный адрес address в эмулированной EEPROM.

4. EEPROM.read(address):

Считывает байт данных из указанного адреса address в эмулированной EEPROM и возвращает это значение.

5. EEPROM.update(address, value):

Обновляет значение в указанном адресе address в эмулированной EEPROM. Если новое значение отличается от текущего, то происходит запись нового значения.

6. EEPROM.get(address, data):

Считывает данные из указанного адреса address в эмулированной EEPROM и сохраняет их в переменную data . Может использоваться для считывания структур или пользовательских типов данных.

7. EEPROM.put(address, data):

Записывает данные из переменной data в указанный адрес address в эмулированной EEPROM. Может использоваться для записи структур или пользовательских типов данных.

8. EEPROM.commit():

Сохраняет все изменения, внесенные в эмулированную EEPROM, во флэш-памяти. Это необходимо вызвать после операций записи, чтобы изменения вступили в силу.

Пример использования библиотеки EEPROM на ESP32, ESP8266.

Библиотеки включена в стандартный набор библиотек Arduino IDE, предназначенных для разработки программного обеспечения для ESP32 и ESP8266. Поэтому нет необходимости дополнительно устанавливать эту библиотеку после установки платы ESP32 или ESP8266 в Arduino IDE.

При создании проекта и работы с платами ESP32 или ESP8266, вы можете сразу начать использовать функции библиотеки EEPROM, такие как EEPROM.write() , EEPROM.read() , EEPROM.commit() и другие. Эти функции позволяют вам записывать и читать данные из эмулированной памяти, сохраняя информацию даже при перезагрузке устройства.

Запись данных:

Чтобы записать данные в флэш-память, используйте функцию EEPROM.write(address, value) , где address — адрес памяти для записи (от 0 до 511), а value — значение данных (от 0 до 255).

/* Сайт автора https://arduino-tex.ru/ Проекты на Arduino https://portal-pk.ru/page-17/proekty-na-arduino.html Проекты на ESP https://arduino-tex.ru/menu/10/11/proekty-na-esp8266-esp32.html Страница урока https://arduino-tex.ru/news/175/rabota-s-bibliotekoi-eeprom-na-esp32-esp8266.html */ #include void setup() < EEPROM.begin(512); // Инициализация EEPROM с размером 512 байт int address = 0; // адрес памяти для записи (от 0 до 511) byte value = 123; // значение данных (от 0 до 255) EEPROM.write(address, value); // Запись данных EEPROM.commit(); // Сохранение изменений >void loop() < // Ваш код здесь >
Чтение данных:

Для чтения данных из флэш-памяти используйте функцию EEPROM.read(address) , где address — адрес памяти для чтения.

/* Сайт автора https://arduino-tex.ru/ Проекты на Arduino https://portal-pk.ru/page-17/proekty-na-arduino.html Проекты на ESP https://arduino-tex.ru/menu/10/11/proekty-na-esp8266-esp32.html Страница урока https://arduino-tex.ru/news/175/rabota-s-bibliotekoi-eeprom-na-esp32-esp8266.html */ #include void setup() < EEPROM.begin(512); int address = 0; //адрес памяти для чтения. byte value = EEPROM.read(address); // Чтение данных // Используйте значение данных (value) по вашему усмотрению >void loop() < // Ваш код здесь >
Освобождение ресурсов:

EEPROM.end() — это функция из библиотеки EEPROM, которая выполняет освобождение ресурсов, занимаемых под эмуляцию EEPROM во флэш-памяти ESP32. Эта функция должна быть вызвана после того, как вы закончили работу с библиотекой и больше не планируете записывать или читать данные из EEPROM.

Принцип работы библиотеки EEPROM на ESP32 следующий: при вызове EEPROM.begin(size) , где size — размер эмулируемой памяти, библиотека резервирует определенное количество флэш-памяти для эмуляции EEPROM. Когда вы вызываете EEPROM.write() для записи данных или EEPROM.read() для чтения данных, библиотека обращается к этой зарезервированной части флэш-памяти.

После того, как вы закончили работу с библиотекой EEPROM, чтобы освободить ресурсы, занятые эмуляцией EEPROM, вы должны вызвать EEPROM.end() .

Пример использования EEPROM.end() :

/* Сайт автора https://arduino-tex.ru/ Проекты на Arduino https://portal-pk.ru/page-17/proekty-na-arduino.html Проекты на ESP https://arduino-tex.ru/menu/10/11/proekty-na-esp8266-esp32.html Страница урока https://arduino-tex.ru/news/175/rabota-s-bibliotekoi-eeprom-na-esp32-esp8266.html */ #include void setup() < EEPROM.begin(512); // Инициализация EEPROM с размером 512 байт // Работа с EEPROM >void loop() < // Ваш код здесь >void saveData() < // Сохранение данных EEPROM.commit(); EEPROM.end(); // Освобождение ресурсов >

Обратите внимание, что вызов EEPROM.commit() перед EEPROM.end() является важным. Функция EEPROM.commit() сохраняет все изменения, которые вы сделали в эмулируемой EEPROM, во флэш-памяти. После сохранения данных, вызов EEPROM.end() завершает работу с библиотекой и освобождает ресурсы.

Итак, освобождение ресурсов с помощью EEPROM.end() важно для эффективного управления памятью и предотвращения утечек памяти при использовании библиотеки EEPROM на ESP32.

Кода для подсчета количества включений устройства с использованием библиотеки EEPROM.

Этот пример демонстрирует, как использовать библиотеку EEPROM на микроконтроллерах ESP32 и ESP8266 для создания счетчика, который подсчитывает количество включений (перезагрузок) устройства. Библиотека EEPROM позволяет нам сохранять данные в энергонезависимой памяти, которые останутся неизменными даже после выключения питания.

/* Сайт автора https://arduino-tex.ru/ Проекты на Arduino https://portal-pk.ru/page-17/proekty-na-arduino.html Проекты на ESP https://arduino-tex.ru/menu/10/11/proekty-na-esp8266-esp32.html Страница урока https://arduino-tex.ru/news/175/rabota-s-bibliotekoi-eeprom-na-esp32-esp8266.html */ #include int address = 0; // адрес памяти для записи (от 0 до 511) byte value_w; // значение данных (от 0 до 255) void setup() < Serial.begin(74880); EEPROM.begin(512); // Инициализация EEPROM с размером 512 байт value_w = EEPROM.read(address); // Чтение данных value_w++; EEPROM.write(address, value_w); // Запись данных EEPROM.commit(); // Сохранение изменений byte value_r = EEPROM.read(address); // Чтение данных // Используйте значение данных (value) по вашему усмотрению Serial.println(); Serial.print("Номер включения устройства: "); Serial.println(value_r); >void loop()

Как это работает:

  1. Подключение библиотеки: Прежде чем начать, мы подключаем библиотеку EEPROM, чтобы получить доступ к функциям для работы с эмулированной энергонезависимой памятью.
  2. Инициализация переменных и адреса: Мы объявляем переменную address , которая будет использоваться как адрес в памяти для хранения количества включений. Мы также создаем переменную value_w , которая будет хранить текущее количество включений.
  3. Функция setup():
    • Мы настраиваем последовательное соединение для вывода данных на монитор порта с помощью Serial.begin(74880) .
    • Инициализируем эмулированную EEPROM с размером 512 байт с помощью EEPROM.begin(512) .
    • Читаем текущее значение (количество включений) из памяти с помощью EEPROM.read(address) .
    • Увеличиваем значение на 1, чтобы учесть текущее включение.
    • Записываем новое значение обратно в память с помощью EEPROM.write(address, value_w) .
    • Сохраняем изменения во флэш-памяти с помощью EEPROM.commit() .
    • Читаем новое значение из памяти и выводим его на монитор порта.
  4. Функция loop(): Эта функция остается пустой, так как в ней нет необходимости для данной задачи.

Кода для подсчета количества включений устройства с использованием библиотеки EEPROM

Итог: Этот код создает счетчик, который увеличивает значение при каждой перезагрузке устройства и сохраняет его в эмулированной энергонезависимой памяти. Таким образом, вы можете отслеживать, сколько раз ваше устройство было включено, даже при перезагрузке или отключении питания. Это полезный способ сохранения и подсчета долгосрочных статистических данных на ESP32 и ESP8266.

Удачи в вашем эксперименте с EEPROM!

Понравился урок. Работа с библиотекой EEPROM на ESP32, ESP8266? Не забудь поделиться с друзьями в соц. сетях.

А также подписаться на наш канал на YouTube, вступить в группу Вконтакте, в группу на Facebook.

Спасибо за внимание!

Технологии начинаются с простого!

Объясните, что такое инициализация?

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

Пример из компьютеров: если стирается флеш-БИОС, машина умирает.
Чтобы такого не происходило (а особенно это неприятно, если микросхема на пайке, а не на кровати), сейчас делают нестираемую часть прошивки, в которой записана процедура восстановления БИОС с внешнего носителя (например, у Асусов сделано так).

Caша писал:
И еще – что такое флэш-память?Я знаю такие:
EEPROM – 24C04,93C46 и т.п.
EPROM(ПЗУ) – 27C512,27C040 и т.п.
Что из этого флэш?Что такое ROM и RAM?В общем я запутался в трех соснах.

RAM — Random Access Memory, память с произвольной выборкой, ОЗУ (оперативное запоминающее устройство). Можно писать, можно читать.
Но обесточил — и она все забыла.

ROM — Read Only Memory, память только для чтения, ПЗУ (постоянное запоминающее устройство).

ПЗУ бывают масочные (т.е., нужный код зашивается в процессе изготовления кристалла), с прожигаемыми перемычками (программируются один раз), электрически программируемые с ультрафиолетовым стиранием (программируются несколько раз, EPROM), электрически перепрограммируемые (можно перепрограммировать, не вынимая из аппаратуры, EEPROM).

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

Память с прожигаемыми перемычками существует в основном в виде программируемых логических матриц (ПЛМ), сложных дешифраторов.

Память с УФ стиранием программируется нулями, а единицы записываются сразу во все ячейки, при освещении УФ лампой.

В EEPROM и нули, и единицы записываются электрически.

Работа с параметрами в 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 и это значение и индекс будут записаны в кэшируемую копию нашего параметра.

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

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

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