Arduino IDE
Как мы уже знаем, в Arduino Uno имеется один килобайт EEPROM. Попробуем задействовать её для хранения однобайтовых данных.
Первая программа будет хранить значение счетчика нажатий кнопки. То есть каждый раз, когда мы нажимаем кнопку, переменная счетчика увеличивается на единицу и сохраняет свое значение в EEPROM. Чтобы проверить программу нам понадобится собрать стенд из урока про кнопки.
Для работы с EEPROM используется стандартная библиотека EEPROM.h. Запись и чтения в эту память ведется побайтово. Это значит, что за раз мы можем записать и считать число от 0 до 255. Запись одного байта осуществляется с помощью функции write:
EEPROM.write( адрес, значение );
Здесь адрес — индекс ячейки памяти, который может принимать значение от 0 до 1023. Максимальный адрес можно легко вычислить зная размер EEPROM у конкретного микроконтроллера: 1кб = 1024 байта, значит максимальный адрес 1024 — 1. Либо можно использовать функцию length — размер памяти, тогда максимальный адрес будет EEPROM.length() — 1.
Второй аргумент значение — это число, которое мы хотим сохранить в ячейке с индексом «адрес«.
Чтение байта осуществляется с помощью функции read:
EEPROM.read( адрес );
Итак, пишем программу.
#include <EEPROM.h> // обязательно подключаем библиотеку
const byte btnPin = 2; // это контакт, к которому подключена кнопка
byte counter = 0; // переменная счетчика
int addr = 0; // адрес ячейки для записи
void setup() {
Serial.begin(9600); // включаем порт для отладки
pinMode(btnPin, INPUT); // активируем контакт за ввод
// сразу после запуска программы считываем
// число из EEPROM и выводим его в COM-порт
byte v = EEPROM.read(addr); // считываем число из памяти
Serial.print("saved counter = ");
Serial.println(v); // выводим считанное в COM-порт
}
void loop() {
// если кнопка нажата, выполняем
if( digitalRead(btnPin) == HIGH ){
counter++; // увеличиваем счетчик на единицу
EEPROM.write(addr, counter); // записываем счетчик
Serial.print("counter = ");
Serial.println(counter); // выводим текущее значение счетчика
delay(200); // пауза защитит от дребезга контактов
}
}
Загружаем программу на Ардуино, открываем монитор последовательного порта и нажимаем кнопку несколько раз. Нетрудно догадаться, что если нажать больше 255 раз, произойдет переполнение переменной. В данном случае это не страшно, просто счетчик автоматически сбросится в ноль.
... counter = 8 counter = 9 counter = 10 counter = 11 counter = 12
Теперь отключим плату Ардуино от питания, затем снова включим и откроем монитор.
saved counter = 12
Участок кода в конце функции setup считал байт из нулевой ячейки EEPROM памяти и вывел его в порт. Работает!
Запись и чтение int и float
Что делать, если нам нужно сохранить переменную типа integer, long или float? Для этих целей в библиотеке EEPROM есть две специальные функции: put и get. Первая позволяет записать в память переменную любого типа, вторая — считать из памяти.
В отличие от однобайтовых функций write и read, в случае использования put и get легко запутаться в адресации. Так, при записи целого типа (int) с помощью put мы займем два байта памяти. И чтобы по-очереди записать два числа нужно будет прибавить к адресу двойку:
int addr = 0; // начальный адрес
int v = 16555; // какое-то большое число
EEPROM.put(addr, v); // записываем в ячейки 0 и 1
addr = addr + 2;
EEPROM.put(addr, v);// записываем в ячейки 2 и 3
Если решим сохранить переменную типа long, то уже потребуется 4 байта. В общем, нужно всегда знать размер переменной, которую хотим разместить в EEPROM, или уметь его вычислять функцией sizeof.
Хорошим примером работы функций put и get послужит сохранение в EEPROM показаний поворотного потенциометра, подключенного к аналоговому входу Ардуино. Предположим, у нас есть прибор с одним потенциометром, с помощью которого настраиваются три коэффициента какого-нибудь PID-регулятора. Как нам уже известно, плата Ардуино Уно имеет 10-разрядный АЦП. Для чтения АЦП мы используем функцию analogWrite, которая вернет целый тип int — а это два байта.
Данная программа постоянно считывает значение с контакта A0 и при нажатии кнопки сохраняет его в EEPROM. После первого нажатия, значение сохраняется в ячейки с адресами 0,1. После второго — 2,3. После третьего — 4,5.
#include <EEPROM.h>
const byte btnPin = 2;
const byte potPin = A0; // к этому контакту подключен потенциометр
byte potIdx = 0;
void setup() {
Serial.begin(9600);
pinMode(btnPin, INPUT);
pinMode(potPin, INPUT);
// считываем значения из EEPROM в цикле
// сначала по адресу 0, потом 2, потом 4
// i - итератор, он же номер потенциометра
Serial.println("Read data from EEPROM");
for( byte i=0; i<3; i++ ){
int v;
EEPROM.get(i*2, v);
Serial.print("value of pot #");
Serial.print(i+1);
Serial.print(" = ");
Serial.println(v);
}
delay(3000);
// пауза 3 секунды
}
void loop() {
int v = analogRead(potPin);
Serial.print("v = ");
Serial.println(v);
if( digitalRead(btnPin) == HIGH ){
EEPROM.put(potIdx*2, v);
Serial.println("Write data to EEPROM");
Serial.print("value of pot #");
Serial.print(potIdx+1);
Serial.print(" = ");
Serial.println(v);
// если мы добрались до третьей ручки
// обнуляем счетчик ручек
if( potIdx == 2 )
potIdx = 0;
else
potIdx++;
delay(1000); // пауза 1 секунда
}
}
После запуска программы и вывода ранее сохраненных в EEPROM значений в последовательный порт начнут сыпаться числа. Крутим потенциометр до нужного нам значения и жмем кнопку. Программа покажет нам сохраняемое значение и подождет 1 секунду.
... v = 491 v = 491 v = 491 Write data to EEPROM value of pot #1 = 491 v = 490 v = 492 ...
Настроив таким образом все три числа, отключим Ардуино от компьютера, а затем снова включим и откроем монитор последовательного порта.
Read data from EEPROM value of pot #1 = 491 value of pot #2 = 492 value of pot #3 = 492
Готово! Теперь можно не настраивать устройства каждый раз после подачи питания, а использовать EEPROM.
Надо заметить, что put и get умеют работать не только с естественными типами int, float, long, char, byte, но и со структурами. Для оценки их размера лучше использовать упомянутую функцию sizeof.
Модуль EEPROM 512кб с I2C
Для эксперимента с внешним модулем EEPROM напишем программу, которая будет сохранять один байт данных в ячейку памяти с индексом 0, и затем читать её оттуда. Используем библиотеку SparkFun_External_EEPROM, которую можно найти в репозитории Arduino IDE, либо по ссылке в конце урока.
Внутри функции setup запускаем шину I2C. А затем указываем размер нашей памяти с помощью функции setMemoryType, единственным аргументом которой является число килобайт. В примере мы использовали микросхему AT24C02 с 2 килобита (2048 бит) EEPROM, так что при вызове функции setMemoryType укажем число 2. Помним, что 1 байт = 8 битам, так что 2 килобита — это 256 байт.
Далее инициализируем обмен данными функцией begin.
Как и в случае со стандартной библиотекой EEPROM для встроенной в Arduino Uno памяти, запись одного байта здесь осуществляется функцией write, а чтение — read.
#include "SparkFun_External_EEPROM.h"
ExternalEEPROM eeprom;
void setup(){
Serial.begin(115200);
delay(3000);
Wire.begin();
// килобайт памяти
// 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1025, 2048
eeprom.setMemoryType(2);
eeprom.begin();
Serial.print("Mem size in bytes: ");
Serial.println(eeprom.length());
byte to_val = 200;
Serial.print("write: ");
Serial.println(to_val);
eeprom.write(0, to_val); //(location, data)
byte from_val = eeprom.read(0);
Serial.print("read: ");
Serial.println(from_val);
}
void loop()
{
}
Загружаем программу на Arduino и смотрим в монитор последовательно порта.
Mem size in bytes: 256 write: 200 read: 200
Функции put и get в данной библиотеке тоже имеются, так что можно легко работать с типами данных: int, float, long, char, byte. Для оценки их размера лучше использовать функцию sizeof.