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

Ниже описан один из способов копирования кода программы из флеш в ОЗУ средствами линкера IAR. Данная заметка основана на IAR Technical Note 11578.



Зачем это нужно
В одном из проектов я решил использовать встроенный в 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).

Основная идея
Идея выполнения кода из ОЗУ следующая:
  1. При загрузке флешу назначен адрес 0x0000 0000. Там записана таблица прерываний, содержащая только Reset Handler
  2. (опционально) После загрузки в коде инициализации на адрес 0x0000 0000 назначается ОЗУ.
  3. В ОЗУ копируется таблица прерываний, код программы и данные.
Второй пункт необязателен. Если не делать ремап ОЗУ, то доступ к памяти будет иметь только шина System interface и программа будет выполняться медленнее, чем из флеш памяти.

Реализация в 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

Комментарии (18)

RSS свернуть / развернуть
Я копировал нужный код в ОЗУ из флешь, мне казалось более логично :)
0
  • avatar
  • ZiB
  • 07 февраля 2012, 10:14
из ОЗУ быстрее работает
до STM32 я правда пока не добрался, но в TMS320Fxx раз в 6 быстрее может быть. Подозреваю, что тут такая же ситуация. Критичные по времени выполнения куски кода при запуске копируются в ОЗУ и работают там.
0
А на STM8L напротив, из ОЗУ страшно медленно работает.
0
там больший упор не на скорость, а на потребление :)
0
Как-то странно. Но я только по TMS могу судить. Там, насколько я помню, из-за конвейера при работе из флеша может получаться существенно медленнее, чем из оперативки.
в Code Composer`е была такая конструкция:
#pragma CODE_SECTION(имя функции, «secureRamFuncs») перед объявлением функции, как раз переносила функцию в RAM
в моей практике обработчики прерываний, например, обязательно туда пихали
0
Там флеш с акселератором и 32-битный, а SRAM — 8битная. Этот вопрос обсуждался в теме «почему я не спешу говорить про растактовку ...». В ARM должно быть лучше, т.к. SRAM тоже 32-битный и работает на частоте ядра.
0
вроде же 16бит и флеш и рам?
www.ti.com/ds_dgm/images/fbd_sprs174s.gif
www.ti.com/litv/pdf/sprs174s
0
«Там» — это в STM8. Ты же «странно» говорил именно про «а на STM8 из флеша быстрее», как я понимаю. Вот я этот момент и пояснил.
Гм, кстати:
Там, насколько я помню, из-за конвейера при работе из флеша может получаться существенно медленнее, чем из оперативки.
Причем тут конвеер? Это часть проца, и работает она одинаково, независимо от того, откуда код тянет. Здесь же играют роль скорость флеша/ОЗУ и ширина их шин данных (так, на STM8 из флеша 4-байтная команда вытягивается за 1-2 такта, а из ОЗУ — за 4).
0
пардон. не так понял.
чорт. забыл уже всё) вроде как флеш сам по себе медленнее, и из-за этого конвейер ждет и работает не «на всю катушку».
0
Там к оперативке идет одна шина и она восьмибитная. а тут обе 32 бит.
К тому же в АРМ на 72мегах (>40) при исполнение из флеш есть такая шняга как медленная работа этой самой флеш.
0
Область адресного пространства 0x0000 0000 — 0x0800 0000 можно назначить флешу, системной памяти или ОЗУ
Что такое системная память?
0
  • avatar
  • Vga
  • 07 февраля 2012, 11:21
Загрузчик.
0
на время записи в EEPROM блокируется чтение всего флеша
Не уже ли у него нет RWW ?? бред какой-то, схалтурили ST.

За статью спасибо.
0
Можете подсказать как сказать линкеру скопировать в RAM только таблицу векторов прерываний? IAR6.5
0
вроде все также, только вместо initialize by copy { readonly, readwrite }; нужно писать только
initialize by copy { readonly}; в настройках линкера
0
*все также как во втором и третьем проекте в статье
0
Спасибо
0
Спасибо, не знал про remap и возможность ускорить выполнение из RAM.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.