Как загружается ARM

    Прошлый мой топик был полностью теоретическим, этот же будет практическим. Практика будет довольно хардкорной (я сам занялся этим вопросом только через год работы с ARMами) — инициализация процессора и памяти. Иными словами: что нужно сделать с процессором, чтобы попасть в функцию main(). Первая часть статьи посвящена инструментам сборки и отладки. Вторая — обработке векторов исключений, третья — инициализации стеков и памяти.
    Но сначала хочу сделать одно уточнение. Многие почему-то считают, что ARM — это обязательно монстр со внешней памятью, кучей обвязки, работающий на частоте не менее 600Mhz, и т.д. Это правда лишь отчасти (если говорить об ARM9 и более поздних семействах). Тот чип, с которым я обычно работаю (AT91SAM7X512), не намного сложнее знакомых многим AVR. Ему для работы нужны только кварц и питание (можно и без кварца, но тогда будет совсем грустно). Всё. Но возможностей у него, конечно, больше, много больше, чем у AVR. Но об этом позже. Сегодняшняя статья никак не будет привязана к конкретному железу.

    Компиляторы, линковщики, дебаггеры


    Вопрос, который волнует очень многих. Есть платные (IAR, Keil MDK, CrossWorks) и бесплатные (gcc-arm). Я в примерах буду использовать gcc-arm. Для винды есть сборки WinARM (кажется, умершая), YAGARTO. В принципе, можно собрать и свою. Есть ещё такая веселая штука, как coLinux, но это совсем другая история. Под Linux кросскомпилятор обычно собирается штатными средствами дистрибутива. Читайте доки, в общем :)
    Ещё существует такая полезная вещь, как стандартная библиотека. Та самая, которая реализует функции наподобие printf, mktime, malloc и всего прочего, к чему привыкли программисты на C. Использовать glibc не получится, ибо она слишком большая. Вместо этого обычно используют бесплатную newlib. Она входит в состав WinARM/YAGARTO, а вот пользователям линукса придется собирать её вручную. Опять же — читайте документацию :)
    С дебаггерами немного сложнее. Можно использовать эмуляторы, но они довольно глючные, когда дело доходит до периферии. Тут у меня опыта нет. Можно использовать отладочные сообщения в COM-порт. Я так делаю всю жизнь. Мне хватает в 99% случаев.
    Но самая классная штука — это JTAG. Устройство, которое подключается к процессору и позволяет дебажить код прямо в камне (ставить брекпойнты, трасировать, просматривать/изменять память ну и т.д.). Правда, стоит денег, с одной стороны, с другой — на плате надо будет разводить под него ножки.

    Обработчики исключений


    Ладно, будем считать, что компилятор поставили и настроили. Давайте теперь что-нибудь запустим. Начнем с самого начала: что происходит, когда процессор сбрасывается (например, после того, как включили питание и напряжение устаканилось). Тут всё просто: процессор начинает исполнять программу с адреса 0x0. Казалось бы — можно разместить с этого адреса код инициализации и работать себе. Но не всё так просто. Дело в том, что в начальных адресах хранятся вектора обработчиков исключений.
    Например, если возникнет прерывание, то обработку его процессор начнет с адреса 0x18, а исключение «неизвестная инструкция» будет обрабатываться с адреса 0x04. В общем, первые 28 байт отведены для таблицы обработчиков исключительных ситуаций (reset — это тоже исключительная ситуация).
    arm exception vectors
    На рисунке это видно более наглядно. С рисунка же видно, что на каждый обработчик отведено 4 байта, или одна команда процессора. (В режиме ARM. Все обработчики вызываются в этом режиме инструкций.)
    Соответсвенно, первым, что мы должны сделать, — это написать обработчики исключений и правильно их разместить. Этим и займемся:
    ldr pc, ResetHandlerAddr
    ldr pc, UndefHandlerAddr
    ldr pc, SWIHandlerAddr
    ldr pc, PrefetchAbtHandlerAddr
    ldr pc, DataAbtHandlerAddr
    nop
    ldr pc, IRQHandlerAddr
    ldr pc, FIQHandlerAddr

    Что делает этот код? Это команды загрузки в регистр pc адресов настоящих обработчиков. Такой себе безусловный переход. Дальше по коду идут переменные, хранящие эти самые адреса:

    ResetHandlerAddr: .word ResetHandler
    UndefHandlerAddr: .word UndefHandler
    SWIHandlerAddr: .word SWIHandler
    PrefetchAbtHandlerAddr: .word PrefetchAbtHandler
    DataAbtHandlerAddr: .word DataAbtHandler
    IRQHandlerAddr: .word IRQHandler
    FIQHandlerAddr: .word FIQHandler

    Тут можно было применить несколько фокусов, ускоряющих обработку прерываний. Например, как видно, обработчик FIQ находится самым последним в списке, так что обработку этого прерывания можно было начать прямо на месте.
    Также можно было использовать регистры AIC (advanced interrupt controller) для прямого перехода на обработчик возникшего прерывания. Но пока не будем усложнять себе жизнь. Пока нам важна только обработка Reset'а.
    Так что давайте напишем сами обработчики максимально простыми. Они будут вешать процессор (бесконечно выполняя команду безусловного перехода на самих себя). Всё равно мы не знаем пока, как обрабатывать исключения, поэтому повисший процессор — вполне допустимо.
    UndefHandler: B UndefHandler
    SWIHandler: B SWIHandler
    PrefetchAbtHandler: B PrefetchAbtHandler
    DataAbtHandler: B DataAbtHandler
    IRQHandler: B IRQHandler
    FIQHandler: B FIQHandler

    B — это команда безусловного перехода (Branch)
    Следующее, что нам нужно сделать, — это настроить указатели стека sp для каждого из режимов работы. Таким образом, если возникнут исключения, — у обработчика уже будет свой стек. Только вначале опишем размеры всех стеков.
    .EQU IRQ_STACK_SIZE, 0x100
    .EQU FIQ_STACK_SIZE, 0x100
    .EQU ABT_STACK_SIZE, 0x100
    .EQU UND_STACK_SIZE, 0x100
    .EQU SVC_STACK_SIZE, 0x100

    Чтобы не мучаться долго, выделим по 256 байт на стек для каждого режима. На самом деле для большинства из этих режимов — это много. Хотя всё зависит от обработчиков. Как видно, тут описаны размеры для 5 из 6 режимов. Остальная память будет использоваться совместно кучей и стеком шестого (user mode) режима.
    Теперь опишем константы для облегчения перехода в разные режимы. За текущий режим отвечает регистр CPSR. Он же выполняет и роль статусного регистра.
    .EQU ARM_MODE_FIQ, 0x11
    .EQU ARM_MODE_IRQ, 0x12
    .EQU ARM_MODE_SVC, 0x13
    .EQU ARM_MODE_ABT, 0x17
    .EQU ARM_MODE_UND, 0x1B
    .EQU ARM_MODE_USR, 0x10

    .EQU I_BIT, 0x80
    .EQU F_BIT, 0x40

    Константы I_BIT и F_BIT — это биты, которые запрещают простые и быстрые прерывания, соответственно. Теперь у нас всё готово для инициализации стеков. Делается это просто: загружаем в регистр r0 указатель на вершину стека, потом переходим в нужный режим, записываем в sp значение r0, затем уменьшаем r0 на размер стека и повторяем.
    .RAM_TOP:
    .word __TOP_STACK
    ResetHandler:
    ldr sp, .RAM_TOP

    msr CPSR_c, #ARM_MODE_FIQ | I_BIT | F_BIT
    mov sp, r0
    sub r0, r0, #FIQ_STACK_SIZE

    msr CPSR_c, #ARM_MODE_IRQ | I_BIT | F_BIT
    mov sp, r0
    sub r0, r0, #IRQ_STACK_SIZE

    msr CPSR_c, #ARM_MODE_SVC | I_BIT | F_BIT
    mov sp, r0
    sub r0, r0, #SVC_STACK_SIZE

    msr CPSR_c, #ARM_MODE_ABT | I_BIT | F_BIT
    mov sp, r0
    sub r0, r0, #ABT_STACK_SIZE

    msr CPSR_c, #ARM_MODE_UND | I_BIT | F_BIT
    mov sp, r0
    sub r0, r0, #UND_STACK_SIZE

    msr CPSR_c, #ARM_MODE_USR

    Инициализация памяти


    Теперь мы находимся в непривилегилированном режиме со включенными прерываниями и настроенным стеком. Кстати, выйти из этого режима просто так нельзя. Только вызвав исключение. Но об этом в следующей статье.
    До перехода в функцию main() осталось совсем чуть-чуть. Надо только перенести кое-какие данные в RAM и обнулить память, которая находится в сегменте .BSS. Это та память, где хранятся глобальные переменные. Дело в том, что стандарт языка C обещает, что глобальные переменные будут обнулены в начале работы, а ARM нам этого не гарантирует. Поэтому обнулим сегмент вручную:

                   MOV     R0, #0
                   LDR     R1, =__bss_start__
                   LDR     R2, =__bss_end__
    LoopZI:
                   CMP     R1, R2
                   STRLO   R0, [R1], #4
                   BLO     LoopZI
    

    Константы __bss_end__ & __bss_start__ любезно предоставлены нам линковщиком.
    Кстати, тут вы можете наблюдать использование условных инструкций (с суффиксом O). Они будут исполняться, пока R1!=R2.
    Также надо перенести из ROM в RAM предварительно инициализированные переменные (те, которые int x=42).
                   LDR     R1, =_etext
                   LDR     R2, =_data
                   LDR     R3, =_edata
    LoopRel: 
                   CMP     R2, R3
                   LDRLO   R0, [R1], #4
                   STRLO   R0, [R2], #4
                   BLO     LoopRel
    

    Если будем писать на C++, то нужно ещё вызвать конструкторы глобальных объектов:
                   LDR     r0, =__ctors_start__
                   LDR     r1, =__ctors_end__
    ctor_loop:
                   CMP     r0, r1
                   BEQ     ctor_end
                   LDR     r2, [r0], #4
                   STMFD   sp!, {r0-r1}
                   MOV     lr, pc
                   BX r2
                   LDMFD   sp!, {r0-r1}
                   B       ctor_loop
    ctor_end:
    


    Ну в общем ивсё. Вызываем main():
                   ldr     r0,=main
                   bx      r0
    


    Поздравляю, теперь мы в сишной функции void main(void). Можно заняться инициализацией периферии. Дело в том, что до этого мы инициализировали только програмную среду. Поэтому процессор сейчас работает на самой низкой частоте из всех возможных, вся периферия отключена. Тут не разгуляешься :)
    Но инициализация периферии — это штука, которая зависит от конкретной железяки, а цель этой статьи — рассказать, как запускать абстрактный ARM.
    И ещё несколько нюансов: этот код напрямую скомпилировать и запустить не получится, потому что здесь не описаны секции, где он располагается. Также я не приводил скрипты линковщика (эти скрипты описывают размещение секций кода и данных в памяти и в образе прошивки).
    Но в интернете полно готовых примеров для запуска той или иной железки. Со скриптами, мейкфайлами и всем-всем-всем. Ищите на сайтах производителей :)

    Следующая статья, судя по всему, будет опять посвящена теории, на этот раз — описанию режимов процессора и исключительных ситуаций.
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 35

      +2
      А кто инициализирует память? например если это ддр то нужно же ей установить настройки в регистры типа таймингов и т.д., или это Вы без внешней памяти пример привели?
        +2
        В данном случае, вероятно имеется в виду, что действие происходит во внутренней SRAM, инициализировать которую не требуется по определению. В случае же с внешней ddr данный код необходим, да.
          +1
          Действительно, если есть внешняя память, то сначала надо инициализировать источник тактовой частоты и внешнюю память. Он этим обычно занимается загрузчик.
          Считайте, что я описал пример самого простого загрузчика, которому хватит внутренней SRAM
          +2
          В общем интересно, если можно еще раскажите о Загрузчиках для ARM :) и о JTAG поподробнее.
          Выходы есть почти на каждом роуторе, кпк (распаять надо)
          Хочется поиграться с кпк, но вот надо jtag USB (com валяется, через переходник плохо работает, разбираться нету время)
            0
            Старт программы с адреса 0x0 это лишь один из вариантов загрузки который предлагает bootrom зашитый производителем (если речь идет о микроконтроллерах на ARM), ведь если нет JTAGа вам же придется как-то залить код через UART, SPI или нечто подобное.
            А вообще представленный вами код в том или ином виде содержится в startup.s предоставляемый производителем чипа, не вижу смысла переписывать его заново, достаточно размеры стеков подправить под свои нужды, и обработчики вставить если это необходимо.
              +5
              А вообще представленный вами код в том или ином виде содержится в startup.s предоставляемый производителем чипа, не вижу смысла переписывать его заново
              Вы, видимо, забыли, что это ознакомительная статья, для того, чтобы показать, как и что загружается, а не написание своей реализации загрузчика.

              Автор, спасибо, было интересно прочитать. С нетерпением жду продолжения.
                +2
                Ну процессор стартует с адреса 0x0. Что там находится — зависит уже от производителя и конфигурации процессора. Там может быть как bootrom, так и flash, можно туда замапить и RAM. Например, AT91SAM7X512 поддерживает все три варианта: изначально туда мапится bootrom, после перепрошивки мапится Flash, а с помощью Мemmory Controller можно замапить туда RAM (например что бы менять адреса обработчиков исключений в рантайме).
                Код действительно повторяет startup.s, Но мне однажды пришлось очень хорошо разобраться, как этот код работает. Думаю, будет интересно не мне одному.
                +1
                А можно какие-то принципиальные схемы, как все подсоединять, и что из этого можно получить…
                Допустим, сделать какую-то простую схемку с парой диодов, и написать программку что они моргали…
                Было бы интересно сделать самому, своими руками =)
                Заранее благодарю!
                  0
                  Купите комплект разработчика(KIT) там уже все припаяли и собрали. В комплекте будет документация, среда разработки, компилятор и JTAG-отладчик. Цена вопроса от 5-6 тысяч. Правда софт скорее всего будет с ограничениями и под винду.

                  Краткая инструкция для желающих себя попробовать:
                  1. Выбираете МК.
                  2. Ищите киты на сайте производителя.
                  3. Ищите на efind.ru и ему подобных этот кит.
                    0
                    Кстати да. Я вот не могу понять, почему народ носится с Arduino, если можно купить демо-плату и получить как минимум те же возможности, а обычно — намного больше.
                    Arduino конечно проще, да. Но с KIT'ами поставляется документация и примеры. Разобраться в них — не сильно сложнее. А сделать можно больше интересных вещей
                    • UFO just landed and posted this here
                    0
                    Да, было бы интересно
                    0
                    Спасибо, толком ничего не знаю о ARM7TDMI

                    если кому интерестно то современный ARM Cortex M3 инициализируется проще и на С
                      +1
                      Эм… насколько я понял из документации — он инициализируется точно так же.
                      Всё же кто-то должен прописать вектора обработчиков, инициализировать стеки и память. При желании это наверное можно сделать на C… Но выглядеть будет странно :)
                      Кстати, надо будет попробовать, спасибо за идею :)
                      А вообще вся периферия ARM7TDMI тоже инициализируеися на C.
                      В этой статье я просто описывал, что надо сделать, что бы C заработал.

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

                        __attribute__ ((section(".isr_vector")))
                        void (* const g_pfnVectors[])(void) =
                        {
                        (void (*)(void))((unsigned long)pulStack + sizeof(pulStack)),
                        // The initial stack pointer
                        ResetISR, // The reset handler
                        NmiSR, // The NMI handler
                        FaultISR, // The hard fault handler
                        FaultISR, // The MPU fault handler
                        FaultISR, // The bus fault handler
                        FaultISR, // The usage fault handler
                        0, // Reserved
                        0, // Reserved
                        0, // Reserved
                        0, // Reserved
                        IntDefaultHandler, // SVCall handler
                        IntDefaultHandler, // Debug monitor handler
                        0, // Reserved
                        PendSV_ISR, // The PendSV handler
                        SysTickTimer_ISR, // The SysTick handler

                        нужно еще в ResetISR скопировать данные в RAM, тоже вполне реализуемо на С

                        еще на каждый перефирийный девайс в Cortex M3 отдельное прерывание.
                        это я так понял пока отличия между архитектурами

                          0
                          Ух ты, буду знать. Спасибо :)
                          А как настроить стеки в других режимах?
                            +1
                            другой режим всего один.
                            есть два стека SP_main и SP_process

                            SP_main используется всегда в режиме обработчика прерываний, туда же сохраняются вложенные прерывания такого наверно в ARM7TDMI нет.

                            SP_process может использоватся (а может и нет) в обычном режиме

                            msr psp, r0
                              0
                              Хм, вижу ARMv7 намного совершенней в этом плане, чем ARMv5.

                              Вложенные прерывания в ARM7TDMI организовать можно, но вручную: в общем обработчике прерываний сохраняем адрес возврата на стеке, разрешаем прерывания, вызываем настоящий обработчик. Что бы сделать стек прерываний небольшим — можно выполнять настоящий обработчик в основном режиме работы, там стек обычно большой.
                              Затем, если придет более высокоприоритетное прерывание (за этим следит AIC) — оно сможет выполнится точно таким же образом.
                    • UFO just landed and posted this here
                        0
                        Ну там можно ожидать Линукс, я думаю :)
                        Или WinCE.
                        Для таких ARM-ов, Линукс — вполне себе хорошее решение, насколько мне кажется. Во всяком случае там уже есть все драйвера и много софта.
                        Если уж очень хочется своё, то можно взять бесплатную realtime os, даташиты на процессор, всю периферию что стоит в ноуте, и писать, писать, писать :)
                        Только сначала надо будет получить доступ к памяти программ. Самый популярный способ — через JTAG, если производитель ноута не предоставит каких-нить стандартных способов перешивки.
                        • UFO just landed and posted this here
                            +1
                            Разбирать и изучать — это к сожалению единственный способ.
                            Но самое поганое не это. Некоторые производители жлобятся выкладывать подробные даташиты по своим продуктам в открытый доступ.
                            Хотя, думаю в дешевых китайских нетбуках стоят такие же дешевые комплектующие, документации по которым вагон :)
                            • UFO just landed and posted this here
                                0
                                Ну насколько я знаю — стереть прошивку совсем и залить что-то своё можно всегда. Защиты обычно не дают скачивать/изменять существующую прошивку.
                          0
                          Попробуйте beagleboard.
                          0
                          Спасибо, очень интересно. Единственный недостаток, не имение под рукой arm'а, хотя это не ваша вина.

                          Хотелось бы попробовать сделать собственную плату, с микропроцессором/контроллером, но руки ни как не дойдут. Стоит ли начинать сразу с arm или лучше начать со всем известных атмелов? Не дадите ли ссылку на магазин где такое добро можно купить, за хорошую цену?
                            +1
                            Ну AVRки (вы же о них?) разве что паять удобно (ибо бывают в больших корпусах). А в остальном — разницы между серией AT91SAM и AVR в плане разводки и сборки — немного. Единственное, что у ARM множество входов питания. И часть ножек — системные, они должны быть подтянуты к земле или питанию.
                            Если покупать демо-плату — то разницы вообще нет, ибо там уже всё разведено и припаяно.
                            Где купить в розницу — не подскажу, ибо мы процессоры покупаем тысячами сразу :)
                              0
                              Попробуйте Cypress PSoC3(ядро i8051), а с него относительно безболезненно можно перейти на PSoC5 (ядро ARM Cortex-M3).
                              Что получите в отличие от Атмел. Наличие реконструируемых цифровых и аналоговых блоков, за счёт чего можно обойтись от обвязки обработав сигнал прямо в кристалле.
                              Универсальный кит под три семейства (PSoC1, PSoC3, PSoC5) www.cypress.com/?rID=37464
                              Дешёвый вариант с акселерометром и поддержкой сенсорных кнопок и разъёмом для подключения безпроводного модуля www.cypress.com/?rID=38235
                              +1
                              Спасибо огроменное! Всегда хотел узнать ARM по-ближе. Буду ждать новых статей.
                                0
                                Интересно посмотреть, как вы настроили окружение. Ну и sbrk() и прочие из newlib интересно.
                                  0
                                  Да, это я наверное опишу в следующей практической статье.
                                  +1
                                  Господа, ну что вы в самом деле! Поиграться с железками можно при цене вопроса от $25!

                                  starterkit.ru/html/index.php
                                  www.evodbg.com/index.php?option=com_content&task=view&id=19&Itemid=46
                                  Причем не только светодиодиками поморгать, но и маленький «аппаратный» web сервер сделать. У меня на таком сайт более 5000 часов крутился…
                                    0
                                    У меня на таком сайт более 5000 часов крутился


                                    А потом что случилось, если не секрет?
                                      +2
                                      Я имел ввиду что он был доступен из интернета, он и сейчас работает, но в домашней локалке, к нему подключен датчик температуры, я за погодой через web наблюдаю.

                                      Вернее наблюдал, пока не написал скрипт на домашнем сервере, подтягивающий с него страничку, парсящий температуру и проговваривающий мне ее голосом (festival — синтезатор речи ) по утрам, пока я на работу собираюсь :D
                                      0
                                      Не советовал бы брать StarterKit'ы на сайте starterkit.ru. У меня есть печальный опыт работы с этими «мастерами»…

                                      Лучше заказывать StarterKit от производителя.

                                    Only users with full accounts can post comments. Log in, please.