Как сделать обновляемую прошивку для ARM в Keil μVision 5. Часть 1

Прошивка исполняемого кода в разные области памяти

Во время работы над проектом iCash я столкнулся со следующей проблемой : устройство надо отдать на сертификацию ( в две компании ), но там могут быть обнаружены ошибки в софте, которые надо будет исправлять. Плюс к этому, сертифицировать необходимо только часть прошивки (кассовое ядро) , и именно вот эту часть позднее ( после проверок ) надо закрепить и не обновлять, а обновлять другую, несертифицируемую ( сервисную ) часть прошивки.

Если не сделать онлайн-обновление обеих частей прошивки через интернет, то придётся :

1. Найти и исправить ошибки.

2. Поехать в сертификационные компании, забрать устройства.

3. Перепрошить эти устройства.

4. Отвезти устройства обратно.

И - по циклу. То есть, теоретически, если ошибки будут выявлять не все сразу, а постепенно - процесс сертификации существенно затянется. 

Решение простое : надо сделать, так чтобы разные части ( редко изменяемые, такие как фискальное ядро, и часто изменяемые, такие как дополнительные, сервисные функции ) исполняемого кода компилировались по разным адресам для того, чтобы их можно было перепрошивать независимо друг от друга и, желательно, удалённо ( про "удалённо" читайте в соответствующей статье про использование Ethernet (lwip) поверх USB ), через интернет.

Также необходимо помнить, что код "самопрошивания" должен запускаться из RAM. Значит часть кода, относящаяся, к процессу прошивки необходимо разместить в RAM, причем этот "RAM-код" не должен вызывать код из ROM, по крайней мере во время процесса обновления.

В моём случае, я разделил адресацию кода на три части :

- загрузчик ( boot ) - отдельный проект. Он должен считать из flash-memory команду на прошивку того или иного ядра. Если команды нет - передавать управление в сервисное ядро. Код для "самоперепрошивки" - должен вызываться в RAM, вместе со всеми вызываемыми из неё функциями.

- сервисное ядро - часть основного проекта,  для сервисных функций ( servcore  ) - предположительно часто изменяемая часть проекта.

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

Итак, в проекте кассы iCash мы использовали микроконтроллер K1921VK01T, поэтому у меня 1 Мбайт ROM и 192 Кбайта RAM. RTOS (или другие OS) не используем. Для проекта используется "чистый" Си, без плюсов.

Я решил адресовать части кода следующим образом :

- boot должен находится в первых 16 Килобайтах ROM ( адреса 0000H размером  4000H ), должен использовать RAM для вызова функций "прошивки", передавать управление (когда нет команд на прошивку) двум остальным частям прошивки.

- servcore должен находится c 4000H ROM. Оно должно уметь передавать управление в другое ядро, вызывая необходимые функции cashcore. Причём надо учитывать, что адресация функций ядра cashcore будет меняться. Любая версия servcore должна уметь взаимодействовать с любой версией cashcore, также и обратное тоже должно быть верным.

- cashcore располагается с адреса 80000H ( с половины мегабайта ). Оно должно уметь вызывать необходимые функции servcore, учитывая изменяемые адреса функций. Должно уметь использовать переменные другого ядра.

Подробнее о принятых решениях и способах реализации я напишу в следующей статье.

В этой же, опишу только реализованные мной способы привязки переменных и функций к физическим адресам памяти.
Где возможно, я использовал GUI Keil'a для настроек, в редактирование настроечных файлов "руками" лез только по необходимости, если без этого невозможно было обойтись.

Настройка boot-проекта

Odoo • A picture with a caption
Options для проекта boot

 Для "кода" было выделено 16 Кб с адреса ROM 0000H
В ОЗУ, на всякий случай, я зарезервировал 16 Кб, остальные 192 минус 16 Кб - под переменные и исполняемый вRAM код

Odoo • A picture with a caption
Options для файла boot_flash.c

В этом файле ( boot_flash.c ) находятся функции, которые перезаписывают ROM микроконтроллера.
Эти функции должны вызываться из RAM, поэтому в пункте "Code/Const" выбрана RAM, если было бы <default>, то код этого файла располагался  бы вместе с остальным кодом - в ROM .

Вместо настроек в GUI Keil можно было бы вручную заполнить scatter файл вот так :

                
; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x00000000 0x00004000 { ; load region size_region ER_IROM1 0x00000000 0x00004000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM1 0x20004000 0x0002C000 { ; RW data boot_flash.o (+RO) .ANY (+RW +ZI) } }

Erase Full Chip - означает, что перед прошивкой первых 16 Кб будет очищаться ВЕСЬ внутренний FLASH микроконтроллера.
Прошивка будет располагаться с адреса 0H размером 4000H .

Odoo • A picture with a caption
Настройка инструмента прошивки , находится на закладке "Debug" настроек проекта.

Настройка xxxxcore-проекта

Для чего два ядра ( две отдельно обновляемые прошивки ) в одном проекте ? Для удобства процессов разработки и отладки.

Servcore и cashcore связаны друг с другом очень "плотно", функции ядер вызываются друг из друга, общие переменные и так далее. Всё это удобнее разрабатывать и отлаживать в едином проекте.

Главное - "развести" ядра по разным, непересекающимся частям памяти.

Odoo • A picture with a caption
Настройки проекта с двумя прошивками.

Первое ядро ( servcore ) располагается с адреса 4000H, ему отведено место вплоть до 7FFFFH ( вдруг ядро разрастётся ;-).
Второе ядро ( cashcore ) - с адреса 80000H ( 512 Кб ), размер вплоть до 512 Кб.

RAM - общая, под оба "ядра" забираем всю эту память под проект.

Odoo • A picture with a caption
Options для одного из файлов, входящих в прошивку, располагаемую с адреса 80000H.

"Ядра" должны быть реализованы так, чтобы адресация ( вызов функций ) происходила либо только внутри этого ядра, либо по заранее определённому адресу, который гарантированно адресует необходимую часть кода, как это сделать - читайте 2-ую часть статьи. 

Ниже приведён scatter файл проекта ( там где  "....0" находятся другие файлы прошивки ядра cashcore, скрыл, соблюдая коммерческую тайну ;-)

  
; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x00004000 0x0007C000 { ; load region size_region ER_IROM1 0x00004000 0x0007C000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM1 0x20000000 0x00030000 { ; RW data .ANY (+RW +ZI) } } LR_IROM2 0x00080000 0x00080000 { ER_IROM2 0x00080000 0x00080000 { ; load address = execution address base64.o (+RO) ....o (+RO) ....o (+RO) ....o (+RO) ....o (+RO) ....o (+RO) ....o (+RO) ....o (+RO) ....o (+RO) ....o (+RO) } }

В этом проекте установлено не стирать ( Do not Erase ) перед прошиванием. Для того чтобы можно было зашить на чип сразу несколько проектов.
В данном случае, сначала прошиваем boot-проект, потом ..cores - проект.

Остальные цифры, я думаю, понятны из предыдущих разъяснений.

Odoo • A picture with a caption
Настройка инструмента прошивки J-Link для проекта ...cores 

В следующих статьях я опишу, как реализовать код boot-a и взаимодействие двух "отдельных" прошивок в рамках одного проекта.

Спасибо за внимание.