Запуск программы откуда угодно [Atmel Studio]

Уже не редкость видеть темы, где разработчики спрашивают о возможности исполнения программ из ОЗУ, внешней памяти и т.п. Этот вопрос вас не должен особо волновать, так как центральному процессору не имеет значения откуда принимать команды для исполнения.

В большинстве своем современные микроконтроллеры основаны на гарвардской архитектуре.

mcc_cb

Главной для нас особенностью будет физическое разделение памяти команд и данных в архитектуре. Это несомненно повышает производительность в частных случаях, так как ЦП не нужно тратить время на переключение шин.

Микроконтроллер

Все эксперименты я буду проводить на ARM контроллере ATSAM4C и среде разработки Atmel Studio. Системы этой серии имеют как раз две раздельные шины в блоке.

Ashampoo_Snap_2015.05.03_18h19m26s_023_

Естественно, по шине System bus подключается оперативная память, а также периферия. Если мы запустим код по этой шине, то его выполнение будет замедленно в ~2 раза как раз из-за невозможности одновременного исполнения команд и их обработки.  Однако в этом случае нам может помочь кэш.

Матрица

К некоторым устройствам подходит и System bus и Code bus. В документации это обозначено в разделе Bus Matrix.

Ashampoo_Snap_2015.05.03_19h04m33s_029_

Выгодно использовать лишь параллельный интерфейс EBI для целей расширения памяти, а остальные только в частных случаях.

Студия

Для того, чтобы программа работала из нужного места, необходимо дать соответствующие инструкции компилятору. Это делается с помощью надстроек Linker-а, скрипт xxx_flash.ld. Есть еще xxx_sram.ld, но он обычно отключен.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
SEARCH_DIR(.)
 
/* Распределение адресного пространства */
MEMORY
{
  rom (rx)  : ORIGIN = 0x01000000, LENGTH = 0x00080000 //512кб выделено во flash-памяти для программы, флаг: чтение и исполнение
  ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00026000 //152кб выделено в регионе SRAM0 для данных, флаг: чтение, запись, исполнение.
}
 
/* Размер стека для программы. */
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : 0x3000;
INCLUDE sam4c_flash.ld

RAM и ROM — это параметры обозначающие адресное пространство памяти программ и памяти данных соответственно. Мы в праве изменять их как хотим, все адреса описаны в документации.

Можно объединить эти регионы в один, сменив скрипт. Toolchain-/Linker-/Miscellaneous- в верхнем окошке меняем часть названия файла на xxx_sram.ld.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
SEARCH_DIR(.)
 
/* Распределение адресного пространства */
MEMORY
{
  rom (rx)  : ORIGIN = 0x01000000, LENGTH = 0x00080000 //Не имеет значения.
  ram (rwx) : ORIGIN = 0x20003000, LENGTH = 0x00023000 //Вся программа здесь. Выделено 12кб для памяти "возможного загрузчика", остальное для программы и ее данных.
}
 
/* Размер стека для программы. */
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : 0x1500;
INCLUDE sam4c_sram.ld

Это то же самое, но теперь вся программа будет в регионе RAM.

Исполнение отдельных функций из ОЗУ

В данной задаче нам Linker вообще не понадобится, разработчики уже позаботились об этой возможности.

1
2
3
__attribute__( ( long_call, section(".ramfunc") ) ) void ram_foobar (void) {
   //Smth...	
}

При запуске контролера специальная подпрограмма скопирует эту функцию в ОЗУ. От вас ничего не потребуется.

Извлечение кода

Скомпилируем простую программу с настройками Linker-а.

1
2
3
4
5
6
7
8
9
#include "sam4.h"
 
int main(void) {		
  PIOB->PIO_OER = PIO_OER_P21; //PB21 - выход
  while(1) {
	PIOB->PIO_CODR = PIO_CODR_P21; //Очень быстрое моргание светодиодом
	PIOB->PIO_SODR = PIO_SODR_P21;
  }	
}

Однако, разместив программу в нужном регионе, вы не сможете ее просто загрузить программатором. Кто ее запустит с нужного адреса? Для начала — получим бинарный образ программы!
Ashampoo_Snap_2015.05.03_19h39m22s_031_
Всегда проявляется в папке проекта, его мы можем открыть с помощью какого-нибудь HEX-редактора и сохранить в массив, например.

1
2
3
4
5
const uint8_t store[] = {
	0x50, 0x49, 0x02, 0x20, 0x9D, 0x01, 0x00, 0x14, 0x99, 0x01, 0x00, 0x14, 0x99, 0x01, 0x00, 0x14, 
	0x99, 0x01, 0x00, 0x14, 0x99, 0x01, 0x00, 0x14, 0x99, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x01, 0x00, 0x14, 
	0x99, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x99, 0x01, 0x00, 0x14, 0x99, 0x01, 0x00,

Следующий вопрос: как ее запустить? Адрес для запуска я определяю из .lss файла, в котором с комментариями мы можем все увидеть.

1
2
3
4
5
6
7
8
9
10
11
12
140002b8 <main>:
#include "sam4.h"
 
int main(void) {
	PIOB->PIO_OER = PIO_OER_P21; //PB21 - выход
140002b8:	f44f 5280 	mov.w	r2, #4096	; 0x1000
140002bc:	f2c4 020e 	movt	r2, #16398	; 0x400e
140002c0:	f44f 1300 	mov.w	r3, #2097152	; 0x200000
140002c4:	6113      	str	r3, [r2, #16]
	while(1) {
		PIOB->PIO_CODR = PIO_CODR_P21; //Очень быстрое моргание светодиодом
140002c6:	6353      	str	r3, [r2, #52]	; 0x34

Соответственно, адрес для запуска будет начинаться с функции main() или с 0x140002b8. Запоминаем это значение.

Загрузчик

Задача загрузчика проста — передать управление функции по адресу. А также скопировать программу в нужный регион, если ее там нет.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "sam.h"
 
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "store.h" //Образ нашей программы
 
typedef int program_t(void);
program_t* fp = (program_t*)(0x140002b8 + 0x1); //Адрес функции main() нашей программы, заметьте, что тип - int: мы можем передавать значения
 
uint8_t *addr = (uint8_t*)0x14000000; //Адрес того региона, куда мы будем загружать. Его вы указали в Linker-е
 
int main() {
   SystemInit();
   PMC->PMC_WPMR = 0x504D43; //Очень черная магия. (на всякий случай)
   PMC->PMC_PCER0 = PMC_PCER0_PID12 | PMC_PCER0_PID11; //Запускаем PIOA и PIOB
 
   memcpy(addr, store, sizeof(store)); //Копируем программу для исполнения в нужный регион. 
   (*fp)(); //Запускаем
 
   while(1); //Останавливаем. ""
}

Заметьте, что копирование производится по адресу 0x14000000, указанного в linker-е, сам запуск идет уже с 0x140002b8+1 — последнее — признак Thumb®-2 инструкций микроконтроллера. Кроме того, вы можете расширить возможности загрузчика, например передавать параметры вашей программе, адреса функций, вообще — создать программное ядро.

Эксперимент: стандартный запуск из ПЗУ

Для сравнения проверим нашу программу без всяких настроек и загрузчиков.

mcc_1

Код находится во FLASH по Code Bus, а данные в SRAM0 по System Bus.

pic_553_1m

48ns — хорошо.

Эксперимент: запуск всего из ОЗУ

Здесь подменой скрипта Linker-а мы запускаем код из встроенного ОЗУ.

mcc_2

Код — SRAM0 по System Bus, данные — SRAM0 по System Bus.

pic_553_2m

У нас уже 85ns — почти в два раза ниже.

Эксперимент: запуск всего из внешней памяти

Подключаем внешнюю память по EBI интерфейсу. Скорость чтения составляет ~32Мб/сек, скорость записи ~64Мб/сек. Но контроллер запущен на более низкой частоте.

mcc_3

Код — EBI по System Bus, данные — EBI по System Bus.

pic_553_3m

Результат — 436ns, оно и не удивительно.

Эксперимент: запуск программы из внешней памяти

Мы также используем внешнюю микросхему памяти по EBI интерфейсу, но с интересной конфигурацией.

mcc_4

Код — EBI по Code Bus, данные — SRAM0 по System Bus.

pic_553_4m

Результат — 374ns. Это самое правильное подключение, разница в скорости сейчас не так заметна из-за простоты программы: память данных почти не используется. С большими приложениями разница будет огромной.

Эксперимент: запуск программы из внешней памяти + КЭШ

Та же конфигурация, но мы добрались до кэш-а микроконтроллера.

mcc_5

Код — EBI (Cached) по Code Bus, данные — SRAM0 по System Bus.

pic_553_5m

Это прорыв — 39ns! Чтобы использовать кэш, необходимо лишь запускать с немного другого адреса, указанного в документации со скобками «Cached».

Заметки

Обе шины, и системная, и кодовая находятся в общем адресном пространстве. Адресацию вы можете почитать в разделе документации «7. Product Mapping and Peripheral Access «.

Прошу обратить внимание на то, что таблица векторов исполняемой программы не используется. Вам необходимо скопировать ее куда-нибудь в память данных и назначить адрес в регистре.

1
SCB->VTOR = (адрес & SCB_VTOR_TBLOFF_Msk);

Без этой таблицы все прерывания и обработчики в исполняемой программе не будут вызываться.

Вы можите оставить комментарий, или поставить трэкбек со своего сайта.

Написать комментарий

XHTML: Вы можете использовать эти теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Bug Report
Локализовано: шаблоны Wordpress