PROGMEM / Arduino
Описание
Хранит данные во флэш-памяти (памяти программ) вместо SRAM.
Ключевое слово PROGMEM
является модификатором переменных, его следует использовать только с типами данных, определенными в pgmspace.h. Оно сообщает компилятору «поместить эту информацию во флэш-памяти», а не в SRAM, куда она обычно направляется.
PROGMEM
является частью библиотеки pgmspace.h. В современные версии IDE она включается автоматически, однако, если вы используете версию IDE ниже 1.0 (2011), сначала вам нужно включить библиотеку вначале вашего скетча, например:
#include <avr/pgmspace.h>
Синтаксис
const dataType variableName[] PROGMEM = {data0, data1, data3…};
dataType
– тип переменной.
variableName
– имя вашего массива данных.
Обратите внимание, что поскольку PROGMEM
является модификатором переменной, нет жесткого и быстрого правила о том, где он должен идти, поэтому компилятор Arduino принимает все приведенные ниже определения, которые также являются синонимами. Однако эксперименты показали, что в различных версиях Arduino (имеющих отношение к версии GCC) PROGMEM
может в одном месте работать, а в другом – нет. Пример строк ниже был протестирован на работоспособность с Arduino 13. Более ранние версии IDE могут работать лучше, если PROGMEM
включен после имени переменной.
const dataType variableName[] PROGMEM = {}; // используйте эту форму
const PROGMEM dataType variableName[] = {}; // или эту форму
const dataType PROGMEM variableName[] = {}; // но не эту
Пока PROGMEM
использовался для одной переменной, но когда у вас есть больший блок данных, который нужно сохранить, который обычно в лучшем случае является массивом (или другой структурой данных C за пределами нашего обсуждения), это становится дополнительной головной болью.
Использование PROGMEM
является также двухэтапной процедурой. После получения данных во флэш-память для чтения данных из памяти программ обратно в SRAM требуются специальные методы (функции), также определенные в библиотеке pgmspace.h, поэтому мы можем сделать с ними что-нибудь полезное.
Пример кода
Следующие фрагменты кода иллюстрируют, как читать и записывать символы (байты) и int
(2 байта) в PROGMEM
.
// сохранить какие-то значения unsigned int
const PROGMEM uint16_t charSet[] = { 65000, 32796, 16843, 10, 11234};
// сохранить какие-то значения char
const char signMessage[] PROGMEM = {"I AM PREDATOR, UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};
unsigned int displayInt;
int k; // переменная счетчика
char myChar;
void setup()
{
Serial.begin(9600);
while (!Serial); // подождать подключения последовательного порта. Необходимо для USB
// прочитать 2-байтовые значения int
for (k = 0; k < 5; k++)
{
displayInt = pgm_read_word_near(charSet + k);
Serial.println(displayInt);
}
Serial.println();
// прочитать значения char
for (k = 0; k < strlen_P(signMessage); k++)
{
myChar = pgm_read_byte_near(signMessage + k);
Serial.print(myChar);
}
Serial.println();
}
void loop()
{
}
Массивы строк
Часто при работе с большими объемами текста, например, в проекте с LCD дисплеем, удобно устанавливать массив строк. Поскольку сами строки являются массивами, на самом деле это пример работы с двумерным массивом.
Это, как правило, большие структуры, поэтому часто желательно помещать их в память программ. Приведенный ниже код иллюстрирует идею.
/*
Демонстрация работы PROGMEM со строками
Как сохранить таблицу строк в программной памяти (флэш)
и извлечь ее.
Настройка таблицы (массива) строк в программной памяти немного сложна, но
ниже приведен хороший пример.
Настройка строк - это двухэтапный процесс. Сначала определяем строки.
*/
#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); // ждем подключения последовательного порта (необходимо для USB)
Serial.println("OK");
}
void loop()
{
/*
Использование таблицы строк в памяти программ требует использования специальных функций
для извлечения данных.
Функция strcpy_P копирует строку из пространства программы в строку в RAM (буфер).
Убедитесь, что ваша принимающая строка в RAM достаточно велика, чтобы удерживать
всё, что вы извлекаете из пространства программы. */
for (int i = 0; i < 6; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Обязательные приведение и разыменование, просто скопируйте.
Serial.println(buffer);
delay( 500 );
}
}
Примечания и предупреждения
Обратите внимание, что переменные, чтобы работать с PROGMEM
, должны быть определены глобально ИЛИ с ключевым словом static
.
Следующий код НЕ будет работать, если находится внутри функции:
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. Если ваш скетч печатает много всего в монитор последовательного порта, мы можете легко заполнить RAM. Если у вас есть свободное пространство во флэш-памяти, то можете легко указать с помощью следующего синтаксиса, что строка должна быть сохранена во флэш-памяти,
Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));