Выполнение программ, хранящихся во внешней флэш-памяти SPI, на процессоре ARM

У меня есть процессор ARM, который может взаимодействовать с внешней микросхемой флэш-памяти. В чип записываются программы, скомпилированные для архитектуры ARM, готовые к выполнению. Что мне нужно знать, так это перенести эти данные с внешней флэш-памяти на процессор ARM для выполнения.

Могу ли я заранее запустить какую-то подпрограмму копирования, когда данные копируются в пространство исполняемой памяти? Я полагаю, что мог бы, но процессор ARM работает под управлением операционной системы, и у меня нет тонны места во флеш-памяти для работы. Я также хотел бы иметь возможность планировать выполнение двух или даже трех программ одновременно, а одновременное копирование нескольких программ во внутреннюю флэш-память невозможно. Операционная система может использоваться для запуска программ, когда они находятся в доступном пространстве памяти, так что все, что нужно сделать заранее, может быть выполнено.


person George Morgan    schedule 25.11.2013    source источник


Ответы (2)


Прочитав существующие ответы @FiddlingBits и @ensc, я думаю, что могу предложить другой подход.

Вы сказали, что ваш флеш-чип не может быть отображен в памяти. Это довольно серьезное ограничение, но мы можем с ним работать.

Да, вы можете запустить процедуру копирования заранее. Пока вы помещаете его в ОЗУ, вы можете его выполнить.

DMA для ускорения:

Если у вас есть периферийный контроллер DMA (например, тот, который доступен в семействе Atmel SAM3N), вы можете использовать контроллер DMA для копирования фрагментов памяти, в то время как ваш процессор действительно делает полезные вещи.

MMU для упрощения:

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

Примечание. Если неясно, MMU - это не то же самое, что MPU

Нет решения MMU, но доступен MPU:

Без MMU задача немного сложнее, но все же возможно. Вам нужно будет понять, как ваш компилятор генерирует код, и прочитать о Позиционно-независимый код (PIC). Затем вам нужно будет выделить область в ОЗУ, из которой вы будете выполнять код внешнего флеш-чипа, и скопировать его части (убедитесь, что вы запускаете его из правильного места). MPU необходимо настроить для генерации ошибки каждый раз, когда задача пытается получить доступ к памяти за пределами назначенной ей области, и тогда вам нужно будет получить правильную память (это может стать сложным процессом), перезагрузить и продолжить выполнение.

Нет доступных MMU и MPU:

Если у вас нет MMU, эта задача становится очень сложной. В обоих случаях у вас есть серьезные ограничения на размер внешнего кода. По сути, ваш код, который теперь хранится на внешней флеш-микросхеме, должен умещаться в точности внутри выделенной области ОЗУ, из которой вы будете его выполнять. Если вы можете разделить этот код на отдельные задачи, которые не взаимодействуют друг с другом, вы можете это сделать, но в противном случае вы не сможете.

Если вы создаете PIC, вы можете просто скомпилировать задачи и последовательно поместить их в память. В противном случае вам нужно будет использовать сценарий компоновщика для управления генерацией кода таким образом, чтобы каждая скомпилированная задача, которая будет сохранена во внешней флэш-памяти, выполнялась из одного и того же предопределенного места в ОЗУ (что либо потребует от вас изучения ld оверлеи или компилируйте их отдельно).

Резюме:

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

Однако вы спросили, можно ли одновременно загружать более одной задачи для выполнения. Если вы используете PIC, как я предлагал, это должно быть возможно. В противном случае вам нужно будет заранее решить, где будет выполняться каждая из задач, и это позволит загружать / запускать некоторые из комбинаций одновременно.

И, наконец, в зависимости от вашей системы и чипа это может быть легко или сложно.

ИЗМЕНИТЬ 1:

Приведена дополнительная информация:

  1. Чип SAM7S (Atmel)
  2. У него есть периферийный контроллер DMA.
  3. У него нет MMU или MPU.
  4. 8К встроенной оперативной памяти, что для нас ограничение.
  5. У него остается примерно 28 КБ флеш-памяти после установки специально написанной операционной системы.

Заданы дополнительные вопросы:

  1. В идеале я хотел бы скопировать программы во флэш-память и выполнять их оттуда. Теоретически это возможно. Было бы невозможно выполнять программы инструкция за инструкцией?

Да, можно выполнять программную инструкцию за инструкцией (но в этом подходе тоже есть ограничение, о котором я расскажу через секунду). Вы должны начать с выделения (выровненного по 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
comment
Прежде всего, спасибо за ваш ответ. Это очень полезная информация. Чип, который я использую, SAM7S имеет периферийный контроллер DMA. К сожалению, у него нет MMU или MPU. 7S, которую я использую, имеет 8 КБ внутренней оперативной памяти, что является для нас ограничением. У него остается примерно 28 КБ флеш-памяти после установки специально написанной операционной системы. В идеале я хотел бы скопировать программы во флэш-память и выполнять их оттуда. Теоретически это возможно. Было бы невозможно выполнять программы инструкция за инструкцией? - person George Morgan; 26.12.2013
comment
Я подробно остановлюсь на вашем вопросе в своем ответе. Однако это будет непросто. Я сам работаю с SAM3N и исследовал это до смерти, так как у нас тоже есть внешняя вспышка, но мало оперативной и внутренней флэш-памяти. - person nonsensickle; 26.12.2013
comment
Хорошо, я расширил свое первоначальное предложение, но боюсь, что предоставил слишком много или слишком мало информации ... Джордж, не могли бы вы сообщить мне, какое из двух это? - person nonsensickle; 26.12.2013
comment
Очень подробное объяснение основы того, что будет происходить. Я тоже провел обширное исследование того, что происходит под капотом. Как я упоминал ранее, операционная система написана специально. Я уже написал свой собственный сценарий компоновщика и вручную копирую .data в RAM, обнуляя .bss и т. Д. Фактически, вся ОС libc независима. Я использую не AVR studio, а Xcode с внешней системой сборки в качестве цели. У меня все настолько просто, насколько это возможно, потому что, среди прочего, я знал, что этот процесс потребует кодирования на этом уровне. - person George Morgan; 26.12.2013
comment
Однако мне до сих пор неясно, как я мог бы использовать такой актив, как Peripheral DMA Controller. У меня есть смутное представление о том, как я буду выполнять физическое копирование с внешней флэш-памяти на внутреннюю флэш-память, откуда, кстати, я и решил выполнить. Может быть, вы могли бы немного подробнее рассказать об этом процессе и о том, как запустить программу после ее копирования. А как насчет разделов .data и .bss самой программы? Должны ли они просто перейти в зарезервированный сегмент ОЗУ? Как правильно сопоставить адреса? И т.д. И еще раз спасибо! - person George Morgan; 26.12.2013
comment
Я действительно хотел бы обсудить с вами кое-что более подробно. Это не то, что можно уместить в комментарии, и нет возможности отправить кому-то мгновенное или личное сообщение в StackOverflow. Если вы не возражаете добавить меня в Skype, мое имя пользователя sonyqrio, я был бы очень признателен. - person George Morgan; 26.12.2013
comment
Эй, я тебя добавил. Вы можете удалить свое имя пользователя из общественного достояния, если хотите. Однако в конце нашего обсуждения я хотел бы добавить то, что имеет отношение к моему ответу, если вас это устраивает. Кроме того, я в Новой Зеландии, поэтому нам нужно найти хорошее время, чтобы поговорить. - person nonsensickle; 27.12.2013
comment
Абсолютно. После этого вы можете опубликовать ответ, чтобы потребовать награду, или любую другую полезную информацию, которую мы можем придумать по ходу дела. - person George Morgan; 28.12.2013

Это зависит от типа вспышки и / или процессора. Флэш-память NOR обычно отображается в памяти, поэтому вы можете сразу перейти в нее. Флэш-память NAND должна быть прочитана (это зависит от SOC) в локальную память (SRAM, DRAM (-> требуется дополнительная инициализация!)).

РЕДАКТИРОВАТЬ:

SPI также не может быть отображен в RAM. Вы должны запрограммировать контроллер SPI SOC и флэш-память SPI. Протокол, который будет использоваться для флэш-памяти SPI, обычно описывается в ее руководстве; очень вероятно, что это общий протокол, поэтому вы, вероятно, сможете повторно использовать существующий драйвер.

person ensc    schedule 26.11.2013
comment
Это микросхема флэш-памяти SPI, что означает, что я могу писать на нее и читать с нее через SPI. Я сомневаюсь, что это можно отобразить в ячейке памяти. - person George Morgan; 27.11.2013
comment
Я провел небольшое исследование, и с помощью некоторой умной модификации компилятора память SPI действительно может быть отображена и использована в качестве ОЗУ. Я ничего не видел в отношении использования SPI в качестве флэш-памяти, но если его можно использовать в качестве ОЗУ (хотя и медленно), его можно будет использовать в качестве флэш-памяти. - person George Morgan; 27.11.2013
comment
конечно, вы можете поиграться с MMU, чтобы прочитать SPI flash на pagefaults. Но не каждый чип ARM имеет MMU, и считывание SPI-флэш-памяти напрямую намного проще. - person ensc; 27.11.2013
comment
Я могу прочитать его напрямую, но как мне его выполнить после этого? - person George Morgan; 27.11.2013