Для решения этой проблемы можно написать отдельный скетч, устанавливающий шифр по умолчанию. Этот скетч потребовалось бы установить в плату Arduino перед основным скетчем.
Второй, менее надежный, но более удобный способ — использовать специальный признак, который записывается в ЭСППЗУ и указывает, что шифр действительно был записан. Недостатком этого решения является малая вероятность того, что в ячейке ЭСППЗУ, где должен храниться признак, уже будет записано его значение. Из-за этого обстоятельства данное решение неприемлемо для коммерческих продуктов, но в данном случае мы можем так рискнуть.
Функция initializeCode читает первый байт из ЭСППЗУ, и, если он равен переменной codeMarkerValue, которой где-то в другом месте присваивается значение 123, она считает, что ЭСППЗУ содержит установленный пользователем шифр, и вызывает функцию readSecretCodeFromEEPROM:
int readSecretCodeFromEEPROM()
{
byte high = EEPROM.read(1);
byte low = EEPROM.read(2);
return (high << 8) + low;
}
Эта функция читает двухбайтный шифр типа int из байтов с адресами 1 и 2 в ЭСППЗУ (рис. 6.5).
Рис. 6.5. Хранение значения типа int в ЭСППЗУ
Чтобы из двух отдельных байтов получить одно значение int, нужно сдвинуть старший байт влево на 8 двоичных разрядов (high << 8) и затем прибавить младший байт.
Чтение хранимого кода из ЭСППЗУ выполняется только в случае сброса платы Arduino. Но запись шифра в ЭСППЗУ должна выполняться при каждом его изменении, чтобы после выключения или сброса Arduino шифр сохранился в ЭСППЗУ и мог быть прочитан в момент запуска скетча.
За запись отвечает функция saveSecretCodeToEEPROM:
void saveSecretCodeToEEPROM()
{
EEPROM.write(0, codeSetMarkerValue);
EEPROM.write(1, highByte(code));
EEPROM.write(2, lowByte(code));
}
Она записывает признак в ячейку ЭСППЗУ с адресом 0, указывающим, что в ЭСППЗУ хранится действительный шифр, и затем записывает два байта шифра. Для получения старшего и младшего байтов шифра типа int используются вспомогательные функции highByte и lowByte из стандартной библиотеки Arduino.
Использование библиотеки avr/eeprom.h
Библиотека EEPROM позволяет писать и читать данные только по одному байту. В предыдущем разделе мы обошли это ограничение, разбивая значение int на два байта перед сохранением и объединяя два байта в значение int после чтения. В качестве альтернативы, однако, можно использовать библиотеку EEPROM, предоставляемую компанией AVR, производящей микроконтроллеры. Она обладает более широкими возможностями, включая чтение и запись целых слов (16 бит) и даже блоков памяти произвольного размера.
Следующий скетч использует эту библиотеку для сохранения и чтения значения int непосредственно, увеличивая его при каждом перезапуске Arduino:
// sketch_06_07_avr_eeprom_int
#include <avr/eeprom.h>
void setup()
{
int i = eeprom_read_word((uint16_t*)10);
i++;
eeprom_write_word((uint16_t*)10, i);
Serial.begin(9600);
Serial.println(i);
}
void loop()
{
}
Аргумент в вызове eeprom_read_word (10) и первый аргумент в вызове eeprom_write_word — это начальный адрес слова. Обратите внимание на то, что слово состоит из двух байтов, поэтому, если понадобится записать еще одно значение int, нужно будет указать адрес 12, а не 11. Конструкция (uint16_t*) перед 10 необходима, чтобы привести адрес (или индекс) к типу, ожидаемому библиотечной функцией.
Еще одна полезная пара функций в этой библиотеке — eeprom_read_block и eeprom_write_block. Эти функции позволяют сохранять и извлекать произвольные структуры данных (допустимого размера).
Например, далее приводится скетч, записывающий строку символов в ЭСППЗУ, начиная с адреса 100:
// sketch_06_07_avr_eeprom_string
#include <avr/eeprom.h>
void setup()
{
char message[] = "I am written in EEPROM";
eeprom_write_block(message, (void *)100,
strlen(message) + 1);
}
void loop()
{
}
В первом аргументе функции eeprom_write_block передается указатель на массив символов, подлежащий записи, во втором — адрес первой ячейки в ЭСППЗУ (100). В последнем аргументе передается число байтов, которые требуется записать. Здесь это число вычисляется как длина строки плюс один байт для завершающего нулевого символа.
Ниже демонстрируется скетч, который читает строку из ЭСППЗУ и выводит ее в монитор последовательного порта вместе с числом, обозначающим длину строки:
// sketch_06_07_avr_eeprom_string_read
#include <avr/eeprom.h>
void setup()
{
char message[50]; // буфер достаточно большого размера
eeprom_read_block(&message, (void *)100, 50);
Serial.begin(9600);
Serial.println(message);
Serial.println(strlen(message));
}
void loop()
{
}
Для чтения строки создается массив емкостью 50 символов. Затем вызывается функция eeprom_read_block, которая читает 50 символов в message. Знак & перед message указывает, что функции передается адрес массива message в ОЗУ.