Во время работы над проектом 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-проекта
Для "кода" было выделено 16 Кб с адреса ROM 0000H
В ОЗУ, на всякий случай, я зарезервировал 16 Кб, остальные 192 минус 16 Кб - под переменные и исполняемый вRAM код
В этом файле ( 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 .
Настройка xxxxcore-проекта
Для чего два ядра ( две отдельно обновляемые прошивки ) в одном проекте ? Для удобства процессов разработки и отладки.
Servcore и cashcore связаны друг с другом очень "плотно", функции ядер вызываются друг из друга, общие переменные и так далее. Всё это удобнее разрабатывать и отлаживать в едином проекте.
Главное - "развести" ядра по разным, непересекающимся частям памяти.
Первое ядро ( servcore ) располагается с адреса 4000H, ему отведено место вплоть до 7FFFFH ( вдруг ядро разрастётся ;-).
Второе ядро ( cashcore ) - с адреса 80000H ( 512 Кб ), размер вплоть до 512 Кб.
RAM - общая, под оба "ядра" забираем всю эту память под проект.
"Ядра" должны быть реализованы так, чтобы адресация ( вызов функций ) происходила либо только внутри этого ядра, либо по заранее определённому адресу, который гарантированно адресует необходимую часть кода, как это сделать - читайте 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 - проект.
Остальные цифры, я думаю, понятны из предыдущих разъяснений.
Как сделать обновляемую прошивку для ARM в Keil μVision 5. Часть 1
Прошивка исполняемого кода в разные области памяти
Микроконтроллеры