Sourcetrail 是一个代码可视化工具,对于接手或分析复杂的 STM32 工程(特别是使用 HAL 库这种层层嵌套的架构)非常有帮助。
1.准备工作
打开 Sourcetrail。
点击主界面的 New Project(新建项目)。
Project Name: 给项目起个名字(例如
STM32_Analysis)。Project Location: 选择一个存放 Sourcetrail 索引文件的文件夹(注意:不要放在 Keil 工程的源代码目录里,最好在该目录旁边新建一个
SourceTrail_Cache文件夹,以免污染源码)。
点击 Add Source Group。
2.选择Source Group类型
这里是关键。因为我们没有现成的编译数据库。
在弹出的窗口中,选择 C/C++ from Empty Source Group。
点击 Next。
3.配置 Source Group(核心步骤)
在这个界面,你需要告诉 Sourcetrail 三件事:代码在哪?头文件在哪?宏定义是什么?
3.1 C Standard (C语言标准)
填什么:选
C99或C11。为什么:STM32 HAL 库通常支持 C99 标准。如果不选,默认的 C89 可能会在解析
for(int i=0;...这种写法时报错。
3.2 Files & Directories (代码在哪里)
这里要添加你的 .c 和 .h 文件所在的根目录。
点击 Add path...,选择你的 STM32 工程的根文件夹(包含
User,Drivers,Middlewares等文件夹的最外层目录)。一般直接把最上层的三个文件添加进去,然后把所有文件中用不到的添加到Excluded Files & Directories,这部分在3.5阐述

3.3 Excluded Files & Directories (排除的文件)
排除 Keil 编译生成的中间文件和工程中用不到的文件夹。
STM32 工程编译后会生成很多中间文件(如
.o,.d,.axf),这些在MDK-ARM/中以工程名称命名的文件夹中。
工程中不需要的文件夹也一定要添加到此,不然会
error fatal,比如此工程中没有用到LVGL就把这个文件夹添加至此。
需要将CMSIS中的DSP也添加至此,不然会报错类似
“arm_math.h”&''math.h'' file not found。
可以参考此工程的Excluded Files & Directories
3.4 Include Paths (头文件在哪里)
这是新手最容易卡住的地方。如果路径不对,Sourcetrail 会提示 "File not found"。你需要把 Keil 里的包含路径复制过来。
如何查找:
打开你的 Keil 工程。
点击魔术棒图标 (Options for Target) -> C/C++ (AC6) 或 C/C++ 选项卡。
找到 Include Paths 栏,点击旁边的
...按钮。你会看到一串路径(例如
../Core/Inc;../Drivers/STM32...)。
在 Sourcetrail 中操作:
你需要将这些路径手动添加进 Sourcetrail 的 Include Paths 列表里。
注意:Sourcetrail 通常需要绝对路径或者相对于 Sourcetrail 工程文件的路径。如果在 Keil 里是相对路径,建议在 Sourcetrail 里浏览选择对应的文件夹(比如
Core/Inc,Drivers/CMSIS/Include,Drivers/STM32xx_HAL_Driver/Inc等)。
解决 RTE_Components.h not found
CMSIS 库有时会默认引用这个文件来检查你开启了哪些组件。这个文件通常是 Keil 动态生成的,或者压根不存在(如果你只是单纯用 HAL 库)。
如果你的工程里真的有这个文件(大概率是有的)
去你的工程目录
MDK-ARM文件夹下搜一下有没有RTE_Components.h。如果有,把它所在的文件夹路径加到 Include Paths。
如果你的工程里没有这个文件
在存放srctrlprj文件的地方新建一个文件夹,新建一个空文件夹叫
MockHeader。在里面新建一个空的文本文件,重命名为
RTE_Components.h(里面什么都不用写)。把这个
MockHeader文件夹的路径,添加到 Sourcetrail 的 Include Paths 里。
原理:Sourcetrail 只要找到这个文件就不报错了,内容为空也没关系。
创建“替身”文件解决 intrinsics.h问题
intrinsics.h 是 ARM 编译器内部极其底层的头文件。有时即便你加了路径,因为版本差异,Sourcetrail 也可能认不出它,或者它其实被包含在编译器二进制里而没有实体文件。
建立替身文件夹:
在存放srctrlprj文件的地方新建一个文件夹,命名为
MockHeader(如果你之前为了RTE_Components.h建过,就用同一个)。
建立替身文件:
在该文件夹里,新建一个空白文本文件。
将其重命名为
intrinsics.h(注意后缀名要改,不要是 .txt)。内容留空即可(或者写一句
// Mock file for Sourcetrail)。
添加路径:
在 Sourcetrail -> Source Group -> Include Paths 中,把你这个
MockHeader文件夹的路径加进去。注意:把这个路径尽量往上移(如果有 Move Up 选项),让它优先被搜索到。
可以参考此工程的Include Paths

3.5 Global Include Paths添加系统头文件路径
解决报错 arm_compat.h, intrinsics.h 。arm_compat.h 和 intrinsics.h 是 AC6 (ARMCLANG) 编译器特有的头文件。
需要把 Keil 安装目录下的编译器自带头文件路径加进去(通常在
C:\Keil_v5)AC5 (ARMCC)和AC6 (ARMCLANG)不能同时添加。

3.6 Compiler Flags中宏定义&Keil 专有关键字
STM32 的代码里充满了 #ifdef。如果你不填这个,Sourcetrail 就不知道你用的是哪款芯片,代码会变灰(被判定为未激活代码)。
如何查找:
回到 Keil 的魔术棒 -> C/C++ 选项卡。
找到 Define 栏。
通常会有类似这样的内容:
USE_HAL_DRIVER,STM32F103xB。
在 Sourcetrail 中操作:
点击 Compiler Flags 下的 Add。
把 Keil 里的定义拆开添加。
例如添加一条:
-DUSE_HAL_DRIVER再添加一条:
-DSTM32F103xB(根据你的具体芯片型号)
Keil 编译器有一些特殊的关键字(如
__weak,__packed,__irq),Sourcetrail 使用的 Clang 解析器可能不认识它们,从而报错。

这里提供一个示例,可以把其中的USE_HAL_DRIVER和STM32F103xB改成对应工程的
-target arm-none-eabi -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -D__ARM_ACLE=200 -std=c99 -DUSE_HAL_DRIVER -DSTM32G431xx -D__weak=__attribute__((weak)) -D__packed=__attribute__((__packed__)) -D__inline=inline -D__forceinline=inline -D__IO=volatile -D__value_in_regs=-D__weak=的意思是告诉 Sourcetrail,遇到__weak就把它当成空字符忽略掉。-D__value_in_regs=意思是遇到这个词就当没看见,替换为空。-target arm-none-eabi意思是别用电脑的 CPU 标准(64位),请用 ARM 嵌入式标准(32位)来理解这些数据类型。
总结操作表
填完这些之后,直接点击保存/下一步,然后开始索引(Start Indexing)。
如果在索引过程中遇到很多报错(Error),尤其是提示 "unknown type name 'uint32_t'" 之类的。索引后还会有许多error,但是只要没有fatal就没有关系,不影响使用。
操作指南(参考)
第一步:界面初识(三栏布局)
打开你的项目,你会看到三个主要区域,这是你的“作战控制台”:
左侧:搜索栏 (Search) —— 入口。在这里输入任何你想找的函数、变量或文件名。
中间:关系图 (Graph) —— 核心。显示谁调用了谁,谁包含谁。
右侧:代码框 (Code) —— 细节。显示对应的源代码,并且代码里的符号都是可点击的超链接。
第二步:实战演练 - 顺藤摸瓜(从 main 开始)
这是分析单片机代码最常用的“自顶向下”分析法。
搜索入口: 在左侧搜索栏输入
main。你应该会看到int main(void),点击它。看懂关系图: 中间的图会刷新,此时
main节点在正中间(高亮显示)。向下的箭头(指向别的节点):代表
main调用了 别的函数。向上的箭头(指向 main):代表谁调用了
main(通常是启动文件里的Reset_Handler,或者没有)。
深入挖掘: 假设你在图中看到了
MX_GPIO_Init或SystemClock_Config。点击图中的
MX_GPIO_Init节点。观察变化:
图:变成了以
MX_GPIO_Init为中心。你可以清楚地看到这个初始化函数里又调用了哪些 HAL 库函数(如HAL_GPIO_WritePin)。代码:右侧代码框自动跳转到了
MX_GPIO_Init的定义处。
第三步:实战演练 - 追根溯源(查找引用)
这是“自底向上”分析法。当你接手别人的代码,想知道“这个全局变量到底在哪里被修改了?”或者“这个底层驱动函数被谁调用了?”时非常有用。
场景:你想知道
HAL_UART_Transmit在你的工程里哪里被用到了。操作:
在左侧搜索
HAL_UART_Transmit并点击结果。或者,在右侧代码框里,看到任何一句代码出现了这个函数,直接点击代码里的文字。
分析图表:
看指向它的箭头(Used By / Called By)。
你会发现可能有多个箭头指向它。这说明工程里有多个地方在发串口数据。
点击那些箭头源头的节点,你就可以快速跳转到具体的业务逻辑代码(比如
Send_Sensor_Data)。
第四步:必备的高效技巧
后退/前进按钮: 在界面左上角有类似浏览器的
<和>按钮。看代码看晕了,点一下后退就能回到上一个查看的函数。只看函数调用 (Call Graph): 默认的图可能很乱(包含了变量使用、继承关系等)。
在 Graph 窗口左上角,有一个下拉菜单(通常显示为 "All")。
尝试把它改成 Call Graph (或者手动关闭 Data 类型的连线)。这样图示就清爽多了,只显示“函数A 调用 函数B”。
书签 (Bookmark): 如果发现了一个关键算法函数(比如
PID_Calc),怕待会找不到了。右键点击该节点 -> Bookmark (Set/Unset)。
以后点击左侧栏的“Bookmarks”标签,就能一键回去。