概念先知道
- 一个工程 = 应用代码 + 构建配置 + 烧录配置:
main.c写功能,CMakeLists.txt/Makefile/Kconfig/defconfig告诉构建系统怎么编,flash_prog_cfg.ini告诉烧录工具怎么烧。 - SDK 内工程:工程目录放在 SDK 的
examples/下,Makefile通过../..自动定位 SDK 根目录,是官方例程的组织方式。 - 改哪个文件最快见效:功能在
main.c,组件开关在defconfig,工程名在CMakeLists.txt。
操作步骤
SDK 里每个 examples/ 下的目录都是一个独立工程。新建一个自己的工程目录(名字用字母数字和下划线):
cd bouffalo_sdk/examples
mkdir my_project
cd my_project这是工程的构建配置,告诉构建系统“找哪个 SDK、入口文件是谁、工程叫什么”。最小内容如下(照抄即可):
cmake_minimum_required(VERSION 3.15)
find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE})
sdk_set_main_file(main.c)
project(my_project)make 命令的入口,它把 SDK 根目录告诉构建系统,并引入 SDK 的构建脚本 project.build:
SDK_DEMO_PATH ?= $(abspath .)
BL_SDK_BASE ?= $(abspath ./../..)
export BL_SDK_BASE
include $(BL_SDK_BASE)/project.build配置菜单入口(make menuconfig 用),内容固定,照抄:
mainmenu "SDK Configuration"
source "$BL_SDK_BASE/Kconfig"工程的默认配置:用 CONFIG_XXX =y/n 开关组件(FreeRTOS、Shell、日志、外设驱动等)。最少可只写两行,其余用 SDK 默认:
CONFIG_FREERTOS =y
CONFIG_SHELL =y应用入口。第一件事永远是 board_init(),然后初始化外设、进主循环:
#include "board.h"
#include "bflb_mtimer.h"
#define DBG_TAG "MAIN"
#include "log.h"
int main(void)
{
board_init();
LOG_I("my project start\r\n");
while (1) {
bflb_mtimer_delay_ms(1000);
}
}回到工程目录执行(Ai-M61/Ai-M62 统一 bl616;BOARD 为开发板类型):
make CHIP=bl616 BOARD=bl616dk按住 BOOT 键短按 EN/RST 进入下载模式后烧录,固件在 build/build_out/ 下:
make flash CHIP=bl616 COMX=/dev/ttyUSB0每个文件是干什么的(逐文件拆解)
CMakeLists.txt:构建的“总入口”
cmake_minimum_required(VERSION 3.15) # 最低 CMake 版本
find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE}) # 找 SDK 的 CMake 包
sdk_set_main_file(main.c) # 指定应用入口文件
project(my_project) # 工程名(用于生成固件名)find_package(bouffalo_sdk ...):让 CMake 找到 SDK 提供的构建包(SDK 根目录cmake/bouffalo_sdk-config.cmake)。$ENV{BL_SDK_BASE}是环境变量,Makefile 会先把它设置好。sdk_set_main_file(main.c):告诉 SDK“这个工程的入口函数在哪个文件”,SDK 会负责链接main与启动代码。project(...):工程名,会出现在固件文件名里(如my_project_...bin)。
Makefile:make 命令的入口
SDK_DEMO_PATH ?= $(abspath .) # 当前工程目录(SDK 构建脚本需要)
BL_SDK_BASE ?= $(abspath ./../..) # SDK 根目录:examples 在 SDK 里,上两级即是
export BL_SDK_BASE # 导出给 CMake 的 $ENV{BL_SDK_BASE}
include $(BL_SDK_BASE)/project.build # 引入 SDK 构建脚本(调 CMake、编译、打包固件)?= 表示“如果环境里没定义才用默认值”,所以你也可以在命令行覆盖:make BL_SDK_BASE=/path/to/sdk ...。
Kconfig:配置菜单入口
mainmenu "SDK Configuration"
source "$BL_SDK_BASE/Kconfig"它是 make menuconfig 图形配置界面的入口,把 SDK 的 Kconfig 树“引用”进来。日常直接改 defconfig 即可,不一定要用菜单。
defconfig:工程的默认配置
CONFIG_FREERTOS =y # 启用 FreeRTOS
CONFIG_SHELL =y # 启用串口命令行
CONFIG_BFLB_LOG =n # 日志开关- 每个
CONFIG_XXX都是一个组件/功能开关,=y启用、=n关闭、注释掉用默认值。 - 编译时可以临时覆盖:
make CONFIG_BFLB_LOG=y,不改文件。 - 例程自带较长 defconfig 是为了开满该例程用到的功能;自己的工程按需精简即可。
main.c:应用入口
#include "board.h"
#include "bflb_mtimer.h"
#define DBG_TAG "MAIN"
#include "log.h"
int main(void)
{
board_init(); /* 初始化开发板(时钟/引脚/串口),第一件事 */
LOG_I("my project start\r\n"); /* 日志打印(走串口) */
while (1) {
bflb_mtimer_delay_ms(1000);
}
}board_init():所有工程的第一个函数,负责把板子“点着”。LOG_I(...):带标签的信息日志;还有LOG_E/W/D/T分级打印。- 需要多任务时,在
main里xTaskCreate创建任务,最后vTaskStartScheduler()(参考 FreeRTOS 例程)。
flash_prog_cfg.ini:烧录配置(可选)
[cfg]
erase = 1 # 0 不擦除 / 1 擦除编程区 / 2 全片擦除
[FW]
filedir = ./build/build_out/my_project_$(CHIPNAME)*.bin # 固件路径(支持通配符)
address = 0x000000 # 烧录起始地址告诉烧录工具“固件在哪、烧到哪”。不创建也能编译,但 make flash 烧录时需要它。
FreeRTOSConfig.h(按需)
需要自定义任务栈/优先级/堆大小时,可在工程目录放一份 FreeRTOSConfig.h(参考其他例程);不放则使用 SDK 公共配置。
如何把自定义库添加到工程
工程写大了以后,你会想把可复用的代码(传感器驱动、协议解析、工具函数等)拆成独立的库,而不是全堆在 main.c 里。bouffalo_sdk 提供两种方式:
| 方式 | 适用场景 | 关键写法 |
|---|---|---|
| 方式一:独立成库(推荐) | 代码可复用、想和 main.c 解耦 | sdk_generate_library() + add_subdirectory() |
| 方式二:直接塞进 app(最简单) | 就一两个文件、不想建库目录 | sdk_add_include_directories() + target_sources(app PRIVATE ...) |
原理:SDK 的
project()宏会把所有用sdk_generate_library()登记过的库统一链接进固件(--whole-archive),所以建好的库不用再手动target_link_libraries。
方式一:独立成库(推荐)
假设要加一个 my_lib 库,工程目录结构变成:
my_project/
├── CMakeLists.txt
├── Makefile
├── Kconfig
├── defconfig
├── main.c
└── my_lib/
├── CMakeLists.txt
├── include/
│ └── my_lib.h
└── src/
└── my_lib.c方式一操作步骤:
在 my_lib/ 目录下新建 CMakeLists.txt:
sdk_generate_library():生成静态库libmy_lib.a(库名取自目录名),并自动登记到 SDK 的全局链接列表。sdk_add_include_directories(include):把my_lib/include加进全局头文件搜索路径,任何源文件都能#include "my_lib.h"。sdk_library_add_sources(src/my_lib.c):把源文件编译进当前库;多个文件写多行即可。
只想给库内部用、不想暴露给外部的头文件目录,用
sdk_add_private_include_directories(...)。
sdk_generate_library()
sdk_add_include_directories(include)
sdk_library_add_sources(src/my_lib.c)在工程顶层 CMakeLists.txt 加一行 add_subdirectory(my_lib)。关键点:必须写在 project(my_project) 之前——因为 project() 宏是在这一步把所有已登记库统一链接进固件,写晚了库不会被编进去。
cmake_minimum_required(VERSION 3.15)
find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE})
sdk_set_main_file(main.c)
add_subdirectory(my_lib) # 引入自定义库(必须写在 project() 之前)
project(my_project)在 main.c 里 #include "my_lib.h" 并调用库函数;重新 make CHIP=bl616 BOARD=bl616dk 即可。编译日志里会多出一行 [register library : libmy_lib] ...,说明库已被登记。
#include "my_lib.h"
int main(void)
{
board_init();
my_lib_init(); /* 来自自定义库 */
while (1) {
bflb_mtimer_delay_ms(1000);
}
}方式二:直接塞进 app(最简单)
只有一两个文件时,不用建库目录,直接把源文件挂到 app 目标上(SDK 已帮你建好 app 这个库,main.c 就在里面):
cmake_minimum_required(VERSION 3.15)
find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE})
sdk_set_main_file(main.c)
sdk_add_include_directories(my_lib) # 头文件目录
target_sources(app PRIVATE my_lib/my_lib.c) # 源文件直接编进 app
project(my_project)target_sources(app PRIVATE ...):把my_lib.c直接编进app,多个源文件用换行/空格隔开。- 这种方式不生成独立的
.a,代码和main.c一起编进libapp.a,最省事;等文件多了再升级成方式一即可。
FAQ
编译提示找不到 SDK / bouffalo_sdk
检查工程位置:工程必须放在 examples/ 下(Makefile 里的 ../.. 才能指向 SDK),或显式指定 make BL_SDK_BASE=<SDK路径>。
固件在哪里?
编译成功后固件在 build/build_out/ 目录,文件名含工程名与芯片名(如 my_project_...bin)。
改了 defconfig 没生效?
先清理再编译:make clean && make CHIP=bl616 BOARD=bl616dk;或在命令后直接追加 CONFIG_XXX=y 覆盖。
加了自定义库后编译报 undefined reference / 找不到头文件?
按顺序检查三点:库目录的 CMakeLists.txt 里 sdk_add_include_directories 是否指向了头文件目录、sdk_library_add_sources 是否列全了 .c;顶层 CMakeLists.txt 里 add_subdirectory(my_lib) 是否写在 project(my_project) 之前。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

