
В этом тексте я написал про то, как настроить программное окружение для разработки для российского микроконтроллера MIK32 (K1948BK018). В тексте произведен частичный разбор того open source кода, который поставляет производитель (компания Mikron).
Абсолютно все исходные материалы взяты из открытых источников.
Постановка задачи
Взять отладочную плату с MIK32 и собрать минималистическую прошивку в которой будет настроен аппаратный таймер, HeardBeat LED, простой планировщик, UART, CLI, NVRAM и модульные тесты. При этом собирать из GNU Make скриптов.
То есть довести прошивку до, так называемой, ортодоксально канонической формы. Сделать ту самую программную коробочку в которую можно положить любое NoRTOS приложение. И настроить для всего этого добра пошаговую отладку.
Аппаратная часть
Что надо из оборудования?
# | Название | Пояснение |
1 | START-MIK32-V1 | Учебно-треннировочкая электронная палата с MIK32 в верхнем слое. |
2 | Кабель USB-A - USB-micro | Чтобы соединить PC и PCB |
3 | Паяльные принадлежности: паяльник, припой, флюс | Комплект на START-MIK32-V1 продается в частично разобранном виде. Поэтому надо самому припаять разъёмы и PLS PLD вилки |
4 | Переходник c USB-UART | Для отладки на UART1 (UART1 выходит только на PLS вилку) |
5 | Перемычки гнездо-гнездо | Чтобы соединить плату и переходник USB-UART |
Все эксперименты по изучению можно проводить на учебно-тренировочной электронной плате START-MIK32-V1. Плата свободно продается буквально на Ozone. Вот она перед Вами.

Мозгом этой электронной платы является микроконтроллер K1948BK018

На корпусе можно увидеть токен K1948BK018. Вот так расшифровывается маркировка микросхемы. К слову, число 018 означает, что корпусирование производилось в городе Калининград.

А это, господа, блок-схема платы START-MIK32-V1. Тут есть встроенный программатор CH552T, 3x LED. Одна USER кнопка, SPI-Flash и прочее.

Процессорное ядро SCR1.
Буквально пара слов про ядро. Микроконтроллер K1948BK018 (MIK32) зиждется на процессоре SCR1 внутри. SCR1 это в сущности бильт ядра RISC-V с конфигом RV32IMC. Говоря простыми словами, это 32-битный микропроцессор, там 32 регистра общего назначения, с 3х стадийным конвейером исполнения команд, со встроенным целочисленным однотактным умножителем (буква М), с возможностью исполнять сжатые 16-битные инструкции (буква С). Есть отдельный регистр PC для program counter, который хранит адрес исполняемой инструкции в конкретный момент времени. Среди регистров общего назначения есть один (x0) который просто всегда в нуле.
Регистр | Пояснение | Пояснение (RUS) | ABI name |
x0 | hardwired to the constant 0 | аппаратно всегда в нуле | zero |
x1 | return address | адрес куда надо возвращаться после отработки функции | ra |
x2 | stack pointer | указатель на границу стека | sp |
x3 | global pointer | глобальный указатель | gp |
x4 | thread pointer | указатель потока | tp |
x5 - x7 | Temporary registers | регистр общего назначения | t0 - t2 |
x8 - x9 | Callee-saved registers | регистр общего назначения | s0 - s1 |
x10 - x17 | Argument registers | регистр общего назначения | a0 - a7 |
x18 - x27 | Callee-saved registers | регистр общего назначения | s2 - s11 |
x28 - x31 | Temporary registers | регистр общего назначения | t3 - t6 |
pc | program counter | счетчик команд | pc |
Что надо из документации?
Для настройки и запуска Toolchain для программирования MIK32 надо разобрать следующий комплект официальной документации.
№ | Название документа | Страниц | Версия |
1 | SCR1 External Architecture Specification | 125 | 1.2.9 |
2 | SCR1 User Manual | 19 | 1.1.4 |
3 | The RISC-V Instruction Set Manual | 145 | 2.2 |
4 | RISC-V ABIs Specification | 56 | 1.0-rc4 |
5 | Техническое описание К1948ВК018, К1948ВК015 RISC-V микроконтроллер MIK32 АМУР | 320 | 2.2.2 |
6 | START-MIK32-V1_Schematic.pdf | 1 | -- |
7 | GNU Make | 224 | 4.3 |
8 | Using the GNU Compiler Collection | 1006 | 10.0.0 |
9 | The GNU linker | 166 | 2.44 |
-- | Итого | 2062 | -- |
Что надо из софтвера?
На самом деле всё минимально необходимые утилиты можно скачать разом прямо с сайта производителя. Буквально на вкладке "среда разработки". Самый простой путь это скачать архив mik32-ide-v1-2-2.zip и распаковать его WinRar-oм. В случае, если у вас уже есть несколько других RISC-V проектов, то компилятор GCC RISC-V можно и вовсе сразу вынести в корень диска C:\. В папку C:\riscv-gcc\bin. Файлы настройки компоновщика *.ld лежат в mik32-ide-v1-1\mik32-ide_edit\workspace\MIK32_SRC\scripts
Вот минимальный набор утилит, которые так или иначе необходимы для разработки на MIK32:
№ | Утилита | Назначение |
1 | WinRAR | Компрессия декомпрессия файлов |
2 | zadig-2.9.exe | Win утилита для установки USB драйверов |
3 | Eclipse IDE for C/C++ Developers | Текстовый редактор |
4 | realpath, cp, echo, mkdir, tee | Windows build tools |
9 | Windows драйвер ASIC CP2102 | Windows драйвер для микросхемы CP2102 |
5 | GNU Make | Система сборки. Утилита для дерижирования программными конвейерами |
6 | riscv64-unknown-elf-gcc.exe | Компилятор GCC собранный специально для RISC-V и остальные утилиты ToolChain-a: cpp, gdb, as, ld, nm, objcopy |
7 | openocd | Отладочный сервер для пошаговой отладки микроконтроллеров |
8 | git | Система контроля версий сорцов |
9 | riscv64-unknown-elf-gdb.exe | Отладочный клиент для пошаговой отладки |
Любая сборка для MCU это сross компиляция. То есть мы на компе A (x86) собираем программу для компа B (RISC-V). При чем компы A и B имеют абсолютно разную архитектуру внутри. Поэтому нужен компилятор.
Самый свежий компилятор для RISC-V можно скачать из github. На день написания текста это была версия 14.2.0-3. Вот ссылка
Сразу за одно можно установить CygWin и установить оттуда hexedit, realpath, grep, sed, find, tee, ctags gawk tsort и прочие полезнейшие утилиты командной строки. Само собой, что ко всем нужным утилитам надо прописать путь в переменной окружения PATH. Чтобы операционка их видела.
Eclipse нам нужен не только в качестве печатной машинки, но также и для последующего Front-End приложения для выполнения пошаговой отладки в купе с отладочным GDB клиентом. Если говорить про то, какой версии надо накатывать Eclipse, то лично я бы порекомендовал Вам Eclipse IDE for C/C++ Developers, Version: 2024-03 (4.31.0), бильт: 20240307-1437 Как по мне, в 2024-03 самый эргономичный HMI.
Фаза 1: Установка драйвера программатора CH552T (Переходник USB-JTAG)
Соединяем плату start-mik32-v1 и LapTop PC кабелем USB-micro. Чтобы OS Windows узнала плату Start, надо установить драйвер USB устройства.

Драйвер устанавливается утилитой zadig-2.9.exe.

После установки в диспетчере задач появится новое устройство:


При первом пуске Eclipse возникнет cообщение, что нужна Java Runtime Environment (JRE) или Java Development Kit (JDK). Это можно установить либо с сайта Oracle (после регистрации через VPN) либо с неофициального сайта softportal.

JRE нам пригодится не только для Eclipse, а также ещё JVM пригодится для пуска локального сервера сборки Jenkins, чтобы следить за многочисленными сборками прошивок и документации.
Фаза 2: Особенности сборки самой прошивки
Особенность сборки прошивок для MIK32 состоит в том, что надо передать компилятору GCC нужный пучок опций. Только и всего. У меня сборка происходит из самостоятельно написанных GNU Make скриптов. Поэтому специфичный для MIK32 пучок опций я поместил в переменную окружения MICROPROCESSOR
$(info CORE_SELECT_MK_INC=$(CORE_SELECT_MK_INC) )
ifneq ($(CORE_SELECT_MK_INC),Y)
CORE_SELECT_MK_INC=Y
CORE_SEL_DIR = $(CORE_DIR)/rv32imc
INCDIR += -I$(CORE_SEL_DIR)
# $(error CORE_SEL_DIR=$(CORE_SEL_DIR))
include $(CORE_SEL_DIR)/scr1_timer/scr1_timer.mk
MICROCONTROLLER=Y
OPT += -DHAS_RV32IMC
OPT += -DHAS_RISC_V
......
MICROPROCESSOR += -march=rv32imc_zicsr_zifencei
MICROPROCESSOR += -mabi=ilp32
MICROPROCESSOR += -msmall-data-limit=8
MICROPROCESSOR += -mstrict-align
MICROPROCESSOR += -mno-save-restore
endif
Особенность MCU ещё отражается в скрипте настройки компоновщика (linker-a).
spifi.ld
OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv")
OUTPUT_ARCH(riscv)
ENTRY(_start)
MEMORY {
rom (RX): ORIGIN = 0x80000000, LENGTH = 4M
ram (RWX): ORIGIN = 0x02000000, LENGTH = 16K
}
STACK_SIZE = 1K;
CL_SIZE = 16;
SECTIONS {
.text ORIGIN(rom) : {
PROVIDE(__TEXT_START__ = .);
*crt0.o(.text .text.*)
*(.text.smallsysteminit)
*(.text.SmallSystemInit)
. = ORIGIN(rom) + 0xC0;
KEEP(*crt0.o(.trap_text))
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
. = ALIGN(CL_SIZE);
PROVIDE(__TEXT_END__ = .);
} >rom
.data :
AT( __TEXT_END__ ) {
PROVIDE(__DATA_START__ = .);
_gp = .;
*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata*)
*(.sdata .sdata.* .gnu.linkonce.s.*)
*(.data .data.*)
. = ALIGN(CL_SIZE);
} >ram
__DATA_IMAGE_START__ = LOADADDR(.data);
__DATA_IMAGE_END__ = LOADADDR(.data) + SIZEOF(.data);
ASSERT(__DATA_IMAGE_END__ < ORIGIN(rom) + LENGTH(rom), "Data image overflows rom section")
/* thread-local data segment */
.tdata : {
PROVIDE(_tls_data = .);
PROVIDE(_tdata_begin = .);
*(.tdata .tdata.*)
PROVIDE(_tdata_end = .);
. = ALIGN(CL_SIZE);
} >ram
.tbss : {
PROVIDE(__BSS_START__ = .);
*(.tbss .tbss.*)
. = ALIGN(CL_SIZE);
PROVIDE(_tbss_end = .);
} >ram
/* bss segment */
.sbss : {
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
} >ram
.bss : {
*(.bss .bss.*)
. = ALIGN(CL_SIZE);
PROVIDE(__BSS_END__ = .);
} >ram
_end = .;
PROVIDE(__end = .);
PROVIDE(end = .);
/* End of uninitalized data segement */
.stack ORIGIN(ram) + LENGTH(ram) - STACK_SIZE : {
FILL(0);
PROVIDE(__STACK_START__ = .);
. += STACK_SIZE;
PROVIDE(__C_STACK_TOP__ = .);
PROVIDE(__STACK_END__ = .);
} >ram
/DISCARD/ : {
*(.eh_frame .eh_frame.*)
}
}
Когда на MCU подается напряжение микропроцессор начитает исполнять код по тому значению, что оказалось прописано в программном счётчике. Cамой первой функцией, которая выполняется вовсе не является функция main(). До запуска main вызывается функция ассемблерная процедура _start.
Секвенция _start первым делом выдерживает паузу, не только чтобы электропитание успело распространиться по всей площади электронной платы, но и чтобы программатор успел подключиться по JTAG до пуска функции main(), _start прописывает регистр стековой памяти sp(x2) , прописывает секцию data, обнуляет секцию bss, прописывает в RAM память функции, которые следует исполнять из RAM памяти, прописывает регистр, указывающий на обработчик прерываний, запускает конструкторы глобальных объектов классов (в случае С++) и только после этого делает пуск функции main(). Адрес функции _start задается в скрипте конфигурации компоновщика *.ld файл.
Этот ассемблерный start-up код обычно называется crt0.S. Вот он перед вами.
crt0.S
#define EXCEPTION_STACK_SPACE 31*4
#define EXCEPTION_SAVED_REGISTERS 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
.globl _start, main
.weak SmallSystemInit, SystemInit
.globl trap_entry
.globl trap_handler
.globl raw_trap_handler
.weak trap_handler, raw_trap_handler
.altmacro
.macro memcpy src_beg, src_end, dst, tmp_reg
LOCAL memcpy_1, memcpy_2
j memcpy_2
memcpy_1:
lw \tmp_reg, (\src_beg)
sw \tmp_reg, (\dst)
add \src_beg, \src_beg, 4
add \dst, \dst, 4
memcpy_2:
bltu \src_beg, \src_end, memcpy_1
.endm
.macro memset dst_beg, dst_end, val_reg
LOCAL memset_1, memset_2
j memset_2
memset_1:
sw \val_reg, (\dst_beg)
add \dst_beg, \dst_beg, 4
memset_2:
bltu \dst_beg, \dst_end, memset_1
.endm
# la uses PC relative addressing (auipc instruction)
# Explicit absolut addressing with lui instruction is used
# to allow startup code to be executed from any PC address
# (la instruction equivalents are left in comments)
.macro la_abs reg, address
.option push
.option norelax
lui \reg, %hi(\address)
addi \reg, \reg, %lo(\address)
.option pop
.endm
.macro jalr_abs return_reg, address
.option push
.option norelax
lui \return_reg, %hi(\address)
jalr \return_reg, %lo(\address)(\return_reg)
.option pop
.endm
.text
_start:
li t0, 128000
start_loop_delay:
#nop
addi t0, t0, -1
bnez t0, start_loop_delay
# Init stack and global pointer
#
la_abs sp, __C_STACK_TOP__
la_abs gp, __global_pointer$
# Init data
#
la_abs a1, __DATA_IMAGE_START__
la_abs a2, __DATA_IMAGE_END__
la_abs a3, __DATA_START__
memcpy a1, a2, a3, t0
# Init ramfunc
#
la_abs a1, __RAM_TEXT_IMAGE_START__
la_abs a2, __RAM_TEXT_IMAGE_END__
la_abs a3, __RAM_TEXT_START__
memcpy a1, a2, a3, t0
# Clear bss
#
la_abs a1, __SBSS_START__
la_abs a2, __BSS_END__
memset a1, a2, zero
# Init mtvec
#
la_abs t0, __TRAP_TEXT_START__
csrw mtvec, t0
#ifdef MIK32V0
# Enable pad_config clocking
#
li t0, (1 << 3)
li t1, 0x50014
sw t0, (t1)
#endif
jalr_abs ra, SmallSystemInit
jalr_abs ra, SystemInit
jalr_abs ra, main
1: wfi
j 1b
// Actions before main: none by default
// (weak symbol here - may be redefined)
SmallSystemInit:
SystemInit:
ret
.section .trap_text, "ax"
// .org should be consistent with
// default mtvec value (set in scr1_arch_description.svh)
//.org 0xC0
trap_entry:
j raw_trap_handler
raw_trap_handler:
// Save registers
addi sp, sp, -(EXCEPTION_STACK_SPACE)
.irp index, EXCEPTION_SAVED_REGISTERS
sw x\index, 4*(index-1)(sp)
.endr
// Call handler
la ra, trap_handler
jalr ra
// restore registers
.irp index, EXCEPTION_SAVED_REGISTERS
lw x\index, 4*(index-1)(sp)
.endr
addi sp, sp, EXCEPTION_STACK_SPACE
mret
// Default handler: infinit loop
// (weak symbol here - may be redefined)
trap_handler:
1: j 1b
В процессоре SCR1 в direct режиме только одно единственное прерывание trap. Внутри процедуры обработчика уже происходит проверка pending битов, чтобы определить подлинную причину прерывания и вызвать соответствующий обработчик. В SCR1 при возникновении прерывания парковка регистров (сохранение в стековую память) процессора происходит программно в том же самом ассемблерном коде, что в crt0.S файле.
В Си-коде обработчик прерываний - это функция trap_handler.
При этом код самого корневого обработчика прерываний trap_entry запускается из RAM памяти. И процессору надо указать адрес этого корневого обработчика регистром MTVEC (Vector base address).
_start: 0x80000000
main: 0x800210d8
trap_entry: 0x020000c0
raw_trap_handler: 0x020000c4
trap_handler: 0x8000ce58
MTVEC: 0x020000c0
__TRAP_TEXT_START__:0x020000c0
Скорее всего Вам пригодится делать программную перезагрузку прошивки. Это можно выполнить ассемблерной вставкой. Надо просто прыгнуть на ассемблерную функцию _start. Но надо учитывать, что всё физические адреса (периферия, GPIO, UART, SPI, TIMER ) останется проинициализированной. Сброса оборудования не будет.
bool rv32imc_boot_addr_asm(const uint32_t app_start_address) {
bool res = true;
/* JR (jump register) performs an unconditional control transfer to the address in
* register rs1. C.JR expands to jalr x0, rs1, 0.*/
__asm__ volatile("jr %0" : : "r" (app_start_address):);
return res;
}
#define clear_csr(reg, bit) \
({ \
unsigned long __tmp; \
if (__builtin_constant_p(bit) && (bit) < 32) \
__asm__ volatile ("csrrc %0, " __xstringify(reg) ", %1" : "=r"(__tmp) : "i"(bit)); \
else \
__asm__ volatile ("csrrc %0, " __xstringify(reg) ", %1" : "=r"(__tmp) : "r"(bit)); \
__tmp; \
})
bool interrupt_disable(void) {
bool res = true;
clear_csr(mie, MIE_MSIE);//Machine Software Interrupt Disable.
clear_csr(mie, MIE_MTIE);//Machine Timer Interrupt Disable.
clear_csr(mie, MIE_MEIE);//Machine External Interrupt Disable.
return res;
}
bool rv32imc_boot_addr(const uint32_t app_start_address) {
bool res = false;
interrupt_disable();
res = rv32imc_boot_addr_asm(app_start_address) ;
return res;
}
Вот, пожалуй, и все основные особенности программирования ядра RV32IMC. Остальной Си код будет, как и в случае программирования любого другого RISC процессора.
Фаза 3: Сборка проекта
Чтобы инициировать сборку достаточно вызвать скрипт build_from_make.bat, который в сущности только удаляет промежуточные файлы и запускает новую свежую сборку на нескольких процессорах LapTop-a. Также благодаря утилите tee.exe формируется полный отчет сборки проекта в виде текстового файла build_log.txt.
echo off
cls
make clean 2>&1 | tee clean_log.txt
make -j8 | tee build_log.txt
По мере движения разработки сборок для платы будет много. Поэтому я сразу завел сервер сборки Jenkins для наблюдения за состоянием разрозненных проектов.

Как и со всяким MCU cкорее всего Вы будете сталкиваться с тем, что прошивка не будет влезать в on-chip память. Чтобы понять какой файл с исходниками занимает больше всего места можно выполнить вот такую консольную команду:
grep -rn "\.o" build/firmware.map | grep -i "0x" | grep -v riscv
Она позволит вам выявить что именно занимает место.
Фаза 4: Загрузка прошивки во Flash память
Перепрошивка происходит утилитой OpenOCD. Прежде всего надо добавить в переменную окружения PATH абсолютный путь к программе OpenOCD. Исполнительный файл поставляется прямо компанией в архиве mik32-ide-v1-1\mik32-ide_edit\tools\bin
Для перепрошивки понадобится конфиг для микроконтроллера mik32.cfg. Вот так выглядит его содержимое mik32.cfg:
proc my_init_proc { } { echo "Disabling watchdog..." }
proc init_targets {} {
reset_config trst_and_srst
set _CHIPNAME riscv
set _CPUTAPID 0xdeb11001
set _SYSTAPID 0xfffffffe
jtag newtap $_CHIPNAME cpu -irlen 5 -ircapture 0x1 -irmask 0x1f -expected-id $_CPUTAPID
jtag newtap $_CHIPNAME sys -irlen 4 -ircapture 0x05 -irmask 0x0F -enable -expected-id $_SYSTAPID -ignore-bypass
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -endian little -chain-position $_TARGETNAME -coreid 0
riscv expose_csrs 2016=mcounten
riscv.cpu configure -event reset-init my_init_proc
}
poll_period 200
init
riscv.cpu arm semihosting enable
puts "init done"
Также нужен конфиг для электронной платы start-link.cfg и её встроенного программатора на основе микроконтроллера CH552T.
# SPDX-License-Identifier: MIT
interface usb_blaster
usb_blaster lowlevel_driver ftdi
usb_blaster vid_pid 0x16c0 0x05dc
usb_blaster pin pin6 s
usb_blaster pin pin8 t
# usb_blaster_device_desc "START-MIK32-V1 USB JTAG"
Сначала надо закрыть все приложения, которые используют порт 6666. Увидеть эти приложения можно командой netstat -aon
C:\Users\aabdev>netstat -aon | grep 6666
TCP 0.0.0.0:6666 0.0.0.0:0 LISTENING 6236
TCP 127.0.0.1:6666 127.0.0.1:58745 ESTABLISHED 6236
TCP 127.0.0.1:58745 127.0.0.1:6666 ESTABLISHED 5408
TCP [::]:6666 [::]:0 LISTENING 6236
Определить имя нежелательного приложения по его PID можно командой tasklist
>tasklist | grep 6236
Image Name PID Session Name Session# Mem Usage
========================= ======== ================ =========== ============
AgentService.exe 6236 Services 0 17,240 K
остановить процесс по PID можно командой. Эту команду надо исполнять от имени администратора
taskkill /F /T /PID 6236
Вот, к примеру, сервис MT AgentService.exe постоянно занимает порт 6666.

Cам скрипт перепрошивки flash_hex.bat выглядит так:
echo off
cls
set project_name=start_mik32_v1_bootloader_gcc_m
set project_dir=%cd%
set workspace_dir=%project_dir%\..\..\..\
echo workspace_dir=%workspace_dir%
echo project_dir=%project_dir%
set artefact_hex=build\%project_name%.hex
rem set Device= ID=0x463 SN=066CFF323535474B43013113
set script="tool\tool_mik32\mik32-uploader\mik32_upload.py"
set FlashTool=C:\Python310\python.exe %script%
set gdb_server=--openocd-exec=openocd.exe
set bit_rate=--adapter-speed=3200
set programmer=--openocd-interface=start-link.cfg
set Device=--openocd-target=mik32.cfg
set options="%artefact_hex%"
set options=%options% --run-openocd
set options=%options% %gdb_server%
set options=%options% %programmer%
set options=%options% %Device%
set options=%options% %bit_rate%
%FlashTool% %options%
В качестве скрипта перепрошивки может выступать даже сам GNU Make скрипт
$(info Flash Mik32 MCU Script)
FIRMWARE_BINARY_FILE=$(BUILD_DIR)/$(TARGET).hex
TOOLS_DIR=$(WORKSPACE_LOC)/../tool
TOOLS_DIR:= $(realpath $(TOOLS_DIR))
TOOLS_DIR := $(subst /cygdrive/c/,C:/, $(TOOLS_DIR))
#@echo $(error TOOLS_DIR=$(TOOLS_DIR))
# Probably it will need to disable all apps at port 6666
# netstat -aon | grep 6666
VENDOR_FLASH_CLI_TOOL:= $(TOOLS_DIR)/tool_mik32/mik32-uploader/mik32_upload.py
MCU_OPEN_OCD_CONFIG=mik32.cfg
BOARD_OPEN_OCD_CONFIG=start-link.cfg
VENDOR_FLASH_CLI_OPTIONS =
VENDOR_FLASH_CLI_OPTIONS += $(FIRMWARE_BINARY_FILE)
VENDOR_FLASH_CLI_OPTIONS += --run-openocd
VENDOR_FLASH_CLI_OPTIONS += --openocd-exec=openocd.exe
VENDOR_FLASH_CLI_OPTIONS += --openocd-interface=$(BOARD_OPEN_OCD_CONFIG)
VENDOR_FLASH_CLI_OPTIONS += --openocd-target=$(MCU_OPEN_OCD_CONFIG)
VENDOR_FLASH_CLI_OPTIONS += --adapter-speed=3200
.PHONY: flash_mik32
flash_mik32: $(FIRMWARE_BINARY_FILE) $(BOARD_OPEN_OCD_CONFIG) $(MCU_OPEN_OCD_CONFIG)
$(info Flash File:$(FIRMWARE_BINARY_FILE) ...)
$(PYTHON_BIN) $(VENDOR_FLASH_CLI_TOOL) $(VENDOR_FLASH_CLI_OPTIONS)
$(info Flash Mikron MCU Target Done)
Сама *.hex прошивка 241k Byte загружается скриптом mik32_upload.py примерно полторы минуты. Получается 2.7....3 kByte в секунду.
Фаза 5: Пуск отладочного сервера
Чтобы условно соединить микроконтроллер, программатор и процесс в операционной системе надо запустить отладочный сервер. Для этого у меня есть *.bat скрипт LaunchGdbServerOpenOCD_mik32.bat. Данный скрипт фактически вызывает утилиту OpenOCD с пучком опций, которые указывают программатор (start-link.cfg) и микроконтроллер (mik32.cfg).
echo off
cls
set GDBServerPath=openocd.exe
set options=-s mik32-uploader/penocd-scripts
set options=%options% -f start-link.cfg
set options=%options% -f mik32.cfg
set options=%options% -c "init;halt"
call %GDBServerPath% %options%
Конфиг для MCU mik32.cfg и конфиг для программатора на основе MCU CH552T start-link.cfg всё тот же, что был в скрипте перепрошивки.
Фаза 6: Настройка GDB клиента
Отлаживаться будем тоже с OpenOCD. Чтобы в программе Eclipse отладочный сервер появился в окне Debug Configuration следует установить Eclipse плагин OpenOCD Debugging.
После установки плагинов перезапускаем Eclipse. Вкладка main выглядит теперь так.

На вкладке Debugger я рекомендую отключить действие Start OpenOCD locally и вместо этого вызывать пуск отладочного сервера из *.bat скрипта. Так у Вас хотя бы останется лог сервера в отдельном окне.

В разделе GDB session надо вставить следующие команды. Они показывают то, где и какая память расположена внутри MIK32
set mem inaccessible-by-default on
mem 0x00040000 0x00090000 rw
mem 0x02000000 0x02004000 rw
mem 0x01000000 0x01002000 ro
mem 0x80000000 0xffffffff ro
set arch riscv:rv32
set remotetimeout 10
set remote hardware-breakpoint-limit 2
Вот справка по памяти внутри MIK32

Выглядит это так. Тут как раз и фигурирует запуск отладочного gdb клиента C:\riscv-gcc\bin\riscv64-unknown-elf-gdb.exe

Продолжение

На вкладке Startup надо отметить, что не надо загружать исполнительный файл. Да... Мы его уже загрузили при помощи python скрипта перепрошивки. Поэтому снимаем флаг Load executable.

продолжение

вкладка Сommon.

Фаза 7: Запуск пошаговой отладки
Как можно заметить, пошаговая GDB отладка более чем возможна на микроконтроллере MIK32. Прямо в Eclipse IDE.

Скорее всего Вы станете собирать прошивки с активированной оптимизацией (опции -Os и -flto).
ifeq ($(PACK_PROGRAM), Y)
OPTIMIZATION = -Os
#When compiling with -flto, no callgraph information is output along with
#the object file.
#This option runs the standard link-time optimizer.
#When invoked with source code, it generates GIMPLE
#(one of GCCs internal representations) and writes
#it to special ELF sections in the object file.
# When the object files are linked together, all the function bodies are read
# from these ELF sections and instantiated as if they had been
#part of the same translation unit.
COMPILE_OPT += -flto
endif
В этом случае пошаговая отладка будет не очень эффективна, так как курсор станет оказываться в случайных неожиданных файлах проекта. В этом случае отлаживаться следует при помощи светодиода (LED). Так Вы поймете по каким строкам кода проходил процессор.
Фаза 8: Запуск UART-CLI
Так как пошаговый отладчик OpenOCD довольно долго грузится (несколько секунд), мне пришлось дополнительно добавить в прошивку для отладки интерфейс командной строки прямо поверх UART1 на скорости 56000 bit/s. Вот такой получился лог загрузки.

К слову с UART-CLI очень удобно отлаживать прошивки и железо. Вот буквально отправкой трёх символов irr можно получить исчерпывающее состояние карты регистров для подсистемы прерываний. Далее сверив со спекой понять, что включено, а что отключено.

Схема ToolChain-(а)
В общем, весь маршрут сорцов от момента написания, до попадания во flash память микроконтроллера MIK32 можно проследить вот на этой схеме toolchain-a. Всё как и везде, только теперь другой префикс toolchain-a (riscv64-unknown-elf- или riscv-none-elf-) и другой скрипт для загрузки прошивки в боевую flash память. А в деталях всё тот же Eclipse, GNU Make, отладочный сервер OpenOCD. В общем, классика жанра.

Достоинства MIK32
1++ MIK32 - это полностью российский микроконтроллер, который разработан и производится на территории Российской Федерации. Компонент первого уровня.
2++ Как Вы уже могли заметить, в ядре SCR1 по физическому адресу 0x00490000 вмонтирован аппаратный 64-битный таймер. Если тактировать его кварцем 32 MHz и прописать в пред-делитель константу 32, то счетчик будет увеличиваться раз в микросекунду. Учитывая щедрую разрядность 64 бит, таймер переполнится через, приготовьтесь, 584 942 лет! Можно даже не беспокоиться за переполнение. За это время можно 4 раза слетать туда и обратно на Проксиму-Центавру.
3++ Присутствует возможность исполнять циклопические прошивки прямо из SPI-Flash (In-place Execution). В связи с этим, размер бинаря на отладке start может достигать 4MByte+.
Итог
Удалось научиться собирать, прошивать, пошагово отлаживать прошивки и даже запустить командную строку для российского микроконтроллера MIK32 (K1948BK018).
Все микроконтроллеры программируются одинаково, если собирать проект из GNU Make скриптов.
Это открывает прямую дорогу для полноценной разработки на MIK32.
Словарь
Акроним | расшифровка |
PID | Process IDentifier |
MIK32 | Mikron 32 bit |
PC | Program Counter |
CSR | Control Status Register |
HMI | Human-machine interface |
ISA | Instruction Set Architecture |
OCD | On Chip Debug |
SCR1 | SyntaCore One |
GDB | GNU Debugger |
RISC | reduced instruction set computer |
MCU | microcontroller unit |
XIP | execute-in-place |
Ссылки
№ | Название текста | URL |
1 | Руководство по началу работы с отладочной платой MIK32-DIP (K1948ВК018, Амур) в ОС GNU/Linux | |
2 | RV32I - Стандартный набор целочисленных инструкций RISC-V | |
3 | SCR1 RISC-V Core | GitHub - syntacore/scr1: SCR1 is a high-quality open-source RISC-V MCU core in Verilog |
4 | Контроллер прерываний | |
5 | Таймер ядра SCR1_TIMER | |
6 | Zadig | |
7 | OpenOCD: руководство пользователя, начало | https://microsin.net/programming/ARM/openocd-manual-part1.html |
8 | Windows драйвер для микросхемы CP2102 | https://www.silabs.com/developer-tools/usb-to-uart-bridge-vcp-drivers |
9 | xPack GNU RISC-V Embedded GCC v14.2.0-3 | https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases |
10 | Professional SW Development Suite | |
11 | RISC-V Calling Conventions | https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc |
Вопросы
--Чем процессор RISC-V (SCR1) отличается от ARM (Cortex-M3)?