Cmake+HighTec:AURIX TriCore嵌入式工程从Makefile到自动化构建
发布时间:2026/9/7 11:53:27 锦皓数字建站

简介一套面向嵌入式开发者的系统资料包重点关注英飞凌TC2XX/TC3XX系列微控制器的工程构建核心讲解如何通过CMake脚本调用HighTec编译器实现makefile的自动生成与项目自动化编译。包内共2000个文件包含1276个txt说明文档和724个html页面以rar压缩包形式提供整体约27.95MB内容覆盖CMake构建系统、生成器表达式、file-api、变量与命令参考等可系统查阅工具链配置、编译选项和查找模块用法。目前已有1051人学习浏览。借助这些文档读者能够掌握CMakeLists.txt的编写逻辑学会通过工具链文件指定编译器设置全局或目标级编译链接参数并借助查找模块管理依赖库从而为TC2XX/TC3XX工程搭建出清晰、可维护的自动化构建流程显著提升嵌入式项目的构建效率和可维护性。1. 从手写makefile到Cmake自动化一次HighTec工程构建的升级实践嵌入式开发做了这么多年AURIX TC3xx系列的工程构建一直是件挺折腾的事。我接手过不少历史项目打开Makefile一看里面全是一个个手动列出的源文件路径、依赖规则、头文件搜索目录每次新增一个.c文件都得小心翼翼地改好几个地方稍微漏掉一个编译报错还算好的最怕的是链接时符号找不到查半天才发现就是忘了把新文件加进去。我当时的工作流是手写makefile然后调用HighTec编译器去编译TriCore架构的固件。HighTec这个编译器本身功能很完整从编译、汇编到链接一步到位但makefile的维护成本实在太高了。尤其是工程规模一大几十个模块、上百个源文件手写规则基本是在给自己挖坑。后来我决定把构建系统切换到Cmake利用Cmake生成makefile再由HighTec编译器完成实际的编译链接工作。这个组合用下来效果非常明显依赖管理、增量编译、模块化配置这些问题都被Cmake在生成阶段解决了HighTec只需要按部就班地执行编译指令就行。这篇文章就把我实际搭建这套构建系统的过程、关键配置和一些踩坑经验整理出来。2. 为什么选择Cmake HighTec的组合2.1 手工makefile的维护困境先说最直接的痛点。手工makefile在嵌入式MCU工程里普遍存在但很少被认真审视。你想想一个典型的AURIX工程光Infineon的iLLD库就有几十个源文件再加上自己写的应用层代码、驱动代码、启动文件源文件总数轻松过百。手动makefile意味着每次增减文件都要同步修改object列表和依赖规则这个操作虽然机械但极其容易出错。而且makefile的变量作用域、隐含规则、自动依赖生成这些概念对团队新成员来说学习曲线相当陡峭。一个不小心make clean之后忘了先make depend各种诡异问题就来了。更麻烦的是不同需求debug版、release版、不同芯片型号需要维护好几套makefile改一个配置就要同步改另外几套维护成本成倍增长。2.2 Cmake在嵌入式构建中的优势Cmake的设计思路和手写makefile完全不同。它把构建过程分成配置阶段和生成阶段配置阶段分析CMakeLists.txt中的声明检测工具链、头文件路径、编译选项然后生成对应构建系统makefile、Ninja等所需的文件。核心优势在于你只需要写一次CMakeLists.txtCmake会替你把依赖关系、增量编译、编译选项传递这些繁琐细节处理好。对嵌入式场景来说Cmake还有一个非常大的好处——它天生支持自定义工具链文件。通过CMAKE_TOOLCHAIN_FILE指定一个专门描述工具链的配置Cmake就可以完全脱离宿主系统编译器使用目标平台的交叉编译器。我们这次要调用的HighTec编译器就是一个交叉编译器所以这个机制非常契合。2.3 整体设计思路我设计的构建流程是这样的Cmake负责扫描工程目录、收集源文件、处理编译选项最终生成makefilemake命令读取Cmake生成的makefile执行具体的编译和链接命令底层调用的就是HighTec的编译器工具链。这里有个很多人容易误解的地方以为Cmake能直接编译嵌入式代码。其实Cmake本身不做编译它只是构建系统的“生成器”真正干活的是make和HighTec。理解这一点后面所有配置逻辑就都顺了。我的目标是建立一个这样的流水线写一次CMakeLists.txt以后不管是换芯片型号、切换debug/release、新增源文件都只需要改几行声明运行两三条命令就完成整个构建。3. 工具链文件编写Cmake适配HighTec的核心环节3.1 HighTec编译器基础和架构感知HighTec编译器的本质是一套针对英飞凌TriCore架构深度定制的GNU工具链它基于GCC体系支持C、C和汇编语言。TriCore和普通的ARM/PC架构有很大不同它是一个MCU和DSP融合的架构指令集、内存模型、启动机制都有特殊性所以编译时必须明确告诉编译器当前的目标架构否则生成的机器码无法在AURIX芯片上运行。在工具链文件中有几个关键参数需要重点配置CMAKE_SYSTEM_NAME要设置成Generic避免Cmake把它当作宿主机系统去检测比如不要设成Linux或WindowsCMAKE_SYSTEM_PROCESSOR设为tricore告诉Cmake目标是一个TriCore处理器最关键的是要指定HighTec工具链的bin目录路径和编译器前缀。3.2 工具链文件核心配置拆解我创建的toolchain-hightec-tricore.cmake文件内容如下# 目标系统类型Generic表示非宿主系统 set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR tricore) # HighTec工具链路径和编译器前缀 set(TOOLCHAIN_PREFIX tricore-elf-) set(TOOLCHAIN_BIN_DIR /opt/hightec/toolchains/tricore/v4.9.4.1/bin) # 指定编译器 set(CMAKE_C_COMPILER ${TOOLCHAIN_BIN_DIR}/${TOOLCHAIN_PREFIX}gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_BIN_DIR}/${TOOLCHAIN_PREFIX}g) set(CMAKE_ASM_COMPILER ${TOOLCHAIN_BIN_DIR}/${TOOLCHAIN_PREFIX}gcc) # 关键参数跳过编译测试的可执行链接 set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) # 编译器前缀makefile中会显示tricore-elf-gcc等命令 set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_BIN_DIR}/..) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)这段配置里有几个细节要特别注意。第一CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY这一行是必不可少的Cmake在配置阶段默认会编译一个小程序来测试编译器是否能正常工作如果目标类型是可执行文件而HighTec又没有嵌入启动代码和链接脚本测试链接必然失败而且失败信息非常误导人我之前就被这个坑过报了一堆莫名其妙的链接错误。第二CMAKE_FIND_ROOT_PATH的路径设置成${TOOLCHAIN_BIN_DIR}/..本质上就是把HighTec的toolchain目录看作一个独立的系统根目录这样Cmake搜索头文件和库的时候会优先在这个目录里找而不是去翻宿主机的/usr/include避免交叉编译时头文件不匹配的问题。3.3 编译选项与链接参数传递工具链文件解决的是“用什么编译器”的问题编译选项和链接参数则放在CMakeLists.txt里配置。对AURIX芯片来说有几个编译选项是这个架构特有的-mtc162指定TriCore 1.6.2指令集架构-fno-common确保全局变量不放置到common块-fno-exceptions和-fno-rtti禁用C异常和RTTI以减小代码体积。链接阶段最核心的是链接脚本它定义了内存布局、堆栈大小、段分配等关键参数用-T参数指定。还有一项重要的链接选项是-Wl,--gc-sections用来丢弃未被引用的段有效减小固件体积。下面这段CMakeLists.txt展示了如何设置这些参数# 编译选项 set(CMAKE_C_FLAGS -mtc162 -fno-common -fno-exceptions \ -Wall -Wextra -O2 -g -ffunction-sections -fdata-sections) # 链接选项 set(CMAKE_EXE_LINKER_FLAGS -T${LINKER_SCRIPT} -Wl,--gc-sections \ -Wl,--no-warn-rwx-segments -nostartfiles)需要注意-nostartfiles这个选项它告诉链接器不要使用默认的启动文件。AURIX的启动逻辑和普通MCU不太一样它需要自己提供__START、__TRAP等符号而不是像ARM那样有一套标准的crt0启动代码。加了这个选项之后启动流程完全由自己控制可定制性更强。4. 从零搭建Cmake HighTec工程完整实操4.1 环境准备和版本问题动手之前先把环境准备好。我用的是Ubuntu 20.04的系统Cmake版本3.16。为什么非要强调版本因为Cmake对工具链文件的支持在3.16之后才比较完善尤其是TRY_COMPILE_TARGET_TYPE这个特性老版本Cmake会在配置阶段尝试链接可执行文件导致交叉编译配置直接失败。如果你用的还是2.8.x或者3.1.x这种老版本赶紧升级别和自己过不去。我见过不少人卡在“cmake 3.1.3...3.26 or higher is required”这种报错上其实就是Cmake版本太旧连语法都识别不了。推荐直接用apt安装新版本sudo apt update sudo apt install cmake cmake --version如果系统源里的版本还是旧可以试加Kitware官方的apt仓库或者直接去cmake官网下载预编译二进制包。装完之后验证一下HighTec工具链能不能正常调用/opt/hightec/toolchains/tricore/v4.9.4.1/bin/tricore-elf-gcc --version能输出版本信息就说明环境准备好了。4.2 工程目录结构设计良好的目录结构是工程可维护性的基础下面是我推荐的结构project/ ├── CMakeLists.txt ├── cmake/ │ ├── toolchain-hightec-tricore.cmake │ └── linker/ │ └── tc397.ld ├── include/ │ ├── Cpu/ │ ├── Ifx_Types.h │ └── ... ├── src/ │ ├── main.c │ ├── Cpu0_Main.c │ ├── Cpu1_Main.c │ ├── startup.c │ └── ... ├── config/ │ ├── Ifx_Cfg.h │ └── ... └── build/顶层CMakeLists.txt是整个构建的入口cmake/toolchain-hightec-tricore.cmake是工具链文件cmake/linker/放链接脚本src/和include/分开放源文件和头文件config/放芯片相关的配置头文件比如中断优先级、系统时钟配置build/是构建输出目录在.gitignore里忽略掉就行。为什么不把工具链文件放在build目录里因为一个团队里可能有多个人协作统一放在cmake/目录下每次团队clone代码后能保证构建环境一致不会有人把工具链文件改了忘了提交。4.3 顶层CMakeLists.txt逐段详解完整的CMakeLists.txt我分层来写先看最基础的版本# 指定Cmake最低版本 cmake_minimum_required(VERSION 3.16) # 工程名称这里只需要C和汇编 project(aurix_demo C ASM) # 设置芯片型号这里以TC397为例 set(MCU_VARIANT tc397) set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/cmake/linker/${MCU_VARIANT}.ld) # 工具链文件通过-DCMAKE_TOOLCHAIN_FILE指定这里不做硬编码 if(NOT CMAKE_TOOLCHAIN_FILE) message(FATAL_ERROR Please specify -DCMAKE_TOOLCHAIN_FILEcmake/toolchain-hightec-tricore.cmake) endif() # 编译选项 set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -mtc162 -fno-common \ -ffunction-sections -fdata-sections -Wall -Wextra -O2 -g) set(CMAKE_ASM_FLAGS ${CMAKE_ASM_FLAGS} -mtc162 -g) # 头文件路径 include_directories( ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/config ${CMAKE_SOURCE_DIR}/src ) # 收集源文件注意src目录下所有.c文件都会被包含 file(GLOB_RECURSE APP_SOURCES ${CMAKE_SOURCE_DIR}/src/*.c ${CMAKE_SOURCE_DIR}/src/*.s ${CMAKE_SOURCE_DIR}/src/*.S ) # 生成可执行目标 add_executable(${PROJECT_NAME}.elf ${APP_SOURCES}) # 链接选项 target_link_libraries(${PROJECT_NAME}.elf PRIVATE) target_link_options(${PROJECT_NAME}.elf PRIVATE -T${LINKER_SCRIPT} -Wl,--gc-sections -Wl,--no-warn-rwx-segments -nostartfiles ) # 生成hex和bin文件 add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD COMMAND ${CMAKE_OBJCOPY} -O ihex ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin COMMENT Generating hex and bin files )这里有几个关键点需要展开讲。file(GLOB_RECURSE ...)这行代码我猜会有人反对因为CMake官方不推荐用glob来收集源文件理由是新增文件后必须重新运行cmake配置才能被识别。但在嵌入式工程里我实测反而觉得这个方式最实用——你新增一个.c文件只要在src目录下重新运行cmake命令就会自动包含进去完全不用手改CMakeLists.txt。代价是每次新增文件后要重新配置但相比手动改makefile这个成本几乎可以忽略。target_link_options是CMake 3.13之后才有的命令所以前面才要求Cmake版本至少3.16。这里把链接脚本、gc-sections、nostartfiles都通过target_link_options传递而不是冗余设置到全局的CMAKE_EXE_LINKER_FLAGS里以后如果要生成多个可执行目标每个目标的链接参数相互独立不会打架。最后的add_custom_command是为了在链接完成后自动生成hex和bin文件。嵌入式的固件发布最终要用的是hex或bin格式直接在POST_BUILD阶段生成省得每次还要手动执行objcopy。4.4 生成makefile并编译所有配置准备好之后构建命令非常简单rm -rf build mkdir build cd build cmake .. -DCMAKE_TOOLCHAIN_FILE../cmake/toolchain-hightec-tricore.cmake make -j8第一行清空build目录这一步不要偷懒尤其是改了工具链文件或链接脚本之后旧缓存可能会导致各种稀奇古怪的问题。Cmake的缓存机制很强大但也经常因为缓存了旧的配置导致新配置不生效所以最保险的做法就是删掉build目录重新生成。-DCMAKE_TOOLCHAIN_FILE参数指向工具链文件这是整个流程的核心。也可以把它写到CMakeLists.txt里用set(CMAKE_TOOLCHAIN_FILE ...)硬编码但我不推荐这种做法因为不同人的HighTec安装路径可能不同硬编码会导致协作同事那边直接配置失败。用-D传参方式每个人只需要修改自己本地的环境变量或者构建命令就行。最终输出会是这样-- The C compiler identification is GNU 9.2.0 -- The ASM compiler identification is GNU -- Check for working C compiler: /opt/hightec/toolchains/tricore/v4.9.4.1/bin/tricore-elf-gcc -- Check for working C compiler: /opt/hightec/toolchains/tricore/v4.9.4.1/bin/tricore-elf-gcc -- works -- Configuring done -- Generating done -- Build files have been written to: /home/user/project/build然后make编译最终会生成aurix_demo.elf、aurix_demo.hex和aurix_demo.bin三个文件整个流程就跑通了。5. 常见问题与排查技巧实录5.1 报错“make没有指明目标并且找不到makefile”这个报错基本可以断定build目录下没有生成makefile也就是cmake配置阶段就没通过。最常见的原因有三个一是忘了指定CMAKE_TOOLCHAIN_FILE参数Cmake使用了宿主机的默认编译器配置阶段失败后没有生成makefile二是工具链文件路径写错了Cmake找不到编译器直接报错退出三是当前目录就不对没进入build目录就在乱敲make。排查时先确认build目录下有没有CMakeCache.txt没有就说明配置阶段完全没成功重新查看cmake输出信息定位具体是哪一步报错。我建议直接删除build目录重新配置比在旧目录里反复调整缓存要快得多。5.2 Cmake版本太低导致的高级特性不可用我见过太多次这种问题了。系统自带的是Cmake 2.8.12.2或者3.1.x然后项目里用了3.16才有的特性比如target_link_options、TryCompileTargetType直接报错CMake 3.1.3...3.26 or higher is required. You are running version 2.8.12.2这种就别想着改CMakeLists.txt来兼容了纯粹是浪费时间。旧版本和新版本在语法和语义上差异很大老老实实装新版本。Ubuntu上用Kitware源的方式最方便wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | sudo apt-key add - sudo apt-add-repository deb https://apt.kitware.com/ubuntu/ focal main sudo apt update sudo apt install cmake装完再跑一次cmake --version确认版本。3.16起步我建议用3.20以上版本对交叉编译工具链的支持更完善。5.3 HighTec编译器找不到头文件或库文件配置通过但编译时总报找不到头文件这通常和Cmake的查找路径有关。Cmake在交叉编译环境下默认会在系统根目录中找头和库但HighTec的头文件并不在宿主机的系统路径里。解决方案有两种。第一种是在工具链文件里设置CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY这样Cmake只会去CMAKE_FIND_ROOT_PATH指定的路径找头文件不会跑到宿主机路径里.但我发现实际做下来这个设置只会影响find_path和find_library这类命令的搜索范围对include_directories设置的路径不生效。第二种也是最直接的在CMakeLists.txt里用include_directories明确指定HighTec相关的头文件路径。我在前面示例里已经把include/和config/加进去了实际情况还要加上HighTec自带的编译器内置头文件路径比如include_directories( /opt/hightec/toolchains/tricore/v4.9.4.1/tricore/include )熟悉GCC的人肯定会在这里提一句可以用${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}能自动获取编译器默认搜索路径。但我实际试下来不同HighTec版本这个变量输出的内容差异很大不如老老实实在CMakeLists.txt里写清楚最多加个判断避免重复包含。5.4 链接阶段失败找不到启动文件和入口符号这个问题的典型报错长这样/usr/bin/ld: warning: cannot find entry symbol __START; defaulting to 00000000看到这段就说明链接器没能找到启动文件或者入口符号定义。AURIX的启动流程不是简单地跳转到main它要先初始化栈指针、Clear BSS段、初始化数据段这些逻辑都在启动文件里完成入口符号一般是__START。解决方案是在工程里加入自己的启动文件通常是汇编文件startup_tc39x.S然后在链接选项里保留-nostartfiles不要用默认的crt0。还有一点CMakeLists.txt里必须声明启用汇编语言支持project(aurix_demo C ASM)如果没有把ASM加进去file(GLOB_RECURSE APP_SOURCES ...)收集到的.S文件不会被当成汇编源文件处理启动代码就彻底丢失了。这个坑我刚开始也踩过编译阶段一切顺利链接阶段突然报找不到符号查了好久才意识到是project()里少了ASM非常隐蔽。5.5 编译产出体积异常大或者链接非常慢这个问题很多时候和-O0还有未使用代码有关。进入调试阶段大家都喜欢用-O0方便断点调试但AURIX这种Flash容量固定的MCU优化等级不上去固件体积很容易膨胀甚至撑爆Flash。我的做法是debug版用-O0 -grelease版用-Os优化体积配合--gc-sections能有效控制最终编译产物体积。如果是链接很慢检查一下是否别把整个iLLD库的几百个文件都glob进去了按需精简源文件集合能大幅缩短链接时间。6. 个人实操经验与踩坑心得这套Cmake HighTec的构建系统我已经用了几个月从最初的手工makefile迁移过来之后最大的感受是整个工程的可维护性提升了一个量级。模块之间怎么组合、哪些源文件参与编译、编译选项怎么配置都清清楚楚写在CMakeLists.txt里新同事接手的时候也不需要再去逐行看复杂的makefile稍微解释一下CMakeLists就能上手改。有几个经验想单独说一下。第一个工具链文件一定要提交到代码仓库里。很多人觉得工具链文件是本机配置不放版本管理这是个严重的错误。同一个工程有人用HighTec v4.6有人用v4.9编译选项和链接行为都有差异如果不统一协作时很容易出现“我这边能编过去你那怎么就不行”。把工具链文件提交上去大家用一致的版本再把编译器安装路径统一约定好协作顺畅得多。第二个构建目录不要提交到代码仓库。build目录里的CMakeCache.txt、生成的makefile都是一次性产物每个人可以在本地随意清理重建提交到仓库里只会制造矛盾。第三个给不同的芯片型号建立独立的链接脚本。TC397和TC375寄存器和内存布局有差异链接脚本不能通用。我现在的做法是在cmake/linker/目录下按芯片型号分文件存放CMakeLists.txt里用set(MCU_VARIANT tc397)来选择换芯片时改一个变量就行。这套体系还有一个扩展空间是CI集成。把构建命令写进Jenkins脚本或者GitLab CI的配置里每次提交代码自动触发编译有错误第一时间就能在CI上看到不用等人去本地编译。从手写makefile到Cmake自动化生成makefile这不光是一次工具链更新而是一直嵌入开发流程的规范化升级。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。