LVGL和FreeRTOS可视化跟踪
一、LVGL用量跟踪
lv_mem_monitor_t mon; lv_mem_monitor(&mon); printf("\r\n========== LVGL Memory Info ==========\r\n"); printf("Total size: %d bytes (%.1f KB)\r\n", mon.total_size, mon.total_size / 1024.0); printf("Free size: %d bytes (%.1f KB)\r\n", mon.free_size, mon.free_size / 1024.0); printf("Used size: %d bytes (%.1f KB)\r\n", mon.total_size - mon.free_size, (mon.total_size - mon.free_size) / 1024.0); printf("Max used (peak): %d bytes (%.1f KB)\r\n", mon.max_used, mon.max_used / 1024.0); printf("Used percent: %d%%\r\n", mon.used_pct); printf("Free blocks: %d\r\n", mon.free_cnt); printf("Used blocks: %d\r\n", mon.used_cnt); printf("Biggest free block:%d bytes\r\n", mon.free_biggest_size); printf("Fragmentation: %d%%\r\n", mon.frag_pct); printf("=====================================\r\n");========== LVGL Memory Info ========== Total size: 20480 bytes (20.0 KB) Free size: 9764 bytes (9.5 KB) Used size: 10716 bytes (10.5 KB) Max used (peak): 11940 bytes (11.7 KB) Used percent: 53% Free blocks: 7 Used blocks: 273 Biggest free block:9484 bytes Fragmentation: 3% =====================================一、字段含义详解
| 字段 | 类型 | 含义 | 说明 |
|---|---|---|---|
mon.total_size | uint32_t | 总内存大小 | LVGL 动态内存池的总大小(即LV_MEM_SIZE配置的值) |
mon.free_size | uint32_t | 当前空闲内存 | 当前未使用的内存字节数 |
mon.free_biggest_size | uint32_t | 最大空闲块大小 | 内存池中最大的连续空闲块(碎片化程度指标) |
mon.free_cnt | uint32_t | 空闲块数量 | 内存池中被分割成多少块空闲区域(越多说明碎片越严重) |
mon.used_cnt | uint32_t | 已使用块数量 | 当前已分配的内存块数量 |
mon.max_used | uint32_t | 历史最大使用量 | 从启动到现在的峰值使用量(字节) |
mon.used_pct | uint8_t | 使用百分比 | 当前使用量占总量的百分比(0-100%) |
mon.frag_pct | uint8_t | 碎片化百分比 | 内存碎片化程度(0-100%,越高越严重) |
根据这些信息判断内存是否够用
| 指标 | 健康状态 | 需要关注 | 危险 |
|---|---|---|---|
free_size | > 20% 总量 | 10-20% | < 10% |
frag_pct | < 30% | 30-50% | > 50% |
free_biggest_size | > 预期单次分配大小 | - | < 预期单次分配大小 |
LVGL 内存分析
当前配置
| 项目 | 值 | 状态 |
|---|---|---|
| LV_MEM_SIZE | 20,480 字节 (20 KB) | ✅ |
| 当前使用 | 10,716 字节 (10.5 KB) | ✅ |
| 峰值使用 | 11,940 字节 (11.7 KB) | ✅ |
| 空闲内存 | 9,764 字节 (9.5 KB) | ✅ 充裕 |
| 碎片化 | 3% | ✅ 非常健康 |
二、FreeFTOS任务跟踪
FREERTOS_CONFIG_H //任务跟踪 extern volatile uint32_t CPU_RunTime; #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (CPU_RunTime = 0ul) #define portGET_RUN_TIME_COUNTER_VALUE() CPU_RunTime /* Run time and task stats gathering related definitions. */ #define configGENERATE_RUN_TIME_STATS 1//启用运行时间统计功能 #define configUSE_TRACE_FACILITY 1//启用可视化跟踪调试 #define configUSE_STATS_FORMATTING_FUNCTIONS 1//开启后才能使用vTaskList--------------------------------------------- 任务名 任务状态 优先级 剩余栈 任务序号 Tmr Svc X 31 172 5 IDLE R 0 118 4 led2 B 2 42 2 led1 B 1 106 1 Lvgl B 5 650 3 ---------------------------------------------一、整体概念
FreeRTOS 的任务运行时间统计功能,核心思想是:
用一个高精度计数器来记录时间,每个任务开始运行时记录开始值,任务切换时计算差值,累加到该任务的运行时间中。
二、关键宏定义的含义
c
extern volatile uint32_t CPU_RunTime; // 全局时间计数器 // 1. 初始化函数:重置计数器 #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (CPU_RunTime = 0ul) // 2. 获取当前时间:返回当前计数值 #define portGET_RUN_TIME_COUNTER_VALUE() CPU_RunTime
这两个宏的作用
| 宏 | 调用时机 | 作用 |
|---|---|---|
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() | 系统启动时(vTaskStartScheduler()内)调用一次 | 初始化时间计数器 |
portGET_RUN_TIME_COUNTER_VALUE() | 每次任务切换时调用 | 获取当前时间值,用于计算任务运行时长 |
三、你的实现流程
1. 定时器5提供时间基准(20kHz)
c
// TIM5 中断服务函数(20kHz = 50us 一次) void TIM5_IRQHandler(void) { if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM5, TIM_IT_Update); CPU_RunTime++; // 每 50us 增加 1 } }时间单位:
20kHz 中断频率 → 每次中断间隔 = 1 / 20000 = 50 微秒
CPU_RunTime每增加 1,代表过去了 50 微秒
2. 系统启动时初始化
c
// FreeRTOS 内核启动时(vTaskStartScheduler() 内部) // 会调用 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() // 相当于执行: CPU_RunTime = 0; // 从 0 开始计数
3. 任务切换时计算运行时间
FreeRTOS 内核在任务切换时会执行以下逻辑(简化版):
c
// 当前任务要切出了,计算它运行了多久 void vTaskSwitchContext(void) { // 1. 获取当前时间 uint32_t current_time = portGET_RUN_TIME_COUNTER_VALUE(); // = CPU_RunTime // 2. 计算当前任务的运行时间增量 uint32_t delta = current_time - last_time; // last_time 是上次切换时的时间 // 3. 累加到当前任务的运行时间统计中 pxCurrentTCB->ulRunTimeCounter += delta; // 4. 记录当前时间,供下次计算用 last_time = current_time; // 5. 切换到下一个任务... }四、数据流向图
text
TIM5 中断 (20kHz) ↓ CPU_RunTime++ (每 50us +1) ↓ [时间计数器] ↓ ┌────────────────┼────────────────┐ ↓ ↓ ↓ 任务切换时 任务切换时 任务切换时 ↓ ↓ ↓ 计算 Task1 计算 Task2 计算 Task3 运行时间 运行时间 运行时间 ↓ ↓ ↓ Task1.counter Task2.counter Task3.counter 增加 delta 增加 delta 增加 delta
五、最终输出:vTaskGetRunTimeStats()
当你调用vTaskGetRunTimeStats(info_buffer)时,会输出类似:
text
Task Name Abs Time % Time -------------- ---------- ------- Idle Task 12345678 45% Task_LED 8765432 32% Task_ADC 6543210 23%
计算公式
text
某任务的运行时间 = 该任务的 ulRunTimeCounter × 时间单位 总运行时间 = 所有任务 ulRunTimeCounter 之和 CPU 使用率 = (某任务运行时间 / 总运行时间) × 100%
六、配置含义
方案1:使用vTaskList()查看任务状态(不需要运行时间统计)
如果你只需要查看任务状态(任务名、状态、优先级、堆栈剩余),不需要 CPU 运行时间统计:
c
/* Run time and task stats gathering related definitions. */ #define configGENERATE_RUN_TIME_STATS 0 // 改为 0 #define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 改为 1(vTaskList 需要)
此时可以使用:
vTaskList()✅ 可用
方案2:使用vTaskGetRunTimeStats()查看 CPU 使用率(需要运行时间统计)
如果你需要查看每个任务的 CPU 使用率:
c
/* Run time and task stats gathering related definitions. */ #define configGENERATE_RUN_TIME_STATS 1 #define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 改为 1
此时可以使用:
vTaskList()✅ 可用vTaskGetRunTimeStats()✅ 可用
方案3:只统计运行时间,不输出格式化信息(不常用)
c
#define configGENERATE_RUN_TIME_STATS 1 #define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 0
这种情况下:
运行时间统计功能已启用,但
vTaskGetRunTimeStats()函数不存在需要自己实现统计信息的输出
一般不推荐这样用
七、软件定时器时机
uint8_t CPU_RunInfo[400] = {0}; void vTimerCallback( TimerHandle_t xTimer ) { //回调函数内部尽量不要使用延时 阻塞性质的操作 memset(CPU_RunInfo,0,400); //信息缓冲区清零 vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息 printf("---------------------------------------------\r\n"); printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n"); printf("%s", CPU_RunInfo); printf("---------------------------------------------\r\n"); } //任务跟踪 timer1 = xTimerCreate("Timer1",1000,pdTRUE,(void*)0,vTimerCallback); // 启动定时器 if(timer1 != NULL) { xTimerStart(timer1, 1000); // 最多等待 1 秒 }一、核心概念
定时器周期 = 采样间隔
采样间隔决定了你能看到系统行为的精细程度和响应速度。
二、两个关键影响
1. 时间分辨率
周期越短→ 采样越密集 → 能看到更细微的状态变化
周期越长→ 采样越稀疏 → 可能错过中间状态
2. 响应延迟
周期越短→ 问题被发现得越快
周期越长→ 问题暴露得越晚
三、取舍关系
| 方向 | 优点 | 代价 |
|---|---|---|
| 周期短 | 细节丰富、响应快 | CPU占用高、输出量大 |
| 周期长 | 资源占用低 | 信息粗糙、响应慢 |
