Допустим имеются некоторые постоянные данные, и участок памяти для их хранения исчерпывается? Многие AVRы для хранения данных имеют ограниченный размер RAM, но могут иметь больше перепрограммируемого пространства (Flash). МК AVR имеют Гарвардскую архитектуру, где Flash используется для программ, а RAM для данных, и каждая из них имеют отдельное адресное пространство. Это - вызов, чтобы заставить постоянные данные быть сохраненными в программном пространстве, и находить эти данные, чтобы использовать их в приложении AVR.
Проблема усилена тем фактом, что Язык C не был разработан для Гарвардской архитектуры, он был разработан для архитектуры Von Neumann, в которой код и данные находятся в одном и том же адресном пространстве. Это означает, что любой компилятор для процессора Гарвардской архитектуры, подобный AVR, должен использовать другие средства? работать с отдельными адресными пространствами.
Некоторые компиляторы используют ненормативные ключевые слова Языка C, или они расширяют стандартный синтаксис способами, которые являются ненормативными. Комплект инструментальных средств AVR имеет отличный подход.
GCC
имеет
специальное
ключевое
слово, __
attribute
__
оно
используется
для того,
чтобы
прикрепить
различные
атрибуты к
вещам типа
объявлений
функций,
переменных, и
печати. Это
ключевое
слово сопровождается
спецификацией
атрибута в
двойных
круглых
скобках. В AVR GCC,
есть
специальный вызваемый
атрибут progmem
.
Этот атрибут
используется
при
объявлении
данных, и
говорит
компилятору
размещать данные
в памяти
программ (Flash).
AVR-Libc обеспечивает
простую
макрокоманду
PROGMEM
она
определена
как
синтаксис
атрибута GCC с
атрибутом progmem.
Эта
макрокоманда
была создана
как удобство
конечному
пользователю,
поскольку мы
будем видеть
ниже. PROGMEM
макрокоманда
определена в <avr/pgmspace.h
>.
Трудно
изменить GCC, чтобы
создать
новые
расширения к
синтаксису
Языка C, так
вместо этого,
avr-libc создал
макрос для
поиска
данных в
программном
пространстве.
Этот макрос
также находится
в <avr/pgmspace.h
>.
Многие
пользователи
поднимают
идею использования
ключевого
слова const
как
средства
объявления
данных
помещаемых в
пространство
программы. Но
это было бы не
правильно.
const
используется
для того,
чтобы
указать компилятору,
что данные
предназначены
"только для чтения".
Оно
используется
для того,
чтобы помочь
компилятору
делать
некоторые преобразования,
или помогать
компилятору
проверять
неправильное
использование
этех
переменных.
Например, ключевое слово const обычно используется во многих функциях, как модификатор на типе параметра. Это говорит компилятору, что функция будет использовать параметр только для чтения, и не будет изменять содержимое такой переменной.
const
предназначен
для
использований
этого типа, а
не как средство
определения
места
хранения данных.
Если это
использовалось
как средство
для определения
хранения
данных, то
это теряет
его
правильное
значение
(изменяет его
семантику) в
других
ситуациях
типа в
функциональном
примере
параметра.
Скажем, Вы имеете некоторые глобальные данные:
unsigned char mydata[11][10] =
{
{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09},
{0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13},
{0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D},
{0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27},
{0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31},
{0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B},
{0x3C,0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44,0x45},
{0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F},
{0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59},
{0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63},
{0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D}
};
и позже, в вашем коде, обращаетесь к этим данным в функции, и сохраняете единственный байт в переменную, например так:
byte = mydata[i][j];
Теперь
Вы хотите
сохранить
ваши данные в
Памяти
Программы.
Использование
PROGMEM макрокоманды,
находящейся
в <avr/pgmspace.h>
и поместите
это после
объявления
переменной,
но перед
инициализацией,
например
так:
#include <avr/pgmspace.h>
.
.
.
unsigned char mydata[11][10] PROGMEM =
{
{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09},
{0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13},
{0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D},
{0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27},
{0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31},
{0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B},
{0x3C,0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44,0x45},
{0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F},
{0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59},
{0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63},
{0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D}
};
Вот теперь ваши данные находятся в Пространстве Программы! Вы можете компилировать, связать, и проверить файл карты, чтобы проверить это - mydata помещен в правильный раздел.
Теперь, когда ваши данные постоянно находятся в Пространстве Программы, и ваш код при чтении данных больше не будет работать. Код, который сгенерирован, отыщет данные, которые расположены в адресе массива mydata, плюс смещения, индексированные i и j переменные. Адрес, который рассчитан для отыскания данных указывает на Пространство Данных! А не на Пространство Программы, где данные расположены на самом деле. Вероятно, что Вы найдёте там некоторый мусор. Проблема – в том, что AVR GCC не знает, что данные находятся в Пространстве Программы.
Решение довольно просто. "Правило бегунка" - чтобы обращаться к данным, сохраненным в Пространстве Программы состоит в том, что для того чтобы обратиться к данным, поскольку Вы обычно были бы (как будто переменная сохранена в Пространстве Данных), подобно так:
byte = mydata[i][j];
тогда возьмите адрес данных:
byte = &(mydata[i][j]);
тогда используйте соответствующий pgm_read_* макрокоманда, и адрес ваших данных становятся параметром для той макрокоманды:
byte = pgm_read_byte(&(mydata[i][j]));
pgm_read_* макрос берет адрес, который указывает на Пространство Программы, и отыскивает данные, которые сохранены в том адресе. Это - то, почему Вы берете адрес смещения в массив. Этот адрес становится параметром для макрокоманды, так что это может генерировать правильный код, чтобы отыскать данные в Пространстве Программы. Там отличны pgm_read_* макрос, чтобы читать отличные размеры данных в данном адресе.
Now that you can successfully store
and retrieve simple data from Program Space you want to store and retrive
strings from Program Space. And specifically you want to store and array of
strings to Program Space. So you start off with your array, like so:
char *string_table[] =
{
"String 1",
"String 2",
"String 3",
"String 4",
"String 5"
};
and then you add your PROGMEM macro
to the end of the declaration:
char *string_table[] PROGMEM =
{
"String 1",
"String 2",
"String 3",
"String 4",
"String 5"
};
Right? WRONG!
Unfortunately, with GCC attributes,
they affect only the declaration that they are attached to. So in this case, we
successfully put the string_table
variable, the array itself, in the Program
Space. This DOES NOT put the actual strings themselves into Program Space. At
this point, the strings are still in the Data Space, which is probably not what
you want.
In order to put the strings in
Program Space, you have to have explicit declarations for each string, and put
each string in Program Space:
char string_1[] PROGMEM = "String 1";
char string_2[] PROGMEM = "String 2";
char string_3[] PROGMEM = "String 3";
char string_4[] PROGMEM = "String 4";
char string_5[] PROGMEM = "String 5";
Then use the new symbols in your
table, like so:
Now this has the effect of putting string_table
in Program Space, where string_table
is an array of pointers to
characters (strings), where each pointer is a pointer to the Program Space,
where each string is also stored.
The PGM_P
type above is also a macro that
defined as a pointer to a character in the Program Space.
Retrieving the strings are a
different matter. You probably don't want to pull the string out of Program
Space, byte by byte, using the pgm_read_byte()
macro. There are other functions declared in
the <avr/pgmspace.h> header file that work with
strings that are stored in the Program Space.
For example if you want to copy the
string from Program Space to a buffer in RAM (like an automatic variable inside
a function, that is allocated on the stack), you can do this:
void foo(void)
{
char buffer[10];
for (unsigned char i = 0; i < 5; i++)
{
strcpy_P(buffer, (PGM_P)pgm_read_word(&(string_table[i])));
// Display buffer on LCD.
}
return;
}
Here, the string_table
array is stored in Program Space,
so we access it normally, as if were stored in Data Space, then take the
address of the location we want to access, and use the address as a parameter
to pgm_read_word
. We use the pgm_read_word
macro to read the string pointer out of the string_table
array. Remember that a pointer is
16-bits, or word size. The pgm_read_word
macro will return a 16-bit unsigned integer.
We then have to typecast it as a true pointer to program memory,PGM_P
. This pointer is an address in
Program Space pointing to the string that we want to copy. This pointer is then
used as a parameter to the function strcpy_P
. The function strcpy_P
is just like the regular strcpy
function, except that it copies a
string from Program Space (the second parameter) to a buffer in the Data Space
(the first parameter).
There are many string functions
available that work with strings located in Program Space. All of these special
string functions have a suffix of _P
in the function name, and are
declared in the <avr/pgmspace.h> header file.
Макросы и функции имели обыкновение отыскивать данные в Пространстве Программы, должны генерировать некоторый дополнительный код, чтобы фактически загрузить данные из программного пространства. Это несет некоторое дополнительное пространство наверху в терминах пространства кода (дополнительные коды операции) и время выполнения. Обычно, и пространство и время минимальны по сравнению с пространственными сбережениями помещения данных в программном пространстве. Но Вы должны знать об этом, так Вы можете свернуть номер запросов в пределах одной функции, которая получает ту же самую часть данных из программного пространства. Всегда поучительно смотреть на результат работы компилятора.