本文代码已上传开源仓库:ESP32-S3教学资产:包括了一些在ESP32-S3上的简单工程,基于ESP-IDF框架 - AtomGit | GitCode在嵌入式开发中,日志系统是调试和监控程序运行状态的核心工具。ESP-IDF 为 ESP32-S3 提供了一套功能强大、配置灵活的日志库 esp_log,远比简单的 printf 更适合专业开发。

本文将结合一份完整的示例代码,带你系统性地掌握 ESP32-S3 日志系统的所有高级特性。

一、日志系统基础架构

1.1 核心头文件与初始化

在使用日志系统前,需要包含核心头文件,并在文件顶部(在 #include "esp_log.h" 之前)定义本文件的日志编译级别:

#include <stdio.h>
// 【关键】定义本文件的编译时日志级别为 VERBOSE(最详细)
// 这决定了哪些级别的日志会被编译进固件
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "esp_log.h"

1.2 日志标签 (TAG)

日志标签是区分不同模块日志的关键。通常为每个 .c 文件或功能模块定义一个静态字符串标签:

static const char *TAG = "MAIN_APP";      // 主程序标签
static const char *TAG_SENSOR = "SENSOR"; // 传感器模块标签
static const char *TAG_NETWORK = "NETWORK"; // 网络模块标签

使用标签可以在后期轻松过滤出特定模块的日志。

二、日志级别 (Log Levels)

ESP-IDF 定义了 6 个日志级别,从严重到详细依次排列。这是日志系统最重要的概念,用于控制信息的粒度

2.1 级别演示

void log_level_demo(void)
{
    ESP_LOGE(TAG, "这是一个ERROR级别的日志 - 用于错误信息");   // 红色
    ESP_LOGW(TAG, "这是一个WARN级别的日志 - 用于警告信息");    // 黄色
    ESP_LOGI(TAG, "这是一个INFO级别的日志 - 用于一般信息");    // 绿色
    ESP_LOGD(TAG, "这是一个DEBUG级别的日志 - 用于调试信息");    // 默认色
    ESP_LOGV(TAG, "这是一个VERBOSE级别的日志 - 用于详细调试");  // 默认色
}

使用建议

  • ERROR:系统无法继续运行的致命错误。
  • WARN:出现了问题但系统可以勉强运行。
  • INFO:系统启动、连接成功等重要里程碑事件。
  • DEBUG/VERBOSE:开发调试时的变量打印、流程追踪。

三、高级格式化与数据转储

3.1 类 printf 格式化

esp_log 完全支持 C 标准 printf 的格式化语法,这使得输出变量非常方便:

void log_format_demo(void)
{
    int value = 42;
    float temperature = 25.6;
    
    ESP_LOGI(TAG, "整数: %d, 浮点数: %.2f", value, temperature);
    ESP_LOGI(TAG, "十六进制: 0x%x, 指针地址: %p", value, &value);
}

3.2 二进制数据转储 (Hex Dump)

在调试通信协议(如 I2C、SPI)或内存数据时,直接打印十六进制数据是最直观的方式。

void log_hex_dump_demo(void)
{
    uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
    
    // 1. 仅打印十六进制
    ESP_LOG_BUFFER_HEX(TAG, data, sizeof(data));
    
    // 2. 打印十六进制 + ASCII 对照表(推荐)
    ESP_LOG_BUFFER_HEXDUMP(TAG, data, sizeof(data), ESP_LOG_INFO);
}

ESP_LOG_BUFFER_HEXDUMP 会以类似 Wireshark 的格式展示数据,非常便于分析。

四、动态日志控制 (运行时配置)

这是 ESP32 日志系统最强大的功能之一。你可以在程序运行时动态调整日志的输出级别,而无需重新编译。

4.1 针对特定标签的控制

void log_level_control_demo(void)
{
    // 设置 "MAIN_APP" 标签仅输出 ERROR 级别
    esp_log_level_set(TAG, ESP_LOG_ERROR);
    
    ESP_LOGE(TAG, "这个会显示");
    ESP_LOGW(TAG, "这个不会显示"); // 被过滤了
    
    // 恢复为 INFO 级别
    esp_log_level_set(TAG, ESP_LOG_INFO);
}

4.2 全局控制

使用通配符 "*" 可以一次性设置所有模块的日志级别:

// 将整个系统的日志级别提高到 WARNING,减少干扰
esp_log_level_set("*", ESP_LOG_WARN);

五、其他实用特性

5.1 时间戳

默认情况下,每条日志前面都会自动加上系统启动后的时间戳(单位:毫秒)。这对于分析程序执行时序和性能瓶颈至关重要。

// 输出示例:I (12345) MAIN_APP: 循环计数: 0
// 其中 12345 就是系统运行时间

5.2 彩色日志

日志系统会根据级别自动给文字上色:

  • Error: 红色
  • Warning: 黄色
  • Info: 绿色
  • Debug/Verbose: 灰色 / 默认色

这让你在串口监视器中一眼就能发现错误。

5.3 多任务安全

在 FreeRTOS 多任务环境中,你可以放心地在不同任务中调用 ESP_LOGx。日志库内部实现了锁机制,不会出现打印乱码的情况。

六、编译烧录测试

完整代码:

#include <stdio.h>
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_timer.h"

static const char *TAG = "MAIN_APP";
static const char *TAG_SENSOR = "SENSOR";
static const char *TAG_NETWORK = "NETWORK";

void log_level_demo(void)
{
    ESP_LOGE(TAG, "这是一个ERROR级别的日志 - 用于错误信息");
    ESP_LOGW(TAG, "这是一个WARN级别的日志 - 用于警告信息");
    ESP_LOGI(TAG, "这是一个INFO级别的日志 - 用于一般信息");
    ESP_LOGD(TAG, "这是一个DEBUG级别的日志 - 用于调试信息");
    ESP_LOGV(TAG, "这是一个VERBOSE级别的日志 - 用于详细调试信息");
}

void log_format_demo(void)
{
    int value = 42;
    float temperature = 25.6;
    char message[] = "Hello ESP32";

    ESP_LOGI(TAG, "整数输出: %d", value);
    ESP_LOGI(TAG, "浮点数输出: %.2f", temperature);
    ESP_LOGI(TAG, "字符串输出: %s", message);
    ESP_LOGI(TAG, "十六进制输出: 0x%x", value);
    ESP_LOGI(TAG, "指针地址: %p", &value);
    ESP_LOGI(TAG, "多个参数: value=%d, temp=%.1f, msg=%s", value, temperature, message);
}

void log_hex_dump_demo(void)
{
    uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                      0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
    size_t len = sizeof(data);

    ESP_LOGI(TAG, "十六进制数据转储演示:");
    ESP_LOG_BUFFER_HEX(TAG, data, len);
    
    ESP_LOGI(TAG, "十六进制+ASCII数据转储:");
    ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_INFO);
}

void log_timestamp_demo(void)
{
    ESP_LOGI(TAG, "带时间戳的日志演示");
    for (int i = 0; i < 3; i++) {
        ESP_LOGI(TAG, "循环计数: %d", i);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void log_color_demo(void)
{
    ESP_LOGI(TAG, "彩色日志演示");
    ESP_LOGE(TAG, "红色 - 错误信息");
    ESP_LOGW(TAG, "黄色 - 警告信息");
    ESP_LOGI(TAG, "绿色 - 一般信息");
    ESP_LOGD(TAG, "默认颜色 - 调试信息");
    ESP_LOGV(TAG, "默认颜色 - 详细信息");
}

void log_tag_demo(void)
{
    ESP_LOGI(TAG, "使用不同的日志标签");
    ESP_LOGI(TAG_SENSOR, "传感器数据读取中...");
    ESP_LOGI(TAG_NETWORK, "网络连接状态检查中...");
    ESP_LOGI(TAG, "主程序逻辑执行中...");
}

void log_level_control_demo(void)
{
    ESP_LOGI(TAG, "日志级别控制演示");
    
    esp_log_level_t current_level = esp_log_level_get(TAG);
    ESP_LOGI(TAG, "当前MAIN标签的日志级别: %d", current_level);
    
    ESP_LOGI(TAG, "设置MAIN标签为ERROR级别");
    esp_log_level_set(TAG, ESP_LOG_ERROR);
    
    ESP_LOGE(TAG, "这个ERROR日志会显示");
    ESP_LOGW(TAG, "这个WARN日志不会显示");
    ESP_LOGI(TAG, "这个INFO日志不会显示");
    
    vTaskDelay(pdMS_TO_TICKS(1000));
    
    ESP_LOGI(TAG, "恢复MAIN标签为INFO级别");
    esp_log_level_set(TAG, ESP_LOG_INFO);
    
    ESP_LOGI(TAG, "现在INFO级别的日志又可以显示了");
}

void log_global_level_demo(void)
{
    ESP_LOGI(TAG, "全局日志级别控制演示");
    
    ESP_LOGI(TAG, "设置全局日志级别为WARNING");
    esp_log_level_set("*", ESP_LOG_WARN);
    
    ESP_LOGE(TAG, "ERROR日志显示");
    ESP_LOGW(TAG, "WARN日志显示");
    ESP_LOGI(TAG, "INFO日志不显示");
    ESP_LOGI(TAG_SENSOR, "传感器INFO日志不显示");
    
    vTaskDelay(pdMS_TO_TICKS(1000));
    
    ESP_LOGI(TAG, "恢复全局日志级别为INFO");
    esp_log_level_set("*", ESP_LOG_INFO);
}

void log_buffer_char_demo(void)
{
    char text[] = "ESP32-S3 Log System Demo";
    ESP_LOGI(TAG, "字符缓冲区转储:");
    ESP_LOG_BUFFER_CHAR(TAG, text, sizeof(text));
}

void log_performance_demo(void)
{
    ESP_LOGI(TAG, "日志性能演示");
    
    int64_t start_time = esp_timer_get_time();
    
    for (int i = 0; i < 100; i++) {
        ESP_LOGD(TAG, "性能测试日志 %d", i);
    }
    
    int64_t end_time = esp_timer_get_time();
    ESP_LOGI(TAG, "100条DEBUG日志耗时: %lld 微秒", end_time - start_time);
}

void log_error_handling_demo(void)
{
    ESP_LOGI(TAG, "错误处理日志演示");
    
    int result = -1;
    if (result != 0) {
        ESP_LOGE(TAG, "操作失败,错误码: %d", result);
    }
    
    result = 0;
    if (result == 0) {
        ESP_LOGI(TAG, "操作成功完成");
    }
}

void log_task_demo(void *pvParameters)
{
    const char *task_tag = (const char *)pvParameters;
    int counter = 0;
    
    while (1) {
        ESP_LOGI(task_tag, "任务运行中,计数器: %d", counter++);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

void log_multi_task_demo(void)
{
    ESP_LOGI(TAG, "多任务日志演示");
    
    xTaskCreate(log_task_demo, "LogTask1", 2048, (void *)"TASK1", 5, NULL);
    xTaskCreate(log_task_demo, "LogTask2", 2048, (void *)"TASK2", 5, NULL);
    
    ESP_LOGI(TAG, "创建了两个日志任务");
}

void log_system_info_demo(void)
{
    ESP_LOGI(TAG, "系统信息日志演示");
    
    ESP_LOGI(TAG, "可用堆内存: %d 字节", esp_get_free_heap_size());
    ESP_LOGI(TAG, "最小可用堆内存: %d 字节", esp_get_minimum_free_heap_size());
}

void log_assert_demo(void)
{
    ESP_LOGI(TAG, "断言日志演示");
    
    int value = 10;
    ESP_LOGI(TAG, "检查值是否为正数: %d", value);
    
    if (value > 0) {
        ESP_LOGI(TAG, "断言通过: 值为正数");
    }
}

void log_raw_output_demo(void)
{
    ESP_LOGI(TAG, "原始输出演示");
    
    printf("这是printf原始输出\n");
    ESP_LOGI(TAG, "这是ESP_LOG格式化输出");
    
    ESP_LOGI(TAG, "混合使用:");
    printf("  - 原始输出1\n");
    ESP_LOGI(TAG, "  - ESP_LOG输出");
    printf("  - 原始输出2\n");
}

void app_main(void)
{
    // 设置所有标签的日志级别为VERBOSE,以便看到所有级别的日志
    esp_log_level_set("*", ESP_LOG_VERBOSE);
    
    ESP_LOGI(TAG, "========================================");
    ESP_LOGI(TAG, "ESP32-S3 日志系统完整示例");
    ESP_LOGI(TAG, "========================================");
    
    vTaskDelay(pdMS_TO_TICKS(1000));
    
    ESP_LOGI(TAG, "\n--- 1. 日志级别演示 ---");
    log_level_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 2. 日志格式化演示 ---");
    log_format_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 3. 十六进制转储演示 ---");
    log_hex_dump_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 4. 时间戳演示 ---");
    log_timestamp_demo();
    
    ESP_LOGI(TAG, "\n--- 5. 彩色日志演示 ---");
    log_color_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 6. 多标签演示 ---");
    log_tag_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 7. 日志级别控制演示 ---");
    log_level_control_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 8. 全局日志级别演示 ---");
    log_global_level_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 9. 字符缓冲区转储演示 ---");
    log_buffer_char_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 10. 性能演示 ---");
    log_performance_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 11. 错误处理演示 ---");
    log_error_handling_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 12. 系统信息演示 ---");
    log_system_info_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 13. 断言演示 ---");
    log_assert_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 14. 原始输出演示 ---");
    log_raw_output_demo();
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    ESP_LOGI(TAG, "\n--- 15. 多任务日志演示 ---");
    ESP_LOGI(TAG, "注意:这将创建后台任务,需要手动停止");
    log_multi_task_demo();
    
    ESP_LOGI(TAG, "\n========================================");
    ESP_LOGI(TAG, "所有演示完成!");
    ESP_LOGI(TAG, "========================================");
    
    while (1) {
        ESP_LOGI(TAG, "主程序运行中...");
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

编译烧录测试后,点击此位置即可打开日志:

I (279) MAIN_APP: ========================================
I (279) MAIN_APP: ESP32-S3 日志系统完整示例
I (279) MAIN_APP: ========================================
I (1289) MAIN_APP: 
--- 1. 日志级别演示 ---
E (1289) MAIN_APP: 这是一个ERROR级别的日志 - 用于错误信息
W (1289) MAIN_APP: 这是一个WARN级别的日志 - 用于警告信息
I (1289) MAIN_APP: 这是一个INFO级别的日志 - 用于一般信息
D (1299) MAIN_APP: 这是一个DEBUG级别的日志 - 用于调试信息
V (1299) MAIN_APP: 这是一个VERBOSE级别的日志 - 用于详细调试信息
I (3309) MAIN_APP:
--- 2. 日志格式化演示 ---
I (3309) MAIN_APP: 整数输出: 42
I (3309) MAIN_APP: 浮点数输出: 25.60
I (3309) MAIN_APP: 字符串输出: Hello ESP32
I (3309) MAIN_APP: 十六进制输出: 0x2a
I (3309) MAIN_APP: 指针地址: 0x3fc98470
I (3319) MAIN_APP: 多个参数: value=42, temp=25.6, msg=Hello ESP32
I (5319) MAIN_APP:
--- 3. 十六进制转储演示 ---
I (5319) MAIN_APP: 十六进制数据转储演示:
I (5319) MAIN_APP: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
I (5319) MAIN_APP: 十六进制+ASCII数据转储:
I (5319) MAIN_APP: 0x3fc98470   01 02 03 04 05 06 07 08  09 0a 0b 0c 0d 0e 0f 10  |................|
I (7329) MAIN_APP:
--- 4. 时间戳演示 ---
I (7329) MAIN_APP: 带时间戳的日志演示
I (7329) MAIN_APP: 循环计数: 0
I (8329) MAIN_APP: 循环计数: 1
I (9329) MAIN_APP: 循环计数: 2
I (10329) MAIN_APP: 
--- 5. 彩色日志演示 ---
I (10329) MAIN_APP: 彩色日志演示
E (10329) MAIN_APP: 红色 - 错误信息
W (10329) MAIN_APP: 黄色 - 警告信息
I (10329) MAIN_APP: 绿色 - 一般信息
D (10329) MAIN_APP: 默认颜色 - 调试信息
V (10339) MAIN_APP: 默认颜色 - 详细信息
I (12339) MAIN_APP: 
--- 6. 多标签演示 ---
I (12339) MAIN_APP: 使用不同的日志标签
I (12339) SENSOR: 传感器数据读取中...
I (12339) NETWORK: 网络连接状态检查中...
I (12339) MAIN_APP: 主程序逻辑执行中...
I (14349) MAIN_APP: 
--- 7. 日志级别控制演示 ---
I (14349) MAIN_APP: 日志级别控制演示
I (14349) MAIN_APP: 当前MAIN标签的日志级别: 5
I (14349) MAIN_APP: 设置MAIN标签为ERROR级别
E (14349) MAIN_APP: 这个ERROR日志会显示
I (15359) MAIN_APP: 现在INFO级别的日志又可以显示了
I (17359) MAIN_APP: 
--- 8. 全局日志级别演示 ---
I (17359) MAIN_APP: 全局日志级别控制演示
I (17359) MAIN_APP: 设置全局日志级别为WARNING
E (17359) MAIN_APP: ERROR日志显示
W (17359) MAIN_APP: WARN日志显示
I (20369) MAIN_APP: 
--- 9. 字符缓冲区转储演示 ---
I (20369) MAIN_APP: 字符缓冲区转储:
I (20369) MAIN_APP: ESP32-S3 Log Sys
I (20369) MAIN_APP: tem Demo
I (22369) MAIN_APP: 
--- 10. 性能演示 ---
I (22369) MAIN_APP: 日志性能演示
I (22369) MAIN_APP: 100条DEBUG日志耗时: 905 微秒
I (24369) MAIN_APP: 
--- 11. 错误处理演示 ---
I (24369) MAIN_APP: 错误处理日志演示
E (24369) MAIN_APP: 操作失败,错误码: -1
I (24369) MAIN_APP: 操作成功完成
I (26369) MAIN_APP: 
--- 12. 系统信息演示 ---
I (26369) MAIN_APP: 系统信息日志演示
I (26369) MAIN_APP: 可用堆内存: 388604 字节
I (26369) MAIN_APP: 最小可用堆内存: 388584 字节
I (28369) MAIN_APP: 
--- 13. 断言演示 ---
I (28369) MAIN_APP: 断言日志演示
I (28369) MAIN_APP: 检查值是否为正数: 10
I (28369) MAIN_APP: 断言通过: 值为正数
I (30369) MAIN_APP: 
--- 14. 原始输出演示 ---
I (30369) MAIN_APP: 原始输出演示
这是printf原始输出
I (30369) MAIN_APP: 这是ESP_LOG格式化输出
I (30369) MAIN_APP: 混合使用:
  - 原始输出1
I (30369) MAIN_APP:   - ESP_LOG输出
  - 原始输出2
I (32379) MAIN_APP: 
--- 15. 多任务日志演示 ---
I (32379) MAIN_APP: 注意:这将创建后台任务,需要手动停止
I (32379) MAIN_APP: 多任务日志演示
I (32379) TASK1: 任务运行中,计数器: 0
I (32379) TASK2: 任务运行中,计数器: 0
I (32389) MAIN_APP: 创建了两个日志任务
I (32389) MAIN_APP:
========================================
I (32399) MAIN_APP: 所有演示完成!
I (32399) MAIN_APP: ========================================
I (32409) MAIN_APP: 主程序运行中...
I (34379) TASK1: 任务运行中,计数器: 1
I (34389) TASK2: 任务运行中,计数器: 1
I (36379) TASK1: 任务运行中,计数器: 2
I (36389) TASK2: 任务运行中,计数器: 2

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐