Я всей душой люблю малоизвестных производителей. Зачастую их продукты имеют фичи, недоступные у их более именитых конкурентов, по очень интересной цене (однажды меня очень выручило наличие встроенной в SoC полуамперной зарядки для аккумулятора и нескольких LDO, способных запитать всю периферию). Кроме того, сроки и условия поставки какого-нибудь Nanjing Qinheng Microelectronics могут приятно удивить разработчиков, привыкших за последние два ковидных года к конскому ценнику и 52+ неделям доставки на ST, TI, Nordic и прочие привычные вещи. Логистика становится особенно приятной, если массовое производство планируется в Китае и на площадке присутствует ваша китайская команда, способная разрулить возникающие проблемы. Да и доставка из Шеньчженя в Шеньчжень проще и предсказуемей, чем со склада глобального дистрибьютора и растаможка в России.
Что готовим, шеф?
Незадолго до новогодних праздников мне в руки попал B91 Generic Starter Kit. Отладка построена на базе TLSR9518A - RISC-V SoC с поддержкой Bluetooth Classic, BLE, собственным AI-движком и кучей других плюшек, позволяющих сделать относительно сложное носимое устройство по достаточно низкой цене. На отладке распаян старший чип из линейки, ориентированный на задачи, связанные с аудио и одновременной поддержкой как классического Bluetooth, так и BLE, но в линейке есть и более простые устройства. В общем - идеальный кандидат на то, чтобы развеять тоску и отвлечь от тазика с оливье.
Telink раньше был известен в первую очередь благодаря сверхдешевым BLE-чипам, построенным на собственном ядре, но с недавнего времени в линейке появились и RISC-V.
На вики вендора присутствуют ссылки на IDE, тулчейн и примеры кода. Если бы мы отлаживали что-то более распространенное в наших краях, то этого было бы достаточно, но здесь производитель приготовил несколько сюрпризов:
-
IDE только под винду, которой у меня нет (да и тысячи вендорских IDE на эклипсе уже набили оскомину. А после того, как WICED-Studio отказалась запускать отладку на CYW954907 пока я не уберу все брейкпойнты, я принял решение по возможности не использовать такие решения). Поэтому мы будем использовать связку CMake + VSCode.
-
Сервер, на котором хранятся IDE и тулчейн, находится где-то в тибетском монастыре, подключенном по dial-up'у, по крайней мере средняя скорость закачки в 10кб/с и более трех тысяч разрывов закачки намекают об этом.
-
Тулчейн неполный, мы можем собрать и зашить бинарь, но gdb-сервер не может стартовать из-за отсутствия одной из библиотек. Эти и другие проблемы мы попробуем решить.
Подготовка проекта и окружения
Я использовал в процессе Ubuntu 21.10, так что если вы работаете с другой ОС, то возможно придется немного скорректировать команды.
sudo apt update && sudo apt upgrade && sudo apt autoremove
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386
sudo apt install -y make cmake wget unzip libncurses5
Тулчейн
Его можно собрать самому по инструкции или же скачать готовую сборку с гитхаба разработчиков ядра. У меня не получилось собрать его с ходу, а разбираться интереса не было. Поэтому, если кто укажет на причину - буду весьма благодарен.
Версия с Mculib
По умолчанию предлагается вендором.
wget https://github.com/andestech/Andes-Development-Kit/releases/download/ast-v3_2_3-release-linux/nds32le-elf-mculib-v5f.txz
mkdir toolchain
tar -xvf nds32le-elf-mculib-v5f.txz -C toolchain
rm nds32le-elf-mculib-v5f.txz
Версия с Newlib
https://github.com/andestech/Andes-Development-Kit/releases/download/ast-v3_2_3-release-linux/nds32le-elf-newlib-v5f.txz
mkdir toolchain
tar -xvf nds32le-elf-newlib-v5f.txz -C toolchain
rm nds32le-elf-newlib-v5f.txz
Инструменты отладки
В комплекте с девбордой идет JTAG-отладчик от вендора, но в принципе не должно возникнуть особых проблем с использованием других аппаратных средств, поддерживающих OpenOCD. Единственный нюанс - TLSR9518A, установленный на плате, предназначен исключительно для оценки и единственный из всей линейки не имеет встроенной FLASH-памяти.
Для работы с вендорским отладчиком потребуется утилита для прошивки внешней флешки и отладчик ICE, модифицированный OpenOCD.
wget https://github.com/andestech/Andes-Development-Kit/releases/download/ast-v3_2_3-release-linux/flash.zip
wget https://github.com/andestech/Andes-Development-Kit/releases/download/ast-v3_2_3-release-linux/ice.zip
unzip flash.zip -d toolchain
unzip ice.zip -d toolchain
rm -rf flash.zip ice.zip
cd toolchain/ice/
chmod +x ICEman.sh
./ICEman.sh
Поддержка семейства TLSR9 также заявлена в JLink, но я писал статью по горячим следам и у меня не было его под рукой.
SDK
В том, что касается SDK, Telink придерживается подхода, похожего на тот, что использовали когда-то Nordic со свими SoftDevice - отдельный SDK под каждую задачу. Нужный вам можно найти на вики, а для примера я решил использовать Driver SDK, предназначенный для работы с периферией чипа. Стоит отметить, что Bluetooth SDK предполагает работу в суперцикле и не имеет ни малейшего намека на поддержку FreeRTOS, в отличие от Driver SDK. Представители вендора не дали прямого ответа, почему сделано именно так, но горячо уверили, что к осени SDK получит мощный апдейт. Заявлена поддержка Zephyr, но без BLE и, судя по треду, ситуация вряд ли улучшится в ближайшее время. Поэтому, если вы захотите начинать разработку следующего устройства на основе православного китайского чипа - имейте в виду такие особенности, они возникают достаточно часто.
wget http://wiki.telink-semi.cn/tools_and_sdk/Driver/B91_Driver_SDK.zip
mkdir driver_sdk
unzip B91_Driver_SDK.zip -d driver_sdk/
rm B91_Driver_SDK.zip
Сборка
Единственное, чего нам не хватает сейчас - инструкции по сборке. За основу я взял UART_DEMO, немного подправив его, чтобы сделать возможной out-of-tree сборку и отладку.
cmake_minimum_required(VERSION 3.0)
project(apptest)
enable_language(C ASM)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS)
set(RISCV_TOOLCHAIN ${CMAKE_CURRENT_LIST_DIR}/../toolchain/nds32le-elf-mculib-v5f/bin)
set(DRIVER_SDK ${CMAKE_CURRENT_LIST_DIR}/../driver_sdk/B91_Driver_Demo)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER ${RISCV_TOOLCHAIN}/riscv32-elf-gcc)
set(CMAKE_CXX_COMPILER ${RISCV_TOOLCHAIN}/riscv32-elf-g++)
set(CMAKE_ASM_COMPILER ${RISCV_TOOLCHAIN}/riscv32-elf-gcc)
set(OBJCOPY ${RISCV_TOOLCHAIN}/riscv32-elf-objcopy)
set(OBJDUMP ${RISCV_TOOLCHAIN}/riscv32-elf-objdump)
set(NM ${RISCV_TOOLCHAIN}/riscv32-elf-nm)
set(READELF ${RISCV_TOOLCHAIN}/riscv32-elf-readelf)
set(SIZE ${RISCV_TOOLCHAIN}/riscv32-elf-size)
add_definitions(-DMCU_STARTUP_FLASH_B91=1)
add_compile_options(-Og -g3 -mcpu=d25f -mext-dsp -mabi=ilp32f -std=c99)
add_compile_options(-ffunction-sections -fdata-sections -fpack-struct -fshort-enums -flto)
add_compile_options(-fmessage-length=0 -fomit-frame-pointer -fno-strict-aliasing -fshort-wchar -fuse-ld=bfd)
add_compile_options(-Wall -Wno-nonnull-compare -Wextra -Wshadow -Werror -Wno-gnu-zero-variadic-macro-arguments)
set(CMAKE_EXE_LINKER_FLAGS "-Og -g3 -nostartfiles -static -T"${DRIVER_SDK}/link/flash_boot.link" -Wl,--gc-sections")
link_libraries(-lB91)
aux_source_directory(${DRIVER_SDK}/common SRCS_COMMON)
aux_source_directory(${DRIVER_SDK}/drivers/B91 SRCS_DRIVERS_B91)
aux_source_directory(${DRIVER_SDK}/vendor/common SRCS_VENDOR_COMMON)
aux_source_directory(${DRIVER_SDK}/vendor/UART_DEMO SRCS_APP)
set(SRCS
${SRCS_COMMON}
${SRCS_DRIVERS_B91}
${SRCS_VENDOR_COMMON}
${SRCS_APP}
${DRIVER_SDK}/boot/B91/cstartup_b91_flash.S)
include_directories(${CMAKE_CURRENT_LIST_DIR})
include_directories(${DRIVER_SDK})
include_directories(${DRIVER_SDK}/vendor/common)
include_directories(${DRIVER_SDK}/drivers/B91)
include_directories(${DRIVER_SDK}/common)
link_directories(${DRIVER_SDK}/drivers/B91)
add_executable(${PROJECT_NAME}.elf ${SRCS})
add_custom_target(BIN ALL
COMMAND ${OBJCOPY} -S -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMAND ${SIZE} ${PROJECT_NAME}.elf
COMMAND ${OBJDUMP} -x -d -C ${PROJECT_NAME}.elf > objdump.txt
COMMAND ${NM} -n -l -C ${PROJECT_NAME}.elf > symbol.txt
COMMAND ${READELF} -a ${PROJECT_NAME}.elf > readelf.txt
DEPENDS ${PROJECT_NAME}.elf
)
Я поместил этот файл в директорию app
, в которой планирую хранить и остальные исходники. Теперь можно запустить сборку.
cd app && mkdir build && cd build
cmake ..
make
Настройка VSCode
Для комфортной работы потребуется создать несколько файлов с настройками для VSCode: c_cpp_properties.json
, launch.json
, settings.json
, tasks.json
, каждый из которых отвечает за определенный функционал среды разработки. Для нас наиболее интересны настройки сборки и отладки.
Сборка (tasks.json)
Эти таски отвечают за сборку бинарника и его загрузку в чип. Задача ICEman
отвечает за gdb-соединение и должна быть запущена в фоне всегда, когда идет отладка чипа или его прошивка. Здесь же можно настроить необходимые зависимости, например, собирать проект каждый раз перед прошивкой, если вы тоже иногда забываете это сделать.
{
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"command": "cmake -Bbuild && cd build && make",
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceRoot}/app/"
},
"problemMatcher": {
"owner": "cpp",
"fileLocation": ["relative", "${workspaceFolder}/build"],
"pattern": {
"regexp": "^(.*):(\d+):(\d+):\s+(warning|error):\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
},
{
"label": "ICEman",
"type": "shell",
"isBackground": true,
"options": {
"cwd": "${workspaceRoot}/toolchain/ice"
},
"command": "./ICEman -Z v5",
},
{
"label": "flash",
"type": "shell",
"options": {
"cwd": "${workspaceRoot}/toolchain/flash/bin"
},
"command": "./SPI_burn -i ./../../../app/build/apptest.bin -v",
"dependsOn":["Build"],
}
]
}
Отладка (launch.json)
Для отладки потребуется один из двух плагинов, Cortex-Debug привычный для тех, кто уже использовал VSCode для отладки ARM-микроконтроллеров или Native Debug. Большой разницы между ними нет, но Cortex-Debug позволяет несколько удобнее работать с Zephyr, FreeRTOS и выводить RTT-сообщения во встроенный терминал VSCode, поэтому я предпочитаю его. Настройки для обоих решений приведены ниже:
{
"configurations": [
{
"type": "gdb",
"name": "Native Debug",
"request": "attach",
"cwd": "${workspaceRoot}",
"valuesFormatting": "parseText",
"executable": "${workspaceRoot}/app/build/apptest.elf",
"preLaunchTask": "ICEman",
"target": ":1111",
"remote": true,
"gdbpath": "${workspaceRoot}/toolchain/nds32le-elf-mculib-v5f/bin/riscv32-elf-gdb",
"autorun": [
"reset-and-hold",
"flushregs",
"break main"
]
},
{
"name": "Cortex-Debug",
"cwd": "${workspaceRoot}",
"executable": "./app/build/apptest.elf",
"request": "launch",
"type": "cortex-debug",
"servertype": "external",
"gdbTarget": "localhost:1111",
"gdbPath": "${workspaceRoot}/toolchain/nds32le-elf-mculib-v5f/bin/riscv32-elf-gdb",
"preLaunchTask": "ICEman",
"runToEntryPoint": "main",
}
]
}
Для использования Cortex-Debug потребуется добавить несколько строчек в настройки проекта settings.json
{
"cortex-debug.openocdPath": "${workspaceRoot}/toolchain/ice/openocd",
"cortex-debug.gdbPath": "${workspaceRoot}/toolchain/nds32le-elf-mculib-v5f/nds32le-elf-mculib-v5f/bin/riscv32-elf-gdb",
"cmake.sourceDirectory": "${workspaceFolder}/app",
"taskExplorer.enableMake": false,
"search.exclude": {
"**/build": true
}
}
И подсказать IntelliSense, где лежат наши исходники и какие объявления мы хотим сделать при сборке. Для этих целей служит c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/app/**",
"${workspaceFolder}/driver_sdk/**"
],
"defines": [
"MCU_STARTUP_FLASH_B91=1",
"DEBUG"
],
"browse": {
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "gcc-arm"
}
],
"version": 4
}
Заключение
В целом этого достаточно для комфортной разработки и отладки. Настройка автосборки для GIthub Actions не сильно отличается от тех шагов, что были проделаны выше, но на всякий случай оставлю рабочий пример для другого не очень распространенного микроконтроллера. Посмотреть на проект целиком можно на гитхабе.
Автор: Егор Марков