Keil: размещение функций и данных по указанному адресу
Добавил(а) microsin
Есть несколько методов, чтобы поместить функции и данные по определенному адресу.
При необходимости компилятор обычно создает секции RO, RW, ZI и XO из одного исходного файла. Эти секции содержат весь код и данные из исходного файла. Чтобы поместить одну функцию или элемент данных по фиксированному адресу, Вы должны позволить линкеру обработать эту функцию или данные отдельно от остальных входных файлов.
Примечание: RO обозначает секцию памяти только для чтения (read only), RW память для чтения и записи (read/write), ZI память автоматически инициализируемую нулями (zero initialized), XO память только для выполнения кода (execute only).
Линкер позволит разместить секцию по определенному адресу следующими способами:
• Вы можете создать так называемый scatter-файл, который определяет регион выполнения по требуемому адресу с описанием секции, которое выбирает только одну секцию. • Для специально именованной секции линкер может получить адрес размещения из имени секции. Специально именованные секции называются __at секции.
scatter-файл это некое подобие файла управления линкером, который определяет регионы памяти для кода и данных. Этот файл по умолчанию получает имя из имени файла исполняемого кода (поле ввода "Name of Executable", что указывается на закладке Output в свойствах проекта) и расширения *.sct, и находится в папке выходных файлов для сборки (например корневая_папка_проекта\_build\nrf52832_xxaa.sct).
scatter-файл по умолчанию Keil генерирует автоматически, если в свойствах проекта на закладке Linker стоит галочка "Use Memory Layout from Target Dialog".
Пример типичного автоматически генерируемого scatter-файла:
Примечание: префикс LR_ обозначает регион загрузки (Load Region), ER_ регион выполнения (Execution Region), RW_ область для чтения и записи, данные (Read/Write).
Все эти магические шестнадцатеричные значения для областей памяти IROM1 (внутренняя память FLASH микроконтроллера nRF52) и IRAM1 (внутреннее ОЗУ микроконтроллера nRF52) берутся из опций, указанных на закладке Target свойств проекта, если установлена вышеупомянутая галочка "Use Memory Layout from Target Dialog".
Если галочку "Use Memory Layout from Target Dialog" снять, то на закладке Linker свойств проекта появляется вручную указать scatter-файл. В этом случае Keil его не будет генерировать автоматически, и появится возможность scatter-файл произвольно редактировать (если Вы понимаете, что делаете).
Это может быть полезно, если Вы хотите создать свою собственную карту памяти, с определенными регионами памяти. Пример, каким образом это может использоваться, см. в статье [2].
Чтобы поместить функцию или переменную по специально указанному адресу, Вы должны поместить её в свою собственную секцию. Для этого есть несколько вариантов:
• Поместить функцию или элемент данных в их собственный исходный файл. • Использовать __attribute__((at(address))), чтобы поместить переменные в отдельной секции по указанному адресу. • Использовать __attribute__((section("name"))) чтобы поместить функции и переменные в именованную секцию. • Использовать директиву AREA из языка ассемблера. В коде ассемблера наименьшей локализуемой единицей является AREA. • Использовать опцию компилятора --split_sections, чтобы сгенерировать одну ELF-секцию для каждой функции в исходном файле. Эта опция приводит к незначительному увеличению размера кода, потому что потенциально уменьшает возможности по совместному использованию функциями адресов, данных и строковых литералов. Однако это может помочь уменьшить размер конечного образа, если линкеру разрешено удалять неиспользуемые функции при использовании armlink --remove.
7.2.6 Placement of __at sections at a specific address site:keil.com 7.3 Example of how to explicitly place a named section with scatter-loading site:keil.com 7.2.7 Restrictions on placing __at sections site:keil.com
--split_sections site:keil.com __attribute__((section("name"))) function attribute site:keil.com __attribute__((at(address))) variable attribute site:keil.com __attribute__((section("name"))) variable attribute site:keil.com #pragma arm section [section_type_list] site:keil.com AREA directive site:keil.com
[Пример размещения переменной по указанному адресу без scatter-загрузки]
Этот пример показывает, как изменить модуль исходного кода, чтобы поместить код и данные по указанному адресу, и это не потребует специального scatter-файла.
Процесс по шагам:
1. Создайте модуль исходного кода main.c со следующим содержимым:
#include < stdio.h >
externintsqr(int n1);
int gSquared __attribute__((at(0x5000))); // размещение по адресу 0x5000
intmain(void)
{
gSquared=sqr(3);
printf("Value squared is: %d\n", gSquared);
return0;
}
2. Создайте исходный файл function.c со следующим кодом:
Опция --map отобразит карту памяти образа. Также по умолчанию применяется опция --autoat.
В этом примере __attribute__((at(0x5000))) задает разместить глобальную переменную gSquared по абсолютному адресу 0x5000. Переменная gSquared помещается в регионе выполнения (execution region) ER$$.ARM.__at_0x00005000 и регионе загрузки (load region) LR$$.ARM.__at_0x00005000.
Примечание: хотя в исходном файле указан адрес 0x5000, имена региона и секции составляются из нормализованного адреса, состоящего из 8 шестнадцатеричных цифр.
Base Addr Size Type Attr Idx E Section Name Object
0x00005000 0x00000004 Zero RW 13 .ARM.__at_0x00005000 main.o
[Пример размещения переменной в именованной секции с применением scatter-загрузки]
Этот пример показывает, как изменить модуль исходного кода, чтобы поместить код и данные по указанному адресу с помощью scatter-файла.
Процесс по шагам:
1. Создайте модуль исходного кода main.c со следующим содержимым:
#include < stdio.h >
externintsqr(int n1);
// Переменная будет размещена в секции foo
int gSquared __attribute__((section("foo")));
intmain(void)
{
gSquared=sqr(3);
printf("Value squared is: %d\n", gSquared);
return0;
}
2. Создайте исходный файл function.c, содержащий следующий код:
intsqr(int n1)
{
return n1*n1;
}
3. Создайте scatter-файл, содержащий следующий регион загрузки:
LR1 0x0000 0x20000
{
ER1 0x0 0x2000
{
*(+RO) ; остальной код и данные только для чтения (read-only)
}
ER2 0x8000 0x2000
{
main.o
}
ER3 0x10000 0x2000
{
function.o
*(foo) ; переменная gSquared будет размещена в ER3
}
; RW and ZI data to be placed at 0x200000
RAM 0x200000 (0x1FF00-0x2000)
{
*(+RW, +ZI)
}
ARM_LIB_STACK 0x800000 EMPTY -0x10000
{
}
ARM_LIB_HEAP +0 EMPTY 0x10000
{
}
}
Примечание: здесь LR обозначает область загрузки (Load Region), ER область выполнения (Execution Region). Регионы ARM_LIB_STACK и ARM_LIB_HEAP необходимы, потому что программа линкуется с библиотеками семихостинга (semihosting libraries).
Опция --map отобразит карту памяти образа. Также по умолчанию применяется опция --autoat.
В этом примере __attribute__((section("foo"))) указывает разместить глобальную переменную gSquared в именованной секции памяти foo. scatter-файл указывает разместить секцию foo в области выполнения (execution region) ER3.
squared=sqr(gValue);
printf("Value squared is: %d\n", squared);
return0;
}
2. Создайте исходный файл function.c, содержащий следующий код:
intsqr(int n1)
{
return n1*n1;
}
3. Создайте scatter-файл, содержащий следующий регион загрузки:
LR1 0x0
{
ER1 0x0
{
*(+RO) ; остальной код и данные только для чтения (read-only)
}
ER2 +0
{
function.o
*(.ARM.__at_0x10000) ; gValue разместится по адресу 0x10000
}
; Данные RW и ZI должны быть размещены по адресу 0x200000
RAM 0x200000 (0x1FF00-0x2000)
{
*(+RW, +ZI)
}
ARM_LIB_STACK 0x800000 EMPTY -0x10000
{
}
ARM_LIB_HEAP +0 EMPTY 0x10000
{
}
}
Примечание: ZI обозначает память, инициализируемую нулями (zero-initialized). Регионы ARM_LIB_STACK и ARM_LIB_HEAP необходимы, потому что программа линкуется с библиотеками семихостинга (semihosting libraries).
Опция --map отобразит карту памяти образа. Карта памяти покажет, что переменная gValue была размещена в области выполнения ER2 (execution region) по адресу 0x10000:
Base Addr Size Type Attr Idx E Section Name Object
0x00001578 0x0000000c Code RO 3 .text function.o
0x00001584 0x0000ea7c PAD
0x00010000 0x00000004 Data RO 15 .ARM.__at_0x10000 main.o
В этом примере размер области выполнения ER1 неизвестен. Таким образом, переменная gValue может быть помещена в ER1 или ER2. Чтобы гарантировать, что gValue будет размещена в ER2, необходимо подключить соответствующий селектор в ER2, и выполнить линковку с опцией командной строки --no_autoat. Если Вы опустите --no_autoat, то gValue будет помещена в отдельный регион LR$$.ARM.__at_0x10000, который содержит регион выполнения ER$$.ARM.__at_0x10000.
[Пример размещения функции в RAM с помощью редактирования опций проекта]
В Keil имеется возможность для отдельного модуля исходного кода указать индивидуальные настройки. Этим можно воспользоваться, чтобы поместить код функции в оперативную память. Это может понадобится для функций самопрограммирования (например загрузчика) или для функций, критичных к времени выполнения (обработчиков прерывания).
Чтобы указать для модуля кода отдельные настройки (на примере проекта для процессора nRF52832 из SDK v12.3), выполните на нем правый клик мышью, и выберите Options.
Откроется окно диалога настроек свойств этого модуля. Чтобы разместить код этого модуля в RAM, в поле Code / Const введите IRAM1 [0x20002C38-0x2000FFFF].
Также можно открыть выпадающий список, и в нем выбрать нужный регион.
[Ссылки]
1. Methods of placing functions and data at specific addresses site:keil.com. 2. RAM retention between system resets (#pragma NOINIT replacement) site:devzone.nordicsemi.com.