使用arm-none-eabi-gcc + openocd + vscode + make開發STM32

makefile文件

爲了簡單上手,我所知的有兩種方法獲得makefile文件。

  1. 使用STM32CubeMX,生成代碼,選擇生成Toolchain/IDE爲Makefile
    這種方法優缺點,主要體現在,它的源文件和Inc目錄需要自己手動維護,在代碼較多時比較麻煩,而且只支持HAL/LL庫,如果自己用STD庫,需要額外維護。
    但是用法上配合STM32CubeMX,可以在生成代碼時直接更新makefile,相對來說並不麻煩。尤其是可以通過STM32CubeMX精簡代碼,做到用什麼,代碼中就只保留什麼源文件。
  2. 爲了自己方便,尤其是懶得每增加一個源文件都去改makefile,我參照網上的資料,修改瞭如下makefile腳本。使用起來相對舒服一些,其中的參數可以按照自己的喜好調整。目錄結構也可以調整爲適合自己的。
#工程的名稱及最後生成文件的名字
TARGET = project_name
# Build path
BUILD_DIR = build


#######################################
# 編譯器參數設置
#######################################
# 選擇自己的編譯器版本
# GCC_PATH=
PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S


#讀取當前工作目錄
TOP=$(shell pwd)

#設定包含文件目錄
INC_FLAGS= -I $(TOP)/Core/CMSIS/Include                 \
					 -I $(TOP)/Core/CMSIS/Device/ST/STM32F10x/Include	\
           -I $(TOP)/Hardware    \
           -I $(TOP)/Core/STM32F10x_StdPeriph_Driver/inc             \
           -I $(TOP)/Driver        \
           -I $(TOP)/User

#######################################
# ASM sources
# 注意,如果使用.c的startup文件,請把下面兩句註釋掉,避免編譯出現錯誤
#######################################
ASM_SOURCES =  \
startup_stm32f103xe.s

#######################################
# CFLAGS
#######################################
# debug build?
DEBUG = 1

# 代碼優化級別
OPT = -Og

# cpu
CPU = -mcpu=cortex-m3

# fpu
# NONE for Cortex-M0/M0+/M3

# float-abi

# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# C defines
# 這個地方的定義根據實際情況來,如果在代碼裏面有定義(如#define STM32F10X_HD),這裏可以不寫
C_DEFS =  \
-D USE_STDPERIPH_DRIVER \
-D STM32F10X_HD

# PreProcess
CFLAGS = $(MCU) $(C_DEFS) $(INC_FLAGS) $(OPT) -std=gnu11 -W -Wall -fdata-sections -ffunction-sections

ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif
# 在$(BUILD_DIR)目錄下生成依賴關係信息,依賴關係以.d結尾
CFLAGS += -MMD -MP -MF"$(addprefix $(BUILD_DIR)/, $(notdir $(@:%.o=%.d)))"

#######################################
# LDFLAGS
#######################################
# LD文件  
LDSCRIPT = STM32F103ZETx_FLASH.ld

# libraries
LIBS = -lc -lm -lnosys 
LIBDIR = 
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections


#######################################
# C/CPP源代碼
#######################################
C_SRC = $(shell find ./ -name '*.c')
C_SRC += $(shell find ./ -name '*.cpp')

C_OBJ = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SRC:%.c=%.o)))
vpath %.c $(sort $(dir $(C_SRC)))
C_OBJ += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))


.PHONY: all clean update


all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin

# 從vpath中讀取所有的.c文件,編譯成.o文件
$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR)
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) -o $@ $<

# 從vpath中u讀取所有的.s文件,編譯成.o文件
$(BUILD_DIR)/%.o: %.s | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

# 將所有的.o文件依據.ld文件,編譯組成.elf文件
$(BUILD_DIR)/$(TARGET).elf: $(C_OBJ)
	$(CC) $(C_OBJ) $(LDFLAGS) -o $@
	$(SZ) $@

# 將.elf文件轉爲.hex格式
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf
	$(HEX) $< $@
	
# 將.elf文件轉爲.bin格式
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf
	$(BIN) $< $@	

# 用於生成BUILD_DIR目錄
$(BUILD_DIR):
	mkdir $@		

clean:
	rm -f $(shell find ./ -name '*.o')
	rm -f $(shell find ./ -name '*.d')
	rm -f $(shell find ./ -name '*.lst')
	rm -f $(shell find ./ -name '*.map')
	rm -f $(shell find ./ -name '*.elf')
	rm -f $(shell find ./ -name '*.bin')
	rm -f $(shell find ./ -name '*.hex')

update:
	openocd -f interface/jlink.cfg  -f target/stm32f1x.cfg -c init -c halt -c "flash write_image erase $(TOP)/$(TARGET).hex" -c reset -c shutdown
 
 

ld文件

ld文件也就是編譯鏈,這個在GCC交叉編譯中比較常見。如下給出一個例子,需要修改的地方不多,主要是修改Memory這一塊,要根據自己的芯片來。對於STM32芯片,只要少量修改都能做到通用

/*
*****************************************************************************
**

**  File        : LinkerScript.ld
**
**  Abstract    : Linker script for STM32F103ZETx Device with
**                512KByte FLASH, 64KByte RAM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used.
**
**  Target      : STMicroelectronics STM32
**
**
**  Distribution: The file is distributed as is, without any warranty
**                of any kind.
**
**  (c)Copyright Ac6.
**  You may use this file as-is or modify it according to the needs of your
**  project. Distribution of this file (unmodified or modified) is not
**  permitted. Ac6 permit registered System Workbench for MCU users the
**  rights to distribute the assembled, compiled & linked contents of this
**  file as part of an application binary file, provided that it is built
**  using the System Workbench for MCU toolchain.
**
*****************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */


/*
* 這個地方需要自己修改,_estack也就是RAM的最高地址,是RAM.ORIGIN+RAM.ORIGIN.LENGTH相加的結果
* 在這裏表示爲  0x20000000+(Dec2Hex)64*1024=0x20010000
*/
_estack = 0x20010000;    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Specify the memory areas */

/*
* Memory的大小和具體的芯片相關,可以看STM的選型手冊
*/
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 512K
}

/* Define output sections */
/*
* 這裏是ld腳本的SECTIONS命令,指明瞭各個.o文件在單片機中如何存放,如果不瞭解,不要修改
*/
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文件

startup文件是一段用匯編寫的文件,極少去修改它。目前也有用C語言寫的版本,大同小異。一般不用管就行。只要在編譯的時候.s編譯成了.o文件,剩下的事情交給編譯器按照ld文件的描述來安排就行了。(詳情請見ld文件中的“.isr_vector"和startup中的"isr_vector"
程序運行過程中,按照ld的設計,先運行startup的代碼進行初始化,然後在starup中跳轉到main函數。如果希望在main函數前進行一些別的初始化操作,那就修改startup文件吧

這裏還需要注意的是,不同的編譯器startup文件會有所不同,一定要找對應編譯器所支持的startup文件。startup文件和ld文件配合使用。這個在官網有提供

openocd部分

如果遇到無法調試的問題,可以參考我的文章
openocd的使用問題彙總
如果仍然有問題無法調試,可以給我留言

vscode部分

使用vscode開發,需要關注幾個點

  1. 插件
    我推薦的插件有
    C/C++
    進行C代碼的支持
    C++ Intellisense
    可以用這個插件來進行代碼提示,效果還不錯。此外如果需要查看函數的被引用情況,這個插件可以提供,算是C/C++的一個互補
    Cortex-Debug
    這個插件可以滿足在vscode上調用openocd進行芯片的調試。(總算可以告別arm-none-eabi-gdb的TUI界面了)。變量監視,斷點都有,也可以檢測Cortex-M3的寄存器,如果用gdb調試比較熟悉,可以不考慮這個。
    需要一些額外配置,具體的可以去插件給的鏈接裏面找。我這裏給出一個例子
{
    // 使用 IntelliSense 瞭解相關屬性。 
    // 懸停以查看現有屬性的描述。
    // 欲瞭解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug",
            "cwd": "${workspaceRoot}",
            "executable": "./build/ZET6-base-develop.elf", //注意修改自己的elf文件
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "device": "STM32F103ZE", //這個不是很重要,寫不寫應該無所謂
            "configFiles": [
                "interface/jlink.cfg",
                "target/stm32f1x.cfg"
            ]
        }
    ]
}

PlatformIO IDE
這個看個人喜好吧,目前我不是很習慣
2. C/C++插件的配置文件
這裏面主要關注的是includePath,compilePath

includePath,是爲了讓C++ Intellisense代碼提示工作正常,避免編譯的時候知道位置,但是vscode不知道庫函數在哪。

compilePath,指定使用的編譯器,仍然是爲了讓C++ Intellisense運行正常,有時候一些類型定義在gcc和arm-none-eabi-gcc中有差異,如果還用gcc,那麼vscode滿屛紅字達成

補充1 GCC下printf函數重定向問題

重定向函數不再是fputc,在GCC下,重寫函數_write

int _write(int fd, char *pBuffer, int size)
{
  for (int i = 0; i < size; i++)
  {
    while (!(USART1->SR & USART_SR_TXE))
    {
    }
    USART_SendData(USART1, pBuffer[i]);
  }
  return size;
}

請參考這個地方的說明:https://blog.csdn.net/zhengyangliu123/article/details/54966402

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章