Pull to refresh

Настройка ToolChain-а Cборки Прошивок для MIK32 (MIK32 + C+ GCC + GNU Make + OpenOCD)

Level of difficultyEasy
Reading time16 min
Views1.2K

В этом тексте я написал про то, как настроить программное окружение для разработки для российского микроконтроллера 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. Вот она перед Вами.

START-MIK32-V1
START-MIK32-V1

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

K1948BK018 (QFN64)
K1948BK018 (QFN64)

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

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

START-MIK32-V1
START-MIK32-V1

Процессорное ядро 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
Схема электрическая принципиальная на плату START-MIK32-V1

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

https://habr.com/ru/articles/890012/

2

RV32I - Стандартный набор целочисленных инструкций RISC-V

https://mpsu.github.io/APS/Other/rv32i.html

3

SCR1 RISC-V Core

GitHub - syntacore/scr1: SCR1 is a high-quality open-source RISC-V MCU core in Verilog

4

Контроллер прерываний

https://wiki.mik32.ru/%D0%9A%D0%BE%D0%BD%D1%82%D1%80%D0%BE%D0%BB%D0%BB%D0%B5%D1%80_%D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9

5

Таймер ядра SCR1_TIMER

https://mik32-amur.ru/Appendix/systim64.html

6

Zadig

https://zadig.akeo.ie/

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

https://syntacore.com/tools/development-tools

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)?

Only registered users can participate in poll. Log in, please.
Вы программировали MIK32 (K1948BK018 )?
40% да4
60% нет6
10 users voted. 1 user abstained.
Only registered users can participate in poll. Log in, please.
Вы программировали ядро RISC-V?
63.64% да7
36.36% нет4
11 users voted. 1 user abstained.
Tags:
Hubs:
+11
Comments16

Articles