При разработке собственного устройства очень часто возникает необходимость сохранять и считывать данные из энергонезависимой памяти. Конечно, для этих целей можно использовать 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 декабря: Автоматическая регулировка усиления каскадов на операционных усилителях и транзисторах. Записаться
Комментарии (6)
randomsimplenumber
06.12.2024 05:20Как правильно писать в eeprom вы так и не рассказали :(
voldemar_d
06.12.2024 05:20для чтения используется функция
EEPROM.read()
, которой в качестве аргумента передается номер ячейки памяти, из которой нужно прочитать значениеНомер начинается с единицы. А здесь используется адрес или индекс, начинающийся с нуля:
vvm13xx
06.12.2024 05:20"Flash‑память (ROM) — это тоже энергонезависимая память, в которой хранится прошивка контроллера; то, что там записано, нельзя изменить в процессе работы программы. "
Специфика Ардуино? Например, на (как минимум некоторых) STM32 можно изменить.
"Но вернемся к EEPROM. Как видно из таблицы, у нас будет как минимум килобайт памяти..."
Но в таблице у Mini 256B. По приведённой таблице можно подумать, что килобайт - максимум (но у Мега R3 четыре килобайта).
randomsimplenumber
06.12.2024 05:20Например, на (как минимум некоторых) STM32 можно изменить.
Так и в Arduino можно. Загрузчик же каким-то образом читает данные с uart и пишет в ROM.
CitizenOfDreams
Не многовато? Optiboot для Атмеги-328 (Arduino Nano) весит 512 байт.