LVGL内存分配管理如何与sct文件协同高效管理?

摘要:引言 LVGL 内存管理,可选默认 lvgl 管理方式,也可选自定义管理方式。 LVGL 内存消耗 这里说的内存管理,就是指“LVGL 要管理的内存”。 这个内存池不能分配的过大,过大则图形缓冲区或其他被分配的位置就可能不足;也不能过小,过
引言 LVGL 内存管理,可选默认 lvgl 管理方式,也可选自定义管理方式。 LVGL 内存消耗 这里说的内存管理,就是指“LVGL 要管理的内存”。 这个内存池不能分配的过大,过大则图形缓冲区或其他被分配的位置就可能不足;也不能过小,过小则可能分配给某些控件的内存不足。所以需要合理配需要管理的内存池的大小 内存管理文件添加 在工程中选择一个合适的文件夹下添加 malloc.c 和 malloc.h 文件,方便添加管理代码。 由于我使用的 arm 为 stm32h743iit6 型号的单片机,其内存分布如下 sct 文件所示: 其默认未初始化的变量都是放到 AXI SRAM 区的,我们一般也是将 lvgl 需要管理的内存放到这个区域,所以在后面 malloc.c 文件中不需要 _attribute_,直接为默认内存即可。 这个内存管理文件原本是正点原子写来管理整个工程的内存的,但是由于我们已经使用了 sct 静态内存管理的方法,所以这里的内存管理文件仅仅当作 lvgl 的内存管理文件使用了,所以需要初始化的内存区域也只有内部的 SRAM 区,外部 SDRAM 的管理用 sct 文件即可。 malloc.c 点击查看代码 #include "malloc.h" /* 内存池(32字节对齐) */ static __ALIGNED(32) uint8_t mem1base[MEM1_MAX_SIZE]; /* 内部SRAM内存池 */ // static __ALIGNED(32) uint8_t mem2base[MEM2_MAX_SIZE] __attribute__((section(".RAM_SDRAM"))); /* 外部SDRAM内存池 */ /* 内存管理表 */ static MT_TYPE mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; /* 内部SRAM内存池MAP */ // static MT_TYPE mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((section(".RAM_SDRAM"))); /* 外部SRAM内存池MAP */ /* 内存管理参数 */ const uint32_t memtblsize[SRAMBANK] = { MEM1_ALLOC_TABLE_SIZE, // MEM2_ALLOC_TABLE_SIZE }; /* 内存表大小 */ const uint32_t memblksize[SRAMBANK] = { MEM1_BLOCK_SIZE, // MEM2_BLOCK_SIZE }; /* 内存分块大小 */ const uint32_t memsize[SRAMBANK] = { MEM1_MAX_SIZE, // MEM2_MAX_SIZE }; /* 内存总大小 */ /* 内存管理控制器 */ struct _m_mallco_dev mallco_dev = { my_mem_init, /* 内存初始化 */ my_mem_perused, /* 内存使用率 */ /* 内存池 */ mem1base, // mem2base, /* 内存管理状态表 */ mem1mapbase, // mem2mapbase, /* 内存管理未就绪 */ 0, // 0, }; /** * @brief 复制内存 * @param *des : 目的地址 * @param *src : 源地址 * @param n : 需要复制的内存长度(字节为单位) * @retval 无 */ void my_mem_copy(void *des, void *src, uint32_t n) { uint8_t *xdes = des; uint8_t *xsrc = src; while (n--) *xdes++ = *xsrc++; } /** * @brief 设置内存值 * @param *s : 内存首地址 * @param c : 要设置的值 * @param count : 需要设置的内存大小(字节为单位) * @retval 无 */ void my_mem_set(void *s, uint8_t c, uint32_t count) { uint8_t *xs = s; while (count--) *xs++ = c; } /** * @brief 内存管理初始化 * @param memx : 所属内存块 * @retval 无 */ void my_mem_init(uint8_t memx) { my_mem_set(mallco_dev.memmap[memx], 0, memtblsize[memx] * 4); /* 内存状态表数据清零 */ mallco_dev.memrdy[memx] = 1; /* 内存管理初始化OK */ } /** * @brief 获取内存使用率 * @param memx : 所属内存块 * @retval 使用率(扩大了10倍,0~1000,代表0.0%~100.0%) */ uint16_t my_mem_perused(uint8_t memx) { uint32_t used = 0; uint32_t i; for (i = 0; i < memtblsize[memx]; i++) { if (mallco_dev.memmap[memx][i]) used++; } return (used * 1000) / (memtblsize[memx]); } /** * @brief 内存分配(内部调用) * @param memx : 所属内存块 * @param size : 要分配的内存大小(字节) * @retval 内存偏移地址 * @arg 0 ~ 0xFFFFFFFE : 有效的内存偏移地址 * @arg 0xFFFFFFFF : 无效的内存偏移地址 */ uint32_t my_mem_malloc(uint8_t memx, uint32_t size) { signed long offset = 0; uint32_t nmemb; /* 需要的内存块数 */ uint32_t cmemb = 0; /* 连续空内存块数 */ uint32_t i; if (!mallco_dev.memrdy[memx]) { mallco_dev.init(memx); /* 未初始化,先执行初始化 */ } if (size == 0) return 0XFFFFFFFF; /* 不需要分配 */ nmemb = size / memblksize[memx]; /* 获取需要分配的连续内存块数 */ if (size % memblksize[memx]) nmemb++; for (offset = memtblsize[memx] - 1; offset >= 0; offset--) /* 搜索整个内存控制区 */ { if (!mallco_dev.memmap[memx][offset]) { cmemb++; /* 连续空内存块数增加 */ } else { cmemb = 0; /* 连续内存块清零 */ } if (cmemb == nmemb) /* 找到了连续nmemb个空内存块 */ { for (i = 0; i < nmemb; i++) /* 标注内存块非空 */ { mallco_dev.memmap[memx][offset + i] = nmemb; } return (offset * memblksize[memx]); /* 返回偏移地址 */ } } return 0XFFFFFFFF; /* 未找到符合分配条件的内存块 */ } /** * @brief 释放内存(内部调用) * @param memx : 所属内存块 * @param offset : 内存地址偏移 * @retval 释放结果 * @arg 0, 释放成功; * @arg 1, 释放失败; * @arg 2, 超区域了(失败); */ uint8_t my_mem_free(uint8_t memx, uint32_t offset) { int i; if (!mallco_dev.memrdy[memx]) /* 未初始化,先执行初始化 */ { mallco_dev.init(memx); return 1; /* 未初始化 */ } if (offset < memsize[memx]) /* 偏移在内存池内. */ { int index = offset / memblksize[memx]; /* 偏移所在内存块号码 */ int nmemb = mallco_dev.memmap[memx][index]; /* 内存块数量 */ for (i = 0; i < nmemb; i++) /* 内存块清零 */ { mallco_dev.memmap[memx][index + i] = 0; } return 0; } else { return 2; /* 偏移超区了. */ } } /** * @brief 释放内存(外部调用) * @param memx : 所属内存块 * @param ptr : 内存首地址 * @retval 无 */ void myfree(uint8_t memx, void *ptr) { uint32_t offset; if (ptr == NULL) return; /* 地址为0. */ offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx]; my_mem_free(memx, offset); /* 释放内存 */ } /** * @brief 分配内存(外部调用) * @param memx : 所属内存块 * @param size : 要分配的内存大小(字节) * @retval 分配到的内存首地址. */ void *mymalloc(uint8_t memx, uint32_t size) { uint32_t offset; offset = my_mem_malloc(memx, size); if (offset == 0xFFFFFFFF) /* 申请出错 */ { return NULL; /* 返回空(0) */ } else /* 申请没问题, 返回首地址 */ { return (void *)((uint32_t)mallco_dev.membase[memx] + offset); } } /** * @brief 重新分配内存(外部调用) * @param memx : 所属内存块 * @param *ptr : 旧内存首地址 * @param size : 要分配的内存大小(字节) * @retval 新分配到的内存首地址. */ void *myrealloc(uint8_t memx, void *ptr, uint32_t size) { uint32_t offset; offset = my_mem_malloc(memx, size); if (offset == 0xFFFFFFFF) /* 申请出错 */ { return NULL; /* 返回空(0) */ } else /* 申请没问题, 返回首地址 */ { my_mem_copy((void *)((uint32_t)mallco_dev.membase[memx] + offset), ptr, size); /* 拷贝旧内存内容到新内存 */ myfree(memx, ptr); /* 释放旧内存 */ return (void *)((uint32_t)mallco_dev.membase[memx] + offset); /* 返回新内存首地址 */ } } /** * @brief 分配内存(外部调用) * @param size : 要分配的内存大小(字节) * @retval 分配到的内存首地址. */ void *lv_mymalloc(uint32_t size) { return (void *)mymalloc(SRAM, size); } /** * @brief 释放内存(外部调用) * @param ptr : 内存首地址 * @retval 无 */ void lv_myfree(void *ptr) { myfree(SRAM, ptr); } /** * @brief 重新分配内存(外部调用) * @param *ptr : 旧内存首地址 * @param size : 要分配的内存大小(字节) * @retval 新分配到的内存首地址. */ void *lv_myrealloc(void *ptr, uint32_t size) { return (void *)myrealloc(SRAM, ptr, size); } malloc.h 点击查看代码 #ifndef __MALLOC_H #define __MALLOC_H #include "headers.h" #ifndef NULL #define NULL 0 #endif /* 定义三个内存池 */ #define SRAM 0 /* 内部内存池 */ #define SDRAM 1 /* CCM内存池(此部分SRAM仅仅CPU可以访问!!!) */ /* 定义内存管理表类型,当外扩SDRAM的时候,必须使用uint32_t类型,否则可以定义成uint16_t,以节省内存占用 */ #define MT_TYPE uint32_t #define SRAMBANK 1 /* 定义支持的SRAM块数. */ /* mem1内存参数设定.mem1完全处于内部SRAM里面. */ #define MEM1_BLOCK_SIZE 64 /* 内存块大小为 64 字节 */ #define MEM1_MAX_SIZE 200 * 1024 /* 最大管理内存 512K */ #define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE / MEM1_BLOCK_SIZE /* 内存表大小 */ // /* mem2内存参数设定.mem2处于CCM,用于管理CCM(特别注意,这部分SRAM,仅CPU可以访问!!) */ // #define MEM2_BLOCK_SIZE 64 /* 内存块大小为 64 字节 */ // #define MEM2_MAX_SIZE 60 * 1024 /* 最大管理内存 32MB */ // #define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE / MEM2_BLOCK_SIZE /* 内存表大小 */ /* 内存管理控制器 */ struct _m_mallco_dev { void (*init)(uint8_t); /* 初始化 */ uint16_t (*perused)(uint8_t); /* 内存使用率 */ uint8_t *membase[SRAMBANK]; /* 内存池 管理SRAMBANK个区域的内存 */ uint32_t *memmap[SRAMBANK]; /* 内存管理状态表 */ uint8_t memrdy[SRAMBANK]; /* 内存管理是否就绪 */ }; extern struct _m_mallco_dev mallco_dev; /* 在mallco.c里面定义 */ void my_mem_set(void *s, uint8_t c, uint32_t count); /* 设置内存 */ void my_mem_copy(void *des, void *src, uint32_t n); /* 复制内存 */ void my_mem_init(uint8_t memx); /* 内存管理初始化函数(外/内部调用) */ uint32_t my_mem_malloc(uint8_t memx, uint32_t size); /* 内存分配(内部调用) */ uint8_t my_mem_free(uint8_t memx, uint32_t offset); /* 内存释放(内部调用) */ uint16_t my_mem_perused(uint8_t memx); /* 获得内存使用率(外/内部调用) */ /* 用户调用函数 */ void myfree(uint8_t memx, void *ptr); /* 内存释放(外部调用) */ void *mymalloc(uint8_t memx, uint32_t size); /* 内存分配(外部调用) */ void *myrealloc(uint8_t memx, void *ptr, uint32_t size); /* 重新分配内存(外部调用) */ void *lv_mymalloc(uint32_t size); void lv_myfree(void *ptr); void *lv_myrealloc(void *ptr, uint32_t size); #endif lv_conf.h 修改 找到MEMORY SETTINGS位置, 将 LV_MEMCUSTOM宏定义修改为 1,即指定自定义内存分配管理方式 将下图红框中的 malloc、free、realloc 修改成 malloc.c 文件中的 lv_mymalloc、lv_myfree、lv_myrealloc,并且将应用的头文件改为"malloc.h"。 测试 我这里测试的是 music 的 demo,是一个较大的 demo,我们可以通过修改 malloc.h 文件中的内存分配位置(下图所示),来管理分配给 lvgl 管理的内存,将其改为不同大小可以看到不同效果,那么就是管理成功了(由于此 demo 较大,则需要分配较大的内存,例如 200K 才能正常运行)。 main.c 调用 在 main 函数中调用 my_mem_init(SRAM);初始化即可(非必要)。 点击查看代码 int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MPU Configuration--------------------------------------------------------*/ MPU_Config(); /* Enable the CPU Cache */ /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); MX_FMC_Init(); MX_LTDC_Init(); MX_DMA2D_Init(); MX_TIM1_Init(); /* USER CODE BEGIN 2 */ /****************** 初始化打印 ******************/ Set_Current_USART(USART1_IDX); // 设置当前使用的USART为USART1 printf("SDRAM 初始化通过!\r\n"); // 打印SDRAM初始化成功信息 /****************** 初始化操作 ******************/ delay_init(480); // 初始化延时函数,参数为系统时钟频率MHz my_mem_init(SRAM); // lcd_init(); // 初始化LCD显示控制器 // gtxxxx_init(); // 初始化触摸屏控制器 GTXXXX /* lvgl */ btim_timx_int_init(2400 - 1, 100 - 1); // 基本定时器TIMX定时中断初始化,1ms中断一次 lv_init(); // 初始化LVGL库 lv_port_disp_init(); // 初始化LVGL显示端口 lv_port_indev_init(); // 初始化LVGL输入设备端口 // lv_obj_t *switch_obj = lv_switch_create(lv_scr_act()); // 创建一个开关对象 // lv_obj_set_size(switch_obj, 120, 60); // 设置开关对象的大小为120x60像素 // lv_obj_align(switch_obj, LV_ALIGN_CENTER, 0, 0); // 将开关对象对齐到屏幕中心 /* demo */ // lv_demo_stress(); lv_demo_music(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { // lcd_test(); // gtxxxx_scan(); // 触摸屏扫描测试,带坐标返回 /* lvgl */ delay_ms(5); lv_timer_handler(); /* LVGL任务处理 */ /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } MEM1_MAX_SIZE 修改为 40 * 1024 效果 可以看到,就只有白屏,因为仅仅初始化了 lcd 后,给 lvgl 后续的图片即控件什么的分配的内存不足,就只能显示白屏了。 MEM1_MAX_SIZE 修改为 200 * 1024 效果 200KB 分配的内存刚好,正常运行。 MEM1_MAX_SIZE 修改为 500 * 1024 效果 某些缓存内存不足,直接在初始化前就进入了内存错误中断,只能看到黑屏,甚至没有背光。 博客导航 博客导航