Библиотека EEPROM
Подробное руководство по библиотеке EEPROM для Arduino: чтение, запись и обновление данных в энергонезависимой памяти микроконтроллера.
Микроконтроллер на платах Arduino с AVR-чипами оснащён EEPROM — энергонезависимой памятью, которая сохраняет данные даже после отключения питания. Представьте её как крошечный жёсткий диск внутри чипа. Библиотека EEPROM даёт удобный доступ к этой памяти: можно читать и записывать отдельные байты или целые структуры данных.
Разные платы Arduino располагают разным объёмом EEPROM:
- ATmega328P — 1024 байта
- ATmega168 и ATmega8 — по 512 байт
- ATmega1280 и ATmega2560 — 4 КБ (4096 байт)
- Arduino и Genuino 101 — эмулированное пространство EEPROM объёмом 1024 байта
Чтобы подключить библиотеку к своему скетчу:
#include <EEPROM.h>
Примеры использования
Список готовых примеров для библиотеки EEPROM:
Функции
read()
Описание
Читает один байт из EEPROM по указанному адресу. Ячейки, в которые ни разу ничего не записывалось, возвращают значение 255.
Синтаксис
EEPROM.read(address)
Параметры
address — адрес ячейки для чтения, начиная с 0 (тип int)
Возвращаемое значение
Значение, хранящееся в указанной ячейке (тип byte)
Пример
#include <EEPROM.h>
int a = 0;
int value;
void setup()
{
Serial.begin(9600);
}
void loop()
{
value = EEPROM.read(a);
Serial.print(a);
Serial.print("\t");
Serial.print(value);
Serial.println();
a = a + 1;
if (a == 512)
a = 0;
delay(500);
}
Практическое замечание
Если вы читаете ячейку впервые (например, на новом или только что прошитом чипе) и получаете 255 — это нормально. Такое значение означает, что ячейка никогда не записывалась. Учитывайте это при инициализации переменных из EEPROM.
---
write()
Описание
Записывает один байт в EEPROM по указанному адресу. Значение записывается безусловно, независимо от того, что там хранилось раньше.
Синтаксис
EEPROM.write(address, value)
Параметры
address — адрес ячейки для записи, начиная с 0 (тип int)
value — значение для записи, от 0 до 255 (тип byte)
Возвращаемое значение
Нет
Важно: Одна операция записи в EEPROM занимает около 3,3 мс. Ресурс памяти ограничен — производитель гарантирует около 100 000 циклов записи/стирания на каждую ячейку. Избегайте частой записи в одни и те же адреса — это продлит жизнь памяти. Если данные меняются редко, используйте
update()вместоwrite().
Пример
#include <EEPROM.h>
void setup()
{
for (int i = 0; i < 255; i++)
EEPROM.write(i, i);
}
void loop()
{
}
---
update()
Описание
Записывает байт в EEPROM только в том случае, если новое значение отличается от уже сохранённого. Это умная альтернатива write() — лишних циклов стирания не происходит.
Синтаксис
EEPROM.update(address, value)
Параметры
address — адрес ячейки для записи, начиная с 0 (тип int)
value — значение для записи, от 0 до 255 (тип byte)
Возвращаемое значение
Нет
Совет: Если ваша программа периодически сохраняет одни и те же данные (например, состояние настроек), используйте
update()вместоwrite(). Это значительно увеличит ресурс ячеек EEPROM, поскольку запись происходит только при реальном изменении значения.
Пример
#include <EEPROM.h>
void setup()
{
for (int i = 0; i < 255; i++) {
// выполняет то же, что EEPROM.write(i, i)
EEPROM.update(i, i);
}
for (int i = 0; i < 255; i++) {
// записать значение "12" в ячейку 3 только при первом запуске
// в оставшиеся 254 раза ячейка не будет перезаписана
EEPROM.update(3, 12);
}
}
void loop()
{
}
---
get()
Описание
Читает из EEPROM данные любого типа или объект. Подходит для примитивных типов (float, int, long) и пользовательских структур (struct).
Синтаксис
EEPROM.get(address, data)
Параметры
address — адрес начала чтения, начиная с 0 (тип int)
data — переменная, в которую будут прочитаны данные; может быть примитивным типом (например, float) или пользовательской структурой
Возвращаемое значение
Ссылка на переданную переменную data
Пример
#include <EEPROM.h>
struct MyObject{
float field1;
byte field2;
char name[10];
};
void setup(){
float f = 0.00f; //Переменная для хранения данных, считанных из EEPROM.
int eeAddress = 0; //Адрес в EEPROM, с которого начинается чтение
Serial.begin( 9600 );
while (!Serial) {
; // ожидание подключения serial-порта. Нужно только для Leonardo
}
Serial.print( "Read float from EEPROM: " );
//Получить данные типа float из EEPROM по адресу 'eeAddress'
EEPROM.get( eeAddress, f );
Serial.println( f, 3 ); //Может вывести 'ovf, nan', если данные в EEPROM не являются корректным float.
// get() также работает с пользовательскими структурами.
eeAddress = sizeof(float); //Сдвинуть адрес на следующий байт после float 'f'.
MyObject customVar; //Переменная для хранения пользовательского объекта, считанного из EEPROM.
EEPROM.get( eeAddress, customVar );
Serial.println( "Read custom object from EEPROM: " );
Serial.println( customVar.field1 );
Serial.println( customVar.field2 );
Serial.println( customVar.name );
}
void loop(){ /* Пустой loop */ }
Как это работает
get() читает столько байт, сколько занимает тип переменной data. Например, для float это 4 байта, для int — 2 байта. Функция сама вычисляет размер через sizeof и последовательно читает нужное количество ячеек, начиная с address. Убедитесь, что при записи и чтении используется одинаковый тип — иначе данные будут интерпретированы неверно.
---
put()
Описание
Записывает в EEPROM данные любого типа или объект. Работает аналогично get(), но в обратном направлении.
Синтаксис
EEPROM.put(address, data)
Параметры
address — адрес начала записи, начиная с 0 (тип int)
data — данные для записи; может быть примитивным типом (например, float) или пользовательской структурой
Возвращаемое значение
Ссылка на переданные данные data
Примечание: Внутри
put()используетupdate(), поэтому запись происходит только при изменении значения. Это автоматически защищает ресурс EEPROM при повторной записи одинаковых данных.
Пример
#include <EEPROM.h>
struct MyObject {
float field1;
byte field2;
char name[10];
};
void setup() {
Serial.begin(9600);
while (!Serial) {
; // ожидание подключения serial-порта. Нужно только для нативного USB-порта
}
float f = 123.456f; //Переменная для записи в EEPROM.
int eeAddress = 0; //Адрес, по которому будут сохранены данные.
//Один простой вызов: первый аргумент — адрес, второй — объект.
EEPROM.put(eeAddress, f);
Serial.println("Written float data type!");
/*put также предназначен для работы с пользовательскими структурами.*/
//Данные для сохранения.
MyObject customVar = {
3.14f,
65,
"Working!"
};
eeAddress += sizeof(float); //Сдвинуть адрес на следующий байт после float 'f'.
EEPROM.put(eeAddress, customVar);
Serial.print("Written custom data type! \n\nView the example sketch eeprom_get to see how you can retrieve the values!");
}
void loop() { /* Пустой loop */ }
Типичная ошибка
Не забывайте следить за адресами при записи нескольких переменных разных типов. Если записать float по адресу 0, следующую переменную нужно записывать начиная с адреса 4 (так как float занимает 4 байта). Смешение адресов приведёт к чтению «мусора».
---
EEPROM[]
Описание
Оператор [] позволяет обращаться к ячейкам EEPROM так же, как к элементам массива через идентификатор EEPROM. Это удобный способ прямого чтения и записи отдельных байт.
Синтаксис
EEPROM[address]
Параметры
address — адрес ячейки для чтения или записи, начиная с 0 (тип int)
Возвращаемое значение
Ссылка на ячейку EEPROM
Пример
#include <EEPROM.h>
void setup(){
unsigned char val;
//Прочитать первую ячейку EEPROM.
val = EEPROM[ 0 ];
//Записать первую ячейку EEPROM.
EEPROM[ 0 ] = val;
//Сравнить содержимое
if( val == EEPROM[ 0 ] ){
//Выполнить какое-либо действие...
}
}
void loop(){ /* Пустой loop */ }
Практическое замечание
Оператор [] — это синтаксический сахар для прямого доступа к байтам. Он ведёт себя как read() при чтении и как write() при присваивании. Если хотите сэкономить ресурс памяти при записи, лучше явно использовать update().
---
length()
Описание
Возвращает количество ячеек в EEPROM текущего микроконтроллера в виде значения типа unsigned int. Удобна для написания кода, который автоматически адаптируется к объёму памяти конкретной платы.
Синтаксис
EEPROM.length()
Возвращаемое значение
Количество ячеек EEPROM в виде unsigned int
Где пригодится
Используйте length() в циклах, чтобы не выходить за границы памяти:
for (int i = 0; i < EEPROM.length(); i++) {
EEPROM.write(i, 0); // Очистить всю EEPROM
}
Это особенно полезно при написании универсального кода, который должен работать на разных платах Arduino с разным объёмом EEPROM.