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"));

Смотрите также