Обычный алгоритм использования загрузчика для МК, только что вынутого из упаковки:
- с помощью программатора/отладчика прошивается загрузчик
- МК монтируется в плату
- используя загрузчик по заранее определенному интерфейсу загружается основная прошивка
Это вполне приемлемо для опытных образцов изделий или для мелкосерийного производства. А что делать, если производство крупносерийное? Или сборка проводится автоматами (а может людьми с функционалом автоматов) — прошил, впаял? Тогда разумно убрать из алгоритма 3го пункт — объединить в одной прошивке и основную программу и загрузчик.
В своей практике я столкнулся с довольно жутким методом получения такой прошивки — в HEX-файл основной прошивки просто дописывался HEX-файл с кодом загрузчика. Конечно. такой подход имеет право быть — как ни крути, но итоговые «прошивки имени др. Франкенштейна» работали как надо. Но чувство, что для решения этой задачи должны быть более корректные методы, меня не оставляло.
Когда Я поискал решения в Интернете, то был неприятно удивлен, что простого и понятного описания решения нет. Собственно, именно это побудило меня написать публикацию, описывающую мое решение этой ситуации. Возможно мое видение решения этой проблемы отличается от максимально правильного, но оно гораздо более логичней, чем сшивание HEX-файлов.
Прежде чем перейти к самой теме публикации, хочу привести список упрощений и инструментов, которые были использованы:
- MPLAB IDE v8.85 (да, весьма устаревшая IDE)
- Microchip C30 Toolsuite v3.12
- объектные файлы в формате COFF (т.е. по умолчанию для этого toolchain-a)
- расположение загрузчика и основной программы статичны
- загрузчик располагается с адреса 0х0400
- основная программа располагается с адреса 0х2000
- память по адресам с 0х0200 по 0х0400 — неиспользуемая
И самое главное — конкретных исходников загрузчика в это публикации не будет.
Компоновка загрузчика в основной проект
Просто скомпилировать…
Начнем с самого простого случая. Добрый Дедушка Мороз прислал Вам на Новый Год готовый загрузчик. Причем он уже потрудился на славу и скомпилировал и скомпоновал его для Вас. Итак, у Вас в руках (на флешке/в сети/на жестком диске) есть файлик – UltraBoot3000.blob. Дальше алгоритм очень простой – просто добавь его к себе в проект.
Касаемо MPLAB IDE его надо добавить в категорию «Object Files». К сожалению, по умолчанию в эту категорию можно добавить только файлы с расширением «o». Отмечу так же, что файлы с расширением «o» получаются так же в процессе компиляции Вашей программы. Чтобы нечаянно не перепутать и не забыть о файле загрузчика, рекомендую держать его с другим расширением, например blob – binary linked object. Чтобы IDE положило файл blob в категорию «Object Files», этой категории нужно скорректировать настройки фильтров. Жмем правой кнопкой мыши на этой категории и выбираем пункт «Filter…». В появившемся окне в поле через точку-с-запятой дописываем необходимый нам шаблон фильтра. В нашем случае в поле должно быть в итоге следующее описание фильтров:
*.o;*.blob
После настройки фильтров можно добавить файл загрузчика в проект.
Запускаем процесс компи… НЕТ! СТОП!
Чтобы корректно собрать прошивку с нашим загрузчиком, нужен правильный скрипт компоновщика. Конечно, если Дедушка Мороз был настолько добр, что и этот скрипт Вам прислал, то просто добавляем его себе в проект (MPLAB IDE поддерживает файлы с расширением «gld»), запускаем процесс сборки проекта и на выходе получаем корректный файл прошивки с уже встроенным кодом загрузчика.
Но что делать, если Дедушка забыл про это скрипт или может быть именно Вы и являетесь тем, кто сделал этот загрузчик и Вам надо встроить его в свой/чужой проект? Читаем дальше…
Подготовка скрипта компоновщика
Собственно не стоит писать скрипт с нуля. Достаточно чуть-чуть переделать скрипт, который устанавливается в комплекте с компилятором. Лежит этот скрипт в папке «${ToolChainPath}\support\dsPIC33F\gld\». Примечание: здесь и далее ${ToolChainPath} – это путь, куда был установлен компилятор С30, по умолчанию это – «c:\Program Files\Microchip\MPLAB C30\». Скопируем оттуда скрипт по умолчанию для нашего устройства, например для МК dsPIC33FJ128GP802 это будет файл «p33FJ128GP802.gld».
Первым делом надо вписать два символа, описывающих начало области загрузчика и основной программы. Например, так:
_Booter = 0x000400;
_mainFW = 0x002000;
Далее в структуре MEMORY {…} в поле program указать начальную позицию (origin) и длину (length), соответствующие началу загрузчика и размеру flash-памяти минус начало загрузчика. Примерно так:
program (xr) : ORIGIN = 0x400,LENGTH = (0x15800 - 0x400)
Следующим шагом будет корректировка вектора сброса. В структуре SECTIONS {…} найдем описание «Reset Instruction». Необходимо чтобы она выглядела следующим образом:
.reset :
{
SHORT(ABSOLUTE(_Booter));
SHORT(0x04);
SHORT((ABSOLUTE(_Booter) >> 16) & 0x7F);
SHORT(0);
} >reset
Осталось только добавить описание зоны загрузчика. Зона описывается в структуре SECTIONS {…}. Это описание необходимо вставить перед описанием зоны «.text». Описание следующее:
.boot _Booter :
{
*(.booter);
. = _mainFW - _Booter;
} >program = 0xFFFF
Итак, скрипт готов.
Создание загрузчика
Сделать загрузчик из программы
Первое, что хотелось бы отметить: загрузчик не должен быть самостоятельной программой. Конечно, в процессе отладки загрузчика его можно реализовать как самостоятельную программу. Но как только Вы планируете его встроить в другую программу его необходимо специально подготовить.
Итак, чего лишается программа, превращаясь в загрузчик:
- Описание конфигурационных бит
- Векторов прерываний
- Вектора сброса
Кроме этого, невозможно корректно встроить константы загрузчика, хранимые во flash-памяти, в код основной программы. Поэтому от них тоже придется отказаться. Примечание: на самом деле способ есть, но он настолько нетривиален, что для массового использования проще отказать от констант в загрузчике.
Доработка исходных текстов
Доработка несложная. Убираем все макросы, описывающие конфигурационные биты. Исключаем использование глобальных констант.
Настройка проекта
Так же необходимо проверить и, при необходимости, скорректировать настройки проекта. Все изменения – во вкладке «MPLAB LINK30», категория «General». Установить чек-боксы: don’t pack data template; don’t create hanldes; don’t create default ISR; remove unused sections.
Доработка скрипта компоновщика
Так же как и для основной программы с загрузчиком скрипт будет отличным от скрипта по умолчанию. Итак, берем скрипт по умолчанию и вносим следующие изменения.
Структуру MEMORY {…} уменьшаем до двух позиций: data и program. Причем начало и длина program соответствуют началу и длине области загрузчика:
{
data (a!xr) : ORIGIN = 0x800, LENGTH = 0x4000
program (xr) : ORIGIN = 0x400, LENGTH = 0x1C00
}
Удаляем полностью описание «Reset Instruction» в структуре SECTIONS {…}. В этой же структуре удаляем описание «Configuration Words». Полностью удаляем структуру SECTIONS {…}, которая описывает вектора прерываний (метка «Section Map for Interrupt Vector Tables»).
В структуре SECTIONS {…} дорабатываем описание зоны «.text», заменив название зоны на «.booter» и приведя ее к следующему виду:
.booter 0x400 :
{
*(.init);
*(.user_init);
*(.handle);
*(.libc) *(.libm) *(.libdsp); /* keep together in this order */
*(.lib*);
*(.dinit);
*(.text);
} >program
Естественно, полученный скрипт надо добавить в проект.
Постобработка выходного файла
После осуществления предыдущих действий можно запустить процесс компиляции. В выводе процесса сборки (для MPLAB IDE это будет в окне Output, вкладка Build) можно увидеть результат компоновки. Например, так:
Program Memory [Origin = 0x400, Length = 0x1c00]
section address length (PC units) length (bytes) (dec)
------- ------- ----------------- --------------------
.booter 0x400 0x7d0 0xbb8 (3000)
Total program memory used (bytes): 0xbb8 (3000) 27%
Data Memory [Origin = 0x800, Length = 0x4000]
section address alignment gaps total length (dec)
------- ------- -------------- -------------------
.nbss 0x800 0 0xa2c (2604)
bootdata 0x47c0 0 0x40 (64)
Total data memory used (bytes): 0xa6c (2668) 16%
Если в program memory больше одной секции – то скорей всего вы не до конца выполнили действия описанные выше. Если там именно одна секция с названием «.booter» — то все сделано правильно.
Также надо обратить внимание на количество секций в data memory.
Теперь надо выполнить постобработку выходного файла. Постобработка проводиться с файлом с расширением «cof». Открываем командную строку в папке с этим файлом. Допустим файл имеет имя ultraboot.cof, тогда выполним команду:
"${ToolChainPath}\bin\pic30-strip.exe" -s --remove-section=.nbss --remove-section=bootdata -o ultraboot.blob ultraboot.cof
Не забываем ${ToolChainPath} заменять на реальный путь. Количество опций «--remove-section=…» должно соответствовать количество секций в data memory (из вывода результата работы компоновщика).
Далее надо провести финальную проверку полученного бинарного файла с загрузчиком. Команда:
"${ToolChainPath}\bin\pic30-objdump.exe" -ht ultraboot.blob
Вывод будет примерно следующим:
ultraboot.blob: file format coff-pic30
Sections:
Idx Name Size VMA LMA File off Algn
0 .booter 000007d0 00000400 00000400 00000058 2**1
CONTENTS, ALLOC, LOAD, CODE
SYMBOL TABLE:
no symbols
Если вы увидите, что секций ровно одна — с названием «.booter» и в символьной таблице нет символов, то можно считать, что все сделано корректно.
И в конце ссылки на примеры файлов для линкера:
- для основного проекта с загрузчиком — drive.google.com/file/d/0B063O4zepkwsNDlCMkJ1S1ZxaE0/view?usp=sharing
- для загрузчика — drive.google.com/file/d/0B063O4zepkwsZU9Nck42cVoyWDA/view?usp=sharing
Комментарии (12)
VT100
23.06.2016 22:28Эти утверждения, как минимум, — спорные:
[quote]Это вполне приемлемо для опытных образцов изделий или для мелкосерийного производства. А что делать, если производство крупносерийное? Или сборка проводится автоматами (а может людьми с функционалом автоматов) — прошил, впаял? Тогда разумно убрать из алгоритма 3го пункт — объединить в одной прошивке и основную программу и загрузчик.[/quote]
В начале этапа «In circuit test» полностью автоматически заливается ПО и всё заверте… Вариантов это сделать — масса. Начиная от прошивки через JTAG, через «bootloader» через JTAG — остальное через «bootloader» и высокоскоростной интерфейс, до использования «default bootloader» от производителя микроконтроллера.
[quote]Первое, что хотелось бы отметить: загрузчик не должен быть самостоятельной программой. Конечно, в процессе отладки загрузчика его можно реализовать как самостоятельную программу. Но как только Вы планируете его встроить в другую программу его необходимо специально подготовить.[/quote]
Каковы Ваши доказательства? Почему не «программу надо специально подготовить для работы с „bootloader'ом“?jinrou85
24.06.2016 06:38Еще раз обращу Ваше внимание, что цель следующая — вынул МК из упаковки, сунул в программатор, прошил один раз, впаял в плату, все работает. Нужно избежать припаивания дополнительного разъема для внутрисхемного программирования (JTAG, SWD например) или дополнительных шагов по загрузки прошивки через bootloader.
Каковы Ваши доказательства? Почему не «программу надо специально подготовить для работы с „bootloader'ом“?
Все просто: в МК не может быть больше одного набора конфигурационных бит и векторов прерываний. Естественно, что приоритет у основной программы.VT100
24.06.2016 21:38На первый абзац уже дали достаточно развёрнутые ответы. Разъёмы (хотя-бы в виде контрактных площадок и иголок) были, есть и будут. Эти правила хорошего тона называются «Design for manufacturing» и «Design for testability»
jinrou85
25.06.2016 19:22При чем здесь контактные площадки для отладочного разъема и предмет статьи? Если нечего сказать — так и не говорите.
Kolyuchkin
Чем Вам U-Boot не нравится?
jinrou85
Во-первых, насколько мне известно, U-Boot не поддерживает dsPIC.
А во-вторых, вопрос был не в том какой загрузчик брать, а о том, как этот загрузчик скомпоновать в одной прошивке с основным проектом.
Kolyuchkin
Про Линукс тоже не сказано, что он поддерживает ту или иную программно-аппаратную архитектуру, в которую его портируют «умелые руки»)) На мой взгляд, у Вас неправильный подход к разработке… Во-первых, U-Boot — загрузчик с открытыми исходниками и модульный — позволяет портировать нужные компоненты под любой МК (все зависит от квалификации разработчика). Во-вторых, на моей практике не встречался еще аппаратно-программный проект, в котором отсутствовала бы фаза тестирования-отладки-настройки и железа и ПО — на этом этапе приходится часто перепрошивать МК и перекраивать железо. В процессе же уже серийного производства загрузчик прошивается на «железном» этапе и с помощью его происходит сначало загрузка технологического и тестового ПО для проверки аппаратной части и сдаче ОТК, а уж потом шьется боевое ПО.
jinrou85
При чем здесь Linux и архитектура МК? На мой взгляд, у Вас недостаточно опыта работы на серьезных предприятиях. Особенно если судить по нижеследующему комментарию.
Процесс разработки изделия и серийное производство — два кардинально различающихся этапа в производственном цикле. Естественно, что на этапе разработки собираются и проверяются десятки прошивок. Но когда изделие уходит в серию интересна только одна — та которая является финалом разработки. Если для прохождения ОТК требуется особое тестовое ПО — то это просто немыслимая глупость. Нормальное производство, выпуская изделие, собирает изделие с финальной прошивкой, а затем проводит «техпрогон» и другие проверки предписанные в технической документации, созданной разработчиками. Если это не так, то как минимум это не соответствует международным нормам, включая пресловутый ISO 9001.
Kolyuchkin
Кажется мне, что это Вы как раз не разрабатывали и не запускали в серию ни одного изделия. На серийном производстве из каждой партии выбирают определенное кодличество изделий (или все, если изделие «серьезное») и «прогоняют» их по ТУ (Технические Условия, если не в курсе). Так вот чтобы эти проверки (в данном случае затрагивающие «специальные свойства» изделия и его аппаратную часть) осуществить, зачастую требуется перевести изделие в «необычное» состояние, для этого и необходимы и технологическое оборудование и технологические прошивки. А еще чаще бывает, что Заказчик даже заводу-изготовителю не предоставляет реальных прошивок требуемых алгоритмов, требуя, чтобы они вводились на месте эксплуатации. Так то.
jinrou85
Это все справедливо только для промежуточных изделий-полуфабрикатов. Например, у завода заказывается только собранная плата, а заказчик монтирует ее и заливает ПО. В таком случае, возможно, на заводе и сделают какую-то тестовую прошивку.
Но изготовление изделий-полуфабрикатов — это не серьезное создание конструктивно законченного изделия. И уж точно это никак не связано с темой публикации.
Kolyuchkin
С Вами все ясно… Оставайтесь при своем мнении… Вы, наверное, каждый день клепаете изделия для ВПК и «регуляторов»…
Kolyuchkin
Тогда у меня возникает следующий вопрос: «Знакомы ли Вы с процессом разработки аппаратно-программных комплексов — от макетирования до серийного производства?» В моем опыте еще ни разу не встречалось ни одно устройство, которое бы не проходило этапы отладки-тестирования-корректировки. Поэтому наличие отладочных и технологических интерфейсов просто необходимо, чтобы те же тестеры, настройщики и программисты не проклинали Вас))) А раз так, то использование проверенного и гибкого в настройках загрузчика — это еще одна необходимость. Есть исходники U-Boot, так вот гораздо проще портировать нужный Вам его функционал, чем писать свой «велосипед» с присущими «велосипедными багами». Еще U-Boot спроектирован так, что позволяет легко менять (обновлять) Вашу прошивку на уже работающем (проданном, находящимся у Заказчика) оборудовании (конечно, если вы предусмотрели это «правило хорошего тона»). Еще хочется добавить, что даже в серийном производстве (приемо-сдаточные испытания все прошли, Ваше изделие получило литеру «О1») есть этапы промпежуточного контроля, на которых в устройство зашивается тестовое (технологическое) ПО, чтобы, например, получить печать ОТК.