PROGMEM
Описание
Хранит константные данные только во флеш-памяти (памяти программ), вместо того чтобы копировать их в SRAM при запуске программы. Описание различных типов памяти, доступных на плате Arduino.
Ключевое слово PROGMEM является модификатором переменной; его следует использовать только с типами данных, определёнными в pgmspace.h. Оно указывает компилятору «хранить эту информацию только во флеш-памяти», вместо того чтобы копировать её в SRAM при запуске, как это происходит обычно.
PROGMEM является частью библиотеки pgmspace.h. Она подключается автоматически в Arduino IDE.
Хотя PROGMEM можно использовать для одной переменной, это действительно имеет смысл только при наличии большого блока данных, который необходимо сохранить, что обычно проще всего сделать в массиве (или другой структуре данных C++, выходящей за рамки нашего обсуждения).
Использование PROGMEM — это двухэтапная процедура. После того как переменная определена с помощью PROGMEM, её нельзя читать как обычную переменную из SRAM: для чтения необходимо использовать специальные функции, также определённые в pgmspace.h.
Важно
PROGMEM полезен только при работе с платами AVR (UNO R3, Leonardo и т.д.). Более новые платы (Due, MKR WiFi 1010, GIGA R1 WiFi и т.д.) автоматически используют пространство программ, когда переменная объявлена как const. Тем не менее, для обратной совместимости PROGMEM по-прежнему можно использовать с новыми платами. Текущая реализация находится здесь.
Синтаксис
const dataType variableName[] PROGMEM = {data0, data1, data3...};
Обратите внимание, что, поскольку PROGMEM является модификатором переменной, нет строгого правила о его расположении, поэтому компилятор Arduino принимает все приведённые ниже определения, которые являются синонимами. Однако эксперименты показали, что в различных версиях Arduino (связанных с версией GCC) PROGMEM может работать в одном месте и не работать в другом. Пример «таблицы строк» ниже был протестирован для работы с Arduino 13. Более ранние версии IDE могут лучше работать, если PROGMEM указан после имени переменной.
const dataType variableName[] PROGMEM = {};// используйте эту формуconst PROGMEM dataType variableName[] = {};// или этуconst dataType PROGMEM variableName[] = {};// не эту
Параметры
dataType: допустимые типы данных: любой тип переменнойvariableName: имя вашего массива данных
Пример кода
Следующие фрагменты кода иллюстрируют, как читать и записывать беззнаковые символы (bytes) и целые числа (int, 2 байта) в PROGMEM.
// сохраняем несколько unsigned int
const PROGMEM uint16_t charSet[] = { 65000, 32796, 16843, 10, 11234};
// сохраняем несколько символов
const char signMessage[] PROGMEM = {"I AM PREDATOR, UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};
unsigned int displayInt;
char myChar;
void setup() {
Serial.begin(9600);
while (!Serial); // ждём подключения serial-порта. Необходимо для Native USB
// put your setup code here, to run once:
// считываем обратно 2-байтовое int
for (byte k = 0; k < 5; k++) {
displayInt = pgm_read_word_near(charSet + k);
Serial.println(displayInt);
}
Serial.println();
// считываем обратно символ
int signMessageLength = strlen_P(signMessage);
for (byte k = 0; k < signMessageLength; k++) {
myChar = pgm_read_byte_near(signMessage + k);
Serial.print(myChar);
}
Serial.println();
}
void loop() {
// put your main code here, to run repeatedly:
}
Массивы строк
Часто бывает удобно при работе с большими объёмами текста, например, в проекте с LCD-экраном, создать массив строк. Поскольку строки сами по себе являются массивами, это фактически пример двумерного массива.
Такие структуры обычно велики, поэтому размещение их в памяти программ часто желательно. Код ниже иллюстрирует эту идею.
/*
Демонстрация строк в PROGMEM
Как хранить таблицу строк в памяти программ (flash) и извлекать их.
Информация обобщена из:
http://www.nongnu.org/avr-libc/user-manual/pgmspace.html
Настройка таблицы (массива) строк в памяти программ немного сложна, но
вот хороший шаблон для подражания.
Настройка строк — это двухэтапный процесс. Сначала определите строки.
*/
#include <avr/pgmspace.h>
const char string_0[] PROGMEM = "String 0"; // "String 0" и т.д. — строки для хранения — измените по необходимости.
const char string_1[] PROGMEM = "String 1";
const char string_2[] PROGMEM = "String 2";
const char string_3[] PROGMEM = "String 3";
const char string_4[] PROGMEM = "String 4";
const char string_5[] PROGMEM = "String 5";
// Затем создайте таблицу для ссылки на ваши строки.
const char *const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};
char buffer[30]; // убедитесь, что этот буфер достаточно большой для самой длинной строки
void setup() {
Serial.begin(9600);
while (!Serial); // ждём подключения serial-порта. Необходимо для Native USB
Serial.println("OK");
}
void loop() {
/* Использование таблицы строк в памяти программ требует применения специальных функций
для извлечения данных. Функция strcpy_P копирует строку из памяти программ
в строку в RAM ("buffer"). Убедитесь, что принимающая строка в RAM
достаточно велика для хранения того, что вы извлекаете из памяти программ. */
for (int i = 0; i < 6; i++) {
strcpy_P(buffer, (char *)pgm_read_ptr(&(string_table[i]))); // Необходимые приведения типов и разыменование, просто копируем.
Serial.println(buffer);
delay(500);
}
}
Примечания и предупреждения
Обратите внимание, что переменные должны быть определены глобально ИЛИ с ключевым словом static, чтобы работать с PROGMEM.
Следующий код НЕ будет работать внутри функции:
const char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n";
Следующий код БУДЕТ работать, даже если определён локально внутри функции:
const static char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n"
Макрос F()
Когда используется инструкция вида:
Serial.print("Write something on the Serial Monitor");
строка для печати обычно сохраняется в RAM. Если ваш скетч печатает много текста в Serial Monitor, вы можете легко заполнить RAM. Этого можно избежать, не загружая строки из пространства FLASH-памяти до тех пор, пока они не понадобятся. Вы можете легко указать, что строку не нужно копировать в RAM при запуске, используя синтаксис:
Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));