3. 开发环境搭建(下):TFLite Micro源码获取与交叉编译链配置、CMake工程搭建、Hello World例程验证
上一节我们把STM32CubeMX和X-CUBE-AI的图形化工具链跑通了。但说实话,真正做嵌入式AI部署,光靠图形界面是不够的。你迟早要面对源码级的工作——比如裁剪算子、优化内存、或者移植一个CubeMX不直接支持的模型。
这时候,TFLite Micro就派上用场了。这一节,我们亲手把它从GitHub上拉下来,配置好交叉编译链,用CMake搭一个干净的工程,最后跑通Hello World。嗯,这套流程走一遍,后面所有实战章节你都会觉得顺手。
3.1 TFLite Micro源码获取
TFLite Micro的官方仓库在tensorflow/tflite-micro。我个人习惯用git clone加指定tag的方式,避免拉到最新的不稳定代码。
git clone https://github.com/tensorflow/tflite-micro.git
cd tflite-micro
git checkout v0.4.0
拉下来之后,目录结构是这样的:
| 目录/文件 | 作用 |
|---|---|
| tensorflow/lite/micro/ | 核心推理引擎,所有算子实现都在这里 |
| tensorflow/lite/micro/examples/ | 官方例程,包括Hello World、Person Detection等 |
| tensorflow/lite/micro/tools/ | 编译脚本、测试工具、内存分析工具 |
| third_party/ | 第三方依赖,比如CMSIS-NN、FlatBuffers |
我曾经踩过一个坑:直接clone master分支,结果编译时发现某个算子接口变了,排查了半天。所以,务必用tag版本。
3.2 交叉编译链配置
我们要在PC上编译出能在STM32上跑的二进制。这需要ARM交叉编译链。我推荐用ARM官方的GNU Arm Embedded Toolchain。
3.2.1 下载与安装
- 访问ARM官网,下载gcc-arm-none-eabi-10.3-2021.10版本(Linux或Windows看你主机系统)。
- 解压到固定路径,比如
/opt/arm-gcc/或C:\arm-gcc\。 - 把bin目录加入系统PATH。
# Linux 示例
export PATH=/opt/arm-gcc/gcc-arm-none-eabi-10.3-2021.10/bin:$PATH
arm-none-eabi-gcc --version
3.2.2 验证工具链
写一个最简单的main.c,交叉编译试试:
// test.c
int main(void) {
return 0;
}
// 编译命令
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -o test.elf test.c
如果没报错,说明工具链能用。这里的关键参数:
-mcpu=cortex-m4:指定CPU内核。STM32F4/G4用m4,H7用m7。-mfloat-abi=hard:启用硬件浮点。TFLite Micro的算子大量使用浮点运算,软浮点性能差很多。-mfpu=fpv4-sp-d16:指定FPU类型。STM32F4是单精度FPv4。
3.3 CMake工程搭建
TFLite Micro官方推荐用Makefile,但我个人更习惯CMake——跨平台、可读性好、IDE支持也方便。我们从头搭一个。
3.3.1 工程目录结构
stm32_tflite_demo/
├── CMakeLists.txt # 顶层CMake
├── third_party/
│ └── tflite-micro/ # 刚才clone的源码(或软链接)
├── src/
│ └── main.cpp # 我们的应用代码
└── cmake/
└── arm-none-eabi.cmake # 工具链文件
3.3.2 工具链文件
创建一个 arm-none-eabi.cmake,告诉CMake用交叉编译链:
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_C_FLAGS "-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Os -ffunction-sections -fdata-sections" CACHE STRING "")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fno-exceptions -fno-rtti" CACHE STRING "")
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--gc-sections -Wl,-Map=output.map" CACHE STRING "")
-fno-exceptions -fno-rtti 能大幅减小二进制体积。嵌入式设备上,异常和运行时类型信息基本用不到。
3.3.3 顶层CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(stm32_tflite_demo C CXX ASM)
# 添加TFLite Micro源码
add_subdirectory(third_party/tflite-micro)
# 添加我们的应用
add_executable(main.elf
src/main.cpp
)
target_link_libraries(main.elf
tensorflow-lite-micro
)
# 生成hex和bin文件
add_custom_command(TARGET main.elf POST_BUILD
COMMAND arm-none-eabi-objcopy -O ihex main.elf main.hex
COMMAND arm-none-eabi-objcopy -O binary main.elf main.bin
)
3.3.4 编译命令
mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-none-eabi.cmake
make -j4
如果一切顺利,你会看到 main.elf、main.hex、main.bin 三个文件。嗯,到这里工程骨架就搭好了。
3.4 Hello World例程验证
光搭架子不行,得跑个真家伙。TFLite Micro官方提供了一个Hello World例程——它用一个小神经网络拟合正弦波。说白了,就是输入一个x,输出预测的sin(x)值。
3.4.1 例程源码结构
在 tensorflow/lite/micro/examples/hello_world/ 下,核心文件就两个:
main.cc:入口,加载模型、分配张量、循环推理。model.cc:模型数据,一个已经训练好的、量化为int8的正弦波拟合模型。
3.4.2 移植到我们的工程
把这两个文件复制到 src/ 目录下,然后修改CMakeLists.txt:
add_executable(main.elf
src/main.cc
src/model.cc
)
重新编译:
cd build
cmake ..
make -j4
3.4.3 在STM32上运行
用STM32CubeProgrammer把 main.hex 烧录到开发板。打开串口助手(115200 8N1),你会看到类似这样的输出:
x_value: 0.000000, y_value: 0.000000, expected: 0.000000
x_value: 0.157080, y_value: 0.156434, expected: 0.156434
x_value: 0.314159, y_value: 0.309017, expected: 0.309017
...
3.5 知识体系总览
下面这张图把本章的核心流程串起来了:
3.6 常见问题与避坑
这一套流程我教过不少学员,也帮人排查过问题。总结几个高频坑:
- 链接错误:undefined reference to `tflite::MicroMutableOpResolver' —— 大概率是CMake没正确链接tensorflow-lite-micro库。检查add_subdirectory路径。
- 烧录后没输出 —— 先确认串口引脚对不对。STM32的USART1默认是PA9/PA10,但有些开发板用了其他引脚。
- 推理结果全是0 —— 模型数据对齐问题。TFLite Micro要求模型数据8字节对齐,检查model.cc中的数组声明前是否有
__attribute__((aligned(8)))。
好了,到这里你的开发环境就彻底搭好了。源码有了,工具链配好了,CMake工程能编译了,Hello World也跑通了。后面每一章的实战,你都可以基于这个工程模板快速开始。