ESP32学习笔记_ESP-LOG(1)——日志宏的基础使用

摘要:
ESP-IDF日志系统采用六级过滤机制(从高到低:None、Error、Warning、INFO、Debug、Verbose),通过CONFIG_LOG_DEFAULT_LEVEL设定默认级别并由CONFIG_LOG_MAXIMUM_LEVEL控制编译时保留的最高日志级别,支持运行时用esp_log_level_set()动态调整;针对不同场景提供三种日志宏:标准ESP_LOGx适用于常规环境,ESP_EARLY_LOGx用于早期启动阶段,ESP_DRAM_LOGx专为中断禁用等受限环境设计,但需注意高频日志输出会因字符串格式化和UART传输阻塞显著降低系统性能,且LOG_LOCAL_LEVEL必须在源文件而非头文件中定义以确保配置精准生效

参考资料:
ESP-IDF 编程指南 - 日志库
ESP-IDF 编程指南 - Configuration Options Reference
ESP- Techpedia - FreeRTOS 常用 API


日志级别层次结构

(从高到低)

级别宏定义输出内容
NoneESP_LOG_NONE无日志输出
ErrorESP_LOG_ERROR错误信息
WarningESP_LOG_WARN警告信息
INFOESP_LOG_INFO一般信息
DebugESP_LOG_DEBUG调试信息
VerboseESP_LOG_VERBOSE详细信息
  • 引导加载程序的日志级别通过 CONFIG_BOOTLOADER_LOG_LEVEL 配置
  • 应用程序的日志级别通过 CONFIG_LOG_DEFAULT_LEVEL 设置

日志过滤级别

ESP_LOGE(TAG, "错误日志");
// 输出 ERROR 级别的日志(仅当当前日志级别 >= ERROR 时输出,下同)

ESP_LOGW(TAG, "警告日志");
// 输出 WARNING 级别的日志

ESP_LOGI(TAG, "信息日志");
// 输出 INFO 级别的日志

ESP_LOGD(TAG, "调试日志");
// 作用:输出 DEBUG 级别的日志

ESP_LOGV(TAG, "详细日志");
// 作用:输出 VERBOSE 级别的日志

/* esp_log_level_set 函数需要确保 menuconfig 中 Enable dynamic log level changes at runtime 选项启用 */

esp_log_level_set(TAG, ESP_LOG_NONE);
// 不输出任何日志

esp_log_level_set(TAG, ESP_LOG_ERROR);
// 只输出 ERROR 级别的日志

esp_log_level_set(TAG, ESP_LOG_WARN);
// 只输出 WARNING 级别及以上的日志(即 WARNING 和 ERROR)

esp_log_level_set(TAG, ESP_LOG_INFO);
// 输出 INFO 及以上级别的日志(INFO、WARNING、ERROR)

esp_log_level_set(TAG, ESP_LOG_DEBUG);
// 输出 DEBUG 及以上级别的日志(DEBUG、INFO、WARNING、ERROR)

esp_log_level_set(TAG, ESP_LOG_VERBOSE);
// 输出所有级别的日志(VERBOSE、DEBUG、INFO、WARNING、ERROR)

esp_log_get_default_level();
// 获取当前日志级别

menuconfig -> Component config -> LOG 中可以设置默认日志过滤级别和是否可以动态调整

Level Name
表示日志详细级别的单个字母(I, W, E, D, V),显示在每条日志消息的开头,用于识别日志级别

时间戳
可以在 (Top) -> Component config -> Log -> Format -> Timestamp 中选择时间戳类型

  • Milliseconds since boot(默认):通过 RTOS 时钟 tick 计数乘以 tick 周期得出
  • System time (HH:MM:SS.sss) 14:31:18.532:以小时、分钟、秒和毫秒显示时间

可以在 menuconfig -> (Top) -> Component config -> FreeRTOS -> Kernel -> configTICK_RATE_HZ 中设置 RTOS 时钟 tick,代表每秒钟多少个 tick
tick 的设置会影响诸如 vTaskDelay(pdMS_TO_TICKS(time)) 等函数的功能;如 tick 设置为 100 时,每一个 tick 为 10ms,即 Delay 的分辨率为 10ms,实际延时时间为 tick 数 × 每个 tick 的时长

tick 计数从 FreeRTOS 调度器启动时开始计数,即 MCU 启动并初始化 RTOS 后,tick 计数器开始递增
System time (HH:MM:SS.sss) 是通过 POSIX 时间函数(如 gettimeofday())获取的系统时间;系统时间在芯片启动时初始化为 0,可以通过 SNTP 网络同步或手动设置(如 settimeofday())来校准,此时间不会因软件重启而重置,除非芯片完全断电

最高日志级别
指定将哪些日志级别包含在二进制文件中,高于此级别的日志会在编译时丢弃,不包含在最终镜像中
最高日志级别可以设置得高于日志级别,从而在二进制文件中包含额外的日志,必要时可以通过 esp_log_level_set() 启用这些日志以帮助调试
CONFIG_LOG_MAXIMUM_LEVEL 选项可以为应用程序启用此功能,如果引导加载程序不支持此功能,其最高日志级别始终与日志级别相同

CONFIG_LOG_MAXIMUM_LEVELmenuconfig 中的路径为 (Top) -> Component config -> Log -> Log Level -> Maximum log verbosity

更改某个源文件的最高日志级别
使用 LOG_LOCAL_LEVEL 参数覆盖特定源文件或组件的最高日志级别,而无需修改 Kconfig 选项
此参数能设置一个本地的最高日志级别,从而启用或排除二进制文件中的特定日志

更改源文件的最高日志级别
在包含 esp_log.h 之前,使用 esp_log_level_t 中的一个值来定义 LOG_LOCAL_LEVEL,指定将哪些日志消息包含在该源文件的二进制文件中

// 在某个 my_file.c 文件中
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "esp_log.h"

不要在头文件中添加该定义,因为头文件采用单次包含的机制,可能无法生效
头文件一般通过 # pragma once 或 include guard 机制,保证在每个编译单元中只被包含一次
如果你头文件中定义 LOG_LOCAL_LEVEL,由于头文件只会被包含一次,其他源文件如果也包含了这个头文件,LOG_LOCAL_LEVEL 的定义就会被复用,导致所有包含该头文件的源文件都使用了同一个日志级别,无法做到“每个源文件单独设置日志级别”
更重要的是,如果头文件被多个源文件包含,LOG_LOCAL_LEVEL 只会在第一次包含时生效,后续包含时不会再重新定义,导致日志级别设置不准确(如果有多个头文件都定义了 LOG_LOCAL_LEVEL,那么在编译单元中,只有第一个被包含并实际展开的 LOG_LOCAL_LEVEL 定义会生效,后续包含的头文件中的定义不会再生效,导致所有包含该头文件的源文件都使用了同一个日志级别)

更改组件的最高日志级别
更改整个组件的最高日志级别:在组件的 CMakeLists.txt 文件中定义 LOG_LOCAL_LEVEL,这确保指定的日志级别适用于组件内的所有源文件,指定将哪些日志消息包含在二进制文件中

# 在组件的 CMakeLists.txt 文件中
target_compile_definitions(${COMPONENT_LIB} PUBLIC "-DLOG_LOCAL_LEVEL=ESP_LOG_VERBOSE")

对程序的影响

ESP32 在使用 ESP_LOGI 进行日志输出时,程序运行会变慢,主要原因

  • ESP_LOGI 等日志宏会先对格式字符串和参数进行解析和格式化(数类型判断、字符串拼接等操作,这些都会占用 CPU 资源),然后通过 UART 输出
  • 这个过程涉及字符串处理和数据传输,尤其是 UART 带宽有限,输出速度较慢,容易成为性能瓶颈;每次日志输出都需要等待数据通过串口发送完成,导致主程序被阻塞,影响整体运行速度

不同场景下的日志输出

LOG V2 的日志处理器会自动检测当前运行环境(如早期启动、禁用中断或 flash 缓存不可访问等受限环境),并动态选择适当的打印函数,以确保在不同环境下高效可靠地输出日志

ESP_LOGx

  • 标准日志宏,适用于正常运行期间的大多数用例
  • 非受限环境下,可在应用程序代码中使用来记录日志,不能在中断服务例程 (ISR)、早期启动阶段或 flash 缓存被禁用时使用
  • 此类宏使用 Newlib 库的 vprintf 函数进行格式处理和日志输出

Newlib 库的 vprintf 函数是一个格式化输出函数,它根据指定的格式字符串,将参数列表(以 va_list 形式传递)格式化后输出到标准输出,它通常作为日志系统底层的输出函数,用于将格式化后的日志信息输出到 UART 或其他目标设备
Newlib 库是 ESP-IDF 默认使用的 C 标准函数库实现之一,提供了如 printf、scanf 等常用 C 语言标准库函数,适用于嵌入式系统开发
va_list 是 C 语言标准库中用于处理可变参数函数(如 printf、vprintf)的类型,它允许函数接收不定数量的参数,并通过相关宏(如 va_start、va_arg、va_end)进行访问和处理

ESP_EARLY_LOGx

  • 专为早期启动阶段的受限环境设计,在堆分配器或系统调用尚未初始化时使用,通常用于关键的启动代码或中断被禁用的关键区域
  • 此类宏使用 ROM 的 printf 函数,以微秒为单位输出时间戳,并且不支持按模块设置日志详细级别

ROM 的 printf 函数是指芯片 ROM(只读存储器)中预置的格式化输出函数,例如 esp_rom_printf,该函数用于将格式化的字符串输出到控制台设备,常用于启动阶段或受限环境下的日志输出
需要注意的是,ROM 版本的 printf 函数通常不支持 float 和 long long 类型的数据格式化,仅支持基本的数据类型
由于其位于 ROM 中,无需占用额外的 flash 或 RAM 空间

ESP_DRAM_LOGx

  • 专为受限环境设计,在中断被禁用或 flash 缓存不可访问时记录日志
  • 可能会影响性能,应谨慎使用
  • 这些宏适用于其他日志宏可能无法可靠运行的关键区域或中断例程
  • 使用 ROM 的 printf 函数,不输出时间戳,将格式参数分配在 DRAM 中以确保缓存禁用时的可访问性,并且不支持按模块设置日志详细级别
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值