杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • SDK 总览与芯片能力
    • 环境搭建与编译构建
    • 烧录与固件升级
    • 文档与版本资源
  • 应用与示例方案

    • demo 示例工程
    • WiFi 摄像头方案 (wifi_camera)
    • WiFi 音箱方案 (wifi_soundbox)
    • WiFi 婴儿监护方案 (wifi_bbm)
    • 公共应用模块库
    • 示例代码库 (example)
  • 系统架构与平台

    • 总体架构与工程分层
    • 系统启动与运行框架
    • 芯片驱动与板级适配
    • 设备管理与文件系统
    • 系统工具库与算法
  • 音频子系统

    • 音频框架与处理节点
    • 音频编解码与音效
    • 播放器与录音器
    • 语音交互与 AI 唤醒
    • LE Audio 与蓝牙音频
    • 音频调试与歌词
  • 视频与显示子系统

    • 摄像头驱动与 ISP
    • 视频编码与图像处理
    • 显示与 GPU 加速
    • 屏幕镜像 (screen_mirror)
  • 无线连接与网络

    • 蓝牙协议栈 (双模蓝牙)
    • WiFi 协议栈与配网
    • 网络协议栈
    • 云平台与 IoT 协议
  • UI 子系统

    • LVGL 集成与应用
    • UI 工程与工具链
  • 配置系统

    • 功能配置
    • 板级配置
    • 网络与蓝牙配置
    • 音频配置与提示音
  • 工具与测试

    • 产测与射频测试工具
    • 固件升级与更新机制
    • 调试与日志工具
  • 硬件参考设计

    • 原理图参考设计
    • 芯片数据手册

调试与日志工具

AC792N SDK 的调试与日志(debug & dlog)子系统,提供从串口(UART)、蓝牙 SPP 等通道输出与合并日志、外置 Flash 日志存储、在线调试数据库等能力,是嵌入式开发阶段定位问题的核心基础设施。

Purpose and Scope

本文档介绍 AC792N_SDK_V3 中与调试与日志相关的完整机制,涵盖:

  • dlog 日志框架的配置入口(dlog_config.c)与输出通道管理(dlog_output_config.c);
  • 日志输出通道(UART / SPP)、合并发送(merge)、环形缓冲(lbuf)与锁同步机制;
  • 外置 Flash 日志存储区(TCFG_DEBUG_DLOG_FLASH_SEL)及 SPI Flash 底层驱动;
  • debug/ 目录下其他调试组件(debug.c、debug_user.c、debug_lite.c、put_float.c、online_db/)的职责定位;
  • 相关配置宏、API、失败模式与扩展方式。

以下内容不在本文档范围,请参考对应目录页:音频通路与编解码调试、蓝牙协议栈调试、功耗测量工具(如 ram_timer 等)以及各产品 demo 的板级调试配置。

概述

在 AC792N 这类资源受限的嵌入式 SoC 上,日志系统是开发与量产阶段最重要的观测手段。SDK 将日志功能组织为两层:

  1. dlog 框架(generic/dlog.h):提供 dlog_printf 等日志写入原语,以及日志数据的缓冲、合并、多通道分发能力;
  2. 应用层调试配置(apps/common/debug/):把 dlog 框架绑定到具体硬件(串口 DMA、外置 SPI Flash、蓝牙 SPP),并提供在线调试数据库(online_db)与浮点打印等辅助工具。

设计上,日志系统必须满足嵌入式环境的三个约束:低中断延迟(日志写入不能长时间关中断)、零丢失(高频日志需要缓冲与背压控制)、可配置(量产固件可裁剪日志以节省 RAM/Flash)。

架构

flowchart TD
    subgraph sg_App["应用层 apps/common/debug"]
        Dbg["debug.c / debug_user.c / debug_lite.c"]
        DlogCfg["dlog_config.c<br/>(配置 + Flash 存储)"]
        DlogOut["dlog_output_config.c<br/>(输出通道管理)"]
        Float["put_float.c"]
        OnDb["online_db/<br/>online_db_deal.c + spp_online_db.c"]
    end

    subgraph sg_Core["dlog 框架(generic/dlog.h)"]
        DlogApi["dlog_printf / log_print"]
        Lbuf["lbuf 环形缓冲"]
        ChList["dlog_output_channel_s 通道链表"]
    end

    subgraph sg_Channels["输出通道"]
        Uart["UART(DMA)"]
        Spp["蓝牙 SPP"]
        Flash["外置 SPI Flash 日志区"]
    end

    Dbg --> DlogCfg
    DlogCfg --> DlogApi
    DlogOut --> DlogApi
    DlogApi --> Lbuf
    Lbuf --> ChList
    ChList --> Uart
    ChList --> Spp
    DlogCfg --> Flash
    OnDb --> Spp
    Float --> DlogApi

各组件职责:

  • dlog_config.c:dlog 系统的总配置与初始化入口。在 TCFG_DEBUG_DLOG_ENABLE 宏使能下编译;定义弱符号 dlog_output_direct() 作为直通输出钩子;当 TCFG_DEBUG_DLOG_FLASH_SEL 打开时,通过 SPI 操作外置 Winbond 类 Flash,把日志写入自定义区域(见 dlog_config.c)。
  • dlog_output_config.c:输出通道的缓冲与调度。用 DLOG_OUT_CHANNEL 宏选择 UART/SPP 通道,用 lbuf 环形缓冲暂存日志,由定时器触发 dlog_output_resume() 将缓冲刷出;支持 DLOG_OUT_MERGE_EN 合并发送以降低 SPP 发送频率(见 dlog_output_config.c)。
  • debug.c / debug_user.c / debug_lite.c:用户态调试命令与通用调试入口,通常绑定串口命令解析。
  • put_float.c:在无浮点打印支持的环境下输出浮点数的辅助实现。
  • online_db/:通过蓝牙 SPP 提供在线读写设备数据库(db)的能力,用于上位机调试工具实时查看/修改参数。

dlog 核心机制

使能与裁剪:TCFG_DEBUG_DLOG_ENABLE

整个 dlog 功能由编译宏 TCFG_DEBUG_DLOG_ENABLE 门控。dlog_config.c 与 dlog_output_config.c 的代码主体都包裹在 #if TCFG_DEBUG_DLOG_ENABLE 中,关闭该宏即可把日志框架整体裁剪出固件,节省 RAM 与 Flash。这是量产固件与开发固件的关键差异点:

#if TCFG_DEBUG_DLOG_ENABLE
extern u32 __attribute__((weak)) dlog_log_data_start_addr[];

struct sys_time dlog_sys_time = {0};

__attribute__((weak))
int dlog_output_direct(void *buf, u16 len)
{
    return 0;
}

来源:dlog_config.c

这里三个弱符号/全局量各有用途:

  • dlog_log_data_start_addr[]:指向日志数据区的起始地址(通常由链接脚本提供),供 Flash 存储布局使用;
  • dlog_sys_time:日志时间戳的全局时间结构,由 RTC 相关代码维护;
  • dlog_output_direct():直通输出钩子。默认实现返回 0(不处理),用户可在别处提供强符号实现,将日志绕过缓冲直接写出——适用于极低延迟的调试场景(如中断上下文)。

输出通道抽象:dlog_output_channel_s

dlog_output_config.c 通过一个链接器段数组管理输出通道。所有通道实例被链接进 dlog_out_channel_begin 到 dlog_out_channel_end 之间,遍历时用宏:

extern const struct dlog_output_channel_s dlog_out_channel_begin[];
extern const struct dlog_output_channel_s dlog_out_channel_end[];

#define list_for_each_dlog_channel(p) \
    for (p = dlog_out_channel_begin; p < dlog_out_channel_end; p++)

来源:dlog_output_config.c

这种"段内数组"设计避免了链表节点在运行期的分配开销,并且新通道只需在源码中定义 const struct dlog_output_channel_s 实例即可自动被遍历到,是嵌入式环境常用的零成本注册机制。通道结构至少包含 is_busy() 回调,供调度器判断该通道是否空闲。

输出缓冲与调度

日志输出使用一个独立分配的 lbuf 环形缓冲,大小由 DLOG_OUTPUT_LBUF_MAX_SIZE 决定(默认 20KB)。初始化代码如下:

int dlog_output_channel_init()
{
    //串口DMA要求4 byte对齐,且为连续ram,因此用dma_malloc和lbuf
    printf("%s", __func__);

    g_dlog_output.lbuf_head = malloc(DLOG_OUTPUT_LBUF_MAX_SIZE);

    if (g_dlog_output.lbuf_head) {
        lbuf_init(g_dlog_output.lbuf_head, DLOG_OUTPUT_LBUF_MAX_SIZE, 4, 0);
    }
    return 0;
}

来源:dlog_output_config.c

注释揭示了关键设计约束:串口 DMA 要求 4 字节对齐且为连续 RAM,因此缓冲用 malloc 而非零散的小块。lbuf_init(..., 4, 0) 中的对齐参数 4 正对应这一 DMA 要求。

刷出(flush)流程由 dlog_output_resume() 驱动,它的三个关键行为:

  1. 忙检查:遍历所有通道,若任一通道 is_busy() 为真则立即返回(背压),防止数据覆盖;
  2. 释放上一帧:lbuf_free(g_dlog_output.last_log) 归还上一次发送的缓冲区;
  3. 弹出并发送:从 lbuf 头部取出一条 struct logbuf,其 buf_len 字段记录长度,随后按合并策略发送。
void dlog_output_resume(u8 flush)
{
    if (NULL == g_dlog_output.lbuf_head) {
        return;
    }

    if (g_dlog_output.timer) {
        return;
    }

    const struct dlog_output_channel_s *p = NULL;

    spin_lock(&g_dlog_output_lock);
    // flush = 1 则不检查busy标记
    if (!flush) {
        list_for_each_dlog_channel(p) { //等所有通道都发送完成后再继续发
            if (p->is_busy && p->is_busy()) {
                spin_unlock(&g_dlog_output_lock);
                return;
            }
        }
    }
    ...
}

来源:dlog_output_config.c

flush 参数语义明确:flush = 1 时不检查 busy 标记,强制把缓存数据全部清空——用于系统重启前或进入低功耗前的兜底落盘。g_dlog_output.timer 作为节流标志,防止在定时器回调尚未复位时被重复触发;DEFINE_SPINLOCK(g_dlog_output_lock) 保护整个刷出流程,因为该函数可能在任务上下文与定时器上下文中被并发调用。

外置 Flash 日志存储(TCFG_DEBUG_DLOG_FLASH_SEL)

当 TCFG_DEBUG_DLOG_FLASH_SEL 打开时,dlog 支持把日志写入外置 SPI Flash 的自定义区域,用于记录掉电后仍可回溯的运行日志(例如崩溃现场)。dlog_config.c 中为此实现了精简的 Winbond(W25X 系列)SPI Flash 驱动。

硬件抽象

struct _dlog_ext_flash_ {
    void *spi;
    int cs_gpio;
};

static struct _dlog_ext_flash_ hdl = {0};

#define FLASH_PAGE_SIZE 256

#define WINBOND_READ_DATA	        0x03
#define WINBOND_DUAL_READ_DATA	    0x3b
#define WINBOND_DUAL_READ	        0xbb
#define WINBOND_READ_SR1            0x05
#define DUMMY_BYTE                  0xff
#define WINBOND_CHIP_ERASE          0xC7
#define W25X_JedecDeviceID          0x9f
#define WINBOND_WRITE_ENABLE        0x06
#define WINBOND_PAGE_PROGRAM_QUAD	0x32
#define W25X_SectorErase		    0x20
#define W25X_PageProgram        	0x02

来源:dlog_config.c

该驱动通过 hdl.spi(SPI 设备句柄)与 hdl.cs_gpio(片选 GPIO)操作 Flash,命令集覆盖:读(0x03/双线 0x3b/0xbb)、写使能(0x06)、页编程(0x02/四线 0x32)、扇区擦除(0x20)、整片擦除(0xC7)、读 ID(0x9f)、读状态寄存器(0x05)。页大小按标准 256 字节定义。

操作原语

所有 Flash 操作都遵循"拉低片选 → 发命令 → 传输数据 → 拉高片选"的时序。写使能与读 ID 是典型示例:

static void spiflash_send_write_enable(void)
{
    cs_gpio(0);
    dev_ioctl(hdl.spi, IOCTL_SPI_SEND_BYTE, WINBOND_WRITE_ENABLE);
    cs_gpio(1);
}
static int W25X_GetChipID(void)
{
    u8 id[0x3] = {0, 0, 0};
    int id_mub = 0;
    int err = 0;

    cs_gpio(0);
    dev_ioctl(hdl.spi, IOCTL_SPI_SEND_BYTE, W25X_JedecDeviceID);

    for (u8 i = 0; i < sizeof(id); i++) {
        err = dev_ioctl(hdl.spi, IOCTL_SPI_READ_BYTE, (u32)&id[i]);
        printf(">>>>>>>>>id = 0x%x", id[i]);
        if (err) {
            printf(">>>>>>>>>>ERR");
            id[0] = id[1] = id[2] = 0xff;
            break;
        }
    }

    cs_gpio(1);
    ...
}

来源:dlog_config.c

设计要点:

  • 自包含驱动:dlog 的 Flash 日志存储不依赖 SDK 通用的 norflash 驱动栈(asm/sfc_norflash_api.h 被包含但真正的 Flash 访问走 dev_ioctl(hdl.spi, ...)),从而可以在日志系统早期(甚至 Flash 驱动未初始化时)独立工作;
  • 错误容错:读 ID 失败时把 ID 填充为 0xff 并继续,避免初始化流程被单点错误卡死,同时通过 printf 打印错误信息辅助定位;
  • CS 时序由 GPIO 控制:cs_gpio() 用 gpio_direction_output() 手动拉片选,适合挂在通用 SPI 控制器之外的软件模拟场景。

日志数据区

日志在 Flash 中的落盘地址依赖链接脚本符号 dlog_log_data_start_addr(弱符号声明,见上文),实际地址由链接脚本在编译期确定,保证日志区与代码/数据区隔离,避免误擦除。

核心流程

日志从产生到输出的完整链路

sequenceDiagram
    participant Task as 业务任务
    participant Dlog as dlog 框架
    participant Lbuf as lbuf 环形缓冲
    participant Timer as 定时器
    participant Out as dlog_output_resume
    participant Chan as 输出通道(UART/SPP)
    participant Flash as 外置Flash(可选)

    Task->>Dlog: dlog_printf(...) / log_print(...)
    Dlog->>Lbuf: 写入日志帧(含时间戳)
    Note over Lbuf: 超过 DLOG_OUTPUT_LBUF_MAX_SIZE 时<br/>丢弃最旧数据或阻塞
    Timer->>Out: dlog_output_timeout_handler()
    Out->>Out: 检查 timer 节流标志
    Out->>Chan: is_busy() == 空闲?
    alt 所有通道空闲
        Out->>Lbuf: lbuf_pop 取出一条 logbuf
        Out->>Chan: 按通道发送(buf_len 字节)
        Chan-->>Out: 发送完成
    else 有通道忙
        Out-->>Timer: 直接返回,等待下次触发
    end
    opt TCFG_DEBUG_DLOG_FLASH_SEL 使能
        Dlog->>Flash: 写使能→页编程→扇区擦除
    end

合并发送(DLOG_OUT_MERGE_EN)

当 DLOG_OUT_MERGE_EN 打开时,短时间内的多条日志会被合并为一条再发送,其目的在于降低 SPP 等慢通道的发送频率——注释明确说明"SPP 发送太频繁,uart 可以不开"。合并由三个宏控制:

宏含义
DLOG_OUT_MERGE_EN是否启用合并(0 关闭 / 1 打开)
DLOG_OUT_MERGE_TIME合并时间窗,单位 ms(默认 100),窗口内日志攒成一条
DLOG_OUT_MERGE_SIZE合并缓冲上限(默认 512 字节),超过立即发送

合并逻辑的核心判断出现在 dlog_output_resume() 中:若当前取出的 log->buf_len 小于 g_dlog_output.total_size(累计待发长度),则继续累积而不是立即发送(见 dlog_output_config.c)。total_size 因此充当"已合并未发送"的水位计数。

生命周期

flowchart LR
    A["上电/编译期<br/>TCFG_DEBUG_DLOG_ENABLE"] --> B["dlog_output_channel_init()<br/>malloc 20KB lbuf + lbuf_init"]
    B --> C["运行期<br/>dlog_printf 写入 + 定时刷出"]
    C --> D["重启/低功耗前<br/>dlog_output_resume(flush=1)"]
    D --> E["dlog_output_channel_deinit()<br/>free lbuf"]

初始化与反初始化函数对称:dlog_output_channel_init() 分配缓冲,dlog_output_channel_deinit() 释放并置空指针(dlog_output_config.c)。注意 late_initcall(dlog_output_channel_init) 在源码中被注释掉,说明实际初始化时机由应用层决定(通常放在 app_core 任务启动早期)。

使用示例

在业务代码中输出日志

dlog 提供与 printf 兼容的 dlog_printf,并支持时间前缀与任务名。dlog_config.c 中的测试代码展示了标准用法:

void dlog_print_test1(void *priv)
{
    static int cnt[3] = {0};
    char time[15] = {0};
    int index = (int)priv;

    extern int log_print_time_to_buf(char *time);
    log_print_time_to_buf(time);

    /* printf("%stask:%s, cnt:%d\n", time, os_current_task(), cnt[index]); */
    dlog_printf("%stask:%s, cnt:%d\n", time, os_current_task(), cnt[index]);
    cnt[index]++;
}

来源:dlog_config.c

要点:

  • 先用 log_print_time_to_buf() 把 RTC 时间格式化进 time 缓冲区,再随日志一起输出,实现带时间戳的日志;
  • 通过 os_current_task() 打印当前任务名,便于多任务环境下快速定位日志来源;
  • 示例代码中 printf 被注释、dlog_printf 生效——这反映了 SDK 的迁移策略:调试期统一走 dlog 通道,便于集中收集。

跨任务注入日志测试

dlog 可以借助系统任务队列在任意任务上下文中打印,验证多任务并发下的日志正确性:

void dlog_print_test(void *priv)
{
    extern void dlog_uart_init();
    dlog_uart_init();
    sys_timer_add((void *)0, dlog_print_test1, 200);
#if 1
    int msg[] = {(int)dlog_print_in_bttask, 0};
    os_taskq_post_type("btstack", Q_CALLBACK, ARRAY_SIZE(msg), msg);
    int msg2[] = {(int)dlog_print_in_btctrler, 0};
    os_taskq_post_type("btctrler", Q_CALLBACK, ARRAY_SIZE(msg2), msg2);
#endif
}

来源:dlog_config.c

这段代码展示了三个能力:dlog_uart_init() 手动初始化 UART 通道;sys_timer_add() 注册周期定时器持续打印;os_taskq_post_type("btstack"/"btctrler", Q_CALLBACK, ...) 把打印函数投递到蓝牙协议栈任务,验证日志系统在非当前任务上下文中也能安全输出。

重定向 printf 到 dlog

dlog_output_config.c 展示了将标准 printf/put_buf 重定向到 dlog 的宏方案(当前被注释,说明默认保持库自带 printf):

#ifdef printf
#undef printf
#endif
#ifdef put_buf
#undef put_buf
#endif

/* #define printf(format, ...)   log_print(3, NULL, format, ##__VA_ARGS__) */

来源:dlog_output_config.c

若取消注释,所有 printf 调用都会走 log_print(3, NULL, ...) 进入 dlog 框架——3 为日志等级参数。这是"全局日志接管"的开关,量产裁剪时可直接移除。

配置选项

dlog_output_config.c 输出通道配置

宏类型默认值说明
DLOG_OUTPUT_LBUF_MAX_SIZE整数20 * 1024日志环形缓冲总大小(字节)。串口 DMA 要求连续 4 字节对齐 RAM,故用 malloc 一次性分配
DLOG_OUTPUT_BY_UART位标志(1 << 0)UART 输出通道使能位
DLOG_OUTPUT_BY_SPP位标志(1 << 1)蓝牙 SPP 输出通道使能位
DLOG_OUT_CHANNEL位组合DLOG_OUTPUT_BY_UART实际启用的输出通道(可组合 UART | SPP,0 表示关闭输出)
DLOG_OUT_MERGE_EN布尔0是否启用短时日志合并(用于降低 SPP 发送频率,可关 UART)
DLOG_OUT_MERGE_TIME整数(ms)100合并时间窗:窗口内日志合并成一条发送
DLOG_OUT_MERGE_SIZE整数(字节)512合并缓冲上限,超过立即发送

dlog_config.c 功能开关

宏说明
TCFG_DEBUG_DLOG_ENABLEdlog 总开关。关闭后 dlog_config.c 与 dlog_output_config.c 主体代码全部不编译
TCFG_DEBUG_DLOG_FLASH_SEL外置 Flash 日志存储开关。打开后启用 W25X 系列 SPI Flash 驱动,把日志写入自定义区域

这两个宏通常在 app_config.h 或板级配置头文件中定义,属于编译期配置,改动后需重新编译固件。

API 参考

以下 API 均在 TCFG_DEBUG_DLOG_ENABLE 使能时可用,来自 dlog_config.c 与 dlog_output_config.c。

int dlog_output_direct(void *buf, u16 len)

弱符号直通输出钩子。默认返回 0;用户提供强符号实现后,日志可绕过缓冲直接写出。

参数:

  • buf (void *):待输出的日志数据指针
  • len (u16):数据长度(字节)

返回: 默认实现返回 0;自定义实现返回实际处理结果(如写入字节数或错误码)。

设计意图: 用于中断上下文或极低延迟场景,避免依赖 lbuf 缓冲与定时器调度的延迟。

void dlog_output_resume(u8 flush)

刷出环形缓冲中的日志到所有输出通道。

参数:

  • flush (u8):1 表示不检查通道 busy 标记、强制清空缓存;0 表示仅当所有通道空闲时才发送。

行为:

  1. lbuf_head 为空或 timer 节流标志置位时直接返回;
  2. 持自旋锁 g_dlog_output_lock;
  3. flush = 0 时遍历通道检查 is_busy(),任一忙则解锁返回;
  4. 释放上一帧 last_log,从 lbuf 弹出下一条 logbuf,按合并策略发送。

int dlog_output_channel_init(void)

分配并初始化输出缓冲。

行为: malloc(DLOG_OUTPUT_LBUF_MAX_SIZE) 分配 20KB 连续 RAM,调用 lbuf_init(head, size, 4, 0) 建立 4 字节对齐的环形缓冲。分配失败时 lbuf_head 为 NULL,后续刷出流程自动跳过。

void dlog_output_channel_deinit(void)

释放输出缓冲。free(lbuf_head) 后置 NULL。用于系统关闭日志功能或低功耗前回收内存。

static void dlog_output_timeout_handler(void)

定时器回调。清除 timer 节流标志后调用 dlog_output_resume(0)。注释掉的代码展示了备选方案:把 dlog_output_resume 投递到 app_core 任务队列执行(避免在定时器上下文做重活)。

dlog_printf(format, ...)

dlog 框架提供的格式化日志输出(generic/dlog.h),用法与 printf 一致。实际输出路径为:写入 lbuf → 定时器触发 → 通道发送。

失败模式、边界情况与并发

通道忙导致的背压丢弃

dlog_output_resume() 在任一通道 is_busy() 时直接返回,不阻塞调用方。这意味着当 UART/SPP 发送速度跟不上日志产生速度时,日志会在 lbuf 中积压;若积压超过 DLOG_OUTPUT_LBUF_MAX_SIZE(20KB),lbuf 会按环形缓冲策略覆盖最旧数据。这是有意的取舍:日志系统优先保证系统实时性,允许在高频日志下丢失部分旧日志,而不是拖慢业务任务。

合并模式下的发送延迟

DLOG_OUT_MERGE_EN = 1 时,日志最多延迟 DLOG_OUT_MERGE_TIME(100ms)才发送(除非累积超过 DLOG_OUT_MERGE_SIZE 提前触发)。调试低延迟事件(如中断时序)时应关闭合并,避免日志时间戳与实际发生时刻偏差。

并发与锁

g_dlog_output_lock 自旋锁保护 dlog_output_resume() 的整个临界区。可能的竞争来源:

  • 定时器上下文调用 dlog_output_timeout_handler() → dlog_output_resume(0);
  • 任务上下文(如 SPP 数据回调或用户主动 flush)调用 dlog_output_resume(1)。

若两者同时进入,自旋锁保证串行化。注意在持锁期间调用 lbuf_free/lbuf_pop 与通道 is_busy() 回调,这些函数必须是可重入且快速的,否则会拉长关中断/自旋时间。源码中把 dlog_output_resume 投递到 app_core 的备选方案(被注释)正是为规避在定时器上下文长时间持锁的权衡。

分配失败降级

dlog_output_channel_init() 中 malloc 失败时 lbuf_head = NULL,后续所有刷出路径都以 if (NULL == g_dlog_output.lbuf_head) return; 优雅退出,日志退化为"直接丢弃",不会引起空指针崩溃。代价是 20KB 连续 RAM 在低内存场景可能分配失败,需在系统内存预算中评估。

Flash 读写错误

外置 Flash 日志存储依赖 dev_ioctl(hdl.spi, ...) 的返回码。W25X_GetChipID() 在 err 非零时把 ID 置为 0xff 并打印 ERR,继续后续流程而非死等。生产环境应关注:Flash 写磨损(页编程前需扇区擦除)、掉电时写入中断(可能产生半页数据,读取端需容错)、以及日志区与文件系统区地址重叠(需与链接脚本核对 dlog_log_data_start_addr)。

性能与运维考量

  • 缓冲即性能边界:20KB lbuf + 定时器节流决定了日志吞吐上限。高频日志(每任务每 tick 打印)会快速填满缓冲并触发覆盖;应通过日志等级或宏裁剪高频路径。
  • DMA 对齐:lbuf 按 4 字节对齐初始化,正是为满足串口 DMA 搬运要求;若改用非对齐缓冲,需确保发送端处理非对齐头。
  • SPP 通道的代价:SPP 走蓝牙协议栈,吞吐远低于 UART。合并发送(DLOG_OUT_MERGE_EN)是 SPP 场景的推荐配置;纯 UART 调试可关闭合并以降低延迟。
  • 量产裁剪:量产固件应关闭 TCFG_DEBUG_DLOG_ENABLE 或把 DLOG_OUT_CHANNEL 置 0,释放 20KB RAM 与相关代码段。
  • 在线调试数据库(online_db):sdk/apps/common/debug/online_db/ 下的 online_db_deal.c 与 spp_online_db.c 通过 SPP 提供设备数据库在线读写能力,供上位机(如杰理调试工具)实时查看/修改参数。其实现细节属于在线调试工具范畴,本文档仅作入口定位。

扩展点

  1. 新增输出通道:定义 const struct dlog_output_channel_s 实例(实现 is_busy() 等回调),放入 dlog_out_channel_begin/end 段区间,即被 list_for_each_dlog_channel 自动遍历——无需修改调度代码。
  2. 自定义直通输出:提供 dlog_output_direct() 强符号实现,接管低延迟输出路径。
  3. 日志全局重定向:取消 printf → log_print(3, NULL, ...) 宏的注释,让全工程 printf 统一进入 dlog 通道。
  4. Flash 日志区定制:通过链接脚本定义 dlog_log_data_start_addr 决定日志区位置;TCFG_DEBUG_DLOG_FLASH_SEL 控制该功能编译。
  5. 其它调试组件:debug.c(通用调试命令)、debug_user.c(用户自定义调试接口)、debug_lite.c(精简版调试)、put_float.c(浮点打印辅助)均可按产品需求增删;在线调试数据库(online_db)可扩展为独立调试通道。

相关链接

  • dlog_config.c — dlog 配置与外置 Flash 日志存储
  • dlog_output_config.c — 日志输出通道与合并调度
  • debug.c / debug_user.c / debug_lite.c — 通用调试入口
  • online_db/ — 在线调试数据库(SPP)
  • put_float.c — 浮点打印辅助
  • dlog 使用说明文档(PDF)
  • dlog 调试文档(RST 源)

相关目录页:功耗与定时器调试工具(ram_timer)、音频/蓝牙协议栈调试、各产品 demo 板级调试配置。

Prev
固件升级与更新机制