При разработке собственного устройства очень часто возникает необходимость сохранять и считывать данные из энергонезависимой памяти. Конечно, для этих целей можно использовать SD карты. Например, к платам семейства Arduino можно легко подключить адаптер для работы с microSD. Однако, часто нам не нужно сохранять такой большой объем данных. Например, если мы делаем умный будильник, где время срабатывания каждый день может быть разным, то эти значения неплохо бы сохранить в EEPROM. Или, если доступ к устройству осуществляется по паролю, то хэш этого пароля (не сам пароль!) неплохо бы тоже сохранить в EEPROM. Если зашьем пароль в прошивку, у нас не будет возможности его поменять, а это не очень хорошо с точки зрения безопасности.
В общем, есть масса ситуаций, когда нужно хранить небольшой объем информации и тогда лучше всего использовать энергонезависимую память на самом микроконтроллере.
В этой статье мы будем говорить об Arduino; но на самом деле на многих МК есть энергонезависимая память, например, на моем любимом МК ATtiny13, материалов о котором на Хабре пока не так много (но я надеюсь это в скором времени исправить).
Какая память бывает
Для того, чтобы избежать путаницы и подмены понятий, предлагаю разобраться с тем, какая память бывает на микроконтроллерах. В МК есть три вида памяти:
-
Flash‑память (ROM) — это тоже энергонезависимая память, в которой хранится прошивка контроллера; то, что там записано, нельзя изменить в процессе работы программы.
-
ОЗУ (RAM) — это оперативная память, используется для размещения переменных, массивов и других данных, при отключении питания эта информация будет потеряна.
-
И наконец, EEPROM — используется для хранения пользовательских данных, содержимое этой памяти можно модифицировать в процессе работы программы.
Вот характеристики объемов памяти для разных моделей Arduino:
Единственная поправка к представленным значениям Flash заключается в том, что из них нужно вычесть как минимум 4Кб на загрузчик Bootloader. Благодаря этому загрузчику мы можем заливать прошивки на плату через USB порт без помощи программатора.
Но вернемся к EEPROM. Как видно из таблицы, у нас будет как минимум килобайт памяти — давайте посмотрим, как мы можем их использовать.
Запись и чтение
Работу с энергонезависимой памятью в Arduino мы начнем с рассмотрения примера записи в память. Для выполнения любых операций с EEPROM необходимо подключить соответствующую библиотеку EEPROM.h. Ниже мы осуществим запись значения в память двумя способами:
#include <EEPROM.h> void setup() { Serial.begin(9600); while (!Serial) { // ждем подключения } byte i= 1; // Значение, которое будем записывать в EEPROM. int eeAddress = 0; //адрес в памяти //Запишем значение в память EEPROM.write(eeAddress, i); eeAddress = eeAddress+1; //Увеличим адрес в памяти на размер сохраненных данных Serial.println("Запись успешно произведена!"); //Запишем значение в память EEPROM.update(eeAddress, i); Serial.println("Запись успешно произведена!"); } void loop() { }
В примере мы использовали две функции EEPROM.write()
и EEPROM.update()
для записи значений в память. Чем они отличаются? Первая функция выполнить запись в ячейку в любом случае, независимо от того, изменилось ли значение, которое мы хотим записать или оно осталось прежним. Вторая функция update
в соответствии со своим названием запишет выполнит операцию записи только в том случае, если записываемое значение отличается от того, котрое уже есть в ячейке. Так в нашем примере кода в памяти был 0
и update
выполнит запись, поместив в память 1
. Если бы в памяти на этом месте уже была 1
, то операция записи бы не выполнялась.
Дело в том, что количество циклов записи в EEPROM не бесконечно. В сети можно найти описания того, как физически работает энергонезависимая память. Но суть в том, что в процессе выполнения операций записи элементы памяти изнашиваются. В среднем производители обычно обещают порядка 100 000 циклов записи (по данным https://docs.arduino.cc/). Поэтому я бы рекомендовал по возможности при записи в EEPROM использовать функцию update, для снижения «износа» модуля памяти.
Теперь поговорим о том, как можно прочитать содержимое энергонезависимой памяти. Здесь все проще: для чтения используется функция EEPROM.read()
, которой в качестве аргумента передается номер ячейки памяти, из которой нужно прочитать значение.
#include <EEPROM.h> // начинаем чтение с нулевого адреса int address = 0; byte value; void setup() { Serial.begin(9600); while (!Serial) { // ждем подключения } } void loop() { // читаем байты из текущей ячейки памяти в цикле value = EEPROM.read(address); // выводим номер ячейки и значение в десятичном виде Serial.print(address); Serial.print("\t"); Serial.print(value, DEC); Serial.println(); address = address + 1; //если дошли до конца памяти, возвращаемся в начало if (address == EEPROM.length()) { address = 0; } delay(500); }
Для того, чтобы ваш скетч (прошивка) была универсальной и могла работать на разных моделях Arduino, используйте функцию EEPROM.length()
для выяснения размера памяти. Это лучше, чем жестко приколачивать значение в коде.
Посмотрим еще два полезных метода — это put
и get
. Они аналогичны операциям записи и чтения, но количество записываемых или читаемых байт зависит от типа данных или пользовательской структуры записываемой переменной. Put
работает по принципу update
, то есть записывает данные только в случае изменения.
#include <EEPROM.h> struct MyObject { float field1; byte field2; char name[10]; }; void setup() { Serial.begin(9600); while (!Serial) { } float f = 123.456f; int eeAddress = 0; . //Пишем float в первую ячейку EEPROM.put(eeAddress, f); Serial.println("Запись успешно произведена!"); //Создаем свой объект MyObject customVar = { 3.14f, 65, "Working!" }; eeAddress += sizeof(float); //Увеличиваем адрес EEPROM.put(eeAddress, customVar); Serial.print("Запись успешно произведена!"); } void loop() { }
Как видно, нам не надо думать о том, сколько байт в памяти занимает тот или иной тип данных.
Теперь давайте посмотрим, как осуществляется чтения данных различных типов из EEPROM.
#include <EEPROM.h> void setup() { float f = 0.00f; int eeAddress = 0; Serial.begin(9600); while (!Serial) { } Serial.print("Читаем из EEPROM: "); EEPROM.get(eeAddress, f); Serial.println(f, 3); //Можем получить 'ovf, nan' если значение f не типа float. secondTest(); //Run the next test. } struct MyObject { float field1; byte field2; char name[10]; }; void secondTest() { int eeAddress = sizeof(float); MyObject customVar; //готовим переменную для чтения MyObject EEPROM.get(eeAddress, customVar); Serial.println("Читаем объекты из EEPROM: "); Serial.println(customVar.field1); Serial.println(customVar.field2); Serial.println(customVar.name); } void loop() { }
При выполнении операций чтения важно понимать, данные какого типа мы хотим получить для того, чтобы не столкнуться с сюрпризами, когда мы получаем совсем не то, что ожидали из‑за неверного типа данных при чтении.
Собственно, представленные функции являются достаточными для работы с EEPROM. Так, если нужно очистить память, то можно с помощью update
записать 0
во все ячейки. Для поиска значения в памяти можно воспользоваться read
и так далее.
Заключение
Мы рассмотрели базовые функции для работы с энергонезависимой памятью в Arduino. С их помощью можно реализовать необходимый для работы с памятью функционал и использовать в своих устройствах.
В декабре в Otus в рамках онлайн-курса «Электроника и электротехника» пройдут два открытых урока, на которые приглашаются все желающие:
-
9 декабря: Отладка встраиваемого программного обеспечения в симуляторах Proteus и NI Multisim. Записаться
-
17 декабря: Автоматическая регулировка усиления каскадов на операционных усилителях и транзисторах. Записаться
ссылка на оригинал статьи https://habr.com/ru/articles/862452/
Добавить комментарий