2、内存架构概览:Flash与RAM的分工、内部SRAM vs 外部SDRAM、显存(Frame Buffer)的分配
做TouchGFX开发,说白了就是在跟内存打交道。你想想看,一张图片从存储到显示,中间要经过多少道内存关卡?我刚开始接触这个领域时,也踩过不少坑。有一次项目都快交付了,发现图片加载出来花屏,查了两天才找到原因——Frame Buffer分配错了区域。
所以这一章,咱们先把内存架构理清楚。Flash和RAM各自干什么活?内部SRAM和外部SDRAM怎么选?显存到底该放哪儿?这些搞明白了,后面讲图片优化你才能听得通透。
2.1 Flash与RAM:谁该干什么活?
嵌入式系统里,Flash和RAM的分工其实很明确。我习惯这么理解:Flash是仓库,RAM是工作台。
- Flash:存代码、存图片原始数据(如PNG、JPG)、存字体文件。掉电不丢失,但读取速度慢,不能直接在上面跑程序。
- RAM:存运行时变量、堆栈、显存(Frame Buffer)。掉电就丢,但读写速度快,CPU可以直接操作。
在TouchGFX里,图片的原始数据(比如一张PNG)通常放在Flash里。但你要显示它,就得先解码到RAM里。解码后的数据就是像素点阵,占的空间比原始文件大得多。
核心原则:Flash管存储,RAM管运行。图片解码后的像素数据,必须待在RAM里才能被LCD控制器读取。
我曾经见过一个新手,把一张800×480的24位图直接放在Flash里当Frame Buffer用,结果屏幕死活不亮。嗯,Flash的读取速度根本跟不上LCD的刷新率,这事儿强求不来。
2.2 内部SRAM vs 外部SDRAM:速度与容量的博弈
现在大部分MCU都内置了SRAM,少则几十KB,多则几百KB。但TouchGFX跑起来,尤其是带大屏的项目,这点空间根本不够用。这时候就得请出外部SDRAM了。
我整理了一张对比表,你一看就明白:
| 对比项 | 内部SRAM | 外部SDRAM |
|---|---|---|
| 访问速度 | 极快(通常几十ns) | 较快(通常几十到上百ns) |
| 容量 | 小(几十KB~1MB) | 大(4MB~64MB常见) |
| 接口 | 内部总线,无需配置 | 需要FMC/FSMC外设,需初始化 |
| 功耗 | 低 | 较高 |
| 掉电保持 | 否 | 否 |
| 典型用途 | 关键变量、堆栈、小尺寸Frame Buffer | 大尺寸Frame Buffer、解码缓存、资源池 |
我个人习惯是:能用内部SRAM的,绝不动外部SDRAM。为什么?因为外部SDRAM的初始化时序、刷新周期、布线干扰,随便一个坑都能让你调一整天。
我的经验:如果项目屏幕分辨率在320×240以下,且颜色深度为16位,内部SRAM通常够用。一旦上了480×272或更大,老老实实上SDRAM吧。
但注意,SDRAM不是插上就能用的。你得先配置FMC时钟、设置时序参数、做读写校验。我遇到过最头疼的一次,是SDRAM的刷新周期没配好,屏幕显示一会儿就出现条纹。后来用逻辑分析仪抓波形才发现,刷新请求把读操作打断了。
2.3 显存(Frame Buffer)的分配策略
Frame Buffer,就是显存。它是LCD控制器直接读取的那块内存区域。TouchGFX支持单缓冲、双缓冲、甚至三缓冲。但不管哪种模式,Frame Buffer的分配位置直接影响性能。
先算一笔账:
- 分辨率:800 × 480
- 颜色格式:RGB565(16位,每像素2字节)
- 单帧大小:800 × 480 × 2 = 768,000 字节 ≈ 750KB
750KB,很多MCU的内部SRAM总共才512KB。所以,大屏项目的Frame Buffer几乎必然放在外部SDRAM里。
但这里有个陷阱:LCD控制器直接读取SDRAM时,会占用FMC带宽。如果你同时用SDRAM做解码缓存,就会出现争抢。我做过一个项目,图片解码和屏幕刷新抢总线,结果帧率直接掉到15fps。
避坑指南:Frame Buffer尽量放在SDRAM的固定区域,并且不要和频繁读写的堆区混在一起。我曾经把动态分配的缓存放在Frame Buffer旁边,结果内存碎片导致显示异常,查了三天才定位到。
TouchGFX里,Frame Buffer的分配通常在TouchGFXConfiguration.cpp里配置。你可以指定使用内部SRAM还是外部SDRAM:
// 使用内部SRAM作为Frame Buffer(适合小屏)
static uint16_t frameBuffer[480 * 272];
// 使用外部SDRAM作为Frame Buffer(适合大屏)
// 需要在链接脚本中定义SDRAM起始地址
#define FRAME_BUFFER_ADDR 0xC0000000
#define FRAME_BUFFER_SIZE (800 * 480 * 2)
// 在TouchGFX HAL中配置
hal->setFrameBufferStartAddress((uint16_t*)FRAME_BUFFER_ADDR, FRAME_BUFFER_SIZE);
我个人建议:如果内部SRAM够放一个Frame Buffer,优先放内部SRAM。因为内部SRAM的访问延迟比SDRAM低一个数量级,触摸响应会更跟手。但如果你用了双缓冲,两个Frame Buffer加起来超过内部SRAM容量,那就只能放SDRAM了。
2.4 一张图看懂内存架构
下面这张图,是我自己总结的TouchGFX内存架构。你看完应该能明白数据是怎么流动的:
从这张图你能看到:Flash里的图片数据,要么解码到内部SRAM(小图),要么解码到外部SDRAM(大图)。解码完成后,LCD控制器从对应的RAM区域读取像素数据,逐行刷新到屏幕上。
这里有个细节:解码过程本身也消耗RAM。比如解码一张JPEG,需要额外的解压缩缓冲区。我一般会在SDRAM里预留一块区域专门做解码用,不和Frame Buffer重叠。
小技巧:在项目初期,先算清楚总内存需求。公式很简单:Frame Buffer大小 × 缓冲数量 + 解码缓存 + 堆栈 + 其他变量。如果总和超过内部SRAM,果断上SDRAM。
嗯,内存架构这块儿,说白了就是权衡。速度、容量、复杂度,三者不可能全都要。我个人习惯是:先保证Frame Buffer的稳定读取,再考虑解码效率。毕竟屏幕不闪、不花,用户才愿意用你的产品。