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

от автора

Год назад я написал статью об отладке 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. Используя возможности метапрограммирования можно существенно уменьшить размер прошивки, т.к. часть вычислений будет выполнена ещё на этапе компиляции, а в программе будет сохраняться лишь результат вычислений


ссылка на оригинал статьи https://habr.com/ru/articles/788776/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *