Работа со встроенной EEPROM в микроконтроллерах на Arduino
Микроконтроллеры AVR, на основе которых создаются платы Arduino и Genuino, обладают встроенной EEPROM: памятью, которая хранит значения переменных, пока плата выключена (подобно крошечному жесткому диску). Описываемая в данной статье библиотека позволяет считывать и записывать данные в EEPROM.
Поддерживаемые микроконтроллеры на различных платах Arduino и Genuino обладают разным размером EEPROM: 1024 байта в ATmega328, 512 байт в ATmega168 и ATmega8, 4 KB (4096 байт) в ATmega1280 и ATmega2560. Платы Arduino и Genuino 101 обладают эмуляцией EEPROM пространства размером 1024 байт.
Функции для работы с EEPROM:
read()
- Описание
- Считывает байт из EEPROM. Ячейки, которые никогда не были записаны, содержат значение 255.
- Синтаксис
EEPROM.read(address)
- Параметры
address
: адрес ячейки для считывания, начинается с 0 (int
).- Возвращаемое значение
- Значение, записанное в ячейку (
byte
). - Пример
/* * EEPROM Read * * Считывает значение каждого байта в EEPROM и выводит его * на компьютер. */ #include <EEPROM.h> // начать чтение с первого байта (адрес 0) в EEPROM int address = 0; byte value; void setup() { // инициализировать последовательный порт и ждать, // пока порт не будет открыт: Serial.begin(9600); while (!Serial) { ; // ждать подключения последовательного порта. Необходимо только для встроенного USB порта. } } void loop() { // прочитать байт из текущего адреса EEPROM value = EEPROM.read(address); Serial.print(address); Serial.print("\t"); Serial.print(value, DEC); Serial.println(); /*** Перейти к следующему адресу, или, если находимся в конце, вернуться в начало. Большие AVR контроллеры обладают большим размером EEPROM, например: - Arduno Duemilanove: 512b EEPROM; - Arduino Uno: 1kb EEPROM; - Arduino Mega: 4kb EEPROM; Вместо жесткого прописывания размера EEPROM, лучше использовать предусмотренную функцию определения размера. Это сделает ваш код портируемым на все микроконтроллеры AVR. ***/ address = address + 1; if (address == EEPROM.length()) { address = 0; } /*** Поскольку размеры EEPROM являются степенями двойки, то возврат к началу EEPROM можно было бы реализовать с помощью операции побитового И со значением (размер - 1). ++address &= EEPROM.length() - 1; ***/ delay(500); }
write()
- Описание
- Записывает байт в EEPROM.
- Синтаксис
EEPROM.write(address, value)
- Параметры
address
: адрес ячейки для записи, начинается с 0 (int
).value
: значение для записи, от 0 до 255 (byte
).- Возвращаемое значение
- Ничего не возвращается.
- Примечание
- Запись в EEPROM занимает 3,3 мс. Память EEPROM обладает жизненным циклом 100 000 операций записи/стирания, поэтому, возможно, вам придется быть осторожными с тем, как часто записываете её.
- Пример
/* * EEPROM Write * * Записывает значения, считанные с аналогового входа 0, в EEPROM. * Эти значения будут оставаться в EEPROM, когда плата будет * выключена, и могут быть извлечены позже при следующем запуске. */ #include <EEPROM.h> /** текущий адрес в EEPROM (т.е. какой байт мы собираемся записать следующим) **/ int addr = 0; void setup() { /** Нет никаких начальных настроек. **/ } void loop() { /*** Необходимо делить на 4, так как диапазон значений на аналоговых входах составляет от 0 до 1023, а каждый байт EEPROM может хранить значения только от 0 до 255. ***/ int val = analogRead(0) / 4; /*** Записать значение в соответствующий байт EEPROM. Эти значения останутся там, когда плата будет выключена. ***/ EEPROM.write(addr, val); /*** Перейти к следующему адресу, или, если находимся в конце, вернуться в начало. Большие AVR контроллеры обладают большим размером EEPROM, например: - Arduno Duemilanove: 512b EEPROM; - Arduino Uno: 1kb EEPROM; - Arduino Mega: 4kb EEPROM; Вместо жесткого прописывания размера EEPROM, лучше использовать предусмотренную функцию определения размера. Это сделает ваш код портируемым на все микроконтроллеры AVR. ***/ addr = addr + 1; if (addr == EEPROM.length()) { addr = 0; } /*** Поскольку размеры EEPROM являются степенями двойки, то возврат к началу EEPROM можно было бы реализовать с помощью операции побитового И со значением (размер - 1). ++addr &= EEPROM.length() - 1; ***/ delay(100); }
update()
- Описание
- Записывает байт в EEPROM. Значение записывается, только если оно отличается от значения уже записанного по этому адресу.
- Синтаксис
EEPROM.update(address, value)
- Параметры
address
: адрес ячейки для записи, начинается с 0 (int
).value
: значение для записи, от 0 до 255 (byte
).- Возвращаемое значение
- Ничего не возвращается.
- Примечание
- Запись в EEPROM занимает 3,3 мс. Память EEPROM обладает жизненным циклом 100 000 операций записи/стирания, поэтому данная функция, в отличие от
write()
, может сохранить время, если записываемые данные часто не меняются. - Пример
/*** EEPROM Update Сохраняет значения, считанные с аналогового входа 0, в EEPROM. Эти значения будут оставаться в EEPROM, когда плата будет выключена, и могут быть извлечены позже при следующем запуске. Если значение не изменилось, то EEPROM не перезаписывается, что поможет без необходимости не сокращать срок службы EEPROM. ***/ #include <EEPROM.h> /** текущий адрес в EEPROM (т.е. какой байт мы собираемся записать следующим) **/ int address = 0; void setup() { /** EMpty setup **/ } void loop() { /*** Необходимо делить на 4, так как диапазон значений на аналоговых входах составляет от 0 до 1023, а каждый байт EEPROM может хранить значения только от 0 до 255. ***/ int val = analogRead(0) / 4; /*** Обновить конкретную ячейку EEPROM. Эти значения останутся там при выключении платы. ***/ EEPROM.update(address, val); /*** Функция EEPROM.update(address, val) эквивалентна следующему: if( EEPROM.read(address) != val ){ EEPROM.write(address, val); } ***/ /*** Перейти к следующему адресу, или, если находимся в конце, вернуться в начало. Большие AVR контроллеры обладают большим размером EEPROM, например: - Arduno Duemilanove: 512b EEPROM; - Arduino Uno: 1kb EEPROM; - Arduino Mega: 4kb EEPROM; Вместо жесткого прописывания размера EEPROM, лучше использовать предусмотренную функцию определения размера. Это сделает ваш код портируемым на все микроконтроллеры AVR. ***/ address = address + 1; if (address == EEPROM.length()) { address = 0; } /*** Поскольку размеры EEPROM являются степенями двойки, то возврат к началу EEPROM можно было бы реализовать с помощью операции побитового И со значением (размер - 1). ++addr &= EEPROM.length() - 1; ***/ delay(100); }
get()
- Описание
- Считывает любой тип данных или объект из EEPROM.
- Синтаксис
EEPROM.get(address, data)
- Параметры
address
: адрес для чтения, начинается с 0 (int
).data
: данные для чтения, могут быть примитивным типом (например,float
) или пользовательской структуройstruct
.- Возвращаемое значение
- Ссылка на переданные данные.
- Пример
/*** Чтобы предварительно записать данные в EEPROM, используйте пример для функции put(). Можно обойтись и без этого, но значения, выводимые этим скетчем, зависят от того, что содержится в EEPROM. Это может заставить объект последовательного порта вывести на печать длинную строку мусора, если в загруженной строке не будет найден нулевой символ. ***/ #include <EEPROM.h> void setup() { float f = 0.00f; // Переменная для хранения данных, прочитанных из EEPROM. int eeAddress = 0; // Адрес EEPROM, откуда следует начать чтение. Serial.begin(9600); while (!Serial) { ; // ждать подключения последовательного порта. Необходимо только для встроенного USB порта. } Serial.print("Read float from EEPROM: "); // Получить данные типа float из EEPROM в месте 'eeAddress' EEPROM.get(eeAddress, f); Serial.println(f, 3); // Это может напечатать 'ovf, nan', если данные в EEPROM // не корректны для float. /*** Так как get возвращает ссылку на 'f', вы можете использовать ее в качестве аргумента То есть: Serial.print( EEPROM.get( eeAddress, f ) ); ***/ /*** Get может использоваться и с пользовательскими структурами. Пример с ними выделен в отдельную функцию. ***/ secondTest(); // Запустить следующий тест. } struct MyObject { float field1; byte field2; char name[10]; }; void secondTest() { int 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() { /* Пустой цикл */ }
put()
- Описание
- Записывает любой тип данных или объект из EEPROM.
- Синтаксис
EEPROM.put(address, data)
- Параметры
address
: адрес для записи, начинается с 0 (int
).data
: данные для записи, могут быть примитивным типом (например,float
) или пользовательской структуройstruct
.- Возвращаемое значение
- Ссылка на переданные данные.
- Примечание
- Функция использует
EEPROM.update()
для реализации записи, поэтому она не перезаписывает значение, если оно не изменилось. - Пример
/*** Этот скетч также можно использовать для предварительной записи в EEPROM данных, используемых в примере для функции get(). Обратите внимание, что, в отличие от однобайтной версии EEPROM.write(), функция put использует обновление. То есть байт будет записан, только если он отличается от записанных в EEPROM данных. ***/ #include <EEPROM.h> struct MyObject { float field1; byte field2; char name[10]; }; void setup() { Serial.begin(9600); while (!Serial) { ; // ждать подключения последовательного порта. Необходимо только для встроенного USB порта. } float f = 123.456f; // Переменная для записи в EEPROM. int eeAddress = 0; // Место, куда мы хотим положить данные. // Простой вызов с адресом и переменной в качестве аргументов. EEPROM.put(eeAddress, f); Serial.println("Written float data type!"); /** Put поддерживает и пользовательские структуры. **/ //Data to store. 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() { /* Пустой цикл */ }
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(){ /* Пустой цикл */ }