废话一下:不得不说,Windows上的Keil MDK软件确实很强大,从设备支持,到代码编辑,再到仿真器调试,硬件开发一条龙都可以搞定,现在还在活跃更新。不过Linux在编译方面能力这么强,怎么不会支持基于ARM Cortex M系列内核的STM32系列主控呢?这不,最近还真折腾上了,使用记事本开发STM32硬件!
不过别一听到“记事本”就退缩啦!在另一篇博客还是会介绍使用Eclipse开发STM32的!届时,图形化编程、调试,还是会很方便的!本文旨在加深对交叉编译的理解、巩固Makefile文件的编写、熟悉STM32硬件开发底层原理,为以后嵌入式系统的开发做准备。
环境准备
软件下载
1、交叉编译工具链下载
https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2019q3/RC1.1/gcc-arm-none-eabi-8-2019-q3-update-linux.tar.bz2?revision=c34d758a-be0c-476e-a2de-af8c6e16a8a2?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,8-2019-q3-update
(如果链接失效,可进入 arm.com -> Resources -> ARM Developer Site (developer.arm.com) -> Tools and Software -> Open Source Software -> Developer Tools -> GNU Toolchain -> GNU RM -> Downloads 下载最新版本)
2、STM32标准外设库下载
https://www.st.com/content/ccc/resource/technical/software/firmware/48/ab/e5/17/0d/79/43/74/stsw-stm32054.zip/files/stsw-stm32054.zip/jcr:content/translations/en.stsw-stm32054.zip
(如果链接失效,可进入 st.com -> Tools & Software -> Embedded Software -> STM32 Embedded Software -> STM32 Standard Peripheral Libraries -> 对应型号F1/F4…)
软件准备
1、安装必需软件包
首先要确保系统有交叉编译的一些必要软件包。具体需要什么软件包可参考OpenWRT的交叉编译系统需求:
https://openwrt.org/docs/guide-developer/build-system/install-buildsystem
比如对于Ubuntu用户,需要执行:(摘自该网站)
sudo apt-get install subversion build-essential libncurses5-dev zlib1g-dev gawk git ccache gettext libssl-dev xsltproc zip
对于Archlinux用户,需要执行:(同样摘自该网站)
sudo pacman -S --needed asciidoc bash bc binutils bzip2 fastjar flex git gcc util-linux gawk intltool zlib make cdrkit ncurses openssl patch perl-extutils-makemaker rsync unzip wget gettext libxslt boost libusb bin86 sharutils b43-fwcutter findutils time
2、解压工具链
将下载的交叉编译工具链解压缩,路径随意。Makefile可以设置工具链位置。
进入bin文件夹,该文件夹下面的文件就是我们要用到的编译器啦
3、解压STM32标准外设库
将下载的外设库解压,位置随意,Makefile可以设置include
路径。
4、设置这两个文件夹为只读
像iostream
、stdio.h
这些文件,我们一般不能修改。修改库文件往往会造成意想不到的后果,为了避免库文件被不小心修改,强烈建议将这些文件设置为只读。
cd ~/Downloads/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/STM32F10x_StdPeriph_Driver/src
chmod 555 *
cd ~/Downloads/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/STM32F10x_StdPeriph_Driver/inc
chmod 555 *
cd ~/Downloads/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/CoreSupport
chmod 555 *
cd ~/Downloads/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x
chmod 555 *
不过注意:尽量不要将包含库文件的文件夹设置成只读的了,这样可能会造成编译器无法在目录中生成.o文件,导致编译失败
搭建工程
上面我们就完成了环境的搭建。下面就让我们新建一个项目文件夹吧。
目录结构
我在主文件夹创建了一个名为“LinuxSTM32
”的文件夹,然后在里面创建了以下文件/文件夹,其目录架构如下:
LinuxSTM32
|- Libraries # 存放自己编写的库
| |- init.h # 自己写的初始化库
| |- init.c
| |- led.h # 自己写的LED库
| |- led.c
|
|- Main # 存放自己编写的主程序
| |- main.c # 自己写的主程序
|
|- Devices # 存放设备相关文件
| |- STM32F103C8 # 待会儿会介绍这些文件从哪而来
| |- stm32f10x.h
| |- stm32f10x_conf.h
| |- system_stm32f10x.h
| |- system_stm32f10x.c
| |- startup_stm32f10x_md.s
|
|- Objects # 存放编译过程中产生的文件
|- bin # 存放目标文件
| |- target.hex #将会产生
|
|- Makefile # Makefile文件
主要源代码
init.h
源代码:
#ifndef __init_H
#define __init_H
void InitGPIOC( void );
void delay_ms( u32 );
#endif
init.c
源代码:
#include "init.h"
#include "stm32f10x.h"
#include "pindef.h"
void InitGPIOC( void ) {
GPIO_InitTypeDef GPIO_InitStructure;
SystemInit();
RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = LED_GPIO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(LED_GPIO_GRP, &GPIO_InitStructure);
}
void delay_ms(u32 i) {
u32 temp;
SysTick->LOAD = 9000*i;
SysTick->CTRL = 0x01;
SysTick->VAL = 0;
do {
temp = SysTick->CTRL;
}
while((temp&0x01)&&(!(temp&(1<<16))));
SysTick->CTRL = 0;
SysTick->VAL = 0;
}
led.h
源代码:
#ifndef __led_H
#define __led_H
void Light( void );
void Mute( void );
#endif
led.c
源代码:
#include "led.h"
#include "stm32f10x.h"
#include "pindef.h"
void Light( void ) {
GPIO_SetBits(LED_GPIO_GRP, LED_GPIO_PIN);
}
void Mute( void ) {
GPIO_ResetBits(LED_GPIO_GRP, LED_GPIO_PIN);
}
main.c
源代码:
#include "init.h"
#include "led.h"
int main( void ) {
InitGPIOC();
while(1) {
Light();
delay_ms(1500);
Mute();
delay_ms(1500);
}
}
pindef.h
源代码:
#ifndef __pindef_H
#define __pindef_H
#define LED_GPIO_GRP GPIOC
#define LED_GPIO_PIN GPIO_Pin_13
#define LED_GPIO_CLK RCC_APB2Periph_GPIOC
#endif
复制必要文件
1、进入解压的固件库,进入文件夹:STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/(不同设备不同路径,根据实际情况修改),
将stm32f10x.h
、system_stm32f10x.c
、system_stm32f10x.h
、以及根据自己设备情况(小容量、中容量、大容量等)选择startup/TRUEstudio中的文件(STM32F103ZE选hd、STM32F103C8选md,注意不是arm文件夹下的,是TRUEstudio的),这几个文件,复制到我们项目文件夹、Devices目录下(参考上面目录结构)
2、进入解压的固件库,进入文件夹:
STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/
将stm32f10x_conf.h
复制到项目文件夹、Devices目录下(参考上面目录结构)
编辑开关文件
根据需要注释Devices目录下stm32f10x_conf.h文件中的包含关系。本例中我们只需要使用GPIO功能,所以只需保留stm32f10x_gpio.h
、stm32f10x_rcc.h
、misc.h
:
开始编译
手动编译测试
关于交叉编译
交叉编译,即在一个平台的处理器上编译生成另外一种处理器可执行的代码。我们现在就要从x86_64平台编译生成ARM Cortex M3平台代码。
手动编译
我们先手动编译试试,看能否产生目标文件。
1、编译源文件
首先进入Libraries文件夹:
cd ./Libraries
然后设置C_INCLUDE_PATH
临时环境变量,将标准外设库的头文件文件夹、CMSIS核心接口文件夹、设备文件夹、库文件夹添加进去:
export C_INCLUDE_PATH=\
/xxxxxx/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/CoreSupport:\
/xxxxxx/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/STM32F10x_StdPeriph_Driver/inc:\
/xxxxxx/LinuxSTM32/Devices/STM32F103C8:\
/xxxxxx/LinuxSTM32/Libraries
设置参数,开始逐个编译:(注意包含stm32f10x_conf.h
)
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 --include /xxxxxx/LinuxSTM32/Devices/STM32F103C8/stm32f10x_conf.h -o led.o led.c
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 --include /xxxxxx/LinuxSTM32/Devices/STM32F103C8/stm32f10x_conf.h -o init.o init.c
可以看到生成了两个.o
文件:
进入Main文件夹,继续编译:
cd ../Main
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 --include /xxxxxx/LinuxSTM32/Devices/STM32F103C8/stm32f10x_conf.h -o main.o main.c
2、编译库文件
进入设备文件夹,编译启动汇编文件:
cd ../Devices/STM32F103C8
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 -g -Wa,--warn -o startup_stm32f10x_md.o startup_stm32f10x_md.s
进入外设库文件夹,继续执行编译:
cd /xxxxxx/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/STM32F10x_StdPeriph_Driver/src
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 -o stm32f10x_gpio.o stm32f10x_gpio.c
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 -o stm32f10x_rcc.o stm32f10x_rcc.c
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 -o misc.o misc.c
进入设备文件夹,继续编译:
cd /xxxxxx/LinuxSTM32/Devices/STM32F103C8
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 -o system_stm32f10x.o system_stm32f10x.c
进入CMSIS库文件夹,继续编译:
cd /xxxxxx/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/CoreSupport
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 -o core_cm3.o core_cm3.c
此时可能会出现错误:
发生这种情况可根据https://gist.github.com/timbrom/1942280修改core_cm3.c
文件第736、753行处:
再次编译,编译成功。
3、连接.o文件
现在我们还差一个引导链接器连接的文件。从外设库STM32F10x_StdPeriph_Lib_V3.5.0/Project/STM32F10x_StdPeriph_Template/TrueSTUDIO/STM3210E-EVAL/复制stm32_flash.ld
到Objects文件夹,然后根据自己设备容量情况编辑:
// STM32F103C8
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = 0x20002000; /* end of 20K RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0; /* required amount of heap */
_Min_Stack_Size = 0x100; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 8K
MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K
}
// STM32F103ZE
// 直接用该文件即可,无需修改
然后将刚刚生成的.o
文件全部移动到Objects文件夹,别漏了哦!
开始连接:
cd /xxxxxx/LinuxSTM32/Objects
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc core_cm3.o init.o led.o main.o misc.o startup_stm32f10x_md.o stm32f10x_gpio.o stm32f10x_rcc.o system_stm32f10x.o -mthumb -mcpu=cortex-m3 -T stm32_flash.ld -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=test.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80 -Wl,--start-group -lc -lm -Wl,--end-group -o target.elf
然后就可以看到,当前目录下多了一个target.elf
文件。
4、生成HEX文件
/xxxxxx/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-objcopy target.elf -O ihex final.hex
如果要生成bin文件,可执行:
arm-none-eabi-objcopy target.elf final.bin
5、烧录
首先连接串口
可以使用stm32flash
在Linux上给STM32系列单片机烧录程序:
sudo stm32flash -w /xxxxxx/LinuxSTM32/Objects/final.hex -v -g 0 /dev/ttyUSB0
6、效果
可以看到,板载LED有规律地闪烁,实验成功。
写Makefile实现自动编译
Makefile文件
Makefile无非将手动的繁琐操作变成自动的了,使用户能通过编辑Makefile中一些主要参数(如工程路径、编译器路径、外设库路径),快速构建出所需的目标代码。这里我写了一个Makefile文件:(写这个文件花了我一整天啊啊啊,稍后会将代码上传至GitHub,欢迎commit!)
# Makefile for STM32F10x
# User Definitions 修改这四行就行了
project_name = STM32F10x_project
device_vol = md
compiler_root = /home/ans/Downloads/gcc-arm-none-eabi-8-2019-q3-update
StdPeriph_root = /home/ans/Downloads/STM32F10x_StdPeriph_Lib_V3.5.0
USER_INCLUDE = export C_INCLUDE_PATH=./src:./src/*/:./src/*/*/
# Automatic Defintitions
target = $(project_name)
device_vol_upper = $(shell echo $(device_vol) | tr a-z A-Z)
USERCODE_SRC := $(wildcard ./src/*.c ./src/*/*.c ./src/*/*/*.c)
USERCODE_OBJ := $(addprefix ./obj/, $(notdir $(USERCODE_SRC:%.c=%.o)))
STDPERIPH_SRC := $(wildcard $(StdPeriph_root)/Libraries/STM32F10x_StdPeriph_Driver/src/*.c)
STDPERIPH_OBJ := $(addprefix ./obj/, $(notdir $(STDPERIPH_SRC:%.c=%.o)))
CMSIS_SRC = $(StdPeriph_root)/Libraries/CMSIS/CM3/CoreSupport/core_cm3.c
CMSIS_OBJ = ./obj/core_cm3.o
SYSTEM_SRC = $(StdPeriph_root)/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
SYSTEM_OBJ = ./obj/system_stm32f10x.o
STARTUP_SRC = $(StdPeriph_root)/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/TrueSTUDIO/startup_stm32f10x_$(device_vol).s
STARTUP_OBJ = ./obj/startup_stm32f10x_$(device_vol).o
FLASH_SRC = ./lib/devices/$(device_vol)/stm32_flash.ld
# Binarary files
ELF_FILE = ./bin/$(project_name).elf
BIN_FILE = ./bin/$(project_name).bin
HEX_FILE = ./bin/$(project_name).hex
########## Compile Configurations ##########
ARMCC = $(compiler_root)/bin/arm-none-eabi-gcc
ARMCC_INCLUDE_DIRS = \
-I $(StdPeriph_root)/Libraries/CMSIS/CM3/CoreSupport\
-I $(StdPeriph_root)/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x\
-I $(StdPeriph_root)/Libraries/STM32F10x_StdPeriph_Driver/inc\
-I ./lib/devices/*/\
-I ./src\
-I ./src/*/\
-I ./src/*/*/
ARMCC_FLAGS = -mthumb -mcpu=cortex-m3
########## Scripts ##########
.PHONY: all elf bin hex clean
all: $(BIN_FILE) $(HEX_FILE)
@echo "Successfully built all"
$(BIN_FILE): $(ELF_FILE)
@echo "Operation not supported"
-mkdir ./bin
#$(compiler_root)/bin/arm-none-eabi-objcopy $(ELF_FILE) $(BIN_FILE)
@echo "Successfully built $(target).bin"
$(HEX_FILE): $(ELF_FILE)
@echo "Building $(target).hex"
$(compiler_root)/bin/arm-none-eabi-objcopy $(ELF_FILE) -O ihex $(HEX_FILE)
@echo "Successfully built $(target).hex"
$(ELF_FILE): \
$(USERCODE_OBJ) \
$(STDPERIPH_OBJ) \
$(CMSIS_OBJ) \
$(SYSTEM_OBJ) \
$(STARTUP_OBJ)
@echo "Linking files"
-mkdir ./bin
$(ARMCC) $(ARMCC_FLAGS) -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=test.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80 -Wl,--start-group -lc -lm -Wl,--end-group -T $(FLASH_SRC) $(wildcard ./obj/*.o) -o $(ELF_FILE)
@echo "Successfully linked files to $(target).elf"
obj/%.o: ./src/%.c
@echo "Building usercode"
-mkdir ./obj
$(USER_INCLUDE)
$(ARMCC) -c $(ARMCC_FLAGS) $(ARMCC_INCLUDE_DIRS) -D STM32F10X_$(device_vol_upper) -D USE_STDPERIPH_DRIVER -o $@ $<
obj/%.o: ./src/*/%.c
@echo "Building usercode"
-mkdir ./obj
$(USER_INCLUDE)
$(ARMCC) -c $(ARMCC_FLAGS) $(ARMCC_INCLUDE_DIRS) -D STM32F10X_$(device_vol_upper) -D USE_STDPERIPH_DRIVER -o $@ $<
obj/%.o: ./src/*/*/%.c
@echo "Building usercode"
-mkdir ./obj
$(USER_INCLUDE)
$(ARMCC) -c $(ARMCC_FLAGS) $(ARMCC_INCLUDE_DIRS) -D STM32F10X_$(device_vol_upper) -D USE_STDPERIPH_DRIVER -o $@ $<
obj/%.o: $(StdPeriph_root)/Libraries/STM32F10x_StdPeriph_Driver/src/%.c
@echo "Building StdPeriph"
-mkdir ./obj
$(USER_INCLUDE)
$(ARMCC) -c $(ARMCC_FLAGS) $(ARMCC_INCLUDE_DIRS) -D STM32F10X_$(device_vol_upper) -D USE_STDPERIPH_DRIVER -o $@ $<
@echo "Successfully built StdPeriph"
$(CMSIS_OBJ): $(CMSIS_SRC)
@echo "Building CMSIS"
-mkdir ./obj
$(ARMCC) -c $(ARMCC_FLAGS) -o $(CMSIS_OBJ) $(CMSIS_SRC)
@echo "Successfully built CMSIS"
$(SYSTEM_OBJ): $(SYSTEM_SRC)
@echo "Building system"
-mkdir ./obj
$(USER_INCLUDE)
$(ARMCC) -c $(ARMCC_FLAGS) $(ARMCC_INCLUDE_DIRS) -D STM32F10X_$(device_vol_upper) -D USE_STDPERIPH_DRIVER -o $(SYSTEM_OBJ) $(SYSTEM_SRC)
@echo "Successfully built system"
$(STARTUP_OBJ): $(STARTUP_SRC)
@echo "Building startup"
-mkdir ./obj
$(ARMCC) -c $(ARMCC_FLAGS) -g -Wa,--warn -o $(STARTUP_OBJ) $(STARTUP_SRC)
@echo "Successfully built startup"
elf: $(ELF_FILE)
bin: $(BIN_FILE)
hex: $(HEX_FILE)
clean:
rm -f -R ./bin ./obj
用法
建立目录结构:
STM32_Project_Template
|-- lib
| |-- devices
| |-- md
| | |-- stm32f10x_conf.h
| | |-- stm32_flash.ld
| |
| |-- hd
|
|-- src
| |-- lib
| | |-- led.h
| | |-- led.c
| | |-- init.h
| | |-- init.c
| | |-- or other files.c/.h
| |
| |-- main.c
| |-- stm32f10x_it.c
| |-- or other files.c/.h
|
|-- Makefile
即建立一个工程文件夹,在该文件夹中放入Makefile文件,然后在该目录下建立src文件夹,存放用户代码,再建立lib文件夹,用来存放硬件相关配置。在src目录下可再建立文件夹。比如可建立lib文件夹用来存放自己写的库文件。Makefile会自动编译src目录下三级目录以内的所有源文件。(这点可以优化)
关于stm32f10x_conf.h
和stm32_flash.ld
在哪里找,请参阅前文。
然后cd进入该工程目录,执行make
即可生成烧录文件(hex文件)。make all
等价于make
,make elf
只会建立elf文件,make hex
会根据elf文件建立hex文件。
总结
在x86_64平台上生成另外一种处理器的代码,就是交叉编译。关于STM32系列代码的编译,在Windows操作系统,GUI界面的Keil,只要点击“编译”按钮,就会自己生成脚本,然后根据脚本编译,简单又方便,但也淡化了这一方面的知识,让人只停留在“会用”的层面,而不能很好地理解在底层到底发生了什么事情、执行了什么步骤。
而通过手动编译整个STM32工程,我们可以更加清晰地认识编译的原理、头文件与源文件的区别。通过亲手编写Makefile文件,将操作步骤“流水线”化,对我们handle大型工程的能力也是有帮助的。
现在是2019年8月。在各种SDK、API、库的帮助下。编写程序,已经是一件很简单的事了。要实现什么功能,调用库就好了。无可否认的是,这样做确实能提高工作效率,能将任务完成得又快又好。但是,这些库也是人开发出来的啊,如果现在人们只是热衷于表面的编写程序,而不去研究底层原理的话,说不定某一天,底层这个地基开始松动,不能支撑整栋大楼的时候,我们整个计算机世界,会不会也摇摇欲坠?
计算机的底层开发,应该永远是最重要的。
还令我担心的是,现在小学生的编程教育,无非只是非常游戏化的。他们所谓的“机器人大赛”,只是在屏幕上拖动方块,形成逻辑而已。可以说,这仅仅是一个“逻辑教育”,而不是“编程教育”。确实,编程需要逻辑思维,但是“逻辑”也仅仅是“编程”的一部分。要写出一个好程序,不仅仅需要逻辑,还需要一颗冷静的心、坚强的毅力、还要学会应对挫折。如果学生们从小就树立“编程只是把库函数凑在一起”的观念,即使他们长大后将库用得如何活灵活现,但是这也就使研究底层的人越来越少了。这对于整个计算机界,恐怕是一个末日。
所以,编程非儿戏,我们需要关注底层。这还不够,我们还应敬仰前人。我们应该感谢为我们提供各种库、各种工具、以及传授以我们知识的前人。STM32的标准外设库是谁开发的?C语言是谁发明的?Make又是谁创造的?你是如何学会这些知识的?唯有敬仰前人、敬仰知识,我们才能走得更远。
参考资料
【STM32开发环境】Linux下开发stm32(一) | 使用gcc-arm-none-eabi工具链编译
https://blog.csdn.net/Mculover666/article/details/84888539
【嵌入式】基于makefile的STM32编译方法探索
https://www.jianshu.com/p/7f77a5486a26
teyann/stm32_flash.ld
https://gist.github.com/teyann/38a8766f0c905fccad61
stm32在linux下开发(一)
https://blog.csdn.net/yanchanchu9519/article/details/79485463
STM32 gcc编译环境搭建
http://www.zhongruitech.com/294956586.html
ARM官网
https://www.arm.com
巨人的肩膀
Linux之父:Linus Torvalds