Sourcetrail 是一个代码可视化工具,对于接手或分析复杂的 STM32 工程(特别是使用 HAL 库这种层层嵌套的架构)非常有帮助。

1.准备工作

  1. 打开 Sourcetrail。

  2. 点击主界面的 New Project(新建项目)。

  3. Project Name: 给项目起个名字(例如 STM32_Analysis)。

  4. Project Location: 选择一个存放 Sourcetrail 索引文件的文件夹(注意:不要放在 Keil 工程的源代码目录里,最好在该目录旁边新建一个 SourceTrail_Cache 文件夹,以免污染源码)。

  5. 点击 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 里的包含路径复制过来。

如何查找:

  1. 打开你的 Keil 工程。

  2. 点击魔术棒图标 (Options for Target) -> C/C++ (AC6) 或 C/C++ 选项卡。

  3. 找到 Include Paths 栏,点击旁边的 ... 按钮。

  4. 你会看到一串路径(例如 ../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 库)。

  • 如果你的工程里真的有这个文件(大概率是有的)

    1. 去你的工程目录 MDK-ARM 文件夹下搜一下有没有 RTE_Components.h。

    2. 如果有,把它所在的文件夹路径加到 Include Paths。

  • 如果你的工程里没有这个文件

    1. 在存放srctrlprj文件的地方新建一个文件夹,新建一个空文件夹叫 MockHeader。

    2. 在里面新建一个空的文本文件,重命名为 RTE_Components.h(里面什么都不用写)。

    3. 把这个 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 就不知道你用的是哪款芯片,代码会变灰(被判定为未激活代码)。

如何查找:

  1. 回到 Keil 的魔术棒 -> C/C++ 选项卡。

  2. 找到 Define 栏。

  3. 通常会有类似这样的内容: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位)来理解这些数据类型。

总结操作表

选项名称

操作指南

示例

C Standard

下拉选择 C99

C99

Files & Directories...

必须。添加工程根目录

D:/Project/MySTM32Bot

Excluded Files...

建议。添加 MDK 的输出目录

D:/Project/MySTM32Bot/MDK-ARM/Objects

Include Paths

必须。添加 Keil 里的头文件路径

D:/Project/MySTM32Bot/Core/Inc (等多条)

Compiler Flags

必须。填宏定义,前缀加 -D

-DUSE_HAL_DRIVER -DSTM32F407xx

Global Include Paths

必须。AC5或者AC6

D:/keil5/ARM/ARMCLANG/include

Precompiled...

空

(不填)

填完这些之后,直接点击保存/下一步,然后开始索引(Start Indexing)。

如果在索引过程中遇到很多报错(Error),尤其是提示 "unknown type name 'uint32_t'" 之类的。索引后还会有许多error,但是只要没有fatal就没有关系,不影响使用。

操作指南(参考)

第一步:界面初识(三栏布局)

打开你的项目,你会看到三个主要区域,这是你的“作战控制台”:

  1. 左侧:搜索栏 (Search) —— 入口。在这里输入任何你想找的函数、变量或文件名。

  2. 中间:关系图 (Graph) —— 核心。显示谁调用了谁,谁包含谁。

  3. 右侧:代码框 (Code) —— 细节。显示对应的源代码,并且代码里的符号都是可点击的超链接。


第二步:实战演练 - 顺藤摸瓜(从 main 开始)

这是分析单片机代码最常用的“自顶向下”分析法。

  1. 搜索入口: 在左侧搜索栏输入 main。你应该会看到 int main(void),点击它。

  2. 看懂关系图: 中间的图会刷新,此时 main 节点在正中间(高亮显示)。

    • 向下的箭头(指向别的节点):代表 main 调用了 别的函数。

    • 向上的箭头(指向 main):代表谁调用了 main(通常是启动文件里的 Reset_Handler,或者没有)。

  3. 深入挖掘: 假设你在图中看到了 MX_GPIO_Init 或 SystemClock_Config。

    • 点击图中的 MX_GPIO_Init 节点。

    • 观察变化:

      • 图:变成了以 MX_GPIO_Init 为中心。你可以清楚地看到这个初始化函数里又调用了哪些 HAL 库函数(如 HAL_GPIO_WritePin)。

      • 代码:右侧代码框自动跳转到了 MX_GPIO_Init 的定义处。


第三步:实战演练 - 追根溯源(查找引用)

这是“自底向上”分析法。当你接手别人的代码,想知道“这个全局变量到底在哪里被修改了?”或者“这个底层驱动函数被谁调用了?”时非常有用。

  1. 场景:你想知道 HAL_UART_Transmit 在你的工程里哪里被用到了。

  2. 操作:

    • 在左侧搜索 HAL_UART_Transmit 并点击结果。

    • 或者,在右侧代码框里,看到任何一句代码出现了这个函数,直接点击代码里的文字。

  3. 分析图表:

    • 看指向它的箭头(Used By / Called By)。

    • 你会发现可能有多个箭头指向它。这说明工程里有多个地方在发串口数据。

    • 点击那些箭头源头的节点,你就可以快速跳转到具体的业务逻辑代码(比如 Send_Sensor_Data)。


第四步:必备的高效技巧

  1. 后退/前进按钮: 在界面左上角有类似浏览器的 < 和 > 按钮。看代码看晕了,点一下后退就能回到上一个查看的函数。

  2. 只看函数调用 (Call Graph): 默认的图可能很乱(包含了变量使用、继承关系等)。

    • 在 Graph 窗口左上角,有一个下拉菜单(通常显示为 "All")。

    • 尝试把它改成 Call Graph (或者手动关闭 Data 类型的连线)。这样图示就清爽多了,只显示“函数A 调用 函数B”。

  3. 书签 (Bookmark): 如果发现了一个关键算法函数(比如 PID_Calc),怕待会找不到了。

    • 右键点击该节点 -> Bookmark (Set/Unset)。

    • 以后点击左侧栏的“Bookmarks”标签,就能一键回去。