STM32 старт с CMSIS

Вот как быстро набирают обороты микроконтроллеры ARM! Уже сложно игнорировать их неоспоримые преимущества. Вот и я ковыряя AVR косо посматривал на STM32, к слову конкретно микроконтроллеры STMicroelectronics далеко не единственные Cortex-M тем более ARM. ARM это огромное семейство различных ядер, и вообще правильно говорить ядро ARM Cortex-M, само по себе ядро это ещё не микроконтроллер и микропроцессор. Но это те ещё дебри которые вылезают в огромную статью, так что не будем об этом. В этой же статье я хотел немного пролить свет на Cortex-M от компании STMicroelectronics, а если ещё точнее то конкретно на пожалуй самый дешевый: STM32F030F4P6. Когда я его покупал (конец 2017г) то цена за 1шт из комплекта 10шт была порядка 35р. 35 КАРЛ! Что вы порекомендуете за 350р/10шт? Ну давайте начнем по порядку.

Я бы начал писать с начала а именно с системы тактирования. Но здесь как то сложно наблюдать что изменилось не так ли? Начать с портов ввода вывода и стандартного Hello World!? Вроде бы тоже как то вырвано из контекста. Попробуем со средств разработки.

Средств разработки существует много, серьезно, прямо целый зоопарк. Среди них существуют три гиганта, IAR Embedded Workbench, Keil uVision, а вот с третьим гигантом немного сложнее, сюда я запишу Eclipse‘о подобные среды а именно среды программирования построенные на базе Eclipse. Выбор пожалуй вопрос индивидуальный, я совсем не хочу разводить холиваров на эту тему, но для читателей скажу что IAR и Keil неплохи своим компилятором(спорный вопрос и если дело дойдет до реальной прошивки я попробую более объективно это показать) но просто отвратны редакторами. Библиотеки. Вот тут я буду нещадно уничтожать как HAL так и SPL, будем писать потому что пишут под тот же AVR, будем дергать регистры, будем писать свои функции. Хватить плодить ардуинщиков, она хороша ТОЛЬКО для старта! Хватит CTRL-C, CTRL-V! Будем думать своей головой. Будет не просто, придется стереть колесико мыши крутя на мониторе Datasheet и Reference Manual. Их наличие перед глазами обязательно! Никаких analogRead и грешных analogWrite. Только регистры лишь хардкор, ниже лишь ASM!

Итак с чего начнем? С покупок! Идем в магазин, интернетмагазин, или куда вы там ходите? И покупаем/заказываем наш микроконтроллер, лучше без отладочной платы.

Далее нам необходим загрузчик, рекомендую приобрести STLink v2, но за неименеем подойдет и прошивка через UART.

А так же необходима плата/переходник TSSOP20-DIP20. Даже без кварца обойдемся! Если есть желание поковырять USB то советую обратить внимание на F042 в том же корпусе. Итак что имеем? 16кб флэш неплохо! 4кб оперативной памяти отлично! Куча периферии мне даже лень перечислять, как вам ПЯТЬ шестнадцати битных таймера + один системный двадцати 4-х битный, и это в таком малыше, дайте два!

Ну и поговорим об окружении/ide. Лично я программирую в Linux, не нужно пугаться, все что там создано универсально. Я попытаюсь описать именно универсальный способ сборки прошивок. Что понадобится? GCC! Это главный козырь *nix систем. О нем можно писать невообразимо много. Конкретно нам понадобится arm-none-eabi-* утилиты, туда же входит отладчик который умеет всё! Утилита OpenOCD версии от 0.9.0, и какой нибудь редактор с подсветкой си, любой который вам удобен. Для тех кто через UART вам же придется довольствоваться stm32 flash loader demonstrator. Для пользователей linux понадобится разрешить юзеру доступ к stlink. Но потому что они продвинутые, описывать не стану, гугл все решит.

Далее возьмем CMSIS библиотеку, это своего рода сборник дэфайнов, который делает работу с регистрами подобной AVR, и даже лучше! Ну например в AVR есть регистр PORTB он определяет что у нас на ножке земля или питание. А в CMSIS это выглядет так: GPIOB->ODR немного сложнее? Но тут сыграло роль бОльшее количество переферии, и даже у портов намного больше регистров. Ну например у того же GPIO аж 11 регистров для настройки порта. Конкретно настройка уровней на выводе занимаются 4 регистра, зачем так много позже разберемся. Начнем! Для начала немного о процессе сборки. GCC компилирует объектные файлы, потом LD занимается распихиванием данных из этих файлов по адресам для конкретного МК. Ну это если коротко. Принимая во внимание огромное разнообразие ARM ядер и ещё большей разновидностью переферии используемой с ними логично предположить что LD не в состоянии знать о том как устроена память в каждом из них, а значит ему нужно это указать и занимается этим специальный скрипт. Привожу скрипт для данного МК выдернутый из SW4STM32.

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x20001000; /* end of RAM */

_Min_Heap_Size = 0; /* required amount of heap */
_Min_Stack_Size = 0x200; /* required amount of stack */

/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
ROM (rx) : ORIGIN = 0x8000000, LENGTH = 16K
}

/* Sections */
SECTIONS
{
/* The startup code into ROM memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >ROM

/* The program code and other data into ROM memory */
.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 */
} >ROM

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

.ARM.extab : {
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} >ROM

.ARM : {
. = ALIGN(4);
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
. = ALIGN(4);
} >ROM

.preinit_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
} >ROM

.init_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
} >ROM

.fini_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4);
} >ROM

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

/* Initialized data sections into RAM memory */
.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> ROM

/* Uninitialized data section into RAM memory */
. = 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 compiler libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}

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

Тут все сложно, ну по крайней мере для меня, ну правда, я не до конца понимаю как это работает. _estack = 0x20001000; эта строка указывает расположение стэка, 0x20000000 это начало оперативной памяти, к ней прибавляем 0х1000 а если в десятеричном, то это 4096, как раз размер оперативной памяти, получается стэк в конце оперативной памяти и растет он к началу. _Min_Heap_Size = 0; и _Min_Stack_Size = 0x200; это соответственно минимальный размер кучи и стэка. К слову! Когда вы видите вывод программы size, которая выводит размер прошивки, вы видите размер вместе со стэком! Что в действительности не означает что прошивка требует именно такого объема флэш! Есть мнение что это одна из причин слухов о большИх размерах кода для ARM. Не верите? Сравните вывод size с бинарным файлом прошивки и количеством прошиваемых байт в МК. RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 4K и ROM (rx)   : ORIGIN = 0x8000000, LENGTH = 16K я думаю тут понятно, это начало RAM и его длинна и начало FLASH и его длинна. .text :{ …… } >ROM этими конструкциями описывается какие секции куда распихивать, тут я уже смутно представляю их работу, поэтому не стану путать догадками, оставлю это вам. В принципе это пока все что нужно знать о скрипте линкера. Я привел свой рабочий скрипт. Замечу ещё что ENTRY(Reset_Handler) это точка входа, а именно то откуда начинает выполняться программа, нам это пригодится.

/**
******************************************************************************
* @file startup_stm32.s
* @author Ac6
* @version V1.0.0
* @date 12-June-2014
******************************************************************************
*/

.syntax unified
.cpu cortex-m0
.thumb

.global g_pfnVectors
.global Default_Handler

/* 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

.equ BootRAM, 0xF1E0F85F
/**
* @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:

/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit

CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4

LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
ldr r2, =_sbss
b LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
movs r3, #0
str r3, [r2]
adds r2, r2, #4

LoopFillZerobss:
ldr r3, = _ebss
cmp r2, r3
bcc FillZerobss

/* Call the application’s entry point.*/
bl main

LoopForever:
b LoopForever

.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
/******************************************************************************
*
* The minimal vector table for a Cortex-M. 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 g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors

g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
.word 0
.word 0
.word 0
.word 0
.word RCC_IRQHandler
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0

/*******************************************************************************
*
* 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 RCC_IRQHandler
.thumb_set RCC_IRQHandler,Default_Handler
/************************ (C) COPYRIGHT Ac6 *****END OF FILE****/

Далее файл startup_stm32.s, стоит понимать что это исходник ASM, то есть это рабочий код, можно прямо на ассемблере прямо в этом файле писать программу. Тут нас интересует .cpu cortex-m0 и .thumb это определения для ASM которые говорят что будут использоваться соответствующие наборы команд. Далее Reset_Handler это то куда МК попадает после Reset. Ещё точнее от сюда начинается выполняться программа, заметьте не с main и не system init а именно с Reset_Handler он же указан в скрипте линкера. Тут происходит вся стартовая инициализация которая тоже для нашего курса не особо интересна, и в конце этого безобразия происходит прыжок в main, в тот самый который нам нужен. Далее идет перечисление аппаратных прерываний, там где стоят нули можно вписывать названия реальных функций на определенные места в соответствии с Reference Manual. Ну например у меня написана функция RCC_IRQHandler заходим в RM на страницу 170 и находим там RCC оно на позиции 4 если считать от SysTick начиная с нуля то это как раз то самое прерывание. Так прописываются все прерывания. RCC_IRQHandler это имя функции! Оно должно быть определено в коде, то есть такая функция должна существовать, если это не нат то мы попадем в .weak   RCC_IRQHandler которая при отсутствии одноименной функции отправит программу в Default_Handler а от туда в бесконечный цикл, но программа скомпилируется! По стартовому коду вроде все. Переходим к могучему make.

#!!!!!!!!!!!!!!!!!!USER CONFIG VARIABLES!!!!!!!!!!!!!!!!
#——————————————————————————-
# Prj and file name
TARGET = template
#Used mcu line
DEFINES += STM32F030
MCU += -mcpu=cortex-m0
#Target file for OpenOCD
OCDTFILE += stlink-v2
#Interface file for OpenOCD
OCDCFILE += stm32f0x
#For Debug add in attach.gdb interface and target file name

#Optimization
OPT += -O0
OPT += -ggdb3

DEFINES += DEBUG
#——————————————————————————-

#Toolchain
#——————————————————————————-
AS = arm-none-eabi-gcc
CC = arm-none-eabi-gcc
LD = arm-none-eabi-g++
CP = arm-none-eabi-objcopy
SZ = arm-none-eabi-size
RM = rm
CXX = arm-none-eabi-g++
GDB = arm-none-eabi-gdb
OCD = openocd
#——————————————————————————-

#OpenOCD config
#——————————————————————————-
OCDCFG = -f interface/$(OCDTFILE).cfg
OCDCFG += -f target/$(OCDCFILE).cfg
OCDCFG += -s scripts
OCDFL = —eval-command=»target remote localhost:3333″
#——————————————————————————-

#startup file
#——————————————————————————-
STARTUP = startup_stm32.s
#——————————————————————————-

#Source path
#——————————————————————————-
SOURCEDIRS := src
#——————————————————————————-

#Header path
#——————————————————————————-
INCLUDES += inc
#——————————————————————————-

#GCC config
#——————————————————————————-
CFLAGS += -mthumb $(MCU)
CFLAGS += -mfloat-abi=soft
CFLAGS += -Wall -pedantic
CFLAGS += $(OPT)
CFLAGS += -fno-builtin
CFLAGS += -Wall -fmessage-length=0
CFLAGS += -ffunction-sections -fdata-sections
CFLAGS += -nostdlib
CFLAGS += -fno-exceptions
CFLAGS += $(addprefix -I, $(INCLUDES))
CFLAGS += $(addprefix -D, $(DEFINES))
#——————————————————————————-

#For C only
#——————————————————————————-
FLAGS = -std=gnu99
#——————————————————————————-
#For C++ only
#——————————————————————————-
CXXFL = -std=gnu++14 -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables
#——————————————————————————-

#Linker script
#——————————————————————————-
LDSCRIPT = LinkerScript.ld
#——————————————————————————-

#Linker config
#——————————————————————————-
LDFLAGS += -nostartfiles -nostdlib -gc-sections -mthumb $(MCU)
LDFLAGS += -T $(LDSCRIPT)
#——————————————————————————-

#ASM config
#——————————————————————————-
AFLAGS += -Wnls -mapcs
#——————————————————————————-

#Obj file list
#——————————————————————————-
OBJS += $(patsubst %.c, %.o, $(wildcard $(addsuffix /*.c, $(SOURCEDIRS))))
OBJS += $(patsubst %.cpp, %.o, $(wildcard $(addsuffix /*.cpp, $(SOURCEDIRS))))
OBJS += $(patsubst %.s, %.o, $(STARTUP))
#——————————————————————————-

#List files for clean project
#——————————————————————————-
MRPROPER += openocd.log
MRPROPER += $(addsuffix /*.o, $(SOURCEDIRS))
MRPROPER += $(addsuffix /*.d, $(SOURCEDIRS))
MRPROPER += $(patsubst %.s, %.o, $(STARTUP))
TOREMOVE += *.elf *.hex *.bin
TOREMOVE += $(TARGET)
TOREMOVE += $(MRPROPER)
#——————————————————————————-

#Make all
#——————————————————————————-
all: size $(TARGET).hex $(TARGET).bin $(TARGET).elf
#——————————————————————————-

#Clean
#——————————————————————————-
clean:
@$(RM) -f $(TOREMOVE)
#——————————————————————————-

#Show programm size
#——————————————————————————-
size: $(TARGET).elf
@echo «—————————————————»
@$(SZ) $(TARGET).elf
#——————————————————————————-

#Compile HEX file
#——————————————————————————-
$(TARGET).hex: $(TARGET).elf
@$(CP) -Oihex $(TARGET).elf $(TARGET).hex
#——————————————————————————-

#Compile BIN file
#——————————————————————————-
$(TARGET).bin: $(TARGET).elf
@$(CP) -Obinary $(TARGET).elf $(TARGET).bin
#——————————————————————————-

#Linking
#——————————————————————————-
$(TARGET).elf: $(OBJS)
@$(LD) $(LDFLAGS) $^ -o $@
#——————————————————————————-

#Compile Obj files from C
#——————————————————————————-
%.o: %.c
@$(CC) $(CFLAGS) $(FLAGS) -MD -c $< -o $@
#——————————————————————————-

#Compile Obj files from C++
#——————————————————————————-
%.o: %.cpp
@$(CXX) $(CFLAGS) $(CXXFL) -MD -c $< -o $@
#——————————————————————————-

#Compile Obj files from asm
#——————————————————————————-
%.o: %.s
@$(AS) $(AFLAGS) -c $< -o $@
#——————————————————————————-

#Load firmware for STM with STLINK V2
#——————————————————————————-
load: $(TARGET).hex
$(OCD) $(OCDCFG) -c «init» -c «reset init» -c «flash write_image erase $(TARGET).hex» -c «reset» -c «shutdown»
#——————————————————————————-

#Run debug with SWD and openocd in PIPE mode
#——————————————————————————-
debug: $(TARGET).elf
$(GDB) $< -x run.gdb
#——————————————————————————-

Это мой собственноручно написанный Makefile. Тут особо и пояснять нечего. Вверху расположились пользовательские определения. Имя выходного файла, дэфайн для CMSIS, тип ядра M0, далее программатор и тип контроллера для его прошивки openocd, потом следуют флаги оптимизации, в GCC config находятся флаги для GCC, -mthumb $(MCU) определяют систему команд, -mfloat-abi=soft говорит о софтверной поддержке чисел с плавающей точкой, -Wall -pedantic всякие варнинги, -fno-exceptions это что то относящиеся к C++ как я понял, потому что валились странный ошибки как будто компилируется под ОС, далее следует определение стандарта C и C++. Вроде бы остальное стандартно для make это поиск файлов для сборки, компиляция линковка, а также получение elf, hex и bin с помощью objcopy. Цель load загружает прошивку в МК, debug запускает openocd и gdb, пока работает не стабильно, лучше ручками.

Перейдем к структуре проэкта, в корневом каталоге распологается makefile startup.s и linkerscript.ld папка inc и src, все по классике, в inc обязательны файлы CMSIS: cmsis_armcc.h, cmsis_armclang.h, cmsis_compiler.h, cmsis_gcc.h, cmsis_iccarm.h, cmsis_version.h, core_cm0.h, stm32f0xx.h, tz_context.h. Для других семейств МК требуются немного другие файлы. В src нужен main.c. Это и есть минимум который получился у меня, вроде не сложно.

Попробуем уже что нибудь? Моргнем?

#include «stm32f0xx.h»

int main(void){
RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
GPIOA->MODER |= GPIO_MODER_MODER0_0;
for(;;){
GPIOA->BSRR = GPIO_BSRR_BR_0;
for(int i = 0; i < 60000; i++);
GPIOA->BSRR = GPIO_BSRR_BS_0;
for(int i = 0; i < 60000; i++);
}
}

Просто? Максимально! Ну и где теперь HAL и SPL? Нука нука расскажите мне о прелести абстракций. Да есть трудности с файлами линкера сборщика и стартовым файлом. Но это мелочи. Что тут в коде происходит? Подключение заголовков,я уж извините, разжевывать не буду. В функции main в которую нас перекидывает как вы помните из Reset_Handler первой строкой включается порт A следующей строкой настраивается на выход пин ноль порта А, Внимание! Тут не уместно использовать присвоение потому что на этом же порту находятся пины SWD, поставите равно и придется поднимать камень с помощью connect under reset в программе STLink, как это работает в OpenOCD я так и не понял. Ну а в цикле все по накатанной сброс пина в данном случае происходит через регистр BSRR, это атомарный регистр. Атомарный означает что присвоением мы затронем лишь необходимые пины, остальные останутся без изменений, это очень удобно и к тому же сокращает количество команд ассемблера потому что мы лишь пишем в регистр, а при использовании битовых масок мы сначала читаем потом применяем маску и далее пишем обратно. Задержка выполнена простым циклом потому что CMSIS не имеет реализацию функции delay, тут уж кто как может, кто то циклы использует, лично я для этого настраивал SysTick (это такой системный таймер который может лишь считать, и он общий для ВСЕХ Cortex-M потому что это часть ядра). Компиляция происходит командой make all очистка make clean загрузка make debug. Теперь немного о размере программа size говорит что размер 1104байт а вот бинарник занимает 592 байта, секция BSS которая равна 512 байтам это стек, он не занимает флеш, он занимает оперативную память стеком. Попробуем оптимизацию? В файле makefile меняем параметр OPT с O0 на Os. Чуть уменьшился, получаем 524 байта, много? НЕТ! В архиве мой рабочий минимальный проэкт, предположу что для Windows пользователей нужно лишь прописать пути до arm-none-eabi-*. Вот пожалуй и все что я хотел рассказать, если будет интересно то я продолжу.

Прикрепленные файлы:

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

Ваш адрес email не будет опубликован.