ESP32-S3开发教程11:日志系统
在嵌入式开发中,日志系统是调试和监控程序运行状态的。ESP-IDF 为 ESP32-S3 提供了一套功能强大、配置灵活的日志库esp_log,远比简单的printf更适合专业开发。本文将结合一份完整的示例代码,带你系统性地掌握 ESP32-S3 日志系统的所有高级特性。
本文代码已上传开源仓库: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
更多推荐



所有评论(0)