Программирование ARM: решение проблем, FAQ Keil: размещение функций и данных по указанному адресу Sat, October 05 2024  

Поделиться

Нашли опечатку?

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

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).

Keil5 project options Name of Executable

scatter-файл по умолчанию Keil генерирует автоматически, если в свойствах проекта на закладке Linker стоит галочка "Use Memory Layout from Target Dialog".

Keil5 project options Linker Use Memory Layout from Target Dialog

Пример типичного автоматически генерируемого scatter-файла:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
 
LR_IROM1 0x0001F000 0x00018400  {    ; load region size_region
  ER_IROM1 0x0001F000 0x00018400  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20002800 0x0000D800  {  ; RW data
    dfu.o (+RO)
   .ANY (+RW +ZI)
  }
}

Примечание: префикс LR_ обозначает регион загрузки (Load Region), ER_ регион выполнения (Execution Region), RW_ область для чтения и записи, данные (Read/Write).

Все эти магические шестнадцатеричные значения для областей памяти IROM1 (внутренняя память FLASH микроконтроллера nRF52) и IRAM1 (внутреннее ОЗУ микроконтроллера nRF52) берутся из опций, указанных на закладке Target свойств проекта, если установлена вышеупомянутая галочка "Use Memory Layout from Target Dialog".

Keil5 project options Target

Если галочку "Use Memory Layout from Target Dialog" снять, то на закладке Linker свойств проекта появляется вручную указать scatter-файл. В этом случае Keil его не будет генерировать автоматически, и появится возможность scatter-файл произвольно редактировать (если Вы понимаете, что делаете).

Keil5 project options Linker use custom scatter file

Это может быть полезно, если Вы хотите создать свою собственную карту памяти, с определенными регионами памяти. Пример, каким образом это может использоваться, см. в статье [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

[Связанная документация]

9.6 --autoat, --no_autoat site:keil.com
9.73 --map, --no_map site:keil.com
9.100 --scatter=filename site:keil.com
9.80 -o filename, --output=filename 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 >
 
extern int sqr(int n1);
 
int gSquared __attribute__((at(0x5000)));  // размещение по адресу 0x5000
 
int main(void)
{
   gSquared=sqr(3);
   printf("Value squared is: %d\n", gSquared);
   return 0;
}

2. Создайте исходный файл function.c со следующим кодом:

int sqr(int n1)
{
   return n1*n1;
}

3. Выполните компиляцию и линковку командами:

armcc -c function.c
armcc -c main.c
armlink --map function.o main.o -o squared.axf

Опция --map отобразит карту памяти образа. Также по умолчанию применяется опция --autoat.

В этом примере __attribute__((at(0x5000))) задает разместить глобальную переменную gSquared по абсолютному адресу 0x5000. Переменная gSquared помещается в регионе выполнения (execution region) ER$$.ARM.__at_0x00005000 и регионе загрузки (load region) LR$$.ARM.__at_0x00005000.

Примечание: хотя в исходном файле указан адрес 0x5000, имена региона и секции составляются из нормализованного адреса, состоящего из 8 шестнадцатеричных цифр.

Карта памяти покажет следующее:

...
 
Load Region LR$$.ARM.__at_0x00005000 (Base: 0x00005000, Size: 0x00000000,
                                      Max: 0x00000004, ABSOLUTE)
  Execution Region ER$$.ARM.__at_0x00005000 (Base: 0x00005000, Size: 0x00000004,
                                             Max: 0x00000004, ABSOLUTE, UNINIT)
 
  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 >
 
extern int sqr(int n1);
 
// Переменная будет размещена в секции foo
int gSquared __attribute__((section("foo")));
 
int main(void)
{
   gSquared=sqr(3);
   printf("Value squared is: %d\n", gSquared);
   return 0;
}

2. Создайте исходный файл function.c, содержащий следующий код:

int sqr(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).

3. Выполните компиляцию и линковку командами:

armcc -c function.c
armcc -c main.c
armlink --map --scatter=scatter.scat function.o main.o -o squared.axf

Опция --map отобразит карту памяти образа. Также по умолчанию применяется опция --autoat.

В этом примере __attribute__((section("foo"))) указывает разместить глобальную переменную gSquared в именованной секции памяти foo. scatter-файл указывает разместить секцию foo в области выполнения (execution region) ER3.

Карта памяти покажет следующее:

  Load Region LR1 (Base: 0x00000000, Size: 0x00001570, Max: 0x00020000, ABSOLUTE)
...
    Execution Region ER3 (Base: 0x00010000, Size: 0x00000010, Max: 0x00002000, ABSOLUTE)
 
    Base Addr    Size         Type   Attr      Idx    E Section Name      Object
 
    0x00010000   0x0000000c   Code   RO          3    .text               function.o
    0x0001000c   0x00000004   Data   RW         15    foo                 main.o
...

Примечание: если Вы не укажете *(foo) в scatter-файле, то секция будет помещена в регион с таким же типом. В нашем примере это RAM.

[Пример размещения переменной по указанному адресу с применением scatter-загрузки]

Этот пример показывает, как изменить исходный код, чтобы поместить код и данные по специальному адресу с помощью scatter-файла.

Процесс по шагам:

1. Создайте модуль исходного кода main.c со следующим содержимым:

#include < stdio.h >
 
extern int sqr(int n1);
 
// Переменная будет помещена по адресу 0x10000:
const int gValue __attribute__((section(".ARM.__at_0x10000"))) = 3;
 
int main(void)
{
   int squared;
 
   squared=sqr(gValue);
   printf("Value squared is: %d\n", squared);
   return 0;
}

2. Создайте исходный файл function.c, содержащий следующий код:

int sqr(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).

3. Выполните компиляцию и линковку командами:

armcc -c function.c
armcc -c main.c
armlink --no_autoat --scatter=scatter.scat --map function.o main.o -o squared.axf

Опция --map отобразит карту памяти образа. Карта памяти покажет, что переменная gValue была размещена в области выполнения ER2 (execution region) по адресу 0x10000:

...
Execution Region ER2 (Base: 0x00001578, Size: 0x0000ea8c, Max: 0xffffffff, ABSOLUTE)
 
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.

Keil5 place function to RAM fig01

Откроется окно диалога настроек свойств этого модуля. Чтобы разместить код этого модуля в RAM, в поле Code / Const введите IRAM1 [0x20002C38-0x2000FFFF].

Keil5 place function to RAM fig02

Также можно открыть выпадающий список, и в нем выбрать нужный регион.

Keil5 place function to RAM fig03

[Ссылки]

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.

 

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


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

Top of Page