Skip to content

概念先知道

  • 一个工程 = 应用代码 + 构建配置 + 烧录配置main.c 写功能,CMakeLists.txt/Makefile/Kconfig/defconfig 告诉构建系统怎么编,flash_prog_cfg.ini 告诉烧录工具怎么烧。
  • SDK 内工程:工程目录放在 SDK 的 examples/ 下,Makefile 通过 ../.. 自动定位 SDK 根目录,是官方例程的组织方式。
  • 改哪个文件最快见效:功能在 main.c,组件开关在 defconfig,工程名在 CMakeLists.txt

操作步骤

1
进入 examples 目录并新建工程目录

SDK 里每个 examples/ 下的目录都是一个独立工程。新建一个自己的工程目录(名字用字母数字和下划线):

cd bouffalo_sdk/examples
mkdir my_project
cd my_project
2
创建 CMakeLists.txt

这是工程的构建配置,告诉构建系统“找哪个 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)
3
创建 Makefile

make 命令的入口,它把 SDK 根目录告诉构建系统,并引入 SDK 的构建脚本 project.build

SDK_DEMO_PATH ?= $(abspath .)
BL_SDK_BASE ?= $(abspath  ./../..)

export BL_SDK_BASE

include $(BL_SDK_BASE)/project.build
4
创建 Kconfig

配置菜单入口(make menuconfig 用),内容固定,照抄:

mainmenu "SDK Configuration"

source "$BL_SDK_BASE/Kconfig"
5
创建 defconfig

工程的默认配置:用 CONFIG_XXX =y/n 开关组件(FreeRTOS、Shell、日志、外设驱动等)。最少可只写两行,其余用 SDK 默认:

CONFIG_FREERTOS =y
CONFIG_SHELL =y
6
编写 main.c

应用入口。第一件事永远是 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);
    }
}
7
编译工程

回到工程目录执行(Ai-M61/Ai-M62 统一 bl616BOARD 为开发板类型):

make CHIP=bl616 BOARD=bl616dk
8
烧录固件

按住 BOOT 键短按 EN/RST 进入下载模式后烧录,固件在 build/build_out/ 下:

make flash CHIP=bl616 COMX=/dev/ttyUSB0

每个文件是干什么的(逐文件拆解)

CMakeLists.txt:构建的“总入口”

cmake
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 命令的入口

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:配置菜单入口

text
mainmenu "SDK Configuration"
source "$BL_SDK_BASE/Kconfig"

它是 make menuconfig 图形配置界面的入口,把 SDK 的 Kconfig 树“引用”进来。日常直接改 defconfig 即可,不一定要用菜单。

defconfig:工程的默认配置

text
CONFIG_FREERTOS =y      # 启用 FreeRTOS
CONFIG_SHELL =y         # 启用串口命令行
CONFIG_BFLB_LOG =n      # 日志开关
  • 每个 CONFIG_XXX 都是一个组件/功能开关,=y 启用、=n 关闭、注释掉用默认值。
  • 编译时可以临时覆盖:make CONFIG_BFLB_LOG=y,不改文件。
  • 例程自带较长 defconfig 是为了开满该例程用到的功能;自己的工程按需精简即可。

main.c:应用入口

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 分级打印。
  • 需要多任务时,在 mainxTaskCreate 创建任务,最后 vTaskStartScheduler()(参考 FreeRTOS 例程)。

flash_prog_cfg.ini:烧录配置(可选)

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 库,工程目录结构变成:

text
my_project/
├── CMakeLists.txt
├── Makefile
├── Kconfig
├── defconfig
├── main.c
└── my_lib/
    ├── CMakeLists.txt
    ├── include/
    │   └── my_lib.h
    └── src/
        └── my_lib.c

方式一操作步骤

1
写库自己的 CMakeLists.txt

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)
2
在工程顶层 CMakeLists.txt 里引入这个库

在工程顶层 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)
3
在 main.c 里调用

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
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.txtsdk_add_include_directories 是否指向了头文件目录、sdk_library_add_sources 是否列全了 .c;顶层 CMakeLists.txtadd_subdirectory(my_lib) 是否写在 project(my_project) 之前

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23