Выполнение кода из ОЗУ в IAR


Зачем это нужно
В одном из проектов я решил использовать встроенный в STM32L EEPROM. Однако, я не учел, что на время записи в EEPROM блокируется чтение всего флеша и выполнение программы из ROM останавливается на несколько мс. Такая остановка меня не устраивала, поэтому пришлось перенести весь код в ОЗУ.
Исходные данные
Для экспериментов используется STM32L Discovery с контроллером STM32L152RBT6 на борту. Компилятор IAR 6.3.
В данном контроллере 128 KB Flash, 16 KB RAM, 4 KB EEPROM.
Карта памяти есть в даташите. Flash расположен по адресам 0x0800 0000 — 0x0801 FFFF, EEPROM — 0x0808 0000 — 0x0808 0FFF, RAM — 0x2000 0000 — 0x2000 3FFF.
Область адресного пространства 0x0000 0000 — 0x0800 0000 можно назначить флешу, системной памяти или ОЗУ.
Процессор стартует с адреса 0x0000 0000.
Процессор Cortex M3 имеет 4 шины, нас интересую три: ICode memory interface, DCode memory interface (работают с областью 0x0000000 — 0x1FFFFFFF) и более медленный System interface (работает с областью 0x20000000 — 0xDFFFFFFF, 0xE0100000 — 0xFFFFFFFF).
Основная идея
Идея выполнения кода из ОЗУ следующая:
- При загрузке флешу назначен адрес 0x0000 0000. Там записана таблица прерываний, содержащая только Reset Handler
- (опционально) После загрузки в коде инициализации на адрес 0x0000 0000 назначается ОЗУ.
- В ОЗУ копируется таблица прерываний, код программы и данные.
Реализация в IAR
Для иллюстрации я собрал три проекта для IAR: выполнение кода из флеш памяти, выполнение кода из RAM без ремапа и с ремапом.
Выполнение кода из флеш памяти
Для начала рассмотрим стандартный проект с выполнением кода из флеш. Используется библиотека CMSIS.
Настройки линкера stm32l1xx_flash.icf:
/*###ICF### Section handled by ICF editor, don't touch! ****/
/*-Editor annotation file-*/
/* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x08000000;
/*-Memory Regions-*/
define symbol __ICFEDIT_region_ROM_start__ = 0x08000000 ;
define symbol __ICFEDIT_region_ROM_end__ = 0x0801FFFF;
define symbol __ICFEDIT_region_RAM_start__ = 0x20000000;
define symbol __ICFEDIT_region_RAM_end__ = 0x20003FFF;
/*-Sizes-*/
define symbol __ICFEDIT_size_cstack__ = 0x400;
define symbol __ICFEDIT_size_heap__ = 0x200;
/**** End of ICF editor section. ###ICF###*/
define memory mem with size = 4G;
define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];
define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__];
define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };
initialize by copy { readwrite };
do not initialize { section .noinit };
place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
place in ROM_region { readonly };
place in RAM_region { readwrite,
block CSTACK, block HEAP };
Flash расположен по адресам 0x0800 0000 — 0x0801 FFFF, RAM — 0x2000 0000 — 0x2000 3FFF. По адресу 0x08000000 располагается секция .intvec (таблица векторов прерываний):
define symbol __ICFEDIT_intvec_start__ = 0x08000000;
...
place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
При инициализации копируется блок readwrite
initialize by copy { readwrite };
Таблица прерываний описана в файле startup_stm32l1xx_md.s:
...
MODULE ?cstartup
;; Forward declaration of sections.
SECTION CSTACK:DATA:NOROOT(3)
SECTION .intvec:CODE:NOROOT(2)
EXTERN __iar_program_start
EXTERN SystemInit
PUBLIC __vector_table
DATA
__vector_table
DCD sfe(CSTACK)
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
...
DCD TIM7_IRQHandler ; TIM7
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Default interrupt handlers.
;;
THUMB
PUBWEAK Reset_Handler
SECTION .text:CODE:REORDER(2)
Reset_Handler
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
...
SECTION .intvec объявляет секцию .intvec, которая упоминалась в настройках линкера. После таблицы прерываний идет Reset_Handler в нем вызывается функция SystemInit и запускается __iar_program_start из библиотеки IAR (\arm\src\lib\thumb\cstartup_M.s).
Функция SystemInit описана в system_stm32l1xx.c. В ней устанавливаются клок и адрес таблицы прерываний.
В main для проверки быстродействия в цикле меняется состояния пина PB.9 через задержку в 32 такта (32 инструкции NOP). При настройке оптимизации High Speed вывод в порт выполняется за один такт (инструкция STRH Rt, [Rn, #offset]).
После компиляции проекта смотрим c.map:
...
Module ro code rw data
------ ------- -------
D:\stm32\stm32l_flash_project\Debug\Obj: [1]
main.o 212
startup_stm32l1xx_md.o 476
stm32l1xx_it.o 18
system_stm32l1xx.o 260
----------------------------------------
Total: 966
command line: [2]
----------------------------------------
Total:
dl7M_tln.a: [3]
exit.o 4
low_level_init.o 4
----------------------------------------
Total: 8
rt7M_tl.a: [4]
cexit.o 10
cmain.o 22
cstartup_M.o 12
----------------------------------------
Total: 44
shb_l.a: [5]
exit.o 20
----------------------------------------
Total: 20
Gaps 6
Linker created 1 024
--------------------------------------------
Grand Total: 1 044 1 024
...
Видно, что линкер расположил все в ro code. 1024 байт в rw data это стек.
Смотрим сигнал на пине PB9:

Продолжительность положительного полупериода — 1,06 мкс, т.е. 34 такта, на один такт больше, чем инструкций. Видимо, разница связана с латентностью флеш (1 wait state) при частоте ЦПУ 16-32 МГц.
Выполнение кода из RAM без ремапа
Изменения в настройках линкера ram.icf
...
define symbol __ICFEDIT_region_RAM_start__ = 0x200000F4;
...
/* intvec location in RAM after remapping in SystemInit */
define symbol RAM_intvec_start = 0x20000000;
...
initialize by copy { readonly, readwrite };
...
place at address mem:RAM_intvec_start { section .intvec_RAM };
...
По адресу 0x20000000 располагаем вторую таблицу прерываний, секция .intvec_RAM (о ней ниже). Таблица занимает F4 байт, поэтому начало ОЗУ сдвигается на это число.
Строка initialize by copy { readonly, readwrite }; сообщает, что при инициализации весь код копируется в ОЗУ.
Изменения в startup_stm32l1xx_md.s:
MODULE ?cstartup
;; Forward declaration of sections.
SECTION CSTACK:DATA:NOROOT(3)
SECTION .intvec:CODE:NOROOT(2)
EXTERN __iar_program_start
EXTERN SystemInit
PUBLIC __vector_table
DATA
__vector_table
DCD sfe(CSTACK)
DCD Reset_Handler ; Reset Handler
DCD Dummy_Handler_ROM ; NMI Handler
...
DCD Dummy_Handler_ROM ; TIM7
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Note: The section .intvec_RAM will be placed in RAM
SECTION .intvec_RAM:CODE:ROOT(2)
DATA
DCD sfe(CSTACK)
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
...
DCD TIM7_IRQHandler ; TIM7
...
; --------------------
; Dummy handler placed in ROM
PUBWEAK Dummy_Handler_ROM
SECTION .text:CODE:REORDER(1)
Dummy_Handler_ROM
B Dummy_Handler_ROM
END
Теперь здесь 2 таблицы прерываний. В первой все метки, кроме Reset_Handler, изменены на Dummy_Handler_ROM. Заглушка Dummy_Handler_ROM добавлена в конец файла. Таким образом, первая таблица используется только при инициализации. Вторая таблица расположена в секции .intvec_RAM и содержит все метки прерываний.
Изменения в system_stm32l1xx.c:
...
//#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
//#else
// SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
//#endif
...
Изменен адрес таблицы прерываний на 0x20000000.
После компиляции в c.map:
...
Module ro code rw code ro data rw data
------ ------- ------- ------- -------
command line: [1]
----------------------------------------------------------
Total:
d:\stm32\stm32l_ram_project\Debug\Obj: [2]
main.o 212 212
startup_stm32l1xx_md.o 264 460 460
stm32l1xx_it.o 18 18
system_stm32l1xx.o 260
----------------------------------------------------------
Total: 524 690 690
dl7M_tln.a: [3]
exit.o 4 4
low_level_init.o 4
----------------------------------------------------------
Total: 4 4 4
rt7M_tl.a: [4]
bwt_init3c.o
cexit.o 10 10
cmain.o 22
copy_init3.o 46
cstartup_M.o 12
data_init3.o 44
----------------------------------------------------------
Total: 124 10 10
shb_l.a: [5]
exit.o 20
----------------------------------------------------------
Total: 20
Gaps 4 2
Linker created 16 8 52 1 024
--------------------------------------------------------------
Grand Total: 692 714 756 1 024
...
Теперь код main.o (212 байт) располагается не в ro code, а в ro data и далее копируется в rw code. Также видно что часть кода startup_stm32l1xx_md.o расположена во флеше, а часть в ОЗУ как и ожидалось.
Сигнал на пине PB9:

Продолжительность положительного полупериода — 1,12 мкс, т.е. 36 тактов. Разница относительно флеш связана с использованием медленной шины System Interface.
Выполнение кода из RAM с ремапом
Изменения в настройках линкера ram_remap.icf
...
define symbol __ICFEDIT_region_RAM_start__ = 0x000000F4;
define symbol __ICFEDIT_region_RAM_end__ = 0x00003FFF;
...
/* intvec location in RAM after remapping in SystemInit */
define symbol RAM_intvec_start = 0x00000000;
...
initialize by copy { readonly, readwrite };
...
place at address mem:RAM_intvec_start { section .intvec_RAM };
...
Здесь по сравнению с предыдущим проектом адреса ОЗУ изменены с 0x2… на 0x0…
startup_stm32l1xx_md.s такой же, как в проекте без ремапа.
Изменения в system_stm32l1xx.c:
...
/*!< Disable all interrupts */
RCC->CIR = 0x00000000;
// remapping
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
SYSCFG->MEMRMP = ((uint8_t)0x03);
/* Configure the System clock frequency, AHB/APBx prescalers and Flash settings */
SetSysClock();
//#ifdef VECT_TAB_SRAM
// SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
//#else
// SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
//#endif
SCB->VTOR = 0x00000000;
...
Здесь включаем тактирование блока SYSCFG, который отвечает за ремап памяти и, собственно, выполняем сам ремап. Важно сделать это до вызова функций, иначе при вызове будут проблемы со стеком.
Сигнал на пине PB9:

Продолжительность положительного полупериода — 1,03 мкс, т.е. 33 такта. Совпадает с количеством тактов на инструкции. Обращение к RAM идет с 0 wait states.
Все три проекта прикреплены к заметке.
- +2
- 07 февраля 2012, 09:58
- magnum16
- 1
Файлы в топике:
flash_and_ram_iar_projects.zip
из ОЗУ быстрее работает
до STM32 я правда пока не добрался, но в TMS320Fxx раз в 6 быстрее может быть. Подозреваю, что тут такая же ситуация. Критичные по времени выполнения куски кода при запуске копируются в ОЗУ и работают там.
до STM32 я правда пока не добрался, но в TMS320Fxx раз в 6 быстрее может быть. Подозреваю, что тут такая же ситуация. Критичные по времени выполнения куски кода при запуске копируются в ОЗУ и работают там.
Как-то странно. Но я только по TMS могу судить. Там, насколько я помню, из-за конвейера при работе из флеша может получаться существенно медленнее, чем из оперативки.
в Code Composer`е была такая конструкция:
#pragma CODE_SECTION(имя функции, «secureRamFuncs») перед объявлением функции, как раз переносила функцию в RAM
в моей практике обработчики прерываний, например, обязательно туда пихали
в Code Composer`е была такая конструкция:
#pragma CODE_SECTION(имя функции, «secureRamFuncs») перед объявлением функции, как раз переносила функцию в RAM
в моей практике обработчики прерываний, например, обязательно туда пихали
Там флеш с акселератором и 32-битный, а SRAM — 8битная. Этот вопрос обсуждался в теме «почему я не спешу говорить про растактовку ...». В ARM должно быть лучше, т.к. SRAM тоже 32-битный и работает на частоте ядра.
«Там» — это в STM8. Ты же «странно» говорил именно про «а на STM8 из флеша быстрее», как я понимаю. Вот я этот момент и пояснил.
Гм, кстати:
Гм, кстати:
Там, насколько я помню, из-за конвейера при работе из флеша может получаться существенно медленнее, чем из оперативки.Причем тут конвеер? Это часть проца, и работает она одинаково, независимо от того, откуда код тянет. Здесь же играют роль скорость флеша/ОЗУ и ширина их шин данных (так, на STM8 из флеша 4-байтная команда вытягивается за 1-2 такта, а из ОЗУ — за 4).
Комментарии (18)
RSS свернуть / развернуть