概述
日志(大白话:程序运行时的"日记",把运行过程一条条记下来,方便你事后翻看)是嵌入式开发最重要的调试手段。Ai-WB2 SDK 内置 blog 日志系统,提供分级输出(大白话:按重要程度分层,如"提示/警告/错误",方便筛选)、组件级过滤(大白话:可以只让某个模块的日志显示出来)、ANSI 彩色显示能力。本教程演示 6 级日志的打印与过滤:设置不同日志级别,观察哪些日志会被打印。
用大白话讲:日志就像你记的"日记本"——程序每做一件事就写一行日记,方便你翻看它到底干了什么、哪里出错了。但日记太多翻起来费劲,所以还分了重要程度(普通记录、警告、错误……),并允许你设置"只显示警告及以上",就像看新闻只关注头条。本教程就是演示这些日志级别和过滤规则。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/system/blog_demo编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 blog_demo 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/system/blog_demo
说明:
cd是"进入目录"的命令,~表示你的用户主目录。这条命令进入 blog_demo 示例工程,后面所有make命令都要在这个目录里执行;如果提示No such file or directory(没有这个目录),说明路径不对,见文末 FAQ。
打开 blog_demo/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/system/blog_demo/blog_demo/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
blog_set_level_log_component(level, "blog_demo") |
设置组件的过滤级别;不设置就看不到过滤效果 |
blog_print(...) |
无条件打印;做"对照组",证明打印本身没问题 |
blog_debug / blog_info / blog_warn / blog_error |
按级别打印;低于过滤级别的会被"筛掉"不输出 |
blog_assert(...) |
断言级日志;条件不成立时提示,用于抓"不该发生"的情况 |
日志级别定义(从高到低过滤):
typedef enum _blog_leve {
BLOG_LEVEL_ALL = 0, /* 全部输出 */
BLOG_LEVEL_DEBUG, /* 调试 */
BLOG_LEVEL_INFO, /* 信息 */
BLOG_LEVEL_WARN, /* 警告 */
BLOG_LEVEL_ERROR, /* 错误 */
BLOG_LEVEL_ASSERT, /* 断言 */
BLOG_LEVEL_NEVER, /* 全部屏蔽 */
} blog_level_t;
📌 设为
BLOG_LEVEL_INFO时,blog_debug不输出、blog_info及以上输出;设为BLOG_LEVEL_NEVER时全部屏蔽。
在工程目录执行编译:
make -j8
说明:
make是"编译工程"的命令,把源代码翻译成开发板能运行的机器码;-j8表示用 8 个核心并行编译,更快。
编译成功后生成固件 build_out/blog_demo.bin(固件:编译后烧进开发板的程序,相当于开发板的"操作系统+你的程序")。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是"烧录"命令,把编译好的固件下载进芯片(烧录:把程序写进芯片的过程);p=/dev/ttyUSB0是串口设备号,要换成你电脑上实际的串口(Windows 下形如COM3),b=921600是烧录波特率(传输速度)。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。若一直卡在等待或报串口打不开,见文末 FAQ。
烧录完成后开发板自动重启运行,打开串口助手(波特率 921600)观察日志,可见按级别过滤的规律:
The log level is LOG_LEVEL_ALL
DEBUG (5)[main.c: 22] The log level is LOG_LEVEL_DEBUG
INFO (11)[main.c: 23] The log level is LOG_LEVEL_INFO
WARN (16)[main.c: 24] The log level is LOG_LEVEL_WARN
ERROR (22)[main.c: 25] The log level is LOG_LEVEL_ERROR
ASSERT (28)[main.c: 26] The log level is LOG_LEVEL_ASSERT
The log level is LOG_LEVEL_NEVER
DEBUG (37)[main.c: 31] The log level is LOG_LEVEL_DEBUG
INFO (43)[main.c: 32] The log level is LOG_LEVEL_INFO
...
规律验证:设置为 BLOG_LEVEL_INFO 后,DEBUG 行不再出现;设置为 BLOG_LEVEL_NEVER 后该组件全部日志消失。
⚠️ 官方默认 DEBUG 级别不打印(即使设置为 DEBUG 也无输出),需要输出 DEBUG 日志时,修改组件配置头
blog_cfg.h中的BLOG_POWERON_SOFTLEVEL_FILE为BLOG_LEVEL_DEBUG。
✅ 预期结果:能看到多组日志,且级别设得越高(如
BLOG_LEVEL_ERROR)打印的日志越少、设置BLOG_LEVEL_NEVER后该组件日志全部消失,即验证通过。若日志固定不变、过滤没起作用,见文末 FAQ。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
blog_set_level_log_component(level, name)
设置某个日志组件的输出级别,低于该级别的日志被过滤不输出。
参数:
level:级别值,可选值:BLOG_LEVEL_ALL(全部输出)/BLOG_LEVEL_DEBUG/BLOG_LEVEL_INFO/BLOG_LEVEL_WARN/BLOG_LEVEL_ERROR/BLOG_LEVEL_ASSERT/BLOG_LEVEL_NEVER(全部屏蔽)name:组件名称字符串(注册组件时的名字,如"tcp"、"axk_mqtt")
返回值:成功返回 0;失败返回负值错误码
blog_print(fmt, ...)
输出日志,不参与级别过滤,任何级别设置下都会打印。
参数:
fmt:格式化字符串,同printf用法,必填...:变参,与fmt占位符对应,可省略
返回值:无
完整代码
以下为 blog_demo/main.c 完整源码,与官方示例(applications/system/blog_demo/blog_demo/main.c)完全一致:
📜 点击展开 blog_demo/main.c 完整代码
/**
* @file main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2022-10-22
*
* @copyright Copyright (c) 2022
*
*/
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <blog.h>
#include "bl_sys.h"
void main(void)
{
blog_set_level_log_component(BLOG_LEVEL_ALL, "blog_demo");
blog_print("The log level is LOG_LEVEL_ALL\n");
blog_debug("The log level is LOG_LEVEL_DEBUG");
blog_info("The log level is LOG_LEVEL_INFO");
blog_warn("The log level is LOG_LEVEL_WARN");
blog_error("The log level is LOG_LEVEL_ERROR");
blog_assert("The log level is LOG_LEVEL_ASSERT");
blog_print("The log level is LOG_LEVEL_NEVER\r\n");
printf("\r\n");
blog_set_level_log_component(BLOG_LEVEL_DEBUG, "blog_demo");
blog_debug("The log level is LOG_LEVEL_DEBUG");
blog_info("The log level is LOG_LEVEL_INFO");
blog_warn("The log level is LOG_LEVEL_WARN");
blog_error("The log level is LOG_LEVEL_ERROR");
blog_assert("The log level is LOG_LEVEL_ASSERT");
blog_print("The log level is LOG_LEVEL_NEVE\r\n");
printf("\r\n");
blog_set_level_log_component(BLOG_LEVEL_INFO, "blog_demo");
blog_debug("The log level is LOG_LEVEL_DEBUG");
blog_info("The log level is LOG_LEVEL_INFO");
blog_warn("The log level is LOG_LEVEL_WARN");
blog_error("The log level is LOG_LEVEL_ERROR");
blog_assert("The log level is LOG_LEVEL_ASSERT");
blog_print("The log level is LOG_LEVEL_NEVER\r\n");
printf("\r\n");
blog_set_level_log_component(BLOG_LEVEL_WARN, "blog_demo");
blog_debug("The log level is LOG_LEVEL_DEBUG");
blog_info("The log level is LOG_LEVEL_INFO");
blog_warn("The log level is LOG_LEVEL_WARN");
blog_error("The log level is LOG_LEVEL_ERROR");
blog_assert("The log level is LOG_LEVEL_ASSERT");
blog_print("The log level is LOG_LEVEL_NEVER\r\n");
printf("\r\n");
blog_set_level_log_component(BLOG_LEVEL_ERROR, "blog_demo");
blog_debug("The log level is LOG_LEVEL_DEBUG");
blog_info("The log level is LOG_LEVEL_INFO");
blog_warn("The log level is LOG_LEVEL_WARN");
blog_error("The log level is LOG_LEVEL_ERROR");
blog_assert("The log level is LOG_LEVEL_ASSERT");
blog_print("The log level is LOG_LEVEL_NEVER\r\n");
printf("\r\n");
blog_set_level_log_component(BLOG_LEVEL_ASSERT, "blog_demo");
blog_debug("The log level is LOG_LEVEL_DEBUG");
blog_info("The log level is LOG_LEVEL_INFO");
blog_warn("The log level is LOG_LEVEL_WARN");
blog_error("The log level is LOG_LEVEL_ERROR");
blog_assert("The log level is LOG_LEVEL_ASSERT");
blog_print("The log level is LOG_LEVEL_NEVER\r\n");
printf("\r\n");
blog_set_level_log_component(BLOG_LEVEL_NEVER, "blog_demo");
blog_debug("The log level is LOG_LEVEL_DEBUG");
blog_info("The log level is LOG_LEVEL_INFO");
blog_warn("The log level is LOG_LEVEL_WARN");
blog_error("The log level is LOG_LEVEL_ERROR");
blog_assert("The log level is LOG_LEVEL_ASSERT");
blog_print("The log level is LOG_LEVEL_NEVER\r\n");
}常见问题与踩坑提示
⚠️ blog_debug 日志不打印
原因:SDK 默认关闭 DEBUG 级别输出(blog_cfg.h 中 BLOG_POWERON_SOFTLEVEL_FILE 默认 BLOG_LEVEL_INFO)
解决:将 blog_cfg.h 中 #define BLOG_POWERON_SOFTLEVEL_FILE (BLOG_LEVEL_INFO) 改为 BLOG_LEVEL_DEBUG 后重新编译
⚠️ 设置了级别但日志没有过滤
原因:blog_set_level_log_component 的第二个参数(组件名)与打印代码所在组件名不一致
解决:确认组件名与工程名一致(本示例为 blog_demo);或直接设置全局级别
⚠️ printf 与 blog 日志混排、格式不一致
原因:printf 不走 blog 系统,无级别前缀与颜色
解决:调试统一使用 blog 系列 API;printf 仅用于无需过滤的原始输出
⚠️ 串口打不开 / 看不到任何日志
原因:USB 转串口驱动未装、串口被占用,Linux 下还可能是没有访问权限
解决:Linux 用 lsusb 确认设备被识别,sudo chmod 666 /dev/ttyUSB0 或把用户加入 dialout 组后重试;Windows 在设备管理器查看 COM 口并安装 CH340/CP210x 驱动;串口助手的波特率要设为 921600
⚠️ 烧录一直卡在等待 / 提示找不到芯片
原因:未进入下载模式、数据线只能充电不能传数据,或波特率不对
解决:烧录时长按 EN 键直到进度条出现;换一根能传数据的数据线;确认 p= 串口号与 b=921600 无误
⚠️ cd 报错 No such file or directory / 找不到 Makefile
原因:没进入示例工程目录就执行了 make,或 SDK 安装路径与教程不同
解决:先 cd ~/Ai-Thinker-WB2/applications/system/blog_demo 再执行 make;若 ~/Ai-Thinker-WB2 不存在,用 find ~ -name "Ai-Thinker-WB2" 找到 SDK 实际位置
运行自检
串口输出 7 组不同级别设置下的日志,且级别越高(如 ERROR)过滤掉越多日志,即 blog 日志系统验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

