Выполнение программ, хранящихся во внешней флэш-памяти SPI на процессоре ARM
У меня есть процессор ARM, который может взаимодействовать с внешней микросхемой флэш-памяти. На чипе записаны программы, скомпилированные для архитектуры ARM, готовые к выполнению. Что мне нужно знать, как это сделать, это получить эти данные из внешней флэш-памяти на процессор ARM для выполнения.
Могу ли я заблаговременно запустить какую-нибудь процедуру копирования, когда данные копируются в исполняемую область памяти? Я полагаю, что мог, но процессор ARM работает под управлением операционной системы, и у меня нет тонны свободного места во флэш-памяти для работы. Я также хотел бы иметь возможность планировать выполнение двух или даже трех программ одновременно, и копирование нескольких программ во внутреннюю флэш-память одновременно невозможно. Операционная система может использоваться для запуска программ, когда они находятся в пределах доступного пространства памяти, так что может быть сделано все, что нужно сделать заранее.
2 ответа
Прочитав существующие ответы @FiddlingBits и @ensc, я думаю, что могу предложить другой подход.
Вы сказали, что ваш Flash-чип не может быть отображен в памяти. Это довольно большое ограничение, но мы можем работать с ним.
Да, вы можете запустить процедуру копирования заранее. Пока вы помещаете его в оперативную память, вы можете выполнить его.
DMA, чтобы сделать это быстрее:
Если у вас есть Периферийный контроллер DMA (например, доступный в семействе Atmel SAM3N), то вы можете использовать контроллер DMA, чтобы копировать фрагменты памяти, в то время как ваш процессор действительно выполняет полезные функции.
MMU, чтобы сделать это проще:
Если у вас есть доступный MMU, вы можете легко сделать это, просто выбрав область ОЗУ, в которой вы хотите, чтобы ваш код выполнялся, скопировав код в него и при каждом сбое страницы, перезагрузив правильный код в тот же регион. Однако @ensc уже предложил это, поэтому я не добавляю ничего нового.
Примечание: в случае, если неясно, MMU не совпадает с MPU
Нет решения MMU, но доступен MPU:
Без MMU задача немного сложнее, но все еще возможно. Вам нужно будет понять, как ваш компилятор генерирует код, и прочитать о позиционно-независимом коде (PIC). Затем вам нужно будет выделить область в ОЗУ, из которой вы будете выполнять код своей внешней флеш-микросхемы, и скопировать туда ее части (убедившись, что вы начнете выполнять ее из правильного места). Необходимо сконфигурировать MPU для генерирования ошибки каждый раз, когда задача пытается получить доступ к памяти за пределами своего назначенного региона, а затем вам нужно будет выбрать правильную память (это может стать сложным процессом), перезагрузить и продолжить выполнение.
Нет MMU и нет MPU:
Если у вас нет MMU, эту задачу сейчас очень сложно выполнить. В обоих случаях вы строго ограничиваете размер внешнего кода. По сути, ваш код, который хранится на внешней микросхеме Flash, теперь должен быть способен точно вписаться в выделенную область в ОЗУ, откуда вы будете его выполнять. Если вы можете разделить этот код на отдельные задачи, которые не взаимодействуют друг с другом, вы не сможете это сделать, а иначе не сможете.
Если вы генерируете PIC, вы можете просто скомпилировать задачи и поместить их в память последовательно. В противном случае вам потребуется использовать скрипт компоновщика для управления генерацией кода, чтобы каждая скомпилированная задача, которая будет храниться во внешней флэш-памяти, выполнялась из одного и того же предопределенного места в ОЗУ (что потребует либо изучения оверлеев ld, либо их компиляции). по отдельности).
Резюме:
Чтобы ответить на ваш вопрос более полно, мне нужно знать, какой чип и какую операционную систему вы используете. Объем доступной оперативной памяти также поможет мне лучше понять ваши ограничения.
Тем не менее, вы спросили, можно ли загружать более одной задачи одновременно. Если вы используете PIC, как я и предлагал, это должно быть возможно сделать. Если нет, то вам нужно будет заранее решить, где будет выполняться каждая из задач, и это позволит одновременно загружать / запускать некоторые комбинации.
И, наконец, в зависимости от вашей системы и чипа это может быть легко или сложно.
РЕДАКТИРОВАТЬ 1:
Дополнительная информация предоставлена:
- Чип SAM7S (Atmel)
- Он имеет периферийный контроллер DMA.
- У него нет MMU или MPU.
- 8 КБ ОЗУ, что является для нас ограничением.
- После установки операционной системы, написанной на заказ, осталось около 28 Кбайт флэш-памяти.
Поставлены дополнительные вопросы:
- В идеале я хотел бы скопировать программы во флэш-память и выполнить их оттуда. Теоретически это возможно. Было бы невозможно выполнить инструкцию программ по инструкции?
Да, можно выполнить инструкцию программы по инструкции (но с этим подходом есть и ограничение, о котором я расскажу через секунду). Вы начнете с выделения (4-байтового) адреса в памяти, куда пойдет ваша отдельная инструкция. Он имеет ширину 32 бита (4 байта) и сразу после него вы помещаете вторую инструкцию, которую вы никогда не измените. Эта вторая инструкция будет вызовом супервизора (SVC), который вызовет прерывание, позволяющее вам извлечь следующую инструкцию, поместить ее в память и начать заново.
Хотя это возможно, но это не рекомендуется, потому что вы тратите больше времени на переключение контекста, чем на выполнение кода, вы не можете использовать переменные (для этого нужно использовать оперативную память), вы не можете использовать вызовы функций (если вы не обрабатываете ветвь вручную инструкции, ой!) и ваша флешка будет написана настолько, что станет очень быстрой и бесполезной. В связи с тем, что последняя сделана из-за того, что Flash стал бесполезным, я предполагаю, что вы хотели выполнить инструкцию по инструкции из ОЗУ. Помимо всех этих ограничений, вам все равно придется использовать некоторое количество оперативной памяти для стека, кучи и глобальных переменных (подробности см. В моем приложении). Эта область может использоваться всеми задачами, выполняемыми из внешней флэш-памяти, но для этого вам нужно будет написать собственный скрипт компоновщика, иначе вы будете тратить свою оперативную память.
То, что сделает это более понятным для вас, это понимание того, как код на С компилируется. Даже если вы используете C++, сначала задайте себе вопрос: где скомпилированы переменные и инструкции на моем устройстве?
В основном то, что вы ДОЛЖНЫ знать, прежде чем пытаться это:
- где будет выполняться код (Flash/RAM)
- как этот код связан со своим стеком, кучей и глобальными переменными (для этой задачи вы должны выделить отдельный стек и отдельное пространство для глобальных, но вы можете совместно использовать кучу).
- где находится стек, куча и глобальные переменные этого внешнего кода (при этом я пытаюсь намекнуть, какой уровень контроля вы должны иметь над своим кодом C)
Изменить 2:
Как использовать Периферийный контроллер DMA:
Для микроконтроллера, с которым я работаю, контроллер DMA на самом деле не подключен к встроенной флэш-памяти для чтения или записи. Если это касается и вас, вы не можете его использовать. Тем не менее, ваша таблица данных неясна в этом отношении, и я подозреваю, что вам нужно будет запустить тест с использованием последовательного порта, чтобы проверить, действительно ли он работает.
В дополнение к этому, я обеспокоен тем, что операция записи при использовании контроллера DMA может быть более сложной, чем вы делаете это вручную из-за кэшированных записей страниц. Вам нужно будет убедиться, что вы выполняете только передачи DMA на страницах, и что передача DMA никогда не пересекает границы страницы. Кроме того, я не уверен, что происходит, когда вы говорите контроллеру DMA записывать данные с флэш-памяти обратно в то же место (что может потребоваться для обеспечения перезаписи только правильных частей).
Опасения по поводу доступной флэш-памяти и оперативной памяти:
Меня беспокоит ваш предыдущий вопрос о выполнении по одной инструкции за раз. Если это так, то вы могли бы также написать переводчика. Если у вас недостаточно памяти, чтобы вместить весь код задачи, которую вам нужно выполнить, вам нужно скомпилировать задачу как PIC с помещением в оперативную память глобальной таблицы смещения (GOT) вместе со всей необходимой для этого памятью. глобалы задачи. Это единственный способ обойти нехватку места для всей задачи. Вы также должны будете выделить достаточно места для его стека.
Если у вас недостаточно оперативной памяти (что, я подозреваю, вам не нужно), вы можете выгрузить свою оперативную память и выгружать ее во Flash каждый раз, когда вам нужно переключаться между задачами на внешнем Flash-чипе, но опять же, я настоятельно рекомендую не писать. на вашу флэш-память много раз. Таким образом, вы можете сделать так, чтобы задачи на внешней флэш-памяти разделяли часть оперативной памяти для их глобальных переменных.
Во всех остальных случаях вы будете писать переводчика. Я даже сделал немыслимое, я пытался придумать, как использовать состояние прерывания контроллера памяти вашего микроконтроллера (раздел 18.3.4 Состояние прерывания в таблице данных) в качестве MPU, но не смог найти даже удаленно умный способ используй это.
Изменить 3:
Я бы посоветовал прочитать раздел 40.8.2 Биты энергонезависимой памяти (NVM) в техническом описании, в котором предполагается, что ваша флэш-память имеет максимум 10000 циклов записи / стирания (мне потребовалось некоторое время, чтобы найти ее). Это означает, что к тому времени, когда вы напишете и удалите область флэш-памяти, где вы будете переключать контексты задач в 10000 раз, эта часть Flash станет бесполезной.
ПРИЛОЖЕНИЕ
Пожалуйста, ознакомьтесь с этой записью в блоге, прежде чем читать мои комментарии ниже.
Где переменные C живут на встроенном чипе ARM:
Я лучше учусь не на абстрактных понятиях, а на конкретных примерах, поэтому постараюсь дать вам код для работы. В основном вся магия происходит в вашем скрипте компоновщика. Если вы прочитаете и поймете это, вы увидите, что происходит с вашим кодом. Давайте рассмотрим один сейчас:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
SEARCH_DIR(.)
/* Memory Spaces Definitions */
MEMORY
{
/* Here we are defining the memory regions that we will be placing
* different sections into. Different regions have different properties,
* for example, Flash is read only (because you need special instructions
* to write to it and writing is slow), while RAM is read write.
* In the brackets after the region name:
* r - denotes that reads are allowed from this memory region.
* w - denotes that writes are allowed to this memory region.
* x - means that you can execute code in this region.
*/
/* We will call Flash rom and RAM ram */
rom (rx) : ORIGIN = 0x00400000, LENGTH = 0x00040000 /* flash, 256K */
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00006000 /* sram, 24K */
}
/* The stack size used by the application. NOTE: you need to adjust */
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : 0x800 ;
/* Section Definitions */
SECTIONS
{
.text :
{
. = ALIGN(4);
_sfixed = .;
KEEP(*(.vectors .vectors.*))
*(.text .text.* .gnu.linkonce.t.*)
*(.glue_7t) *(.glue_7)
*(.rodata .rodata* .gnu.linkonce.r.*) /* This is important, .rodata is in Flash */
*(.ARM.extab* .gnu.linkonce.armextab.*)
/* Support C constructors, and C destructors in both user code
and the C library. This also provides support for C++ code. */
. = ALIGN(4);
KEEP(*(.init))
. = ALIGN(4);
__preinit_array_start = .;
KEEP (*(.preinit_array))
__preinit_array_end = .;
. = ALIGN(4);
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
. = ALIGN(0x4);
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*crtend.o(.ctors))
. = ALIGN(4);
KEEP(*(.fini))
. = ALIGN(4);
__fini_array_start = .;
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
__fini_array_end = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*crtend.o(.dtors))
. = ALIGN(4);
_efixed = .; /* End of text section */
} > rom /* All the sections in the preceding curly braces are going to Flash in the order that they were specified */
/* .ARM.exidx is sorted, so has to go in its own output section. */
PROVIDE_HIDDEN (__exidx_start = .);
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > rom
PROVIDE_HIDDEN (__exidx_end = .);
. = ALIGN(4);
_etext = .;
/* Here is the .relocate section please pay special attention to it */
.relocate : AT (_etext)
{
. = ALIGN(4);
_srelocate = .;
*(.ramfunc .ramfunc.*);
*(.data .data.*);
. = ALIGN(4);
_erelocate = .;
} > ram /* All the sections in the preceding curly braces are going to RAM in the order that they were specified */
/* .bss section which is used for uninitialized but zeroed data */
/* Please note the NOLOAD flag, this means that when you compile the code this section won't be in your .hex, .bin or .o files but will be just assumed to have been allocated */
.bss (NOLOAD) :
{
. = ALIGN(4);
_sbss = . ;
_szero = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = . ;
_ezero = .;
} > ram
/* stack section */
.stack (NOLOAD):
{
. = ALIGN(8);
_sstack = .;
. = . + STACK_SIZE;
. = ALIGN(8);
_estack = .;
} > ram
. = ALIGN(4);
_end = . ;
/* heap extends from here to end of memory */
}
Это автоматически сгенерированный скрипт компоновщика для SAM3N (ваш скрипт компоновщика должен отличаться только в определениях области памяти). Теперь давайте рассмотрим, что происходит, когда ваше устройство загружается после выключения.
Первое, что происходит, - это то, что ядро ARM считывает адрес, сохраненный в таблице векторов флэш-памяти, который указывает на ваш вектор сброса. Вектор сброса - это просто функция, и для меня он также автоматически генерируется Atmel Studio. Вот:
void Reset_Handler(void)
{
uint32_t *pSrc, *pDest;
/* Initialize the relocate segment */
pSrc = &_etext;
pDest = &_srelocate;
/* This code copyes all of the memory for "initialised globals" from Flash to RAM */
if (pSrc != pDest) {
for (; pDest < &_erelocate;) {
*pDest++ = *pSrc++;
}
}
/* Clear the zero segment (.bss). Since it in RAM it could be anything after a reset so zero it. */
for (pDest = &_szero; pDest < &_ezero;) {
*pDest++ = 0;
}
/* Set the vector table base address */
pSrc = (uint32_t *) & _sfixed;
SCB->VTOR = ((uint32_t) pSrc & SCB_VTOR_TBLOFF_Msk);
if (((uint32_t) pSrc >= IRAM_ADDR) && ((uint32_t) pSrc < IRAM_ADDR + IRAM_SIZE)) {
SCB->VTOR |= 1 << SCB_VTOR_TBLBASE_Pos;
}
/* Initialize the C library */
__libc_init_array();
/* Branch to main function */
main();
/* Infinite loop */
while (1);
}
Теперь подождите немного дольше, пока я объясняю, как код C, который вы пишете, вписывается во все это.
Рассмотрим следующий пример кода:
int UninitializedGlobal; // Goes to the .bss segment (RAM)
int ZeroedGlobal[10] = { 0 }; // Goes to the .bss segment (RAM)
int InitializedGlobal[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 11 }; // Goes to the .relocate segment (RAM and FLASH)
const int ConstInitializedGlobal[10] = { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }; // Goes to the .rodata segment (FLASH)
void function(int parameter)
{
static int UninitializedStatic; // Same as UninitializedGlobal above.
static int ZeroedStatic = 0; // Same as ZeroedGlobal above.
static int InitializedStatic = 7; // Same as InitializedGlobal above.
static const int ConstStatic = 18; // Same as ConstInitializedGlobal above. Might get optimized away though, lets assume it doesn't.
int UninitializedLocal; // Stacked. (RAM)
int ZeroedLocal = 0; // Stacked and then initialized (RAM)
int InitializedLocal = 7; // Stacked and then initialized (RAM)
const int ConstLocal = 91; // Not actually sure where this one goes. I assume optimized away.
// Do something with all those lovely variables...
}
Это зависит от типа вспышки и / или процессора. NOR вспышка обычно отображается в памяти, так что вы можете перейти прямо в нее. NAND flash должен быть прочитан (зависит от SOC) в локальную память (SRAM, DRAM (-> требуется дополнительная инициализация!)).
РЕДАКТИРОВАТЬ:
SPI также не может быть сопоставлен с RAM. Вы должны запрограммировать контроллер SPI SOC и флэш-память SPI. Протокол, который будет использоваться для прошивки SPI, обычно описывается в его руководстве; Весьма вероятно, что это общий протокол, поэтому вы, вероятно, можете использовать существующий драйвер.