From 5ce0b3f881bb088fc58a4c4a1b97f3fa8d2e74e9 Mon Sep 17 00:00:00 2001 From: NeoZeng Date: Wed, 16 Nov 2022 20:58:27 +0800 Subject: [PATCH] add tutorial --- Makefile | 4 +- README.md | 14 ++- VSCode+Ozone使用方法.md | 212 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 222 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index ff3c1c8..808adee 100644 --- a/Makefile +++ b/Makefile @@ -260,6 +260,8 @@ vpath %.c $(sort $(dir $(C_SOURCES))) OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o))) vpath %.s $(sort $(dir $(ASM_SOURCES))) +# 以下是编译命令,命令之前被粉色高亮的@就是静默输出的指令.删除前面的@会将输出显示到命令行. +# 如@$(CC) -c $(CFLAGS) ...... 去掉第一个@即可. $(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) @$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@ @@ -283,7 +285,7 @@ $(BUILD_DIR): # clean up ####################################### clean: - -rm -r $(BUILD_DIR) + rm -r $(BUILD_DIR) ####################################### # dependencies diff --git a/README.md b/README.md index b915b55..17992c8 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,23 @@ 当前版本更新日期:2022.11.03 -本说明仅针对电控组2023赛季框架,如有变动以日期靠后的版本为准 +本说明仅针对电控组2023赛季框架,如有变动以日期靠后的版本为准。**==由于当前仍然处在测试开发阶段,请定期拉取(`git pull`)获取最新更新。==** - 开发方式: - 本框架使用stm32cubemx生成,基于makefile,使用gcc-arm-none-eabi编译(make命令)。若需使用keil5开发,请在stm32cubemx的`project manager`标签页下将工具链改为MDK,然后在keil中自行添加所需包含的.c文件和头文件。关于如何在keil下添加dsplib,请参考文档。 + 本框架使用stm32cubemx生成,基于makefile,使用gcc-arm-none-eabi编译(make命令)。 - VSCode可通过Cortex-Debug利用OpenOCD进行调试,jlink/stlink/dap-link都支持,具体的使用方法和环境配置教程在[VSCode+Ozone使用方法](./VSCode%2BOzone%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95.md)中。 + > !deprecated:若需使用keil5开发,请在stm32cubemx的`project manager`标签页下将工具链改为MDK,然后在keil中自行添加所需包含的.c文件和头文件。关于如何在keil下添加dsplib,请参考文档。 + > + > ***强烈推荐使用VSCode进行开发,Ozone进行调试。*** - 推荐使用 SEGGER ozone进行调试(暂时只支持jlink,提供可视化模块)。 + VSCode可通过Cortex-Debug利用OpenOCD进行调试,jlink/stlink/dap-link都支持,具体的使用方法和环境配置教程在[VSCode+Ozone使用方法](./VSCode+Ozone使用方法.md)中。 + + 推荐使用 SEGGER ozone进行调试。 - 分层: - 本框架主要代码分为BSP、Module、APP三层。三层的代码分别存放在同名的三个文件夹中,这三个文件夹存放在根目录下。开发过程中主要编写APP层代码,Module层与BSP层不建议修改。如需添加module(如oled屏幕、其他传感器和外设等),请按照规范编写并联系组长提交commit到dev分支,完善后合并至主分支。 + 本框架主要代码分为**BSP、Module、APP**三层。三层的代码分别存放在同名的三个文件夹中,这三个文件夹存放在根目录下。开发过程中主要编写APP层代码,Module层与BSP层不建议修改。如需添加module(如oled屏幕、其他传感器和外设等),请按照规范编写并联系组长提交commit到dev分支,完善后合并至主分支。 BSP层构建与HAL之上。HAL库和实时系统、DSP支持等文件都在`HAL_N_Middlewares`文件夹下(包括Cube生成的外设初始化的Inc和Src文件夹)。 diff --git a/VSCode+Ozone使用方法.md b/VSCode+Ozone使用方法.md index debc814..c357ce7 100644 --- a/VSCode+Ozone使用方法.md +++ b/VSCode+Ozone使用方法.md @@ -127,7 +127,7 @@ FPB是flash patch breakpoint闪存指令断点的缩写,用于提供代码断 DWT是data watch trace数据观察与追踪单元的缩写,用于比较debug变量的大小,并追踪变量值的变化。当你设定了比较断点规则(当某个数据大于/小于某个值时暂停程序)或将变量加入watch进行查看,DWT就会开始工作。DWT还提供了一个额外的计时器,即所有可见的TIM资源之外的另一个硬件计时器(因为调试其他硬件定时器的计时由于时钟变化可能定时不准,而DWT定时器是始终正常运行的)。它用于给自身和其他调试器模块产生的信息打上时间戳。我们的bsp中也封装了dwt计时器,你可以使用它来计时。 -ITM是instrument trace macrocell指令追踪宏单元的缩写,它用于提供非阻塞式的日志发送支持(相当于大家常用的串口调试),SEGGER RTT就可以利用这个模块,向上位机发送日志和信息。 +ITM是instrument trace macrocell指令追踪宏单元的缩写,它用于提供非阻塞式的日志发送支持(相当于大家常用的串口调试),SEGGER RTT就可以利用这个模块,向上位机发送日志和信息。这个硬件还可以追踪CPU执行的所有指令,这也被称作**trace**(跟踪),并将执行过的指令全部通过调试器发送给上位机。当debug无法定位bug所在的时候,逐条查看cpu执行的指令是一个绝佳的办法,特别是你有大量的中断或开启了实时系统时。 以上三个模块都需要通过TPIU(trace port interface unit)和外部调试器(j-link等)进行连接,TPIU会将三个模块发来的数据进行封装并通过DWT记录时间,发送给上位机。 @@ -235,7 +235,7 @@ VSCode常用快捷键包括: gcc your_source_code_name.c -o output ``` -然而,你面对的是一个拥有几百个.c和.h文件以及大量的链接库,如果要将所有文件都输入进去,那将是一件苦恼的事。Makefile在gcc命令上提供了一层抽象,通过编写makefile来指定参与编译的文件和编译选项,再使用`make`命令进行编译,它会自动将makefile的内容“翻译”为gcc命令。这样,编译大型项目就不是一件困难的事了。 +然而,你面对的是一个拥有几百个.c和.h文件以及大量的链接库,如果要将所有文件都输入进去,那将是一件苦恼的事。Makefile在gcc命令上提供了一层抽象,通过编写makefile来指定参与编译的文件和编译选项,再使用`make`命令进行编译,它会自动将makefile的内容“翻译”为gcc命令。这样,编译大型项目就不是一件困难的事了。更多关于makefile的指令介绍,参见[附录3](##附录3:Makefile指令介绍)。 > 实际上,在使用keil MDK开发的时候,它调用的仍然是底层的arm cc工具链中的编译器和链接器,在配置“魔术棒”添加项目文件以及包含目录的时候,实际做的使其和makefile差不多。keil使用的参数可以在魔棒的C/C++选项卡下看到。 @@ -246,6 +246,8 @@ mingw32-make -j24 # -j参数表示参与编译的线程数,一般使用-j12 ``` > 注意,多线程编译的时候输出的报错信息有时候可能会被打乱(多个线程同时往一个terminal写入程序运行的信息),要是看不清报错,请使用`mingw32-make`,不要进行多线程编译。 +> +> 我对make的编译命令进行了静默处理,只输出error和warning以及最后的生成文件信息。如果想要解除静默(就是下面所说的“你可以看到大致如下的输出”),需要修改Makefile。**本仓库下的makefile中已经用注释标明。** ![image-20221112191712534](assets\image-20221112191712534.png) @@ -555,3 +557,209 @@ windows菜单搜索J-link license manager,点击添加license,将注册机 ## 附录2:在VSCode中启用SEGGER RTT日志 > 待补充。 + + + + + +## 附录3:Makefile指令介绍 + +> 如果想要进一步学习Makefile,可以参考这个链接:[Makefile Tutorial By Example](https://makefiletutorial.com/)。你会发现,当项目越来越大的时候,makefile也会变得复杂起来,这就有了后继者**CMake**。cmake可以根据一定的规则,生成makefile,然后再利用make命令调用gcc进行程序的编译。~~也许以后还会有ccccmake~~ + +```makefile +# makefile是CubeMX自动生成的,我们需要自己添加新编写的源文件路径和头文件文件夹,也可以额外加入自己需要的参数满足需求 +###################################### +# target +###################################### +TARGET = basic_framework # 编译生成的目标文件名,如本项目会生成basic_framework.elf/bin/hex三个 +# 注意,makefile会自动生成一个叫@的变量,其值等于TARGET. +# 在makefile中获取变量的值需要通过$(var_name),即加上括号并在前面使用$ + +###################################### +# building variables +###################################### +# debug build? +DEBUG = 1 # 是否启用debug编译.程序分为DEBUG版和RELEASE版,后者在编译时不会插入调试符号和调试信息相关支持的内容,使得程序运行速度提高. +# optimization +OPT = -Og # 编译优化等级,-Og表示调试级,常见的级别请看代码块下面的表格. + + + +####################################### +# paths +####################################### +# Build path +BUILD_DIR = build # 编译的中间文件和目标文件存放路径,为了区分项目文件和编译输出,一般构建一个build(构建)文件夹,用于存放上述文件. 这个表达式也在生成了一个BUILD_DIR变量(可以把Makefile当作一种语言) + +###################################### +# source +###################################### +# C sources, 参与编译的C源代码全部放置于此.注意如果换行写需要在行尾空格之后加反斜杠,最后一行不要加 +# p.s. C语言的宏如果不能一行写完,也要在行尾加反斜杠,表示一行没有结束 +C_SOURCES = \ +HAL_N_Middlewares/Src/main.c \ +HAL_N_Middlewares/Src/gpio.c \ +HAL_N_Middlewares/Src/adc.c \ +HAL_N_Middlewares/Src/can.c + +# ASM sources 汇编源文件,第一个是stm32的启动文件,包含了bootloader的信息使得程序可以找到main函数的入口,第二个文件是添加对segger rtt viewer的支持. +ASM_SOURCES += \ +startup_stm32f407xx.s \ +HAL_N_Middlewares/Middlewares/Third_Party/SEGGER/RTT/SEGGER_RTT_ASM_ARMv7M.s + +####################################### +# binaries, 下面是要执行的指令 +####################################### +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 # 和C语言的宏类似,如果在Makefile里定义或给make命令传递了GCC_PATH变量会执行以下内容.但实际上我们执行的是else的内容 +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变量,其保存的内容实际上是gcc编译器的路径.makefile中要获取一个变量的值,需通过$(var).这里makefile会自动在环境变量里寻找gcc路径.CC里保存的内容是arm-none-eabi-gcc,就是我们添加到环境变量的arm gnu工具链的路径下的一个可执行文件.你可以尝试在cmd中输入arm-none-eabi-gcc,会发现这是一个可执行的程序.之前我们在验证安装的时候就运行了arm-none-eabi-gcc -v命令. +CC = $(PREFIX)gcc +# 定义了一个AS变量,稍后会用于C/ASM混合编译 +AS = $(PREFIX)gcc -x assembler-with-cpp +# 定义变量.objcopy能够将目标文件进行格式转换.我们实际上要生成的目标文件是.elf,objcopy可以将其转化为hex和bin格式,用于其他用途. +CP = $(PREFIX)objcopy +# size命令可以获取可执行文件的大小和包含内容信息. +SZ = $(PREFIX)size +endif +HEX = $(CP) -O ihex # 这里用到了上面定义的CP,命令含义为将其转换成hex,i的前缀表示intel格式 +BIN = $(CP) -O binary -S # 转化为二进制文件 + +####################################### +# CFLAGS, 在编译C语言程序的时候给GCC编译器传入的参数 +####################################### +# cpu +CPU = -mcpu=cortex-m4 # 目标CPU类型.我们前面介绍过,不同的平台支持的汇编指令不同,一条相同的C语言表达式在翻译成汇编的时候会有不同的实现.比如8051单片机就只有加法器,因此他的乘除法都是通过多次加法和减法实现的,编译器就要完成这一工作.再比如STM32F4系列拥有浮点运算单元(FPU),可以直接在硬件上实现浮点数的加减法.这里指定编译的目标平台是cortex-m4内核的mcu. + +# fpu 上面说了我们的f407是有FPU的,需要传入特殊的参数.fpv4-sp-d16表示float point,m4内核,single presicion, 16个dword(4字节)运算寄存器. +FPU = -mfpu=fpv4-sp-d16 + +# float-abi 使用软件还是硬件实现浮点运算.也就是我们说的如果没有FPU就只能使用软件实现浮点运算.这里选择hard硬件 +FLOAT-ABI = -mfloat-abi=hard + +# mcu 把上面几个变量合起来弄成一条长的参数 +# Thumb是ARM体系结构中的一种16位指令集,这里-mthumb会启用它,感兴趣的同学可以进一步搜索. +MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI) + +# macros for gcc +# AS defines +AS_DEFS = # 汇编的一些宏定义 + +# C defines +C_DEFS = \ # C语言的宏定义 +-DUSE_HAL_DRIVER \ # 使用HAL库.HAL库的许多头文件和源文件里会判断是否定义了这个宏 +-DSTM32F407xx \ # HAL库会根据使用的MCU的不同进行条件编译,这是一个很好的封装技术 +-DARM_MATH_CM4 \ # 启用ARM MATH运算库,我们在卡尔曼滤波和最小二乘法的时候会用到矩阵运算 +-DARM_MATH_MATRIX_CHECK \ # 启用矩阵乘法库 +-DARM_MATH_ROUNDING # 对数学库的输出结果进行取整防止溢出? + +# AS includes +AS_INCLUDES = # 汇编包含目录.汇编语言也和C一样可以多个文件联合编译,在没有C语言的时候大家都是利用这种方式开发的.在一些运算资源极其受限的情况下也会直接编写汇编. + +# C includes, C语言的包含目录,将所有参与编译的头文件目录放在这里,注意是目录不需要精确到每一个文件. +# 不想一行写完记得行尾加\,最后一行不要加 +C_INCLUDES = \ +-IHAL_N_Middlewares/Inc \ +-IHAL_N_Middlewares/Drivers/STM32F4xx_HAL_Driver/Inc + + +# compile gcc flags, gcc的编译参数,这些参数自己感兴趣的话去搜索一下.这还将之前定义的一些参数以变量的形式放过来. +ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections + +CFLAGS += $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections + +ifeq ($(DEBUG), 1) +CFLAGS += -g -gdwarf-2 +endif + + +# Generate dependency information +CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)" + + +####################################### +# LDFLAGS,传递给链接器的参数 +####################################### +# link script +LDSCRIPT = STM32F407IGHx_FLASH.ld # 需要参与链接的文件.这个文件指明了特定MCU的内存分布情况,使得链接器可以按照此规则进行链接和地址重映射. + +# libraries,要添加的库,这里我们要使用编译好的math运算库.在CubeMX里面生成的时候可以在第三方库选择DSP运算库,生成makefile时会自动添加进来. +LIBS = -lc -lm -lnosys \ +-larm_cortexM4lf_math +LIBDIR = \ # 和上一行命令对应,这里引入库的目录,gcc会自动去目录里寻找需要的库文件 +-LHAL_N_Middlewares/Drivers/CMSIS/Lib/GCC +LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections + +# default action: build all +all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin + + +####################################### +# build the application +####################################### +# list of objects +# OBJECTS保存了所有.c文件的文件名(不包含后缀),可以理解为一个文件名列表.notdir会判断是否是文件夹 +OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o))) +vpath %.c $(sort $(dir $(C_SOURCES))) # 对.c文件进行排序,百分号%是通配符,意为所有.c文件 +# list of ASM program objects +# 把所有.s文件的文件名加到OBJECTS里面 +OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o))) +vpath %.s $(sort $(dir $(ASM_SOURCES))) # 对.s文件的文件名也进行排序 + +# 以下是编译命令,命令之前被高亮的@就是静默输出的指令.删除前面的@会将输出显示到命令行. +# 如@$(CC) -c $(CFLAGS) ...... 去掉第一个@即可. + +# 意味根据makefile,在BUILD_DIR变量指定的路径下将参与编译的所有.c文件编译成.o文件 +$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) + @$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@ + # 上面这句话翻译一下实际上是gcc -c -many_param build/xxx -o build + # 意思是将所有参与编译的文件都列出来,传递一堆编译参数,让他们生成.o文件,并且放在build文件夹下 +# 意为根据makefile,将.s文件编译成.o文件,具体和上一条命令差不多 +$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR) + @$(AS) -c $(CFLAGS) $< -o $@ +# 根据前两步生成的目标文件(.o,这些文件的名字保存在OBJECTS变量里),进行链接生成最终的.elf +$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile + @$(CC) $(OBJECTS) $(LDFLAGS) -o $@ + @$(SZ) $@ # 输出生成的.elf文件的大小和格式信息 + +$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR) + $(HEX) $< $@ # elf转换成hex + +$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR) + $(BIN) $< $@ # 转换成bin + +$(BUILD_DIR): # 如果makefile所处的文件目录下没有build文件夹,这里会新建一个build文件夹. + @mkdir $@ + +####################################### +# clean up,清除编译信息,可以在命令行中通过rm -r build执行,实际上就是把build文件夹删掉 +####################################### +clean: + rm -r $(BUILD_DIR) + +####################################### +# dependencies +####################################### +-include $(wildcard $(BUILD_DIR)/*.d) # 包含所有的依赖文件(d=dependency),这是编译产生的中间文件,当hello.c包含hello.h而后者又包含了其他头文件时,会产生一个hello.d,它包含了hello.h中包括的其他的头文件的信息,提供给hello.c使用. + +# *** EOF *** + +``` + +- **编译优化等级**: + +| 优化级别 | 说明 | 备注 | +| -------- | ------------------------------------------------ | ------------------------------------------------------------ | +| -O0 | 关闭所有优化 | 代码空间大,执行效率低 | +| -O1 | 基本优化等级 | 编译器在不花费太多编译时间基础上,试图生成更快、更小的代码 | +| -O2 | O1的升级版,推荐的优化级别 | 编译器试图提高代码性能,而不会增大体积和占用太多编译时间 | +| -O3 | 最危险的优化等级 | 会延长代码编译时间,生成更大体积、更耗内存的二进制文件,大大增加编译失败的几率和不可预知的程序行为,得不偿失 | +| -Og | O1基础上,去掉了那些影响调试的优化 | 如果最终是为了调试程序,可以使用这个参数。不过光有这个参数也是不行的,这个参数只是告诉编译器,编译后的代码不要影响调试,但调试信息的生成还是靠 -g 参数的 | +| -Os | O2基础上,进一步优化代码尺寸 | 去掉了那些会导致最终可执行程序增大的优化,如果想要更小的可执行程序,可选择这个参数。 | +| -Ofast | 优化到破坏标准合规性的点(等效于-O3 -ffast-math ) | 是在 -O3 的基础上,添加了一些非常规优化,这些优化是通过打破一些国际标准(比如一些数学函数的实现标准)来实现的,所以一般不推荐使用该参数。 |