Литмир - Электронная Библиотека
Содержание  
A
A

{

  byte high = EEPROM.read(1);

  byte low = EEPROM.read(2);

  return (high << 8) + low;

}

Эта функция читает двухбайтный шифр типа int из байтов с адресами 1 и 2 в ЭСППЗУ (рис. 6.5).

Программируем Arduino. Основы работы со скетчами - _48.jpg

Рис. 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 в ОЗУ.

Так как текст завершается нулевым символом, в монитор последовательного порта выводится только ожидаемый текст, а не все 50 символов.

Ограничения ЭСППЗУ

Операции чтения/записи с памятью ЭСППЗУ выполняются очень медленно — около 3 мс. Кроме того, надежность хранения гарантируется только для 100 000 циклов записи, после чего появляется вероятность искажения записанных данных. По этой причине старайтесь не выполнять запись в цикле.

Использование флеш-памяти

Объем флеш-памяти в Arduino намного больше, чем объем любой другой памяти. В Arduino Uno, например, объем флеш-памяти составляет 32 Кбайт против 2 Кбайт ОЗУ. Это делает флеш-память привлекательным местом для хранения данных, особенно если учесть, что она сохраняет данные после выключения питания.

Однако есть несколько препятствий, мешающих использованию флеш-памяти для хранения данных.

• Флеш-память в Arduino гарантирует сохранность данных только для 100 000 циклов записи, после чего она становится бесполезной.

• Флеш-память хранит программу, поэтому, если в расчеты вкрадется ошибка и часть программы окажется затертой данными, в скетче могут произойти невероятные события.

• Флеш-память содержит также загрузчик, уничтожение или искажение которого может превратить плату Arduino в «кирпич», после чего восстановить ее можно будет только с помощью аппаратного программатора (как описывалось в главе 2).

• Записывать данные во флеш-память можно только блоками по 64 байта.

Несмотря на все сказанное, в целом довольно безопасно использовать флеш-память для хранения постоянных данных, не изменяющихся в процессе выполнения скетча.

Для платы Arduino Due была создана сторонняя библиотека, позволяющая выполнять операции чтения/записи с флеш-памятью, чтобы компенсировать отсутствие ЭСППЗУ в этой модели. Более полную информацию об этом проекте можно получить по адресу http://pansenti.wordpress.com/2013/04/19/simple-flash-library-for-arduino-due/.

Самый простой способ создать строковую константу, хранящуюся во флеш-памяти, — использовать функцию F, упоминавшуюся в одном из предыдущих разделов. Напомню ее синтаксис:

Serial.println(F("Program Started"));

Этот прием работает только при использовании строковых констант непосредственно в вызове функции вывода. Нельзя, например, присвоить результат указателю на тип char.

Более гибкий, но более сложный способ заключается в использовании директивы PROGMEM (Program Memory — память программы) для сохранения любых структур данных. Однако данные должны быть постоянными — они не могут изменяться в процессе выполнения сценария.

Следующий пример иллюстрирует, как можно определить массив целых чисел (int), хранящийся во флеш-памяти:

// sketch_06_10_PROGMEM_array

#include <avr/pgmspace.h>

PROGMEM int value[] = {10, 20, 25, 25, 20, 10};

void setup()

{

  Serial.begin(9600);

  for (int i = 0; i < 6; i++)

  {

    int x = pgm_read_word(&value[i]);

    Serial.println(x);

  }

}

void loop()

{

}

Директива PROGMEM перед объявлением массива гарантирует, что он будет храниться только во флеш-памяти. Но прочитать значение элемента из такого массива можно только с помощью функции pgm_read_word из биб­лиотеки avr/pgmspace:

int x = pgm_read_word(&value[i]);

Символ & перед именем массива в параметре указывает, что функции передается адрес данного элемента массива во флеш-памяти, а не его значение.

24
{"b":"566417","o":1}