add tutorial

This commit is contained in:
NeoZeng 2022-11-16 20:58:27 +08:00
parent 45dacf1021
commit 5ce0b3f881
3 changed files with 222 additions and 8 deletions

View File

@ -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

View File

@ -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文件夹

View File

@ -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执行的指令是一个绝佳的办法特别是你有大量的中断或开启了实时系统时。
以上三个模块都需要通过TPIUtrace 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](##附录3Makefile指令介绍)。
> 实际上在使用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日志
> 待补充。
## 附录3Makefile指令介绍
> 如果想要进一步学习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 的基础上,添加了一些非常规优化,这些优化是通过打破一些国际标准(比如一些数学函数的实现标准)来实现的,所以一般不推荐使用该参数。 |