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] = И далее код будет получать эти данные, например так: byte = mydata[i][j]; Теперь Вы хотите сохранить данные в памяти программ (flash). Используйте макрос PROGMEM и поместите его в декларацию переменной, но перед инициализатором:
#include <avr/pgmspace.h> Теперь Ваши данные хранятся в памяти программ. Можно скомпилировать, слинковать, и проверить карту памяти - массив mydata будет лежать в правильной секции. Но теперь код, получающий данные не будет работать, так как начало массива все равно интерпретируется компилятором как адрес в пространстве данных. Проблема в том, что для AVR GCC несвойственно знать, что данные могут лежать в пространстве программ. Решение проблемы довольно простое. Сначала нам надо получить адрес необходимых данных. Он равен &(mydata[i][j]). После этого можно использовать макрос для чтения данных из памяти программ по этому адресу: byte = pgm_read_byte(&(mydata[i][j])); Имеются различные макросы pgm_read_* для чтения данных разного типа и размера. Все они принимают адрес, указывающий на память программ (flash), и возвращают данные, сохраненные по этому адресу. Макросы обеспечивают для этого генерацию корректного кода. [Сохранение строк в памяти программ и получение их оттуда] Предположим, у нас есть массив строк:
char *string_table[] = Теперь добавляем макро PROGMEM:
char *string_table[] PROGMEM = Верно? Нет! К сожалению, атрибуты GCC затрагивают только объявление, к которому они присоединены. В этом случае мы действительно поместили переменную string_table, т. е. сам массив, в память программ, но не сами строки. Строки так и остались в памяти данных (RAM), что наверное не совсем то, то Вы хотели. Чтобы поместить строки во flash, нужно явно объявить каждую строку:
char string_1[] PROGMEM = "String 1"; И потом использовать новые символы в массиве:
PGM_P string_table[] PROGMEM = Теперь мы разместили массив string_table во flash, и массив string_table является массивом указателей на строки. Каждый указатель при этом указывает на строку во flash, где строка и хранится. Например, Вы хотите скопировать строку из flash в буфер RAM (например в автоматическую переменную внутри функции, расположенную в стеке). Для это нужно сделать следующее:
void foo(void) Смысл приведенного кода очевиден - получение данных из массива происходит через указатель, выбираемый как 16-битное беззнаковое целое макросом pgm_read_word. Далее строка копируется функцией strcpy_P. Имеется множество функций для манипуляции строками в памяти программ с индексом _P, работающих так же, как и обычные строковые функции. Все эти функции с индексом _P также определены в заголовке <avr/pgmspace.h>. [Предостережение] Макрос и функции, используемые для получения данных из flash, генерируют некоторый дополнительный код, который больше по объему, чем код доступа к памяти RAM. Таким образом, это создает дополнительный расход памяти программ и замедление работы кода. Этот дополнительный расход и замедление достаточно малы, поэтому выигрыш при размещении данных во flash получается значительный. Однако об этом необходимо знать, чтобы при необходимости минимизировать количество обращений к памяти внутри одной функции и/или цикла. В этом может помочь поучительный просмотр дизассемблированного кода компилятора. При написании статьи с удовольствием слушался Jo Manji "Beyond The Sunset" на "Radio Jazz". |
|
Последнее обновление ( 02.08.2010 ) |