使用Make/CMake编译ARM裸机程序(基于HT32F52352 Cortex-M0+)

参考文章: 

Linux下使用VSCode,GCC,OpenOCD实现STM32一键编译烧录调试(CMake篇)

然后是遇到了一些坑,这里统一做一下总结 

大多数ARM-Cortex-M系列的配置方式都类似,本方法同样适用于STM32

为了方便,本文先用大家耳熟能详的STM32F103C8来解释一些操作

目录

一.MDK中的工程配置和编译方式

 1.源文件编译

2.编译启动文件

3.链接

 二.使用GNU Make+gcc-arm-none-eabi代替MDK

1.使用arm-none-eabi-gcc实现对工程源文件的编译

2.编译启动文件(arm-none-eabi-as)

3.链接(arm-none-eabi-gcc)

4.编写Makefile脚本一键完成操作

三.使用CMake自动生成构建脚本


一.MDK中的工程配置和编译方式

        一般来说,如果把工程放在MDK中进行开发,大致需要如下步骤:

  •         新建工程,选择芯片,选择编译器(AC5 、 AC6),配置编译宏(比如STM32的USE_STDPERIPH_DRIVER STM32F103MD等),配置汇编宏(STM32F103好像不需要)
  • 新建工程文件夹,导入源文件(用户代码,CMSIS代码和官方库代码等)
  • 添加头文件所在文件夹路径
  • 添加启动文件
  • 编写代码
  • 构建
  • 烧录/调试(不讨论,另一篇文章简单写了一些OpenOCD的FLASH编写)

 那么MDK是如何完成对工程中所有源文件编译并生成最终的.elf .bin .hex文件的操作的呢?

 1.源文件编译

打开一下MDK的魔法棒,选择C/C++选项卡,可以看到下面的指令:(Compiler control string)

--c99 --gnu -c --cpu Cortex-M3 -g -O0 --apcs=interwork --split_sections -I ./CMSIS -I ./Libraries -I ./STDriver -I ./User -I ./Driver
-IC:/Users/CharX/AppData/Local/Arm/Packs/Keil/STM32F1xx_DFP/2.2.0/Device/Include
-D__UVISION_VERSION="536" -DSTM32F10X_MD -DUSE_STDPERIPH_DRIVER -DSTM32F10X_MD
-o .\Objects\*.o --omf_browse .\Objects\*.crf --depend .\Objects\*.d

这里面的东西就是我们编译源文件的时候需要用到的参数,都是ARM_CC编译器的,比如:

  • -c99 :使用C99标准
  • -c:compile只编译而不链接,不指定目标文件的时候默认生成的是把.c改成.o的文件和.d文件
  • -depend:   dependence,生成该文件的依赖信息并存入某一文件
  • -cpu Cortex-M3:指定目标处理器为Cortex-M3架构,用于正确地生成目标机器地汇编码和机器码
  • -Idir :指定头文件包含文件夹路径(可以看到和上面那个Include Path的文件夹是一致的)
  • -Ddef :指定编译宏(可以看到和Define栏里面的一致)
  • -apce=interwork :..后面的那些我也不太懂干嘛的了哈哈哈哈待会照般就好(m3没有fpu,像m4及以上的处理器要启用的话需要再指定编译指令,比如-mfloat-abi=hard -mfpu=fpv4-sp-d16等)
  • 然后_UVISION什么的不讨论,在GNU中不需要

所以最后去掉一些无关紧要指令后的编译指令是: 

$(cc) -c --cpu Cortex-M3 -g -O0 --apcs=interwork --split_sections -I ./CMSIS -I ./Libraries -I ./STDriver -I ./User -I ./Driver
-IC:/Users/.../Arm/Packs/Keil/STM32F1xx_DFP/2.2.0/Device/Include
-DSTM32F10X_MD -DUSE_STDPERIPH_DRIVER -DSTM32F10X_MD
-o .\Objects\*.o --depend .\Objects\*.d

 最后得到$(name).o 和 $(name).d

2.编译启动文件

 如果对MDK用的比较多的话,我们还会知道里面有个启动文件,比如stm32f103c8的stm32f103_md.s.如果我们打开这个文件的话我们会看到里面会有一很多DCD EXPORT LDR 等指令和寄存器的名称。但是这些指令和名称事实上还不完全是处理器能够辨别的,以及其中一些符号只是为了提供一个链接地址,供最后链接的时候吧某些特定的服务函数链接到指定的地方。所以.s文件也是需要被编译成处理器能识别的文件并提供符号表。同样的,在"魔术棒"的选项卡里面选择"ASM",同样看到Assemly control string:

--cpu Cortex-M3 -g --apcs=interwork 
-I C:\Users\CharX\AppData\Local\Arm\Packs\Keil\STM32F1xx_DFP\2.2.0\Device\Include 
--pd "__UVISION_VERSION SETA 536" --pd "STM32F10X_MD SETA 1" --list ".\Listings\*.lst" --xref -o "*.o" --depend "*.d" 

 然后这里面有很多参数,但其实比较重要的是最上面那一行:

--cpu Cortex-M3 -g --apcs=interwork 
  •  然后这个地方的话和上面源文件的编译很像,-g表示开启调试
  • 然后这里注意的是因为ASM Define里面没有任何内容,所以下面的参数里面也没有define相关的,然后如果要在asm编译器里面使用汇编宏,它的格式是:--defsym MACRO=value(HT32F52352会用到)

 所以总的去掉一些无关紧要的指令,启动文件编译的话就是:

$(asm) --cpu Cortex-M3 -g --apcs=interwork --xref "*.o" --depend "*.d"

最后会生成$(name).o 和 $(name).d 

3.链接

 我们知道,MDK最后生成的是一个.axf文件,我们需要的也是axf以及由其转换出来的.bin和.hex。而刚刚的两个步骤中生成的都是零散.o和.d文件,上面也说了,.d文件只是描述了依赖关系,对于运行没啥作用。所以这一步就是要把生成的.o全部合成为一个.elf文件,当然这个不是简单的把多个文件合并,而是将链接表中的符号一一对应,相对地址转换为程序地址,不需要的符号和代码最终也不会被加入到最终的axf文件里面。

同样的看到“魔术棒”中的Linker选项卡(Linker control string)

--strict --scatter ".\Objects\TIM.sct" 
--summary_stderr --info summarysizes --map --xref --callgraph --symbols 
--info sizes --info totals --info unused --info veneers 
 --list ".\Listings\TIM.map" 
-o .\Objects\TIM.axf 

这里很多我也看不懂,--scatter 那个.sct应该就是指定链接脚本,用于指定变量 数据和代码的存放位置。这个是比较关键的。其它的选项看起来用处也不大,等下也用不到,就不讨论了。.axf文件就是我们要的目标文件。

ld指令执行完成之后就可以得到我们要的.axf  和 .map文件 

 MDK的工程构建流程大致就是如上

然后ARM_CC编译器是商业授权的,要收费。与之相对的,开源免费的是GNU方案。ARMCC的优点就是由ARM公司自己设计,对代码的优化更加适合芯片本身,通常使用ARMCC编译出来的工程会比使用GNU编译出来的工程占用更少的代码空间(同优化等级)并且编译也更快。具体生成的代码大小差异没有比较过,但是编译的话也不慢很多,在使用了make -j或者ninja -j16之后,速度差距并不大。

 二.使用GNU Make+gcc-arm-none-eabi代替MDK

下面就开始讨论如何使用gnu 的交叉编译工具链代替MDK的编译工具。

平台是HT32F52352(Cortex-M0+)所以有些东西和上面的STM32F103不太一样了,但是大同小异,类比一下就好

1.使用arm-none-eabi-gcc实现对工程源文件的编译

从MDK里面的编译参数里面可以知道,要编译一个源文件(不链接),需要在编译指令中加入:

  • INCLUDES(-I{includes_path})
  • -c
  • source.c
  • 编译宏(-D{define})
  • 目标处理器架
  • 编译模式

 跟MDK的ARMCC类似,有些指令和格式会有点不一样。这里直接给出一句指令作为参考:

arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -mthumb-interwork \
gdwarf-2 -MD -Wall -Os -mapcs-frame -ffunction-sections -fdata-sections \
-I../CMSIS -I../HTDriver \
-DHT32F52342_52 -DUSE_HT32_DRIVER -DUSE_HT32F52352_SK -DUSE_MEM_HT32F52352 \
-c main.c
  • arm-none-eabi-gcc :gnu 的gcc交叉编译工具
  • -mcpu=cortex-m0 -mthumb -mthunm-interwork : 针对架构的编译指令
  • gdwarf-2......:C编译器指令,用于选择警告提醒和一些我也不是很懂的东西
  • -I../CMSIS -I../HTDriver :指定头文件包含文件夹,gcc会在这些path里面搜索需要的头文件
  • -c 只编译不链接
  • main.c源文件

 这一套操作下来同样也是得到一堆.o和.d

2.编译启动文件(arm-none-eabi-as)

gcc-arm-none-eabi toolchains的.s采用了和ARMCC不一样的格式,启动文件一般芯片厂商会提供,在里面选择GNU的直接用就可以。比如ht32f52352官方提供的GNU启动文件是startup_ht32f5xxxx_gcc_01.s

直接参考如下代码:

arm-none-eabi-as -mcpu=cortex-m0 -mthumb -mthumb-interwork \
--defsym USE_HT32_CHIP=4 startup_ht32f5xxxx_gcc_01.s -o startup_ht32f5xxxx_gcc_01.o

因为该芯片在汇编启动文件的时候需要指定USE_HT32_CHIP这个宏的值,所以加入了一条:

--defsym USE_HT32_CHIP=4

这个指令完成之后就可以得到startup_ht32f5xxxx_gcc_01.o 和 startup_ht32f5xxxx_gcc_01.d这个文件

3.链接(arm-none-eabi-gcc)

链接文件直接使用gcc即可,gnu gcc官方也推荐使用gcc进行链接而不是ld

也是直接给出指令:

arm-none-eabi-gcc -T./linker.ld \
-mcpu=cortex-m0-mthumb -mthumb-interwork \
-Wl,-Map=ht32.map \
-Wl,--gc-sections --specs=nano.specs ...(all .o files) \
-o ht32.elf

指令中的那个linker.ld是gcc的链接文件,一般也是由厂家直接提供;...表示所有生成的.o文件 

这个指令执行完之后就得到了ht32.elf和ht32.map文件

4.编写Makefile脚本一键完成操作

由3就可以知道最后一步需要输入全部的目标文件,对于一个大工程来说这这倒也是一件非常痛苦的事情,所有我们可以使用make来完成这个工作。下面附上一个参考的完整makefile和注释

TARGET(demo)

#选择工具链
CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy

#设定目标平台的信息和操作模式
ARCH_OPTION = -mcpu=cortex-m0 -mthumb -mthumb-interwork

#设定编译需要包含的路径(头文件所在的文件夹)
INCLUDE_PATH += \
-I../ \
-I../CMSIS \
-I../HTDriver

#启动文件的路径
STARTUP_FILE_PATH = ../startup/startup_ht32f52352.s

#链接脚本的路径:
LINKER_FILE_PATH  = ./linker.ld

#源文件(*.c)编译参数
CC_OPTION= \
$(ARCH_OPTION) \
-gdwarf-2 -MD -Wall -Os -mapcs-frame -ffunction-sections -fdata-sections \
-DHT32F52342_52 -DUSE_HT32_DRIVER -DUSE_HT32F52352_SK -DUSE_MEM_HT32F52352 \
-c

#启动文件(*.s)编译参数
ASM_OPTION = \
$(ARCH_OPTION) \
--defsym USE_HT32_CHIP=4 \

#链接参数
LD_OPTION = \
-T$(LINKER_FILE_PATH) \
$(ARCH_OPTION) \
-Wl,-Map=demo.map -Wl,--gc-sections --specs=nano.specs \

#指定需要被编译文件目标(需要带上路径或者使用vpath自动搜索)
SOURCE_PATH += \
../main.c \
.  \
.  \
../.../xxx.c

#默认目标:build,依赖生成的.elf和.bin和.hex
build:demo.elf demo.bin demo.hex

#.elf目标生成规则($^表示依赖的所有文件)
demo.elf:$(SOURCE_PATH) startup_ht32f5xxxx_gcc_01.o
    $(CC) $(CC_OPTION) $(INCLUDE_PATH) $(SOURCE_PATH)
    $(LD) $(LD_OPTION) $^ -o demo.elf

#.bin目标生成规则
demo.bin:demo.elf
    $(OBJCOPY) -Obinaary demo.elf demo.bin

#.hex目标生成规则
demo.hex:demo.elf
    $(OBJCOPY) -Oihex demo.elf demo.hex

#startup_ht32f5xxxx_gcc_01.o目标生成规则
startup_ht32f5xxxx_gcc_01.o:startup_ht32f5xxxx_gcc_01.s
    $(AS) $(AS_OPTION) startup_ht32f5xxxx_gcc_01.s -o startup_ht32f5xxxx_gcc_01.o

#其它*.o目标生成规则($^表示全部文件)
%o:%c
$(CC) $(CC_OPTION) $(INCLUDE_PATH) $^

将Makefile按照上面的注释写好执行make 或者 make -j即可生成出目标文件(demo.elf demo.bin demo.hex)(这个文件是凭借印象盲打的,可能有错,但是大致思路如此)

三.使用CMake自动生成构建脚本

其实感觉也没比make方便到哪去,不过这个生成出来的make程序运行起来有进度显示,好看一点[Doge]哈哈哈哈。还有一点就是子模块管理会比直接使用Makefile会方便一点。比如需要添加某一个外设的时候只需要在外设那个文件夹里面加入新的文件路径并在总的include.cmake里面加入其头文件所在文件夹即可。思路和上面两个差不多,总的就是要指定编译器,设置包含,设置子目录等等等等。然后在这里有一个坑就是直接使用SET(CMAKE_C_COMPILER arm-none-eabi-gcc)会导致cmake在检查编译器的时候报错,因为它的检查方式使用指定的编译器编译一个文件并从可执行文件中获得输出来确定编译器是否可用。但是很显然用arm-none-eabi-gcc编译出来的程序在Windows下是执行不了的。所以需要使用SET(CMKAE_C_COMPILER_WORKS TRUE)来绕过检查。

然后先看一下文件结构:

.
├── build
├── cmake
│   ├── includes.cmake
│   └── options.cmake
├── CMakeLists.txt
├── CMSIS
│   ├── CMakeLists.txt
│   ├── core_cm0plus.h
│   ├── core_cm3.h
│   ├── core_cmFunc.h
│   ├── core_cmInstr.h
│   ├── ht32f5xxxx_01.h
│   ├── ht32f5xxxx_conf.h
│   ├── system_ht32f5xxxx_01.c
│   └── system_ht32f5xxxx_01.h
├── config.h
├── HTLibraries
│   ├── ht32_cm0plus_misc.h
│   ├── ht32_config.h
│   ├── ht32_dependency.h
│   └── ht32_div.h
├── HTSources
│   ├── CMakeLists.txt
│   ├── ht32_cm0plus_misc.c
│   ├── ht32_div.c
│   ├── ht32f5xxxx_adc.c
│   ├── ht32f5xxxx_bftm.c
│   ├── ht32f5xxxx_ckcu.c
│   └── ht32f5xxxx_cmp.c
├── linker.ld
├── main.c
└── Startup
    └── startup_ht32f5xxxx_gcc_01.s
  • 根目录一个CMakeLists.txt (根脚本)
  • 库源码文件夹一个CMakeLists.txt
  • CMSIS文件夹一个CMakeLists.txt

这里把三个文件先贴出来:(太晚了,有空再来说说各个指令的具体作用)

./CMakeLists.txt:

CMAKE_MINIMUM_REQUIRED(VERSION 3.10)

#指定为交叉编译环境
SET(CMAKE_SYSTEM_NAME Generic)
SET(CMAKE_SYSTEM_PROCESSOR arm)

#绕过检查
SET(CMAKE_C_COMPILER_WORKS TURE)
SET(CMAKE_CXX_COMPILER_WORKS TURE)

#指定编译链
SET(CMAKE_C_COMPILER "arm-none-eabi-gcc")
SET(CMAKE_CXX_COMPILER "arm-none-eabi-g++")
SET(CMAKE_ASM_COMPILER "arm-none-eabi-as")
SET(CMAKE_AR "arm-none-eabi-ar")
SET(CMAKE_OBJCOPY "arm-none-eabi-objcopy")
SET(CMAKE_OBJDUMP "arm-none-eabi-objdump")

PROJECT(ht32 C)

#INCLUDE文件夹
INCLUDE(${PROJECT_SOURCE_DIR}/cmake/includes.cmake)

#包含了${MCU_FLAGS} 和 设定了${CMKAE_C_FLAGS}
INCLUDE(${PROJECT_SOURCE_DIR}/cmake/options.cmake)

#启用对汇编的支持
ENABLE_LANGUAGE(ASM)
SET(STARTUP_SRC ${PROJECT_SOURCE_DIR}/Startup/startup_ht32f5xxxx_gcc_01.s)
SET(CMAKE_ASM_FLAGS "${MCU_FLAGS} --defsym USE_HT32_CHIP=4")

#配置链接指令
SET(LINKER_SCRIPT ${PROJECT_SOURCE_DIR}/linker.ld)
SET(CMAKE_EXE_LINKER_FLAGS "-T${LINKER_SCRIPT} ${MCU_FLAGS} -Wl,-Map=$(PROJECT_NAME).map -Wl,--gc-sections --specs=nano.specs")

#添加子模块
ADD_SUBDIRECTORY(./CMSIS)
ADD_SUBDIRECTORY(./HTSources)

#添加源文件
AUX_SOURCE_DIRECTORY(. MAIN_SRCS)

#编译主目录的文件并指定可执行文件
ADD_EXECUTABLE(${PROJECT_NAME}.elf ${MAIN_SRCS} ${STARTUP_SRC})

#链接静态库和可执行文件
TARGET_LINK_LIBRARIES(${PROJECT_NAME}.elf libcmsis libhtdriver)


#生成.bin和.hex
SET(ELF_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf)
SET(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin)
SET(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex)

SET(BIN_OPTION "-O binary")
SET(HEX_OPTION "-O ihex")

ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME}.elf POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -Oihex ${ELF_FILE} ${HEX_FILE}
    COMMAND ${CMAKE_OBJCOPY} -Obinary ${ELF_FILE} ${BIN_FILE}
)

./HTSources/CMakeLists.txt

INCLUDE(${PROJECT_SOURCE_DIR}/cmake/options.cmake)
INCLUDE(${PROJECT_SOURCE_DIR}/cmake/includes.cmake)

AUX_SOURCE_DIRECTORY(. HTDRIVER_SRCS)

#生成静态库
ADD_LIBRARY(libhtdriver ${HTDRIVER_SRCS})

./CMSIS/CMakeLists.txt

INCLUDE(${PROJECT_SOURCE_DIR}/cmake/options.cmake)
INCLUDE(${PROJECT_SOURCE_DIR}/cmake/includes.cmake)

AUX_SOURCE_DIRECTORY(. CMSIS_SRCS)

#生成静态库
ADD_LIBRARY(libcmsis ${CMSIS_SRCS})

 这些写完之后找个build文件夹执行cmake ..(cmake .. -G Ninja)

然后

make -j (Ninja)

就可以得到二进制文件了

 

可以看到使用-j参数之后耗时也是很短的 

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在非root用户下安装OpenCV4并使用g++/CMake编译,可以按照以下步骤进行操作: 1. 安装必要的依赖项 ``` sudo apt-get update sudo apt-get install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev ``` 2. 下载OpenCV4源代码 ``` git clone https://github.com/opencv/opencv.git cd opencv git checkout 4.0.0 ``` 3. 创建一个build目录并进行编译 ``` mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=$HOME/opencv_install -DCMAKE_BUILD_TYPE=Release .. make -j8 make install ``` 其中,`$HOME/opencv_install` 是你想要安装OpenCV4的路径。 4. 配置环境变量 将以下内容添加到你的`.bashrc`文件中: ``` export PKG_CONFIG_PATH=$HOME/opencv_install/lib/pkgconfig:$PKG_CONFIG_PATH export LD_LIBRARY_PATH=$HOME/opencv_install/lib:$LD_LIBRARY_PATH ``` 然后执行以下命令使其生效: ``` source ~/.bashrc ``` 5. 编写CMakeLists.txt并进行编译 在你的项目目录中创建一个 `CMakeLists.txt` 文件,并在其中添加以下内容: ``` cmake_minimum_required(VERSION 2.8) project(your_project_name) find_package(OpenCV REQUIRED) add_executable(your_executable_name your_source_files.cpp) target_link_libraries(your_executable_name ${OpenCV_LIBS}) ``` 将 `your_project_name` 和 `your_executable_name` 分别替换为你的项目名和可执行文件名。 然后在项目目录下创建一个 `build` 目录,并在其中进行编译: ``` mkdir build cd build cmake .. make ``` 编译完成后,你的可执行文件将位于 `build` 目录下。 以上就是在非root用户下安装OpenCV4并使用g++/CMake编译的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值