Arduino:Справочник языка Arduino/PROGMEM

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Ключевое слово PROGMEM[1]

Записывает данные не в SRAM, а во flash-память (т.е. в программную память). Описание типов памяти, доступных на Arduino, можно найти тут.

Ключевое слово RROGMEM — это модификатор переменных, и его можно использовать только с теми типами данных, которые определены в библиотеке pgmspace.h. Он говорит компилятору «разместить информацию во flash-памяти», а не в SRAM, как это происходит обычно.

PROGMEM является частью библиотеки pgmspace.h, которая доступна только для архитектуры ARM. Поэтому, если вы собираетесь использовать PROGMEM, то начать скетч нужно именно с подключения этой библиотеки. Примерно так:

#include <avr/pgmspace.h>

Синтаксис

const dataType variableName[] PROGMEM = {data0, data1, data3...}; 

dataType  это тип переменной
variableName  название массива данных

Поскольку PROGMEM — это модификатор переменных, то какого-то четкого правила о том, куда его поставить, нет. Следовательно, компилятор Arduino примет любую из конструкций, описанных ниже. Они, по сути, являются синонимами.

Впрочем, ряд экспериментов выявил, что в разных версиях Arduino (в зависимости от версии GCC) PROGMEM нужно ставить по-разному, потому что если поставить его в одном месте, то он будет работать, а если в другом — нет. К примеру, конструкции, указанные ниже, протестированы для работы с Arduino 13 (1 и 2 можно использовать, а 3 лучше не надо). Кроме того, в ранних версиях IDE модификатор будет работать лучше, если поставить его после имени переменной.

const dataType variableName[] PROGMEM = {};   // используйте эту форму
const PROGMEM  dataType  variableName[] = {}; // или эту
const dataType PROGMEM variableName[] = {};   // но не эту

Хотя PROGMEM можно использовать и с одной переменной, он наиболее полезен при сохранении большого блока данных. Лучше всего — в виде массива (или в другой структуре данных языка C, о котором не рассказывается в этой статье).

Процедуру использования PROGMEM можно поделить на два этапа. Сначала данные размещаются во flash-память, а после этого — из программной памяти обратно в SRAM, для чего потребуются специальные функции, тоже имеющиеся в библиотеке pgmspace.h.

Пример

Коде ниже демонстрирует, как использовать PROGMEM для считывания и записи символом (байтов) и целых чисел (2 байтов).

#include <avr/pgmspace.h>

// сохраняем несколько беззнаковых целых чисел
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;
int k;    // переменная-счечтик
char myChar;

void setup() {
  Serial.begin(9600);
  while (!Serial);

  // Тут размещаем настройки — их понадобится запустить всего раз.
  // Повторно считываем 2-байтное целое:
  for (k = 0; k < 5; k++)
  {
    displayInt = pgm_read_word_near(charSet + k);
    Serial.println(displayInt);
  }
  Serial.println();

  // Повторно считываем символ:
  int len = strlen_P(signMessage);
  for (k = 0; k < len; k++)
  {
    myChar =  pgm_read_byte_near(signMessage + k);
    Serial.print(myChar);
  }

  Serial.println();
}

void loop() {
  // Здесь размещаем главный код, который будет постоянно повторяться:

}

Массивы строк

При работе с большим количеством текста (например, для вывода на LCD-экран), как правило, удобно пользоваться массивом строк. Поскольку строки сами по себе являются массивами, это, по сути, будет примером двухмерного массива.

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

/*
Демонстрация работы со строками при помощи PROGMEM
Как записывать таблицу строк в программную память (flash-память),  а затем доставать их оттуда.

Это выжимка отсюда:
http://www.nongnu.org/avr-libc/user-manual/pgmspace.html

Размещение таблицы (массива) строк в программную память — не самая простая задача,
но данный скетч, будем надеться, поможет разобраться, как это сделать.

Запись строк — это 2-этапный процесс. Для начала определяем строки.
*/

#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.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. Таким образом, если в вашем скетче много подобных строк, которые выводятся на Serial Monitor, то ресурс RAM-памяти может закончиться очень быстро. Однако если у вас есть свободное место во flash-памяти, то вышеуказанную инструкцию можно изменить таким образом, чтобы строка сохранялась именно туда. Для этого используется следующий синтаксис:

Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));

См.также

Внешние ссылки