RISC-V и его ассемблер, часть 1
Содержание
Чтобы скомпилировать компилятор, нужен компилятор
Те кто подписан на мой канал в Телеграме знают, что в начале года я решил приобщиться к миру встраиваемой электроники и приобрёл набор разработчика на базе микроконтроллера Мик32 Амур от Микрона. Тогда я сделал только первые шаги: настроил расширение Platform IO в Visual Studio Code и опробовал плату на самой примитивной программе мигания светодиодом. Но если уж начал погружаться в мир разработки электроники, то нужно спускаться на всю глубину. Ведь всё самое интересное там, куда мало кто заходит! В этом небольшом цикле статей я расскажу об изучении ассемблера Risc-V в контексте его применения на отечественных микроконтроллерах. Итак, что нам нужно, чтобы научить микроконтроллер делать что-то полезное? Ну, помимо кода программы, конечно. Прежде всего, необходимы компилятор с поддержкой нужной архитектуры и утилита прошивки через серийный порт. Также нам понадобится подходящая плата с микроконтроллером и программатор, чтобы её прошивать. Конечно, прошивать можно и через встроенный программатор, т.к. платы разработки чаще всего поддерживают такую функциональность. Но моя глобальная цель - научиться использовать как можно более полный инструментарий разработчика, в том числе и интерфейс отладки JTAG.
Установка всего необходимого
Что ж, прежде чем начать писать код, нужно подготовить всё необходимое. Утилиту OpenOCD можно установить прямо из репозитория, т.к. она универсальная и не зависит от целевой архитектуры:
А вот тулчейн не подойдёт - в репозитории он собран для линуксового таргета, а не для embedded. Тулчейн берём из репозитория xPack Binary Tools. Это тот самый тулчейн, который используется под капотом Platform IO, так что нет смысла выдумывать что-то другое. Скачиваем свежайший релиз под свою платформу и распаковываем в удобное место. Я буду использовать путь $HOME/Developer/Tools/riscv64
. Теперь нужно загрузить всё необходимое для работы с МК Мик32 Амур. Создаём директорию mik32
и в неё клонируем набор для сборки приложений. В эту же директорию скачиваем утилиту заливки прошивки mik32-uploader
из официального репозитория. В итоге должна получиться вот такая структура директорий:
Для удобства дальнейшей работы я создал симлинк toolchain
на директорию с gcc, чтобы не писать каждый раз так много букв. Кроме того, я завёл новую переменную окружения в .zshrc
:
В целом этого уже достаточно для сборки и прошивки программ для МК. Для более удобной работы скорее всего понадобятся дополнительные библиотеки HAL (Hardware Abstraction Layer), чтобы не работать напрямую с регистрами МК. Для МК Амур их можно взять в официальном репозитории, но мы же хотим писать на ассемблере, так что HAL нам ничем не поможет.
Пробуем собрать базовый пример
Но прежде чем перейти к интересному, нужно проверить, что наш сборочный тулчейн вообще работает. Для этого возьмём базовую программу мигания светодиодом из руководства по запуску платы Elbear. В своей рабочей директории проектов создаём новую директорию Blink
с файлом blink.c
внутри, а в него помещаем код примера:
// Вывод, к которому подключен светодиод - PORT_2_7
void
void
int
Теперь попробуем этот код скомпилировать. Для этого нам потребуется указать дополнительные параметры компиляции:
- указать архитектуру процессора и его настройки;
- перечислить пути к директориям с заголовочными файлами;
- указать дополнительные параметры линковки, чтобы учесть специфику адресного пространства МК;
- добавить стартовый код инициализации МК из SDK – находится в
mik32v2-shared/runtime/crt0.S
. В итоге, чтобы не писать это всё вручную каждый раз, получился вот такой скрипт:
#!/bin/bash
if ; then
fi
FILENAME="firmware.elf"
OPTS="-march=rv32imc_zicsr_zifencei -mabi=ilp32 -mcmodel=medlow -std=gnu11 -Os -s \
-flto -fsigned-char -ffunction-sections -fdata-sections -fstrict-volatile-bitfields \
-fno-strict-aliasing -fno-common -fno-builtin-printf -nostartfiles -ffreestanding"
DEFINES="-DMIK32V2 -DOSC_SYSTEM_VALUE=32000000L"
INCLUDES="-I /mik32/mik32v2-shared/include \
-I\
/mik32/mik32v2-shared/periphery -I\
/mik32/mik32v2-shared/libs -I" /mik32/mik32v2-shared/runtime
LIBS="-L /mik32/mik32v2-shared/ldscripts"
WARNINGS="-Wall -Wextra"
CC=" /toolchain/bin/riscv-none-elf-gcc \
-Wl,-T,\
/mik32/mik32v2-shared/ldscripts/eeprom.ld,-Map, .map,-gc-sections,--print-memory-usage -lc \
/mik32/mik32v2-shared/runtime/crt0.S " /mik32/mik32v2-shared/libs/*.c
if [; then
fi
OBJ=" /toolchain/bin/riscv-none-elf-objcopy"
Этот скрипт я положил в директорию $RISCV_TOOLS_PATH/mik32
и назвал mik32-gcc
, а также сделал симлинк в директорию ~/.local/bin
, чтобы иметь возможность вызывать его из любого места. Теперь можно перейти в директорию нашего тестового проекта, в которой находится файл blink.c
. Чтобы не захламлять директорию проекта, создаём в ней сборочную директорию, например build
и переходим в неё. Для сборки прошивки вызываем нашу утилиту:
Если всё было настроено правильно, то в нашей сборочной директории появятся следующие файлы:
firmware.bin
firmware.elf
firmware.elf.map
firmware.hex
Теперь можно попробовать залить нашу прошивку на плату!
Медведь снова мигает огоньком
Для загрузки нашей прошивки на плату нужны всего два ингедиента (помимо самой прошивки): утилита OpenOCD и скрипт загрузки от Микрона. Команда загрузки прошивки тоже довольно длинная и содержит множество аргументов, так что удобнее будет использовать следующий скрипт:
#!/bin/bash
UPLOADER=
/mik32/mik32-uploader
Я точно так же создал симлинк ~/.local/bin/mik32-upload
и теперь прошивку можно выполнить из директории build
простой командой:
Главное не забыть подключить плату через программатор к компьютеру. Тогда в случае успеха мы увидим следующий вывод:
mik32-uploader-v0.3.3
Using MIK32V2
Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : set servers polling period to 200ms
Info : clock speed 500 kHz
Info : JTAG tap: riscv.cpu tap/device found: 0xdeb11001 (mfg: 0x000 (<invalid>), part: 0xeb11, ver: 0xd)
Info : JTAG tap: riscv.sys tap/device found: 0xfffffffe (mfg: 0x7ff (<invalid>), part: 0xffff, ver: 0xf)
Info : datacount=2 progbufsize=6
Info : Examined RISC-V core; found 1 harts
Info : hart 0: XLEN=32, misa=0x40001104
Info : starting gdb server for riscv.cpu on 3333
Info : Listening on port 3333 for gdb connections
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : accepting 'tcl' connection on tcp/6666
Clock init... OK!
MCU clock init...
Uploading driver... OK!
Uploading data... OK!
Run driver...
EEPROM writing successfully completed!
[14:19:26] Wrote 896 bytes in 0.25 seconds (effective 3.5 kbyte/s)
В случае ошибок они будут выведены в консоль, также дополнительно можно посмотреть лог OpeOCD в файле openocd.log
. Если же всё прошло хорошо, то мы увидим как плата Elbear замигала зеленым светодиодом! Теперь настройку сборочного тулчейна для микроконтроллера Амур можно считать законченой. Однако, всё самое интересное только начинается, ведь изначальный план был - научиться мигать светодиодом на ассемблере! Но об этом я расскажу в следующей части.