Путешествие Embox на AMUR
Всем привет.
Embox продолжает своё путешествие по отечественным микропроцессорам. В этой серии мы опишем путешествие на AMUR (MIK32). Кто не знает, это — полностью отечественный микроконтроллер К1948ВК018 на архитектуре RISC-V, который производится в РФ на мощностях компании МИКРОН, входящей в ГК «Элемент».
На хабре уже есть статья про данный МК, на плате ELBEAR ACE-UNO от ELRON, в которой, как следует из названия, для запуска демо проекта (помигать светодиодами) потребовалось 2 недели. На самом деле мы потратили куда больше времени, так как нам было необходимо перенести Embox на данный МК, а значит пришлось разбираться с неочевидными вещами (о которых мы также расскажем в этой статье), но в результате помигать светодиодами можно за пару часов. Сразу прикреплю видео и инструкцию, как воспроизвести эксперимент. Единственное, мы использовали плату не от компании ELBEAR, а ту, которую компании МИКРОН нам любезно предоставила.
Начнем с короткого видео с результатами
И короткой инструкцией для воспроизведения
Короткая английская версия у нас на вики (https://github.com/embox/embox/wiki/mikron_mik32_dev)
Устанавливаем окружение Embox как обычно (для RISC-V):
Скачиваем прошивальщик от МИКРОН здесь (https://github.com/MikronMIK32/mik32-uploader):
git clone git@github.com:MikronMIK32/mik32-uploader.git
Собираем темплейт под плату:
make confload-platform/mikron/mik32; make
Запускаем терминал на устройстве /dev/ttyUSB0 (отключаем контроль потока) с частотой 115200. Например, minicom (и в настройках отключаем контроль потока)
sudo minicom -b 115200 -D /dev/ttyUSB0
Прошиваем плату
./platform/mikron/scripts/uploader_run.sh
OPENOCD — openocd,
где
MIK32-UPLOADER_DIR — папка, куда скачали mik32-uploader.
В консоли можно набирать команды. В результате, что то такое должно получиться:
Управляем светодиодами
Как видно из скриншота мы уже добавили команду pin, которая позволяет управлять внешними выводами.
На нашей плате два светодиода:
Для обоих светодиодов активный уровень низкий (они подключены по схеме PULLUP).
Таким образом, чтобы зажечь красный, нужно выполнить команду: pin GPIOA 10 reset
для того, чтобы его погасить:
pin GPIOA 10 set
Для зеленого светодиода нужно сделать то же самое, но указать номер пина 9.
Портирование на плату
Портирование на плату (STM32) описано в статье https://habr.com/ru/articles/776712/
В принципе, здесь те же пункты, как и в STM, но добавляется архитектурная часть и драйвера (для STM они разработаны) :
Архитектурная часть (загрузочный код, первичные обработчики прерываний и исключений и так далее).
Платформенная часть (настройка клоков, включение базовых модулей и так далее).
Контроллер прерываний.
Таймер — без него не будет времени и вытесняющей многозадачности.
Драйвер последовательного порта.
Описание устройств (device tree).
Драйвер GPIO.
Конфигурация системы (темплейт).
Архитектурная часть
Вообще архитектурная часть для RISC-V у нас уже имелась и была отлажена на нескольких платформах. Но оказалось, что есть несколько неочевидных нюансов, на которые нам пришлось потратить время и добавить соответствующие патчи (fixups).
Первый фиксап, который нам потребовался, это определение номера ядра, на котором выполняется код. Мы анализируем его при загрузке в boot.S, и если ядро не нулевое, то передаем управление в другую ветку. Делаем мы это в режиме machine, вычитывая mhartid регистр
csrr t0, mhartid
В нашем случае мы просто отрезали передачу управления вне зависимости от значения mhartid:
#if !OPTION_GET(BOOLEAN,mik32_fixup)
bnez t0, secondary_hart_loop
#endif /* !OPTION_GET(BOOLEAN,mik32_fixup) */
А вот второй фиксап был куда более серьезный. Он касается указания базового адреса вектора прерываний (регистр TRAP_VECTOR_REG). Как объяснили в компании МИКРОН, нижний байт этого регистра зафиксирован в 0xC0. Поэтому нам пришлось «шаманить» следующим образом (Подсмотрели в их коде примеров MikronIDE):
/**
* Set up mtvec (Machine Trap Vector). Whenever an exception or
* interrupt occurs, the CPU will "call" the function given by this
* register.
*/
la t0, riscv_trap_handler
#if OPTION_GET(BOOLEAN,mik32_fixup)
la t0, _start
#endif
csrw TRAP_VECTOR_REG, t0
То есть если указано, что нужны fixups для MIK32, то после адреса таблицы загружается адрес начала программы (ROM) и уже он заносится в регистр.
Еще пришлось добавить смещение к нашему «вектору прерывания» 0xC0, для этого внесли такой код:
#if OPTION_GET(BOOLEAN,mik32_fixup)
.section .mik32_fixup, "ax"
.globl _mik32_fixup_interrupt
//.org _start + 0xC0
.org 0x40
_mik32_fixup_interrupt:
.cfi_startproc
j riscv_trap_handler
.cfi_endproc
#endif
А в наш линкер скрипт для RISC-V добавили две строчки:
. = ALIGN(0x80); /* FIXUP for mik32 */
KEEP(*(.mik32_fixup));
Кроме того, если вы обратили внимание, то код вектора прерываний содержит простой прыжок на оригинальный вектор прерываний:
_mik32_fixup_interrupt:
.cfi_startproc
j riscv_trap_handler
.cfi_endproc
#endif
То есть, для MIK32 мы не напрямую указываем адрес обработчика, а используем слово, в котором стоит прыжок на первичный обработчик исключений.
Может в будущем найдем какое то более изящное решение. Но пока так.
В довершение добавлю строчки конфигурацию для архитектурной части системы (файл mods.conf):
include embox.arch.system(core_freq=32000000)
include embox.arch.riscv.kernel.boot(mik32_fixup=true)
include embox.arch.riscv.kernel.cpu_idle
include embox.arch.riscv.kernel.locore
include embox.arch.riscv.kernel.interrupt
include embox.arch.riscv.kernel.context
include embox.arch.riscv.libarch
По сути все как обычно для RISC-V, но мы указываем, что нужно использовать mik32_fixups.
Платформенная часть
Платформенная часть у нас состоит из трех следующих: init, idle, shutdown. На данный момент для платы MIK32, мы переопределили две — init и shutdown:
package platform.mikron.mik32
module platform_init extends platform.platform_init {
source "platform_init.c"
@NoRuntime depends embox.driver.clk.mikron.mikron_pm
@NoRuntime depends platform.mikron.drivers.mikron_wakeup
}
module platform_shutdown extends platform.platform_shutdown {
source "platform_shutdown.c"
}
Код init содержит настройку питания и клоков:
void platform_init() {
mikron_wakeup_init(PCC_OSCILLATORTYPE_OSC32M | PCC_OSCILLATORTYPE_OSC32K);
mikron_pm_init();
}
Модуль wakeup содержит работу с клоками. В нашем случае на плате есть внешний 32MHz кварц и внутренняя 32kHz RC цепочка.
Модуль PM (power management) управляет питанием. Сейчас мы просто включаем там все наши блоки, в будущем хотим сделать возможным управление питанием по имени блока.
Сейчас код в основном заимствован из примеров Mikron_IDE и не очень оптимальный для embox. В будущем доработаем эти части. В том числе, например, для включения (отключения) тактирования отдельных модулей.
Контроллер прерываний
Контроллер прерываний часто входит в спецификацию архитектуры. В частности, в RISC-V есть PLIC, CLINT и так далее. Но в данном микроконтроллере используется собственный контроллер EPIC.
Не буду вставлять код, кому интересно — можно посмотреть драйвер: https://github.com/embox/embox/tree/master/src/drivers/interrupt/mikron/epic.
Таймер
Обычно таймер для RISC-V используется из состава CLINT, но в MIK32 добавлены регистры конфигурации. Поэтому мы также добавили отдельный драйвер. Также не буду прикреплять исходники, только ссылку на драйвер: https://github.com/embox/embox/tree/master/src/drivers/clock/mikron_clk.
Драйвер последовательного порта
Тут тоже ничего особенного, код драйвера: https://github.com/embox/embox/tree/master/src/drivers/serial/mikron_usart.
Разве что лишний раз отмечу, что в embox можно создавать устройства (ttyS0 ttyS1), что бывает очень удобно, а также можно использовать описание устройств (аналог device tree из Линукс), который мы на данный момент активно развиваем.
Драйвер GPIO
У embox достаточно функциональные драйвера, в частности, драйвер GPIO может устанавливать разные режимы отдельных контактов, устанавливать и считывать значение с них, а также можно устанавливать обработчики прерываний. На текущий момент реализованы все функции, но не проверена установка прерываний. Это задача на будущее. Зато уже сейчас установка режима соответствующих выводов делается в драйвере UART, ну, а мигание светодиодом — из командной строки, как было показано выше.
Особых нюансов тут нет, поэтому просто прикрепляю ссылку на драйвер (который будет улучшаться в будущем): https://github.com/embox/embox/tree/master/src/drivers/gpio/mikron.
Описание устройств
Описание устройств (аналог device tree) мы добавили относительно недавно. Данная технология еще достаточно сырая, но уже показала себя очень удобным способом описания. Поэтому мы продолжаем ее развивать.
Она как-то была описана в статье про STM (https://habr.com/ru/articles/830156). Здесь тоже приведу в пример некоторые части, чтобы показать направление развития.
Описание клоков:
struct clk_conf clks[] = {
[0] = {
.status = ENABLED,
.dev = {
.name = "PM",
.regs = {
REGMAP("BASE", (PM_BASE_ADDRESS), 0x100),
},
.clocks = {
VAL("CLK_VAL", 32000000UL),
}
},
.type = VAL("SYSCLK_PLL", 1),
},
[1] = {
.status = ENABLED,
.dev = {
.name = "WU",
.regs = {
REGMAP("BASE", (WU_BASE_ADDRESS), 0x100),
},
.clocks = {
VAL("HSECLK_VAL", 3200000UL),
},
},
},
};
UART0
struct uart_conf uarts[] = {
[0] = {
.status = ENABLED,
.name = "UART0",
.dev = {
.name = "UART0",
.regs = {
REGMAP("BASE_ADDR", (UART_0_BASE_ADDRESS), 0x100),
},
.irqs = {
VAL("", EPIC_UART_0_INDEX),
},
.pins = {
PIN("TX", GPIO_PORT_A, 5, 1),
PIN("RX", GPIO_PORT_A, 6, 1),
},
.clocks = {
VAL("", "CLK_UART0"),
}
},
.baudrate = 115200,
},
И ссылка на файл описания: https://github.com/embox/embox/blob/master/board_config/mikron_mik32.conf.h.
Конфигурация системы (template)
Как обычно, чтобы все это собрать вместе, нам нужен темплейт. Мы подготовили два темплейта (mik32 и mik32_tish). Второй с более продвинутым шелом и даже с синхронизацией потоков. Но в остальном они идентичны и содержат следующие команды:
include embox.cmd.help
include embox.cmd.sys.version
include embox.cmd.sys.uname
include embox.cmd.cpuinfo
include embox.cmd.testing.ticker
include embox.cmd.hardware.pin
Сконфигурировать соответственно командами:
make confload-platform/mikron/mik32
и
make confload-platform/mikron/mik32_tish
и затем:
make
Вот ссылка, если хочется посмотреть содержание темплейтов подробнее: https://github.com/embox/embox/tree/master/platform/mikron/templates.
Отладка
При отладке мы столкнулись с парой проблем, которые хотим «подсветить».
Во первых, используется недоработанный openocd. То есть, нельзя скачать openocd, собрать его, приконектиться и сделать прошивку из gdb командой load. По тем же причинам нужно использовать скрипты описания платы, они есть либо в Mikron_IDE, либо в прошивальщике от МИКРОН: https://github.com/MikronMIK32/mik32-uploader.
Во вторых, для установки точки останова нужно использовать команду hbreak (hb) вместо команды break (b). И этих точек всего две.
Заключение
В заключении хочется сказать, что хотя данный МК и не является мощным, но очень радует тот факт, что появляются отечественные решения полного цикла. Таким образом можно развивать все составляющие, в том числе фабрики чипов. Ну и конечно экосистему ПО. Поскольку одного процессора, каким бы он хорошим ни был, недостаточно. Нужно, чтобы на базе этого процессора можно было создавать конечные изделия. И Embox как раз в этом очень сильно помогает.
Полезные ссылки
Документация : https://mikron.ru/products/mikrokontrollery/mk32-amur/#!/tab/672102497–3
МИКРОН на гитхаб: https://github.com/MikronMIK32
Uploader: https://github.com/MikronMIK32/mik32-uploader
HAL с примерами драйверов: https://github.com/MikronMIK32/mik32-hal/
Форум: https://forum.mik32.ru/index.php
Страница тех поддержки: https://mikron.ru/products/mikrokontrollery/mk32-amur/#!/tab/672102497–5
P. S.
P.S. Хотим выразить благодарность отделу техподдержки компании Микрон и лично Александру Квашину.
P.P. S. Мы будем выступать в Питере 27–28 сентября на конференции «СТАЧКА 24»: https://spb24.nastachku.ru/.
Планируем принести и показать несколько наших демок, в том числе данную плату.