Как стать автором
Обновить

Миландр + GCC + VSCode. Пробуем мигать светодиодом на отечественном ARM32 микроконтроллере

Уровень сложностиСредний
Время на прочтение15 мин
Количество просмотров11K

Год назад я написал статью об отладке STM32 микроконтроллеров из под VSCode, с компиляцией в GCC и сборкой с помощью CMake. А в декабре мне в руки попали две единицы отечественных микроконтроллеров К1986ВЕ92FI (MDR1211FI1). Производитель имеет свою библиотеку SPL на C, а также неплохую базу примеров инициализации и применения различной периферии в Keil и IAR; однако я, average C++20+ enjoyer , решил попробовать перенести свой тулчейн на новое железо.

Отладочная плата

Разработанная плата представляет собой стандартный "джентльменский набор" – порты программирования, USB-C, три светодиода (индикатор питания, индикатор режима STANDBY и управляемый светодиод) и два кварцевых резонатора под HSE и LSE.

P.S.У стабилизатора напряжения перепутаны 1 и 3 выводы, так что он установлен пузом к платеP.S.2 Будьте прилежны при пайке QFN микросхем, иначе есть риск обнаружить неработу из-за того, что ваш микроконтроллер чуть менее чем полностью опирается на термопад, тогда как контакты по периметру просто висят в воздухе
P.S.У стабилизатора напряжения перепутаны 1 и 3 выводы, так что он установлен пузом к плате
P.S.2 Будьте прилежны при пайке QFN микросхем, иначе есть риск обнаружить неработу из-за того, что ваш микроконтроллер чуть менее чем полностью опирается на термопад, тогда как контакты по периметру просто висят в воздухе

Программатор

Для отладки используется китайский J-Link V9/V11. Детально процесс настройки ПО SEGGER описан на сайте производителя МК. Приведу здесь только содержимое файлов, что расположены по пути %APPDATA%\SEGGER\JLinkDevices.

спойлер

Содержимое файла JLinkDevices.xml

<Device>
      <ChipInfo Vendor="Milandr"
       Name="K1986BE1QI"
       WorkRAMAddr="0x20100000"
       WorkRAMSize="0x4000"
       Core="JLINK_CORE_CORTEX_M1" />
       <FlashBankInfo Name="K1986BE1QI Flash"
       BaseAddr="0x0"
       MaxSize="0x20000"
       Loader="Devices\Milandr\MDR32F1QI.FLM"
       LoaderType="FLASH_ALGO_TYPE_OPEN"
       AlwaysPresent="1"/>
 </Device>
 <Device>
       <ChipInfo Vendor="Milandr"
        Name="K1986BE92QI"
        WorkRAMAddr="0x20000000"
        WorkRAMSize="0x8000"
        Core="JLINK_CORE_CORTEX_M3"/>
        <FlashBankInfo Name="K1986BE92QI Flash"
        BaseAddr="0x8000000"
        MaxSize="0x20000"
        Loader="Devices\Milandr\MDR32F9Q2I.FLM"
        LoaderType="FLASH_ALGO_TYPE_OPEN"
        AlwaysPresent="1" />
 </Device>

Содержимое файла Devices.xml

<Database>
<Device>
       <ChipInfo Vendor="Milandr"
        Name="K1986BE1QI"
        WorkRAMAddr="0x20000000"
        WorkRAMSize="0x8000"
        Core="JLINK_CORE_CORTEX_M3"/>
        <FlashBankInfo Name="K1986BE1QI Flash"
        BaseAddr="0x8000000"
        MaxSize="0x20000" 
Loader="C:\Users\Filipp\AppData\Roaming\SEGGER\JLinkDevices\Milandr\MDR32F1QI.FLM"
        LoaderType="FLASH_ALGO_TYPE_OPEN"
        AlwaysPresent="1" />
 </Device>
 <Device>
       <ChipInfo Vendor="Milandr"
        Name="K1986BE92QI"
        WorkRAMAddr="0x20000000"
        WorkRAMSize="0x8000"
        Core="JLINK_CORE_CORTEX_M3"/>
        <FlashBankInfo Name="K1986BE92QI Flash"
        BaseAddr="0x8000000"
        MaxSize="0x20000" 
Loader="C:\Users\Filipp\AppData\Roaming\SEGGER\JLinkDevices\Milandr\MDR32F9Q2I.FLM"
        LoaderType="FLASH_ALGO_TYPE_OPEN"
        AlwaysPresent="1" />
 </Device>
</Database>

Создание проекта

В дальнейшем описании предполагается, что у вас установлены приложения, и расширения VSCode, указанные в предыдущей статье. Также необходимо скачать с сайта производителя Software pack для Keil MDK 5 и Standard Peripherals Library.

В корень проекта скопируем файлMDR32F9Q2I.svd (брать из папки Standard Peripherals Library\SVD\MDR32FxQI), и папку Standard Peripherals Library\Libraries\SPL\MDR32FxQI, которую переименуем в MDR32FxQI_SPL. Также создадим две директории: CoreSupport и DeviceSupport. В папку CoreSupport скопируем все файлы из Standard Peripherals Library\Libraries\CMSIS\MDR32FxQI\CoreSupport\CM3, а в DeviceSupport – все файлы из Standard Peripherals Library\Libraries\CMSIS\MDR32FxQI\DeviceSupport\MDR32F9Q2I\inc и Standard Peripherals Library\Libraries\CMSIS\MDR32FxQI\DeviceSupport\MDR32F9Q2I\startup.

На данный момент директория проекта выглядит следующим образом:

./:
MDR32F9Q2I.svd
./CoreSupport/:
core_cm3.h
core_cmFunc.h
core_cmInstr.h
./DeviceSupport/:
MDR32F9Q2I.h
system_MDR32F9Q2I.c
system_MDR32F9Q2I.h
./MDR32FxQI_SPL/:
inc/
src/
MDR32FxQI_config.h

Минимальный код

Для запуска МК также потребуются linker-скрипт и startup-файл, назовём их MDR32F9Q2I.ld и startup_gcc_MDR32F9Q2I.s соответственно. За основу возьмём файлы для STM32F103C8. Подробное описание того, что в них происходит можно увидеть на примерах из видео1 и видео2.

MDR32F9Q2I.ld
/*
Abstract: Linker script for MDR32F9Q2I
128 Kb FLASH + 32 Kb RAM 
*/

ENTRY(Reset_handler);

MEMORY
{
FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM(xrw)  : ORIGIN = 0x20000000, LENGTH = 32K
}

_estack = ORIGIN(RAM) + LENGTH(RAM);    /* end of RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data : 
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM AT> FLASH

  
  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}

startup_gcc_MDR32F9Q2I.s
.syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb

/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss

.global __Vectors
.global Default_Handler

/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
  .section .isr_vector,"a",%progbits
  .type __Vectors, %object
  .size __Vectors, .-__Vectors

__Vectors:
  .word _estack             /* Top of Stack */
  .word Reset_Handler       /* Reset Handler */
  .word NMI_Handler         /* NMI Handler */
  .word HardFault_Handler   /* Hard Fault Handler */
  .word MemManage_Handler   /* MPU Fault Handler */
  .word BusFault_Handler    /* Bus Fault Handler */
  .word UsageFault_Handler  /* Usage Fault Handler */
  .word 0                   /* Reserved */
  .word 0                   /* Reserved */
  .word 0                   /* Reserved */
  .word 0                   /* Reserved */
  .word SVC_Handler         /* SVCall Handler */
  .word DebugMon_Handler    /* Debug Monitor Handler */
  .word 0                   /* Reserved */
  .word PendSV_Handler      /* PendSV Handler */
  .word SysTick_Handler     /* SysTick Handler */
  /* External Interrupts*/
  .word CAN1_IRQHandler            /*   0 CAN1 Handler */
  .word CAN2_IRQHandler            /*   1 CAN2 Handler */
  .word USB_IRQHandler             /*   2 USB Host Handler */
  .word 0                          /*     Reserved */
  .word 0                          /*     Reserved */
  .word DMA_IRQHandler             /*   5 DMA Handler */
  .word UART1_IRQHandler           /*   6 UART1 Handler */
  .word UART2_IRQHandler           /*   7 UART2 Handler */
  .word SSP1_IRQHandler            /*   8 SSP1 Handler */
  .word 0                          /*     Reserved */
  .word I2C_IRQHandler             /*  10 I2C Handler */
  .word POWER_IRQHandler           /*  11 POWER Handler */
  .word WWDG_IRQHandler            /*  12 WWDG Handler */
  .word 0                          /*     Reserved */
  .word Timer1_IRQHandler          /*  14 Timer1 Handler */
  .word Timer2_IRQHandler          /*  15 Timer2 Handler */
  .word Timer3_IRQHandler          /*  16 Timer3 Handler */
  .word ADC_IRQHandler             /*  17 ADC Handler */
  .word 0                          /*     Reserved */
  .word COMPARATOR_IRQHandler      /*  19 Comparator Handler */
  .word SSP2_IRQHandler            /*  20 SSP2 Handler */
  .word 0                          /*     Reserved */
  .word 0                          /*     Reserved */
  .word 0                          /*     Reserved */
  .word 0                          /*     Reserved */
  .word 0                          /*     Reserved */
  .word 0                          /*     Reserved */
  .word BACKUP_IRQHandler          /*  27 BKP Handler */
  .word EXT_INT1_IRQHandler        /*  28 EXT_INT1 Handler */
  .word EXT_INT2_IRQHandler        /*  29 EXT_INT2 Handler */
  .word EXT_INT3_IRQHandler        /*  30 EXT_INT3 Handler */
  .word EXT_INT4_IRQHandler        /*  31 EXT_INT4 Handler */


/*******************************************************************************
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called.
 * @param  None
 * @retval : None
*******************************************************************************/

  .section .text.Reset_Handler
  .weak Reset_Handler
  .type Reset_Handler, %function
Reset_Handler:

/* Call the clock system initialization function.*/
    bl  SystemInit

/* Copy the data segment initializers from flash to SRAM */
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  movs r3, #0
  b LoopCopyDataInit

CopyDataInit:
  ldr r4, [r2, r3]
  str r4, [r0, r3]
  adds r3, r3, #4

LoopCopyDataInit:
  adds r4, r0, r3
  cmp r4, r1
  bcc CopyDataInit
  
/* Zero fill the bss segment. */
  ldr r2, =_sbss
  ldr r4, =_ebss
  movs r3, #0
  b LoopFillZerobss

FillZerobss:
  str  r3, [r2]
  adds r2, r2, #4

LoopFillZerobss:
  cmp r2, r4
  bcc FillZerobss

/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
  bl main
  bx lr
.size Reset_Handler, .-Reset_Handler




/*******************************************************************************
 * @brief  This is the code that gets called when the processor receives an
 *         unexpected interrupt.  This simply enters an infinite loop, preserving
 *         the system state for examination by a debugger.
 *
 * @param  None
 * @retval : None
*******************************************************************************/
    .section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
  b Infinite_Loop
  .size Default_Handler, .-Default_Handler


/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/

  .weak NMI_Handler
  .thumb_set NMI_Handler,Default_Handler

  .weak HardFault_Handler
  .thumb_set HardFault_Handler,Default_Handler

  .weak MemManage_Handler
  .thumb_set MemManage_Handler,Default_Handler

  .weak BusFault_Handler
  .thumb_set BusFault_Handler,Default_Handler

  .weak UsageFault_Handler
  .thumb_set UsageFault_Handler,Default_Handler

  .weak SVC_Handler
  .thumb_set SVC_Handler,Default_Handler

  .weak DebugMon_Handler
  .thumb_set DebugMon_Handler,Default_Handler

  .weak PendSV_Handler
  .thumb_set PendSV_Handler,Default_Handler

  .weak SysTick_Handler
  .thumb_set SysTick_Handler,Default_Handler

  .weak CAN1_IRQHandler
  .thumb_set CAN1_IRQHandler,Default_Handler
  
  .weak CAN2_IRQHandler
  .thumb_set CAN2_IRQHandler,Default_Handler

  .weak USB_IRQHandler
  .thumb_set USB_IRQHandler,Default_Handler

  .weak DMA_IRQHandler
  .thumb_set DMA_IRQHandler,Default_Handler

  .weak UART1_IRQHandler
  .thumb_set UART1_IRQHandler,Default_Handler

  .weak UART2_IRQHandler
  .thumb_set UART2_IRQHandler,Default_Handler

  .weak SSP1_IRQHandler
  .thumb_set SSP1_IRQHandler,Default_Handler

  .weak I2C_IRQHandler
  .thumb_set I2C_IRQHandler,Default_Handler


  .weak POWER_IRQHandler
  .thumb_set POWER_IRQHandler,Default_Handler

  .weak WWDG_IRQHandler
  .thumb_set WWDG_IRQHandler,Default_Handler

  .weak Timer1_IRQHandler
  .thumb_set Timer1_IRQHandler,Default_Handler

  .weak Timer2_IRQHandler
  .thumb_set Timer2_IRQHandler,Default_Handler

  .weak Timer3_IRQHandler
  .thumb_set Timer3_IRQHandler,Default_Handler

  .weak ADC_IRQHandler
  .thumb_set ADC_IRQHandler,Default_Handler

  .weak COMPARATOR_IRQHandler
  .thumb_set COMPARATOR_IRQHandler,Default_Handler

  .weak SSP2_IRQHandler
  .thumb_set SSP2_IRQHandler,Default_Handler

  .weak BACKUP_IRQHandler
  .thumb_set BACKUP_IRQHandler,Default_Handler

  .weak EXT_INT1_IRQHandler
  .thumb_set EXT_INT1_IRQHandler,Default_Handler

  .weak EXT_INT2_IRQHandler
  .thumb_set EXT_INT2_IRQHandler,Default_Handler

  .weak EXT_INT3_IRQHandler
  .thumb_set EXT_INT3_IRQHandler,Default_Handler

  .weak EXT_INT4_IRQHandler
  .thumb_set EXT_INT4_IRQHandler,Default_Handler

main.c файл создадим максимально простым

#define __ASM __asm
#define __NOP() __ASM volatile("nop")

int main(){
  while(1){
    __NOP();
  }
}

Настройка VSCode

Для компиляции и запуска проекта потребуются также слегка изменить файлы .vscode/launch.json и .vscode/tasks.json

launch.json
{
    "configurations": [
        {
            "name": "Cortex Debug",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "jlink",
            "cwd": "${workspaceRoot}",
            // "executable": "build/mdr_test.elf",
            "executable": "${command:cmake.launchTargetPath}",
            "preLaunchTask": "CMake: build",
            "preRestartCommands": [
                "load",
                "enable breakpoint",
                "monitor reset"
            ],
            "liveWatch": {
                "enabled": true,
                "samplesPerSecond": 2
            },
            "runToEntryPoint": "main",
            "showDevDebugOutput": "raw",
            "device": "K1986BE92QI",
            "svdFile": "${workspaceRoot}/MDR32F9Q2I.svd",
        }
    ]
}

tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "cmake",
      "label": "CMake: build",
      "command": "build",
      "targets": [
          "ALL_BUILD"
      ],
      "problemMatcher": [],
      "group": "build"
    }
  ]
}

CMake

На GitHub можно найти шаблон для CMakeLists.txt под ARM GCC проекты. Возьмём его за основу и слегка модифицируем.

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)
# specify cross compilers and tools
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_AR arm-none-eabi-ar)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(SIZE arm-none-eabi-size)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-register")
# project settings
project(mldr_test C CXX ASM)

add_compile_options(-mfloat-abi=soft)
add_compile_options(-mcpu=cortex-m3 -mthumb)
add_compile_options(-ffunction-sections -fdata-sections -fno-common -fmessage-length=0)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions -fno-rtti>)

include_directories(
        ${CMAKE_SOURCE_DIR}/CoreSupport
        ${CMAKE_SOURCE_DIR}/DeviceSupport
        ${CMAKE_SOURCE_DIR}/MDR32FxQI_SPL
)


set(SOURCES
    startup_gcc_MDR32F9Q2I.s
    DeviceSupport/system_MDR32F9Q2I.c
    main.c
)

set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/MDR32F9Q2I.ld)

add_link_options(-Wl,-gc-sections,--print-memory-usage,-Map=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map)
add_link_options(-mcpu=cortex-m3 -mthumb -mthumb-interwork)
add_link_options(-T ${LINKER_SCRIPT})

add_executable(${PROJECT_NAME}.elf ${SOURCES} ${LINKER_SCRIPT})
target_compile_definitions(${PROJECT_NAME}.elf PRIVATE
    USE_MDR32F9Q2I
)
set_target_properties(${PROJECT_NAME}.elf PROPERTIES
EXPORT_COMPILE_COMMANDS ON
CXX_STANDARD 20 
CXX_STANDARD_REQUIRED True
)
# Generate .hex and .bin files
set(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex)
set(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin)
add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -Oihex $<TARGET_FILE:${PROJECT_NAME}.elf> ${HEX_FILE}
    COMMAND ${CMAKE_OBJCOPY} -Obinary $<TARGET_FILE:${PROJECT_NAME}.elf> ${BIN_FILE}
    COMMENT "Building ${HEX_FILE} Building ${BIN_FILE}"
)

Модификация файлов производителя

Для успешного запуска необходимо добавить определения для gcc в файл system_MDR32F9Q2I.c. Находим следующий код:

/** @addtogroup __MDR32F9Q2I_System_Private_Defines MDR32F9Q2I System Private Defines
  * @{
  */

#if defined (__ARMCC_VERSION) /* ARM Compiler */
    extern uint32_t __Vectors;
    #define __VECTOR_TABLE_ADDRESS &__Vectors
#elif defined (__ICCARM__) /* IAR Compiler */
    extern uint32_t __vector_table;
    #define __VECTOR_TABLE_ADDRESS &__vector_table
#elif defined (__CMCARM__) /* Phyton CMC-ARM Compiler */
    extern uint32_t __Vectors;
    #define __VECTOR_TABLE_ADDRESS &__Vectors
#endif

и добавляем перед #endif

#elif defined (__GNUC__) /* ARM GCC */
    extern uint32_t __Vectors;
    #define __VECTOR_TABLE_ADDRESS &__Vectors

Аналогичным образом нужно модифицировать файл MDR32FxQI_config.h, чтобы получить следующий код:

#if defined (__ICCARM__) /* IAR Compiler */
    #define __attribute__(name_section)
    #if defined (USE_MDR32F1QI)
        #pragma section = "EXECUTABLE_MEMORY_SECTION"
        #define IAR_SECTION(section) @ section
    #elif defined (USE_MDR32F9Q2I)
        #define IAR_SECTION(section)
    #endif
    #define __RAMFUNC __ramfunc
#endif
#if defined (__CMCARM__) /* Phyton CMC-ARM Compiler */
    #define __attribute__(name_section)
    #define IAR_SECTION(section)
    #define __RAMFUNC __ramfunc
#endif
#if defined (__ARMCC_VERSION) /* ARM Compiler */
    #define IAR_SECTION(section)
    #define __RAMFUNC
#endif
#if defined (__GNUC__) /* ARM GCC */
    #define IAR_SECTION(section)
    #define __RAMFUNC
#endif

можем запускаться

Пробуем мигать светодиодом

Возьмём за основу проект HelloWord из инструкции с сайта Миландра.

Добавим необходимые директории и файлы в CMakeLists.txt

include_directories(
        ${CMAKE_SOURCE_DIR}/CoreSupport
        ${CMAKE_SOURCE_DIR}/DeviceSupport
        ${CMAKE_SOURCE_DIR}/MDR32FxQI_SPL
        ${CMAKE_SOURCE_DIR}/MDR32FxQI_SPL/inc
)
set(SOURCES
    startup_gcc_MDR32F9Q2I.s
    DeviceSupport/system_MDR32F9Q2I.c
    main.c
    MDR32FxQI_SPL/src/MDR32FxQI_rst_clk.c
    MDR32FxQI_SPL/src/MDR32FxQI_port.c
)

А также модифицируем main.c под новые задачи

#include <MDR32FxQI_port.h>
#include <MDR32FxQI_rst_clk.h>

void Delay(int waitTicks);

int main() {
  // Заводим структуру конфигурации вывода(-ов) порта GPIO
  PORT_InitTypeDef GPIOInitStruct;
  // Включаем тактирование порта C
  RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE);
  // Инициализируем структуру конфигурации вывода(-ов) порта значениями по
  // умолчанию
  PORT_StructInit(&GPIOInitStruct);
  // Изменяем значения по умолчанию на необходимые нам настройки
  GPIOInitStruct.PORT_Pin = PORT_Pin_2;
  GPIOInitStruct.PORT_OE = PORT_OE_OUT;
  GPIOInitStruct.PORT_SPEED = PORT_SPEED_SLOW;
  GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL;
  // Применяем заполненную нами структуру для PORTC.
  PORT_Init(MDR_PORTC, &GPIOInitStruct);
  //
  while (1) {
    if (PORT_ReadInputDataBit (MDR_PORTC, PORT_Pin_2) == 0){
      PORT_SetBits(MDR_PORTC, PORT_Pin_2); // светоидиот на нашей плате
    }else{
      PORT_ResetBits(MDR_PORTC, PORT_Pin_2);
    }
    // Задержка
    Delay(100000);
  }
}

// Простейшая функция задержки, позднее мы заменим ее на реализацию через таймер
void Delay(int waitTicks) {
  int i;
  for (i = 0; i < waitTicks; i++) {
    __NOP();
  }
}

Включаемся, прошиваемся, радуемся красивой мигалке

красивое
красивое

Пробуем собраться с C++

Не мудрствуя лукаво, переименуем main.c в main.cpp и попробуем собрать тот же самый проект. Наблюдаем exit code 1, а также несколько ошибок следующего вида:

[build] C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/thumb/v7-m/nofp\libg.a(libc_a-abort.o): in function `abort':
[build] abort.c:(.text.abort+0xa): undefined reference to `_exit'
[build] C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/thumb/v7-m/nofp\libg.a(libc_a-signalr.o): in function `_kill_r':
[build] signalr.c:(.text._kill_r+0xe): undefined reference to `_kill'
[build] C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/thumb/v7-m/nofp\libg.a(libc_a-signalr.o): in function `_getpid_r':

Дело в том, что в стандартных библиотеках arm-none-eabi-gcc не предусмотрены реализации этих функций. Чтобы решить данную проблему необходимо подключить дополнительно библиотеки NoSys и Nano. Добавим в CMakeLists.txt следующие строки (рекомендую расположить их там же, где уже расположены существующие add_compile_options и add_link_options):

add_compile_options(
    $<$<C_COMPILER_ID:GNU>:--specs=nosys.specs>
    $<$<C_COMPILER_ID:GNU>:--specs=nano.specs>
)

add_link_options(
    $<$<C_COMPILER_ID:GNU>:--specs=nosys.specs>
    $<$<C_COMPILER_ID:GNU>:--specs=nano.specs>
)

Пробуем. Видим, что компилятор выдаёт варнинги

[build] C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/../lib/gcc/arm-none-eabi/13.2.1/thumb/v7-m/nofp\libg_nano.a(libc_a-readr.o): in function `_read_r':
[build] readr.c:(.text._read_r+0x10): warning: _read is not implemented and will always fail

однако проект собирается и успешно запускается

P.S.

Настройка clangd в VSCode

Clangd представляет собой language server наподобие Microsoft IntelliSense, однако я нахожу его более удобным ввиду дополнительных возможностей по настройке форматирования кода и проверки синтаксиса (.clang-format и .clang-tidy).
Чтобы он корректно работал в вашем vscode проекте необходимо проделать ряд действий

  1. В настройках CMake Tools в разделе "Copy Compile Commands" прописать
    ${workspaceRoot}/compile_commands.json

  2. В папку .vscode поместить файл settings.json следующего содержания (имя пользователя не забудьте заменить на своё)

{
    "cortex-debug.variableUseNaturalFormat": false,
    "clangd.arguments": [
        "--enable-config",
        "--log=verbose",
        "-header-insertion=never",
        "--query-driver=C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/arm-none-eabi-gcc.exe,C:/Users/Filipp/scoop/apps/gcc-arm-none-eabi/current/bin/arm-none-eabi-g++.exe",
      ]
}
  1. В CMakeLists.txt добавить команду

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

P.P.S

почему C++

На хабре уже не раз поднималась тема удобства и способов использования C++ в микроконтроллерах - например, в статьях уважаемого@lamerok. Для себя же я выделил следующие преимущества:

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

  2. Благодаря шаблонам можно управлять настройками структур/классов/функций ещё на уровне компиляции. Один из простых примеров управление размером буфера в драйвере UART контроллера, притом что сам буфер инкапсулирован внутри класса и при этом выделяется статически на этапе компиляции, а не динамически при старте программы

  3. В C++20 появились концепты, благодаря чему можно ещё более просто абстрагироваться от различных низкоуровневых реализаций интерфейсов, что обеспечивает более простую миграцию с одного микроконтроллера на другой

  4. Используя возможности метапрограммирования можно существенно уменьшить размер прошивки, т.к. часть вычислений будет выполнена ещё на этапе компиляции, а в программе будет сохраняться лишь результат вычислений

Теги:
Хабы:
Всего голосов 23: ↑21 и ↓2+28
Комментарии51

Публикации

Истории

Работа

QT разработчик
5 вакансий
Программист C++
108 вакансий
DevOps инженер
42 вакансии

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань