Реклама ⓘ
Главная » Микроконтроллеры
Призовой фонд
на апрель 2024 г.
1. 100 руб.
От пользователей

Реклама ⓘ

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кб оперативной памяти отлично! Куча периферии мне даже лень перечислять, как вам ПЯТЬ шестнадцати битных таймера + один системный двадцати четырех битный, и это в таком малыше, дайте два!

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

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

Теги:

Опубликована: 0 0
Я собрал 0 0
x

Оценить статью

  • Техническая грамотность
  • Актуальность материала
  • Изложение материала
  • Полезность устройства
  • Повторяемость устройства
  • Орфография
0

Средний балл статьи: 0 Проголосовало: 0 чел.

Комментарии (28) | Я собрал (0) | Подписаться

0
Публикатор #
На форуме автоматически создана тема для обсуждения статьи.
Ответить
+1
BARS_ #
но просто отвратны редакторами.
И что же плохого в Keil (про IAR говорить не буду, писал в нем для STM8, редактор действительно ужасен)? Редактор там мало чем отличается от того же Eclipse. Только сама IDE работает быстрее и стабильнее, чем выкидыши на основе Eclipse. А еще обладает такой замечательной вещью, как отладчик.

Ну и МК лучше взять сразу 100 или 103 серии в корпусе LQFP48, распаянный на плате. Очень удобная вещь. Запитал от USB и программь сколько влезет. Цена три копейки.
Ответить
0

[Автор]
CoBa31Rus #
Смешно, предметно есть то чего не умеет Eclipse? И да на мой взгляд редактор ужасен, чего только стоит дерево проэкта? Eclipse удобнее в разы, да он монструозен не спорю. Есть у меня и F103CB и F103VE, разница в написании кода и построении проэкта не существенная, .
Ответить
+1
BARS_ #
предметно есть то чего не умеет Eclipse?
Как минимум наличие нормального отладчика и работой всего "из коробки" без танцев с бубном и кривой работой java. или вы не пользуетесь отладчиком? О том, что сборка проекта выполняется на порядок быстрее и говорить не буду. Плюс абсолютно нормальный перенос проекта в компа на комп, достаточно скинуть папку и открыть файл проекта. А вот в эклипсе эта функция отсутствует напрочь. Я вот тоже баловался Eclipse, но после того, как попробовал Keil возвращаться вообще не хочется.

редактор ужасен
Повторю еще раз свой вопрос. Что в нем ужасного? Дерево проекта? А что с ним не так? Подключенные к проекту файлы отображаются, а полное дерево даром не нужно, достаточно списка функций.

разница в написании
Речь не про разницу в коде, а в содержании самого МК.
Ответить
0
Илья #
В Keil нормальный редактор? Через час-два работы с ним кровь из глаз уже не остановить. После работы в Visual Studio на этот keil и iar даже смотреть не хочется.
Ну а если GCC не нравится (хотя это скорее показатель кривых рук), то качает с сайта ARM родной чисто arm-овский тулчейн и радуетесь такому же отличному компилятору, что и в кейл . Вот только он бесплатен, что как бы намекает.

Что такого в отладчике keil-а особенного то? Тот же trueSTUDIO умеет все тоже самое и даже больше. openOCD в связке с VS2017 так же по функционалу превосходит keil.
Ответить
+1
BARS_ #
Еще раз повторю вопрос, что не так в редакторе Keil именно для ARM? Он вообще ничем не отличается от Eclipse. Вы в нем явно не работали даже. Подсветка синтаксиса ровно такая же, автозавершение один в один, проверка кода на лету тоже есть. Это первое. Второе, речь про отсутствие отладчика в Eclipse, без которого IDE даром не нужна. А в Keil в отладчике ничего особенного, он просто там есть. Зато при каждой установки eclipse нужны танцы с бубном, чтобы компилятор нормально завелся. Про перенос проекта вы тоже отмалчиваетесь, ибо сказать нечего.
Ответить
0
Артем #
Есть отладка в эклипсе, ее руками прикручивать надо. Код комплит в Кейла научился члены структуры дополнять?
Ответить
0

[Автор]
CoBa31Rus #
Отладка? А что соб-но с ней не так, чего там нет? Переносимость? Смешно! Теже самые ctrl-c ctrl-v. Ужасен Keil, например, сборка проекта перед тем как все проидексируется(даже Geany такого себе не позволяет), кровь из глаз от вида редактора, напрочь не логичные настройки проекта, дерево проекта это вообще что то, то что на диске и в дереве может отличаться на столько что человек просто не разберется. Я и Эклипсом не пользуюсь. make all наше все, можем сравнить скорость если есть желание.
Ответить
0
BARS_ #
ут не уместно использовать присвоение так как на этом же порту находятся пины SWD, поставите равно и придется поднимать камень с помощью connect under reset
Поставите равно и ничего не изменится. Пины SWD имеют приоритет над этой настройкой. Пока SWD не отключен в соответствующем регистре МК, эта настройка не воспринимается пинами SWD. Даже если включить периферию, которая есть на этих ножках, то все равно ничего не изменится. периферия просто не получит доступ к пинам.
Ответить
0

[Автор]
CoBa31Rus #
Ага да, типо в инете мало вопросов по этому поводу, ну возьмите и первой строчкой в main запилите присвоение всему порту.
Ответить
0
BARS_ #
Представьте себе, делал так, случайно, на этапе изучения МК. Более того, еще и периферию включал, висящую на выводах SWD и JTAG. Ну и ничего не происходило. Тупо не работала периферия. А вот если в регистре
AFIO->MAPR
выставить вот такую маску
AFIO_MAPR_SWJ_CFG_DISABLE
то только тогда SWD отрубится. При этом данное действие надо произвести ДО настройки портов.
Ответить
0

[Автор]
CoBa31Rus #
Может от контроллера зависит? Мне вот после присвоения прямо в момент написания приходилось настраивать на connect under reset, swd конечно работает но он слушает пины до этой самой строчки и 4 провода уже не помогут нужен физический reset либо кнопкой либо 5м проводом.
Ответить
0
BARS_ #
Только что проверил на 103 камне. SWD как работал, так и работает.
Ответить
0

[Автор]
CoBa31Rus #
Ок проверю на 103м дома.
Ответить
0

[Автор]
CoBa31Rus #
Действительно работает, а вот на 030 нет. Но там и GPIO разный набор регистров имеют
Ответить
0
BARS_ #
Да, в 030 он на 303 и 205 ядро похож. Надо будет на них проверить
Ответить
0
Влад #
1. Если начинать, то лучше с готовой отладочной платы, например на али полно на основе STM32F103C8 по цене около 100р. Все таки ничего с аппаратной частью в этом случае проблем будет меньше.
2. Убивать HAL и SPL лучше после того, как научишься с ними работать. Все таки они создавались для облегчения работы с CMSIS.
И по сути своей HAL и SPL это только обертки над указателями из CMSIS..
3. Про редакторы на 100% соглашусь с Ильей! Visual studio от майкрософт - пример хорошего редактора. По сравнению с ним редактор Keil`а полное гамно.
Ответить
0
BARS_ #
2. Убивать HAL и SPL лучше после того, как научишься с ними работать. Все таки они создавались для облегчения работы с CMSIS.
Никакого облегчения они не дают. Скорее наоборот.
Ответить
0
Влад #
Ну как сказать... Все таки они дают абстрактно более осмысленный код. Думаю, что начинающему будет проще именно начинать с HAL или SPL. Но это я по себе сужу.
Вот пример выдернул
GPIOA->BSRR = GPIO_BSRR_BR_0;
и
GPIO_SetBits(GPIOA, GPIO_Pin_0);
Очевидно, что второй вариант проще для понимания начинающему. Но естественно со временен уже будет понятно, что за поле такое этот BSRR и что проще использовать первый вариант.
Конечно если бы не было ни хола ни спл возможно бы было легче всего. Довести до совершенства работу с одним инструментом проще, чем со множеством.
Ответить
0
BARS_ #
Я бы не сказал, что это понятнее. Все регистры бьются с даташитом. А вот что имеет в виду SPL еще разобраться надо.
Ответить
0

[Автор]
CoBa31Rus #
Это вы прям утрируете, а если dma? Или spi? Там понятнее? А если флаги отслеживать? А прерывания? Вопрос религии.
Ответить
0
BARS_ #
Чего, чего? dma? spi? Это ж просто невероятно сложно в настройке. Целых 5-6 строк кода! Не смешите. Если что и приводить в пример, так это USB и FSMC. И что сложного во флагах и прерываниях? Вы о чем вообще?
Ответить
0

[Автор]
CoBa31Rus #
На HAL и SPL для настройки SPI, например, кода выходит больше, может и не сложно но выглядит это отталкивающе, на CMSIS же настройка походит на таковую в AVR, USB сложен в настройке не спорю, сам еще до конца не раскурил его. FSMC не пробовал.
Ответить
0
BARS_ #
Ну так я про то и говорю, что без SPL оно все нагляднее. Похоже мы друг друга малость не так поняли =)
Ответить
0

[Автор]
CoBa31Rus #
1. Начинать понятие растижимое, если кто то работал на avr/pic и платы сам изготавливал то можно пропустить отладочные платы. Ну а если только дуйню юзал то да, лучше взять дискавери или нуклео.
2. Таки вроде не считаю себя начинающим но и до профи далеко, где то между=) А вот жти библиотеки мало того что код раздувают так еще не дают абсолютно никакого понимания железки, кому то и не нужно понимать, но не мне.
3. Тут все индивидуально, сред на стм как грязи, бери что нравится, кто то vim юзает и не жалуется, кто то даже покупает тотже keil, iar. Это все холивар.
Ответить
0
Павел #
Значит путь у вас правильный, особенно если что-то не работает в какой-то IDE , то надо искать сначала - именно по этому пути : все лишнее выбрасываем и оставляем только суть.
Находим причину - говорим ой блин! Исправляем и возвращаемся обратно в свою среду...
Лично я сейчас на этапе отладки freertos на stm32F407 под Atollic , если можете поделится опытом как начинается отладка (у меня GDB через JLink) буду благодарен...
Ответить
0
Malkin5778 #
STM-ки желательно уметь программировать как минимум тремя способами: CMSIS, SPL, HAL, везде свои + и -. Ещё freertos, отдельная песня. Я начинал изучение с reference manual и работы с регистрами, и только через 2 года после этого поставил куб. Да, HALовские spi и i2c работают дольше, чем если написать свои. Насчёт USB - наверное лучше готовые. Сейчас пользуюсь Atollic, Keil, Cube. Пики и ардуины больше не брал в руки
Ответить
0
Александр #
Скажите, а где настройки тактирования, там же куча всего? Или если не трогать, там все по умолчанию?
Ответить
Добавить комментарий
Имя:
E-mail:
не публикуется
Текст:
Защита от спама:
В чем измеряется сила тока?
Файлы:
 
Для выбора нескольких файлов использйте CTRL

Программатор Pickit3
Программатор Pickit3
Конструктор регулируемого преобразователя напряжения LM317 Паяльная станция Hakko 936
вверх