概念先知道
- 日志(Log):程序运行时打印到串口的文字,用来观察运行状态、定位 bug,相当于程序的“日记”。
- 分级日志:按严重程度分级,例程用到的级别从高到低为 F(fatal 致命)、E(error 错误)、W(warning 警告)、I(info 信息)、D(debug 调试)、T(trace 跟踪)。
- 等级颜色:不同级别用不同颜色区分,一眼就能看出严重程度。颜色由
BFLB_LOG_COLOR_*宏实际定义(bflb_log_conf.h,例程目录的bflog_conf_user.h是参考副本):F 品红、E 红、W 黄、I 无色(BFLB_LOG_SGR_RESET)、D 白、T 暗淡白(BFLB_LOG_SGR_FAINT)。例程配置中绿色 INFO 那行被注释掉了,所以实际输出 INFO 不带颜色。 - 日志标签(Tag):给日志分组起名字(如
MAIN、TEST),可以按标签整体开启或关闭过滤,方便只看某一部分日志。 - 同步 / 异步模式:同步模式直接立即输出;异步模式(同目录的
freertos_async例程)先把日志放入队列,由专门线程输出,适合日志量大的场景。
例程功能简介
本页对应博流官方 SDK 的 barebone_sync 例程(examples/bflog/barebone_sync),演示基于 BFLB_LOG 组件的日志打印、分级与标签过滤:
- 创建 4096 字节内存池的日志记录器,模式为同步(
BFLB_LOG_MODE_SYNC); - 创建 UART 流式输出通道(带颜色),并把输出函数接到
uart0; - 主循环每秒打印 F/E/W/I/D/T 六级日志,并循环切换三种过滤状态:全部开启 → 只开
MAIN→ 全部关闭; log_test.c里定义了TEST标签,演示多文件下标签的定义与过滤效果。- 同族例程(
examples/bflog/):freertos_async(FreeRTOS 多线程异步日志)。
操作步骤
本页不需要额外接线。在终端进入 SDK 的日志例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/bflog/barebone_sync执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:
make CHIP=bl616 BOARD=bl616dk用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0打开串口助手(波特率 2000000)。程序每秒循环一轮:先打印 enable all output(MAIN 与 TEST 两组日志都显示),再打印 enable only MAIN output(只显示 MAIN 组),最后打印 disable all output(两组都不显示),如此循环。每轮都会输出 F/E/W/I/D/T 六级日志(hello world this is ... 与 hello test this is ...)。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
例程调用的 API 介绍
bflb_log_create(log, pool, size, mode)
创建日志记录器(recorder),负责接收日志并分发给输出通道。
参数:
log:bflb_log_t记录器对象(例程为example_recorder)pool:日志内存池指针,例程为 4KB 对齐数组example_poolsize:内存池大小(字节),例程EXAMPLE_LOG_POOL_SIZE = 4096mode:BFLB_LOG_MODE_SYNC同步模式 / 异步模式
返回值:成功返回 0;失败返回负值错误码
bflb_log_direct_create(direct, type, color, lock, unlock)
创建日志输出通道(direct),例程为流式输出并开启颜色。
参数:
direct:bflb_log_direct_t输出通道对象type:输出类型,例程BFLB_LOG_DIRECT_TYPE_STREAM(流式)color:BFLB_LOG_DIRECT_COLOR_ENABLE开启颜色lock/unlock:互斥锁回调,例程为NULL
返回值:成功返回 0;失败返回负值错误码
bflb_log_direct_init_stream(direct, stream_output)
绑定流式输出的实际输出函数(例程逐个调用 bflb_uart_putchar 写到 uart0)。
参数:
direct:流式输出通道对象stream_output:输出回调,形如uint16_t (*)(void *, uint16_t)
返回值:成功返回 0;失败返回负值错误码
bflb_log_append(log, direct)
把输出通道添加到记录器,之后日志才会被转发到该通道。
参数:
log:记录器对象direct:输出通道对象
返回值:成功返回 0;失败返回负值错误码
bflb_log_direct_resume / bflb_log_resume(direct / log)
把输出通道 / 记录器恢复为工作状态(创建后默认挂起,必须 resume 才能输出)。
参数:
direct/log:对应对象指针
返回值:成功返回 0;失败返回负值错误码
bflb_log_global_filter("MAIN", enable)
按标签全局开启 / 关闭某组日志输出。
参数:
tag_string:标签名,例程"MAIN"/"TEST"enable:true开启 /false关闭
返回值:成功返回 0;失败返回负值错误码
BFLB_LOG_F / E / W / I / D / T(recorder, fmt, ...)
分级日志宏:F 致命、E 错误、W 警告、I 信息、D 调试、T 跟踪。
参数:
recorder:记录器对象(例程&example_recorder)fmt, ...:printf 风格格式串与参数
返回值:无
BFLB_LOG_DEFINE_TAG / BFLB_LOG_GET_TAG(name, string, enable)
定义日志标签:BFLB_LOG_DEFINE_TAG(MAIN, "MAIN", true) 把标签放进专门的 section,之后用 BFLB_LOG_GET_TAG(MAIN) 取出并赋给 BFLB_LOG_TAG,本文件中的日志宏就会带上该标签。
参数:
name:标签变量名string:标签字符串(打印与过滤时使用)enable:初始是否使能
返回值:无
完整代码
以下为 barebone_sync 例程完整源码,与官方示例(examples/bflog/barebone_sync)一致,默认折叠,点击展开:
📜 点击展开 barebone_sync/main.c 完整代码
#define DBG_TAG "MAIN"
#include "bflb_mtimer.h"
#include "bflb_uart.h"
#include "bflb_rtc.h"
#include "bflb_clock.h"
#include "board.h"
#include "bflb_log.h"
/*!< Adding the BFLB_LOG tag allows the use of tag filtering functionality */
/*!< If not added, the tag can still be displayed, but tag filtering will not work */
/*!< Define a BFLB_LOG tag */
BFLB_LOG_DEFINE_TAG(MAIN, DBG_TAG, true);
/*!< Cancel the previous BFLB_LOG tag */
#undef BFLB_LOG_TAG
/*!< Define the BFLB_LOG tag as a new tag */
#define BFLB_LOG_TAG BFLB_LOG_GET_TAG(MAIN)
/*!< Bflog bare-metal synchronization routine */
/*!< bflb_log barebone sync example */
struct bflb_device_s *uart0 = NULL;
static struct bflb_device_s *rtc = NULL;
/*!< Current UTC Timestamp 2022-12-16 17:52 */
uint32_t timestamp_base = 1671184300;
/** @defgroup example_bflb_log_port port
-----------------------------------------------------------------------------
* @{
----------------------------------------------------------------------------*/
uint64_t bflb_log_clock(void)
{
return bflb_mtimer_get_time_us();
}
uint32_t bflb_log_time(void)
{
return BFLB_RTC_TIME2SEC(bflb_rtc_get_time(rtc)) + timestamp_base;
}
char *bflb_log_thread(void)
{
return "";
}
/*---------------------------------------------------------------------------
* @} example_bflb_log_port port
----------------------------------------------------------------------------*/
#define EXAMPLE_LOG_POOL_SIZE 4096
bflb_log_t example_recorder;
static uint32_t example_pool[EXAMPLE_LOG_POOL_SIZE / 4];
bflb_log_direct_stream_t example_uart_stream;
uint16_t example_uart_stream_output(void *ptr, uint16_t size)
{
for (size_t i = 0; i < size; i++) {
bflb_uart_putchar(uart0, ((char *)ptr)[i]);
}
return size;
}
void example_log_init(void)
{
void *record = (void *)&example_recorder;
void *direct = (void *)&example_uart_stream;
/*!< Create a logger, configure the memory pool, set the memory pool size, and set the mode to synchronous.*/
/*!< create recorder */
if (0 != bflb_log_create(record, example_pool, EXAMPLE_LOG_POOL_SIZE, BFLB_LOG_MODE_SYNC)) {
printf("bflb_log_create faild\r\n");
}
/*!< Create an outputter, of the stream outputter type, enable color output, and set the mutex to NULL. */
/*!< create stream direct */
bflb_log_direct_create(direct, BFLB_LOG_DIRECT_TYPE_STREAM, BFLB_LOG_DIRECT_COLOR_ENABLE, NULL, NULL);
/*!< Configure the output function of the stream output */
bflb_log_direct_init_stream((void *)direct, example_uart_stream_output);
/*!< Add the outputter to the logger */
/*!< connect direct and recorder */
bflb_log_append(record, direct);
/*!< Restore the outputter to working mode*/
/*!< resume direct */
bflb_log_direct_resume(direct);
/*!< Restore the logger to working mode */
/*!< resume record */
bflb_log_resume(record);
}
extern void test_log(void);
int main(void)
{
board_init();
/*!< uart0 already initialized in bsp/board */
uart0 = bflb_device_get_by_name("uart0");
rtc = bflb_device_get_by_name("rtc");
bflb_rtc_set_time(rtc, 0);
example_log_init();
uint8_t test = 0;
while (1) {
if (test == 0) {
bflb_log_global_filter("MAIN", true);
bflb_log_global_filter("TEST", true);
printf("\r\n============================== enable all output\r\n\r\n");
} else if (test == 1) {
bflb_log_global_filter("MAIN", false);
bflb_log_global_filter("TEST", true);
printf("\r\n============================== enable only MAIN output\r\n\r\n");
} else if (test == 2) {
bflb_log_global_filter("MAIN", false);
bflb_log_global_filter("TEST", false);
printf("\r\n============================== disable all output\r\n\r\n");
}
if (++test >= 3) {
test = 0;
}
BFLB_LOG_F(&example_recorder, "hello world this is fatal error\r\n");
BFLB_LOG_E(&example_recorder, "hello world this is error\r\n");
BFLB_LOG_W(&example_recorder, "hello world this is warning\r\n");
BFLB_LOG_I(&example_recorder, "hello world this is information\r\n");
BFLB_LOG_D(&example_recorder, "hello world this is degug information\r\n");
BFLB_LOG_T(&example_recorder, "hello world this is trace information\r\n");
test_log();
bflb_mtimer_delay_ms(1000);
}
}📜 点击展开 barebone_sync/log_test.c 完整代码
#define DBG_TAG "TEST"
#include "bflb_log.h"
BFLB_LOG_DEFINE_TAG(TEST, DBG_TAG, true);
#undef BFLB_LOG_TAG
#define BFLB_LOG_TAG BFLB_LOG_GET_TAG(TEST)
extern bflb_log_t example_recorder;
void test_log(void)
{
BFLB_LOG_F(&example_recorder, "hello test this is fatal error\r\n");
BFLB_LOG_E(&example_recorder, "hello test this is error\r\n");
BFLB_LOG_W(&example_recorder, "hello test this is warning\r\n");
BFLB_LOG_I(&example_recorder, "hello test this is information\r\n");
BFLB_LOG_D(&example_recorder, "hello test this is degug information\r\n");
BFLB_LOG_T(&example_recorder, "hello test this is trace information\r\n");
}FAQ
怎么开启 blog 日志(BFLB_LOG)
分三层开启:
- 编译期(工程配置):在工程
defconfig中设置CONFIG_BFLB_LOG =y,SDK 才会把bflb_log组件编进来(本页例程已默认开启)。未开启时LOG_*宏会退化为空操作,看不到日志。 - 头文件(日志级别):
components/utils/log/bflb_log/bflb_log_conf.h是默认配置(例程目录里的bflog_conf_user.h是它的参考副本),通过宏控制编译范围与输出:BFLB_LOG_ENABLE:总开关,注释掉后日志代码不参与编译;BFLB_LOG_LEVEL_ENABLE:编译进固件的最高级别(例程为BFLB_LOG_LEVEL_TRACE,即 F/E/W/I/D/T 全量);BFLB_LOG_LEVEL_DEFAULT/BFLB_LOG_DIRECT_LEVEL_DEFAULT:记录器/输出通道默认过滤级别,低于该级别的日志不记录/不输出。
- 运行期(标签过滤):
BFLB_LOG_DEFINE_TAG(name, tag, enable)的enable决定该标签初始是否输出;运行中用bflb_log_global_filter("MAIN", true/false)按标签动态开关(本页例程就是每秒切换三种过滤状态)。
自建工程可复制例程的 defconfig 与 bflog_conf_user.h 起步,确认第 1、2 层开启后,日志就会从 uart0 输出。
日志没有任何输出
先确认串口波特率是 2000000;再检查记录器与输出通道是否都执行了 resume(挂起状态下不会输出),以及过滤状态是否被全部关闭(bflb_log_global_filter 两个标签都 false 时无输出)。
串口看不到彩色日志,只有普通文字
颜色依赖串口工具支持 ANSI 转义序列:请使用支持 ANSI 颜色显示的串口/终端工具(如 MobaXterm、Xshell、Windows Terminal 等),并确认创建输出通道时传入了 BFLB_LOG_DIRECT_COLOR_ENABLE(例程已开启)。颜色以代码中的 BFLB_LOG_COLOR_* 宏为准(F 品红 / E 红 / W 黄 / I 无色 / D 白 / T 暗淡白);例程参考配置里 INFO 的绿色定义被注释,如需绿色把该行取消注释并重新编译即可。
看不到 TEST 标签的日志
过滤状态为 enable only MAIN output 时 TEST 被关掉了,这是例程演示的标签过滤效果。想同时看两组,保持两个标签都 true 即可。
日志时间显示不对
日志头的时间来自 bflb_log_time(),例程以 RTC 时间加固定 timestamp_base 计算。RTC 未校时或基值不符合当前时间时,显示会偏离真实时间,但不影响功能验证。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

