makefile文件
爲了簡單上手,我所知的有兩種方法獲得makefile文件。
- 使用STM32CubeMX,生成代碼,選擇生成Toolchain/IDE爲Makefile
這種方法優缺點,主要體現在,它的源文件和Inc目錄需要自己手動維護,在代碼較多時比較麻煩,而且只支持HAL/LL庫,如果自己用STD庫,需要額外維護。
但是用法上配合STM32CubeMX,可以在生成代碼時直接更新makefile,相對來說並不麻煩。尤其是可以通過STM32CubeMX精簡代碼,做到用什麼,代碼中就只保留什麼源文件。 - 爲了自己方便,尤其是懶得每增加一個源文件都去改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開發,需要關注幾個點
- 插件
我推薦的插件有
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