Прочитав существующие ответы @FiddlingBits и @ensc, я думаю, что могу предложить другой подход.
Вы сказали, что ваш флеш-чип не может быть отображен в памяти. Это довольно серьезное ограничение, но мы можем с ним работать.
Да, вы можете запустить процедуру копирования заранее. Пока вы помещаете его в ОЗУ, вы можете его выполнить.
DMA для ускорения:
Если у вас есть периферийный контроллер DMA (например, тот, который доступен в семействе Atmel SAM3N), вы можете использовать контроллер DMA для копирования фрагментов памяти, в то время как ваш процессор действительно делает полезные вещи.
MMU для упрощения:
Если у вас есть доступный MMU, вы можете легко сделать это, просто выбрав область ОЗУ, в которой вы хотите, чтобы ваш код выполнялся, копируя код в нее и при каждой ошибке страницы, перезагружая правильный код в ту же самую область. Однако это уже было предложено @ensc, поэтому я пока не добавляю ничего нового.
Примечание. Если неясно, MMU - это не то же самое, что MPU
Нет решения MMU, но доступен MPU:
Без MMU задача немного сложнее, но все же возможно. Вам нужно будет понять, как ваш компилятор генерирует код, и прочитать о Позиционно-независимый код (PIC). Затем вам нужно будет выделить область в ОЗУ, из которой вы будете выполнять код внешнего флеш-чипа, и скопировать его части (убедитесь, что вы запускаете его из правильного места). MPU необходимо настроить для генерации ошибки каждый раз, когда задача пытается получить доступ к памяти за пределами назначенной ей области, и тогда вам нужно будет получить правильную память (это может стать сложным процессом), перезагрузить и продолжить выполнение.
Нет доступных MMU и MPU:
Если у вас нет MMU, эта задача становится очень сложной. В обоих случаях у вас есть серьезные ограничения на размер внешнего кода. По сути, ваш код, который теперь хранится на внешней флеш-микросхеме, должен умещаться в точности внутри выделенной области ОЗУ, из которой вы будете его выполнять. Если вы можете разделить этот код на отдельные задачи, которые не взаимодействуют друг с другом, вы можете это сделать, но в противном случае вы не сможете.
Если вы создаете PIC, вы можете просто скомпилировать задачи и последовательно поместить их в память. В противном случае вам нужно будет использовать сценарий компоновщика для управления генерацией кода таким образом, чтобы каждая скомпилированная задача, которая будет сохранена во внешней флэш-памяти, выполнялась из одного и того же предопределенного места в ОЗУ (что либо потребует от вас изучения ld оверлеи или компилируйте их отдельно).
Резюме:
Чтобы более полно ответить на ваш вопрос, мне нужно знать, какой чип и какую операционную систему вы используете. Объем доступной оперативной памяти также поможет мне лучше понять ваши ограничения.
Однако вы спросили, можно ли одновременно загружать более одной задачи для выполнения. Если вы используете PIC, как я предлагал, это должно быть возможно. В противном случае вам нужно будет заранее решить, где будет выполняться каждая из задач, и это позволит загружать / запускать некоторые из комбинаций одновременно.
И, наконец, в зависимости от вашей системы и чипа это может быть легко или сложно.
ИЗМЕНИТЬ 1:
Приведена дополнительная информация:
- Чип SAM7S (Atmel)
- У него есть периферийный контроллер DMA.
- У него нет MMU или MPU.
- 8К встроенной оперативной памяти, что для нас ограничение.
- У него остается примерно 28 КБ флеш-памяти после установки специально написанной операционной системы.
Заданы дополнительные вопросы:
- В идеале я хотел бы скопировать программы во флэш-память и выполнять их оттуда. Теоретически это возможно. Было бы невозможно выполнять программы инструкция за инструкцией?
Да, можно выполнять программную инструкцию за инструкцией (но в этом подходе тоже есть ограничение, о котором я расскажу через секунду). Вы должны начать с выделения (выровненного по 4 байта) адреса в памяти, куда будет идти ваша единственная инструкция. Его ширина составляет 32 бита (4 байта), и сразу после него вы поместите вторую инструкцию, которую вы никогда не измените. Эта вторая инструкция будет иметь вид вызов супервизора (SVC), который вызовет прерывание, позволяющее выбрать следующую инструкцию, поместить ее в память и начать заново.
Хотя возможно, это не рекомендуется, потому что вы потратите больше времени на переключение контекста, чем на выполнение кода, вы не можете фактически использовать переменные (для этого вам нужно использовать ОЗУ), вы не можете использовать вызовы функций (если вы вручную не обрабатываете ветвь инструкции, ай!), и ваша вспышка будет записана так, что очень быстро станет бесполезной. Что касается последнего, о том, что Flash становится бесполезным, я предполагаю, что вы хотели выполнять инструкцию за инструкцией из ОЗУ. Помимо всех этих ограничений, вам все равно придется использовать некоторую RAM для вашего стека, кучи и глобальных объектов (подробности см. В моем приложении). Эту область могут совместно использовать все задачи, выполняемые с внешней флэш-памяти, но вам нужно будет написать для этого собственный сценарий компоновщика, иначе вы потратите впустую свою оперативную память.
Что сделает это более ясным, так это понимание того, как компилируется код C. Даже если вы используете C ++, начните с вопроса: где на моем устройстве скомпилированы переменные и инструкции?
В основном то, что вы ДОЛЖНЫ знать, прежде чем пытаться это сделать, это:
- где будет выполняться код (Flash / RAM)
- как этот код связан со своим стеком, кучей и глобальными объектами (вы должны выделить отдельный стек для этой задачи и отдельное пространство для глобальных объектов, но вы можете совместно использовать кучу).
- где находятся стек, куча и глобальные переменные этого внешнего кода (этим я пытаюсь намекнуть на то, какой контроль вам нужно будет иметь над своим кодом C)
Изменить 2:
Как использовать периферийный контроллер DMA:
Для микроконтроллера, с которым я работаю, контроллер DMA фактически не подключен к Embedded Flash ни для чтения, ни для записи. Если это так и для вас, вы не можете его использовать. Однако ваша таблица данных неясна в этом отношении, и я подозреваю, что вам нужно будет запустить тест с использованием последовательного порта, чтобы увидеть, может ли он действительно работать.
В дополнение к этому, я обеспокоен тем, что операция записи при использовании контроллера DMA может быть более сложной, чем вы выполняете ее вручную, из-за записи кэшированных страниц. Вам нужно будет убедиться, что вы выполняете только передачу DMA внутри страниц и что передача DMA никогда не пересекает границу страницы. Кроме того, я не уверен, что произойдет, когда вы скажете контроллеру DMA записать из флэш-памяти обратно в то же место (что вам может потребоваться сделать, чтобы перезаписать только правильные части).
Обеспокоенность по поводу доступной флэш-памяти и ОЗУ:
Меня беспокоит ваш предыдущий вопрос о выполнении по одной инструкции за раз. В таком случае вы можете написать интерпретатор. Если у вас недостаточно памяти для хранения всего кода задачи, которую вы должны выполнить, вам нужно будет скомпилировать задачу как PIC с глобальной таблицей смещения (GOT), помещенной в оперативную память вместе со всей необходимой памятью для этого. глобальные объекты задачи. Это единственный способ избежать недостатка места для всей задачи. Вам также нужно будет выделить достаточно места для его стека.
Если у вас недостаточно оперативной памяти (а я подозреваю, что у вас ее не будет), вы можете выгружать свою оперативную память и выгружать ее во Flash каждый раз, когда вам нужно переключаться между задачами на внешнем чипе Flash, но, опять же, я настоятельно не рекомендую писать к вашей флеш-памяти много раз. Таким образом, вы можете сделать так, чтобы задачи на внешней флеш-памяти совместно использовали часть ОЗУ для своих глобальных объектов.
Во всех остальных случаях вам придется писать переводчика. Я даже сделал немыслимое, я попытался придумать способ использовать состояние прерывания контроллера памяти вашего микроконтроллера (раздел 18.3.4 Статус прерывания в таблица) в качестве MPU, но не смогли найти даже удаленно умный способ его использования.
Изменить 3:
Я бы посоветовал прочитать раздел 40.8.2 Биты энергонезависимой памяти (NVM) в таблице данных, что предполагает, что ваша флеш-память имеет максимум 10 000 циклов записи / стирания (мне потребовалось время, чтобы ее найти). Это означает, что к тому времени, когда вы напишете и сотрете область флеш-памяти, в которой вы будете переключать контекст задачи 10 000 раз, эта часть 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...
}
person
nonsensickle
schedule
25.12.2013