Keil: размещение функций и данных по указанному адресу |
![]() |
Добавил(а) microsin |
Есть несколько методов, чтобы поместить функции и данные по определенному адресу. При необходимости компилятор обычно создает секции RO, RW, ZI и XO из одного исходного файла. Эти секции содержат весь код и данные из исходного файла. Чтобы поместить одну функцию или элемент данных по фиксированному адресу, Вы должны позволить линкеру обработать эту функцию или данные отдельно от остальных входных файлов. Примечание: RO обозначает секцию памяти только для чтения (read only), RW память для чтения и записи (read/write), ZI память автоматически инициализируемую нулями (zero initialized), XO память только для выполнения кода (execute only). Линкер позволит разместить секцию по определенному адресу следующими способами: • Вы можете создать так называемый scatter-файл, который определяет регион выполнения по требуемому адресу с описанием секции, которое выбирает только одну секцию. scatter-файл это некое подобие файла управления линкером, который определяет регионы памяти для кода и данных. Этот файл по умолчанию получает имя из имени файла исполняемого кода (поле ввода "Name of Executable", что указывается на закладке Output в свойствах проекта) и расширения *.sct, и находится в папке выходных файлов для сборки (например корневая_папка_проекта\_build\nrf52832_xxaa.sct). scatter-файл по умолчанию Keil генерирует автоматически, если в свойствах проекта на закладке 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". Если галочку "Use Memory Layout from Target Dialog" снять, то на закладке Linker свойств проекта появляется вручную указать scatter-файл. В этом случае Keil его не будет генерировать автоматически, и появится возможность scatter-файл произвольно редактировать (если Вы понимаете, что делаете). Это может быть полезно, если Вы хотите создать свою собственную карту памяти, с определенными регионами памяти. Пример, каким образом это может использоваться, см. в статье [2]. Чтобы поместить функцию или переменную по специально указанному адресу, Вы должны поместить её в свою собственную секцию. Для этого есть несколько вариантов: • Поместить функцию или элемент данных в их собственный исходный файл. [Связанные концепции] 7.2.6 Placement of __at sections at a specific address site:keil.com [Связанная документация] 9.6 --autoat, --no_autoat site:keil.com [Дополнительная информация] --split_sections 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. Откроется окно диалога настроек свойств этого модуля. Чтобы разместить код этого модуля в RAM, в поле Code / Const введите IRAM1 [0x20002C38-0x2000FFFF]. Также можно открыть выпадающий список, и в нем выбрать нужный регион. [Ссылки] 1. Methods of placing functions and data at specific addresses site:keil.com. |