Линковка и стартовый код в PIC32.

PIC
Транслятор с ассемблера в машинные коды, а значит и наши знания assembler MIPS32, были бы безполезны, без способности превратить код в прошивку микроконтроллера. Эта статья описывает скрипт линковки для 32MX320F128L. Ассемблера не очень много, в основном описываются сами процессы и структура файлов.


Это четвёртая статья из цикла: один, два, три, четыре.

Ассемблер транслирует код в объектные файлы, в каждом объектном файле находятся функции и данные в разных секциях. Секция это такая группа функций.

Линковка
Линковщик (linker) собирает программу из кусочков (секций):
  • знает по каким адресам надо разместить кусок памяти
  • соединяет функции из разных файлов, пишет вместо заглушек адреса вызываемых функций
  • вырезает незадействованные функции
  • помещает собранную прошивку в один файл (elf), в котором тоже есть секции, но уже все данные одной секции идут непрерывным куском
  • приклеивает к секции стартовый адрес
  • проверяет влезают ли все секции в отведённую область памяти
  • ругается, если каких-то функций или данных не хватает


Процессоры (микроконтроллеры) это конечные автоматы. У каждого есть начальное состояние, в которое процессор входит при подаче высокого уровня на ножку резета. Состояние процессора определяется набором регистров. При reset внутренний регистр текущей команды (счётчика команд или program counter (PC)) устанавливается на определённый адрес.

Открываем ww1.microchip.com/downloads/en/DeviceDoc/61143H.pdf
Ищем «Memory map». Не обманитесь картинкой: адреса стартуют снизу и растут вверх.
Загрузка происходит с адреса 0xBFC00000. Достучаться до этого адреса только с помощью программатора.
В случае Chipkit Uno32 бутлоадер уже зашит в память с завода.

Код который мы можем менять стартует с адреса 0x9D001000. Это такое соглашение для тех кто пишет загрузчики под PIC32, правда соблюдается оно не на всех чипах.
Этот адрес находится в Program Flash (0x9D000000-0x9D01FFFF). Размер этого сегмента памяти 0x1FFFF+1 байт или 128КБ. Этот сегмент содержит всю программируемую флешпамять контроллера.

Чтобы разобраться с кодом, я буду использовать дизассемблирование:
pic32-objdump -S -b elf32-tradlittlemips -mmips -Mgpr-names=O32,cp0-names=mips4,cp0-names=mips4,hwr-names=mips4,reg-names=mips4  -D main.elf


Прерывания:
Прерывания в PIC32 сделаны немного не привычно. У нас нет спецальной таблицы прерываний с адресами обработчиков.
Есть одна большая функция, которая обрабатывает все прерывания:
General Exception Vector Handler
9d000180 <_gen_exception>:
9d000180:       3c1a9d00        lui     k0,0x9d00
9d000184:       275a14cc        addiu   k0,k0,5324
9d000188:       03400008        jr      k0
9d00018c:       00000000        nop


Не впечатляет, правда? А что же такое 0x9d0014CC?

9d0014cc <_general_exception_context>:
9d0014cc:       27bdffa8        addiu   sp,sp,-88
9d0014d0:       afa10004        sw      at,4(sp)
9d0014d4:       afa20008        sw      v0,8(sp)
...
9d001510:       afb90044        sw      t9,68(sp)
9d001514:       afbf0048        sw      ra,72(sp)
9d001518:       00004012        mflo    t0
9d00151c:       afa8004c        sw      t0,76(sp)
9d001520:       00004010        mfhi    t0
9d001524:       afa80050        sw      t0,80(sp)
9d001528:       40046800        mfc0    a0,$13
9d00152c:       40056000        mfc0    a1,$12
9d001530:       0f400567        jal     9d00159c <_general_exception_handler>
9d001534:       00000000        nop
9d001538:       8fa80050        lw      t0,80(sp)
9d00153c:       01000011        mthi    t0
9d001540:       8fa8004c        lw      t0,76(sp)
9d001544:       01000013        mtlo    t0
9d001548:       8fa10004        lw      at,4(sp)
9d00154c:       8fa20008        lw      v0,8(sp)
...
9d001588:       8fb90044        lw      t9,68(sp)
9d00158c:       8fbf0048        lw      ra,72(sp)
9d001590:       27bd0058        addiu   sp,sp,88
9d001594:       000000c0        sll     zero,zero,0x3
9d001598:       42000018        eret

Этот код сохраняет почти все регистры, вызывает _general_exception_handler и востанавливает регистры.


9d00159c <_general_exception_handler>:
9d00159c:       0b400567        j       9d00159c <_general_exception_handler>
9d0015a0:       00000000        nop

Эта функция ничего не делает (крутится в бесконечном цикле).

Как видно, это тупик. Да, действительно, на PIC32 можно использовать единственную функцию для всех прерываний, но так же можно и применять отдельные обработчики прерываний, до 64х штук.

Казалось бы вот тут-то мы и увидим привычную табличку из 64х элементов, каждый из которых будет указывать на фукнцию обработчик.
Вместо этого на каждый вектор прерывания выделяется 32байта под код (или 8 команд).
Этого как раз может хватить для несложного обработчика или для прыжка на настоящий обработчик.

Здесь уже не нужно сохранять/востанавливать все регистры по одной команде и скорость обработки прерывания значительно повышается.

Наша прошивка будет содержать не только код, но и данные, поэтому мы будем выделять несколько сегментов:
exception_mem 0x9D000000 — вектора прерывания.
kseg0_program_mem 0x9D001000 — флешка под код
kseg0_eeprom_mem 0x9D01F000 — флешка под константы и пользовательские настройки

Вспоминаем про chipKIT-UNO32-application-32MX320F128L.ld

Надо описать карту памяти:
MEMORY
{
  kseg0_program_mem    (rx)  : ORIGIN = 0x9D001000, LENGTH = 0x1E000
  kseg0_eeprom_mem           : ORIGIN = 0x9D01F000, LENGTH = 0x1000
  kseg0_boot_mem             : ORIGIN = 0x9FC00490, LENGTH = 0
  exception_mem              : ORIGIN = 0x9D000000, LENGTH = 0x1000
  kseg1_boot_mem             : ORIGIN = 0xBFC00000, LENGTH = 0
  debug_exec_mem             : ORIGIN = 0xBFC02000, LENGTH = 0
  config3                    : ORIGIN = 0xBFC02FF0, LENGTH = 0
  config2                    : ORIGIN = 0xBFC02FF4, LENGTH = 0
  config1                    : ORIGIN = 0xBFC02FF8, LENGTH = 0
  config0                    : ORIGIN = 0xBFC02FFC, LENGTH = 0
  kseg1_data_mem       (w!x) : ORIGIN = 0xA0000000, LENGTH = 0x4000
  sfrs                       : ORIGIN = 0xBF800000, LENGTH = 0x100000
  configsfrs                 : ORIGIN = 0xBFC02FF0, LENGTH = 0x10
}

Ничего сложного:
  • название сегмента
  • права доступа (чтение, запись, выполнение)
  • начальный адрес
  • размер


Константы:
_RESET_ADDR              = 0x9D001000;
_EEPROM_ADDR             = 0x9D01F000;


Размер стека и кучи:
EXTERN (_min_stack_size _min_heap_size)
PROVIDE(_min_stack_size = 0x800) ;
PROVIDE(_min_heap_size = 0x800) ;


Наш стартовый код
SECTIONS
{
  /* Boot Sections */
  .reset _RESET_ADDR :
  {
    KEEP(*(.reset))
  } > kseg0_program_mem


Пример описания памяти для функций обработчиков прерываний:
.app_excpt _GEN_EXCPT_ADDR :
  {
    KEEP(*(.gen_handler))
  } > exception_mem
  .vector_0 _ebase_address + 0x200 :
  {
    KEEP(*(.vector_0))
  } > exception_mem
  ASSERT (_vector_spacing == 0 || SIZEOF(.vector_0) <= (_vector_spacing << 5), "function at exception vector 0 too large")


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

Дальше идёт ещё много всяких секций но понять их нужность можно только подробно разобрав стартовый код.

Осталось за кадром:
  • Выставление тактовой частоты
  • Инициализация стека и кучи
  • Загрузка данных из флеша в память, для глобальных переменных (не константных)
  • Заполнение нулями глобальных переменных без значения по-умолчанию.


Код, использованный в статье (не мой):
github.com/rkujawa/chipKIT-minimal-application

  • +1
  • 04 апреля 2014, 15:00
  • ihanick

Комментарии (3)

RSS свернуть / развернуть
Спасибо.
Проголосовать не могу нет ни силы ни рейтинга.
Публикации хорошие и очень нужные на фоне того, что на русском про MIPS
и частности PIC32MX их очень мало.
Но для начинающего сложновато.
Совсем для «чайников» не планируете?
А про настройку PLL будет?
0
Совсем для «чайников» не планируете?
Тут проблема курицы и яйца, линковка — навороченная тема, а без неё будут непонятности с прерываниями. Без прерываний, трудо показать использование таймеров (нет наглядности (особенно с захватом (Input Capture (IC))), без таймеров сложно показывать PLL. У меня на руках только 80MHz, значит PLL придётся делать простым (можно только несколько частот выбрать для PBDIV и посмотреть на разницу в моргании).

Скорее всего следующая серия про startup и ещё следующая про прерывания будут сильно проще.
В принципе можно стартап отложить и прыгнуть сразу на прерывания.

Проголосовать не могу нет ни силы ни рейтинга.
Ну, это не важно, главное, чтобы меня за эти пики здесь не забанили. Сейчас в моде STM32F4Discovery, но это уже совсем другая история.

про MIPS и частности PIC32MX их очень мало.
А каким железом пользуетесь?

Я брал chipkit uno32 потому что хотел задачи из Кнута прорешать на mips32 (на MIX это перебор как-то) на старости лет.

Хотя, сейчас уже понимаю, что можно и PIC32MZ попробовать использовать в своей авто/мото тематике вместо STM32F4 (который сейчас), тогда должно быть сильно больше про переферию ( USB, CAN, IC, шаговые моторы, SPI)
0
А каким железом пользуетесь?
Пока ни каким, заказал макетку с PIC32MX795, решил разобраться как работать с ним пока едет.

Я программированием ни когда не занимался (в молодости ковырял немного Z80 ASM).
Но всегда было интересно. И вот решился, как хобби.

Сейчас в моде STM32F4Discovery
Наверно поэтому и выбрал PIC32.

Зарегался специально ради этих публикаций.
Хотелось бы узнать про последовательность действий:
— что происходит после подачи питания;
— если нет bootloader`а;
— последовательность конфигурирования, чтобы запустить простейшую программу;
Не используя ни каких библиотек, чтобы разобраться получше с архитектурой.
У Вас в постах наверно это всё есть, но я пока не разобрался.
А Data Sheet и Reference Manual с Googletranslate тяжело осваивать.
Спасибо.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.