COKPOWEHEU

View on GitHub

Практическое применение RISC-V при программировании микроконтроллеров

(Оглавление)

1.1. Специфика микроконтроллеров.

Встраиваемая система (англ. embedded system) — специализированная микропроцессорная система управления, контроля и мониторинга, концепция разработки которой заключается в том, что такая система будет работать, будучи встроенной непосредственно в устройство, которым она управляет (определение из Википедии). Как следует из определения, такая система обычно не обладает “стандартными” средствами ввода-вывода - монитором, клавиатурой, мышкой. Вместо этого она взаимодействует с окружающей средой при помощи множества разнообразных датчиков, кнопочек, исполнительных механизмов и индикаторов. Например, встроенная в стиральную машину система может уметь управлять асинхронным мотором (несколько линий ШИМ, датчики Холла, измерители тока), подогревом воды (управление реле нагревателя, датчик температуры), оценивать массу заложенного белья (датчик нагрузки или ускорения), управлять водяным клапаном, позволять пользователю выбирать режим работы (“крутилка” энкодер, кнопки, знаковый дисплей). При этом важным является не умение проводить сложные расчеты, не умение запускать множество интерактивных программ одновременно, а именно взаимодействие с периферией и быстрая реакция на внешние события. Если в процессе работы двигатель по какой-то причине замедлился, надо срочно подстраивать управляющие сигналы, а то и останавливать его, если что-то пошло совсем не так. В ряде случаев роль играют такие параметры, как энерноэффективность и занимаемый объем. Поскольку подобные системы обычно предоставляются пользователю в виде законченного монолитного устройства, то и схемотехнику, и софт также обычно делают монолитными. Вместо кучи драйверов под все существующее оборудование, жестко прописывают только то, которое реально установлено на плате. Вместо возможности запуска сторонних программ со всеми защитами и разделением прав доступа, сразу закладывают весь возможный функционал без возможности расширения. В конце концов, кому придет в голову вскрывать корпус той же стиральной машины чтобы подпаять туда дисплей и любоваться графикой?! Хотя умельцы, конечно, находятся… Отсюда вытекает желание производителей уместить на одном чипе не только вычислительное ядро (процессор), но и память, и периферию.

Рассматривать будем микроконтроллер GD32VF103 (не путать GD32VF103 с GD32F103 - первый на ядре RISC-V, второй на ARM).

1.2. Железо

Такие контроллеры вполне доступны в свободной продаже как в виде отдельных микросхем, так и установленными в отладочные платы вроде Longan Nano. Стоит отметить, что корпус не слишком дружелюбен к радиолюбителям (планарный с шагом выводов 0.5 мм), хотя и поддается мастерам ЛУТа. Бывают и более противные корпуса вроде 200-ногих BGA, про которые здесь упоминать не стоит.

Преимуществом данного контроллера перед многими другими является то, что для его программирования можно обойтись вообще без программатора, поскольку с завода в нем встроен код, позволяющий обновлять прошивку через USB или UART. Чтобы перейти в этот режим надо на ножку BOOT0 подать лог.1, а на BOOT1 - лог.0, после чего перезагрузить контроллер. После прошивки надо вернуть BOOT0 в лог.0 и снова перезагрузить контроллер чтобы проверить работу прошитого кода.

Для прошивки через USB:

    $ dfu-util -a 0 -s 0x08000000 -D firmware.bin

Здесь firmware.bin - файл прошивки

Для прошивки через UART придется сначала соединить TX0 контроллера с RX переходника USB-UART (или COM-UART, или USB-TTL), вывод RX0 контроллера с TX переходника и, разумеется, земли. Подключать контроллер при этом к USB нельзя - иначе будет пытаться прошиться именно через него. Лучше подать питание с переходника.

    $ stm32flash /dev/ttyUSB0 -w firmware.bin

Здесь /dev/ttyUSB0 - имя переходника в системе

Теоретически, такие контроллеры можно программировать и при помощи специального программатора-отладчика через разъем JTAG, и это позволяет просматривать память, проходить программу по шагам, ставить точки останова и т.д. Вот только мне найти такой программатор не удалось. Управляется этот вариант программой openocd.

UPD: JTAG запустить все-таки удалось. Правда, для этого нужен специально патченный openocd.

1.3. Софт

Никакой экзотики, все программы ставятся из стандартного репозитория (кроме openocd, если найдете программатор. Вот его надо собирать из исходников, предоставленных GigaDevice, кстати, в них есть ошибки, на которые компилятор ругается…):

    gcc-riscv64-unknown-elf, make -- компиляция и сборка.
    dfu-util, stm32flash, openocd -- прошивка
    screen -- отладка через UART
    kicad или другая программа трассировки плат -- если возникнет желание делать отладочную плату, модули или финальное изделие самостоятельно

Еще, разумеется, текстовый редактор (или IDE), эмулятор терминала, калькулятор и прочие служебные программы по вкусу.

Компиляция, анализ и прошивка существующего ассемблерного файла:

    $ riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 -mcmodel=medany -nostdlib main.S -o firmware.elf
      -march - список расширений, поддерживаемых ядром:
       i - base interger instructions, ver.2.0 (в отличие от e, где версия 1.9)
       m - multiplication and division (integer)
       a - atomic instructions
       c - compressed instructions, возможность кодирования инструкций не только по 32 бита, но и по 16. Но физику не обманешь, и закодировать так можно далеко не все.
      -mabi - размеры типов данных для взаимодействия ОС с программами
      -mcmodel - рекомендация для компилятора Си как размещать код. Для medlow только в младших 2 ГБ памяти, medany - в любых 2 ГБ. Не уверен, что разница есть, но вдруг захотим исполнять код из ОЗУ...
      -nostdlib - не подключать стандартную библиотеку. Все равно без настроек она не заработает

При использовании компилятора из репозитория ALT Linux обнаружилось еще несколько флагов, обусловленных тем, что там нет отдельного компилятора для компьютера и отдельного для контроллера. Приходится указывать, что у нас нет операционной системы, нет динамического размещения кода и т.д.

Конвертация из обычного elf в чистый бинарный файл без отладочной и прочей информации

    $ riscv64-unknown-elf-objcopy -O binary firmware.elf firmware.bin

Дизассемблирование обычного elf файла. Позволяет проверить во что разворачиваются макросы, псевдооперации, адреса и конструкции языков высокого уровня

    $ riscv64-unknown-elf-objdump -D -S firmware.elf > firmware.lss

Проверка занимаемой памяти, чтобы оценить сколько процентов камня использовано

    $ riscv64-unknown-elf-size res/firmware.elf

Команды для прошивки были приведены ранее. Удобнее будет оформить эти команды в makefile.

При работе с UART, особенно если переходников несколько, бывает удобно дать им осмысленные имена, чтобы не по /dev/ttyUSB0, /dev/ttyACM50 обращаться, а хотя бы /dev/tty_ft232_0 - сразу понятно какой именно переходник используется. Для этого можно воспользоваться демоном udev. В каталог /etc/udev/rules.d добавляется файл с именем, начинающимся на 99-, 98- или еще что-то ближе к сотне (у меня это 98-usbserial.rules). В файле прописываются идентификаторы интересующего нас устройства и то, как мы хотим его назвать в системе:

    SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", PROGRAM="/bin/bash -c \"ls /dev | grep tty_ft232r_ | wc -l \"", SYMLINK+="tty_ft232r_%c"

Эта строчка описывает, что устройство должно иметь определенные idVendor и idProduct (их можно посмотреть в lsusb), и должно получить название /dev/tty_ft232r_0. Ноль на конце получается при выполнении кода, указанного в PROGRAM. Он считает сколько этих /dev/tty_ft232r_ уже в наличии, и какой номер выдать следующему. Аналогичные строчки можно прописать в том же файле для переходников других производителей.

Заодно допишу правило, подставляющее имя интерфейса (части составного устройства USB) для своих поделок (спасибо @intelfx с ЛОР):

    SUBSYSTEM=="tty", ATTRS{manufacturer}=="COKPOWEHEU" ENV{CONNECTED_vusb}="yes"
    ENV{CONNECTED_vusb}=="yes", SUBSYSTEM=="tty", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep tty_$attr{interface}_ | wc -l \"", SYMLINK+="tty_$attr{interface}_%c"

Таким образом, поиграться с таким контроллером может каждый. А если есть навыки работы с паяльником, то и не только поиграться, но и сделать на его основе что-нибудь полезное.

Кстати

GD32VF103 не единственный микроконтроллер на основе RISC-V. Я тут немного покопался и нашел К1986ВК025 от Миландра , МК32 АМУР от Микрона и здоровенное семейство, производимое китайской компанией WCH, например ch32v307 с довольно интересной начинкой. Периферия у них от GD’шки, разумеется, будет отличаться, но вот само ядро - не столь сильно. Причем миландровский контроллер заточен под счетчики электроэнергии, так что у кого-то есть вполне реальные шансы столкнуться с ним на практике. Но в любом случае, если знать ассемблер и принцип работы хотя бы одного контроллера (я вон вообще с AVR начинал), осваивать остальные будет значительно проще.

Д/З:

  1. Начать знакомиться с общим курсом по RISC-V. Философия, система команд и т.п.

  2. Установить окружение для сборки и сравнить результат компиляции с “эталонным” - правильные ли адреса, нет ли в начале какого-то мусора. Мы в это уже воткнулись, когда запускали на ALT linux. Не обязательно команды и адреса будут совпадать 1 к 1, но основные адреса должны быть правдоподобными.

Дополнительная информация

Видеокурс от UNИX

Моя статья на Хабре часть 2

Еще с Хабра

CC BY 4.0