Год назад я написал статью об отладке STM32 микроконтроллеров из под VSCode, с компиляцией в GCC и сборкой с помощью CMake. А в декабре мне в руки попали две единицы отечественных микроконтроллеров К1986ВЕ92FI (MDR1211FI1). Производитель имеет свою библиотеку SPL на C, а также неплохую базу примеров инициализации и применения различной периферии в Keil и IAR; однако я, average C++20+ enjoyer , решил попробовать перенести свой тулчейн на новое железо.
Отладочная плата
Разработанная плата представляет собой стандартный "джентльменский набор" – порты программирования, USB-C, три светодиода (индикатор питания, индикатор режима STANDBY и управляемый светодиод) и два кварцевых резонатора под HSE и LSE.
Программатор
Для отладки используется китайский 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 проекте необходимо проделать ряд действий
В настройках CMake Tools в разделе "Copy Compile Commands" прописать
${workspaceRoot}/compile_commands.json
В папку .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",
]
}
В CMakeLists.txt добавить команду
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
P.P.S
почему C++
На хабре уже не раз поднималась тема удобства и способов использования C++ в микроконтроллерах - например, в статьях уважаемого@lamerok. Для себя же я выделил следующие преимущества:
Классы позволяют использовать более простую структуру проекта, а также более наглядно "демонстрируют" работу с различными единицами одних и тех же сущностей
Благодаря шаблонам можно управлять настройками структур/классов/функций ещё на уровне компиляции. Один из простых примеров управление размером буфера в драйвере UART контроллера, притом что сам буфер инкапсулирован внутри класса и при этом выделяется статически на этапе компиляции, а не динамически при старте программы
В C++20 появились концепты, благодаря чему можно ещё более просто абстрагироваться от различных низкоуровневых реализаций интерфейсов, что обеспечивает более простую миграцию с одного микроконтроллера на другой
Используя возможности метапрограммирования можно существенно уменьшить размер прошивки, т.к. часть вычислений будет выполнена ещё на этапе компиляции, а в программе будет сохраняться лишь результат вычислений