Главная arrow Программирование arrow AVR arrow AVR Studio +gcc: как разместить строки и константы на flash Monday, August 21 2017  
ГлавнаяКонтактыАдминистрированиеПрограммированиеСсылки
UK-flag-ico.png English Version
GERMAN-flag-ico.png Die deutsche Version
map.gif карта сайта
нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Поделиться:

AVR Studio +gcc: как разместить строки и константы на flash Версия для печати
Написал microsin   
12.01.2010

Несмотря на указание const при декларации констант, компилятор все равно для их хранения использует ОЗУ (при старте программы они просто копируются из flash в RAM). Это несомненно полезно с точки зрения быстродействия кода, но для программ, активно использующих ОЗУ и/или имеющих большой объем констант (например, строковых), памяти ОЗУ может оказаться недостаточно. Обойти проблему позволяет атрибут PROGMEM и подпрограммы для работы с данными из flash. Их можно использовать, если включить заголовочный файл <avr\pgmspace.h>.

Функции заголовка подробно описаны в C:\WinAVR-20090313\doc\avr-libc\avr-libc-user-manual\pgmspace_8h.html. Принцип работы gcc, описание проблемы подробно описаны в файле C:\WinAVR-20090313\doc\avr-libc\avr-libc-user-manual\pgmspace.html. Далее дан его почти дословный перевод.

[Данные в памяти программ]

Многие микроконтроллеры AVR имеют недостаточно памяти RAM для сохранения в нем данных и констант, однако они менют в своем распоряжении горяздо больший объем памяти программ (flash). Во flash вполне могли бы поместиться константы, что съэкономит драгоценное место в RAM. Однако микроконтроллеры AVR имеют гарвардскую архитектуру, в которой четко разделены память программ (flash) и память данных (RAM), и каждая имеет свое отдельное адресное пространство. Имеется связанная с этим некоторая проблема, чтобы сохранять данные констант во flash, и затем считывать эти данные в программе AVR.

Проблема усугубляется еще тем, что язык C был разработан не для гарвардской архитектуры, а для архитектуры фон Неймана, где код и данные сосуществуют в едином, общем адресном пространстве. Поэтому любой компилятор для гарвардской архитектуры, например AVR, должен иметь разные методы для работы с разными адресными пространствами.

Некоторые компиляторы C (например IAR Embedded Workbench for AVR) используют нестандартные ключевые слова, либо расширяют стандартный синтаксис. Набор инструментов WinAVR/gcc используют другой способ.

Компилятор GCC имеет специальное ключевое слово __attribute__, которе используется для подсоединения различных атрибутов к функциям, определениям, переменным и типам. Это ключевое слово сопровождается спецификацией атрибута в двойных круглых скобках. В AVR GCC имеется специальный атрибут progmem. Он используется при декларации данных, и говорит комилятору поместить данные в памяти программ (flash).

Библиотека AVR-Libc предоставляет простой макрос PROGMEM, который задает синтаксис GCC-атрибута progmem. Эта макрокоманда была создана для удобства конечного пользователя, как мы увидим далее. Макрос PROGMEM задан в заголовочном файле <avr/pgmspace.h>. Поскольку сложно модифицировать GCC для создания нового расширения синтаксиса C, вместо этого avr-libc имеет макросы для получения данных их flash. Они также размещены в заголовке <avr/pgmspace.h>.

[О ключевом слове const]

Многие пользователи полагают, что использование ключевого слова const декларирует размещение данных в памяти программ (flash). Это происходит из-за неверного понимания назначения ключевого слова 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];

Теперь Вы хотите сохранить данные в памяти программ (flash). Используйте макрос PROGMEM и поместите его в декларацию переменной, но перед инициализатором:

#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 будет лежать в правильной секции.

Но теперь код, получающий данные не будет работать, так как начало массива все равно интерпретируется компилятором как адрес в пространстве данных. Проблема в том, что для AVR GCC несвойственно знать, что данные могут лежать в пространстве программ.

Решение проблемы довольно простое. Сначала нам надо получить адрес необходимых данных. Он равен &(mydata[i][j]). После этого можно использовать макрос для чтения данных из памяти программ по этому адресу:

byte = pgm_read_byte(&(mydata[i][j]));

Имеются различные макросы pgm_read_* для чтения данных разного типа и размера. Все они принимают адрес, указывающий на память программ (flash), и возвращают данные, сохраненные по этому адресу. Макросы обеспечивают для этого генерацию корректного кода.

[Сохранение строк в памяти программ и получение их оттуда]

Предположим, у нас есть массив строк:

char *string_table[] =
{
    "String 1",
    "String 2",
    "String 3",
    "String 4",
    "String 5"
};

Теперь добавляем макро PROGMEM:

char *string_table[] PROGMEM =
{
    "String 1",
    "String 2",
    "String 3",
    "String 4",
    "String 5"
};

Верно? Нет! К сожалению, атрибуты GCC затрагивают только объявление, к которому они присоединены. В этом случае мы действительно поместили переменную string_table, т. е. сам массив, в память программ, но не сами строки. Строки так и остались в памяти данных (RAM), что наверное не совсем то, то Вы хотели. Чтобы поместить строки во flash, нужно явно объявить каждую строку:

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

И потом использовать новые символы в массиве:

PGM_P string_table[] PROGMEM =
{
    string_1,
    string_2,
    string_3,
    string_4,
    string_5
};

Теперь мы разместили массив string_table во flash, и массив string_table является массивом указателей на строки. Каждый указатель при этом указывает на строку во flash, где строка и хранится.

Например, Вы хотите скопировать строку из flash в буфер RAM (например в автоматическую переменную внутри функции, расположенную в стеке). Для это нужно сделать следующее:

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;
}

Смысл приведенного кода очевиден - получение данных из массива происходит через указатель, выбираемый как 16-битное беззнаковое целое макросом pgm_read_word. Далее строка копируется функцией strcpy_P. Имеется множество функций для манипуляции строками в памяти программ с индексом _P, работающих так же, как и обычные строковые функции. Все эти функции с индексом _P также определены в заголовке <avr/pgmspace.h>.

[Предостережение]

Макрос и функции, используемые для получения данных из flash, генерируют некоторый дополнительный код, который больше по объему, чем код доступа к памяти RAM. Таким образом, это создает дополнительный расход памяти программ и замедление работы кода. Этот дополнительный расход и замедление достаточно малы, поэтому выигрыш при размещении данных во flash получается значительный. Однако об этом необходимо знать, чтобы при необходимости минимизировать количество обращений к памяти внутри одной функции и/или цикла. В этом может помочь поучительный просмотр дизассемблированного кода компилятора.

При написании статьи с удовольствием слушался Jo Manji "Beyond The Sunset" на "Radio Jazz".

Последнее обновление ( 02.08.2010 )
 

Комментарии  

  1. #8 Имя обязательное
    2014-09-1014:01:51 Пример с массивом не работает, gcc выдает: предупреждение: атрибут __progmem__ проигнорирован -Wattributes. А так - спасибо, позволяет экономить кучу памяти в строках.
  2. #7 Александр
    2012-05-0121:01:18 Спасибо большое за статью, расчистил ОЗУ в своем проекте.
    Подскажите, если я объявляю два массива констант примерно вот так:

    u8 Const1[] =
    {
    0×1, 0×02, 0×03, 0×04, 0×05, 0×06
    };
    u8 Const2[] PROGMEM =
    {
    0×1, 0×02, 0×03, 0×04, 0×05, 0×06
    };

    то можно ли как-то в процедуре, которой передается указатель на константу, понять в какой памяти она расположена? Т. е. сейчас проблема в том, что пришлось сделать две процедуры, одна работает с константами в ОЗУ, другая с PROGMEM.

    microsin: к сожалению, мне неизвестен универсальный способ обращения из одной процедуры и к RAM, и к flash (PROGMEM). Наверное, простого способа сделать это нет, так как адресное пространство и RAM, и flash совершенно разное, и компилятор генерирует для каждого случая индивидуальный код. Т. е. метод адресации выбирается на этапе компиляции. Выбор метода адресации на этапе выполнения все равно будет связан с потерей и кода, и что самое неприятное - быстродействия.
  3. #6 Вячеслав Мезенцев
    2011-07-1501:49:50 Там в исходниках есть такая вот фича:
    /* The real thing. */
    # define PSTR(s) (__extension__( {static char __c[] PROGMEM = (s); &__c[0];}))

    Эта фича позволяет писать внутри функций так:
    char * mystr = PSTR( ":)" );

    Небольшая такая автоматизация. Увы, но это мало помогает, если пишёшь для IAR и GCC сразу. В IAR'е очень удобно можно объявлять строки прямо в коде функций. PSTR() тут поможет, но флеш-строки порою используются в флеш-структурах, где, увы, такой фокус уже не проходит.

    Это единственная неудобная вещь, когда пишешь код, который одновременно может компилиться в IAR и GCC (на EC++ без специфики).

    Если кому интересно, вот недоделанная пока заготовка кросс-компиляторного шаблона:
    https://mysvn.ru/cop/Example/

    Компилируется в IAR 5.51 и AVR-GCC. Для переключения компилятора комментим три строки в Defines.h. Открывать проект в VS2008 (c WinAVR-20100110) либо в IAR 5.51.
  4. #5 Alex
    2011-03-2211:37:22 Спасибо за перевод! Но вот хочется разместить не только во Flash, но еще и с заданного адреса во Flash. Может кто-нибудь подсказать как такое сделать в AVR Studio +gcc.

    microsin: в документации к Вашему WinAVR все подробно на пальцах описано, как это делать - см. doc\avr-libc\avr-libc-user-manual.pdf, FAQ "How do I relocate code to a fixed address?". Или, как обычно, воспользуйтесь Google, ключевые слова для поиска "__attribute__" section "—section-start="
  5. #4 Антон
    2011-03-1614:27:43 Огромное спасибо!
  6. #3 Алексей
    2011-01-1723:00:14 Спасибо большое, помогли решить проблему!
  7. #2 Дмитрий
    2010-08-0216:50:34 Огромное вам спасибо за ваши труды, статья помогла при разработке очень важного проекта!!!
  8. #1 Юрий
    2010-04-1912:12:52 Спасибо за грамотный перевод, ибо я в общих чертах понял особенности работы с Flash, при прочтении на английском, но в вашем переводе все значительно упростилось!

Добавить комментарий

:D:lol::-);-)8):-|:-*:oops::sad::cry::o:-?:-x:eek::zzz:P:roll::sigh:

Защитный код
Обновить

< Пред.   След. >

Top of Page
 
microsin © 2017