4. 显存操作与页寻址模式:GDDRAM结构、页地址模式、列地址范围、水平/垂直寻址模式
好,咱们今天聊点实在的。显存操作,说白了就是搞清楚数据怎么在驱动芯片里存放和搬运。很多新手调OLED,上来就写数据,发现屏幕花屏或者显示错位,十有八九是没搞懂GDDRAM的结构和寻址模式。我当年第一次调SSD1306的时候,就因为这个坑折腾了一整天。
4.1 GDDRAM结构:屏幕背后的“画板”
GDDRAM,全称是Graphic Display Data RAM。你可以把它想象成一块二维画板。每个像素点对应画板上的一个bit。点亮就是1,熄灭就是0。
对于一块128×64像素的单色OLED,它的GDDRAM大小是多少?
很简单:128列 × 64行 = 8192个bit = 1024个字节。
但驱动芯片内部并不是把64行当成一个连续平面来管理的。它把64行分成了8个页(Page),每页8行。
| 参数 | 数值 | 说明 |
|---|---|---|
| 总行数 | 64 | COM0 ~ COM63 |
| 页数 | 8 | Page0 ~ Page7 |
| 每页行数 | 8 | 每页包含8个COM |
| 列数 | 128 | SEG0 ~ SEG127 |
| 每页字节数 | 128 | 每列1个bit,8行凑1字节 |
| 总字节数 | 1024 | 8页 × 128字节 |
这里有个关键点:每页的8行,在内存里是纵向排列的。一个字节的bit0对应页内第0行,bit7对应第7行。你写一个字节进去,实际上是同时点亮或熄灭同一列上的8个像素点。
核心理解:GDDRAM的地址 = 页地址 + 列地址。页地址决定哪一组8行,列地址决定哪一列。两者组合,才能定位到唯一的字节位置。
4.2 页地址模式:最常用的操作方式
页地址模式,是SSD1306默认的寻址方式,也是我个人用得最多的模式。为什么?因为它简单、直观,适合逐页刷新。
工作原理是这样的:
- 你通过命令设置当前页地址(0~7)。
- 再设置列起始地址(低4位和高4位两个命令)。
- 然后连续写入数据,列地址会自动递增。
- 当列地址到达127(最后一列),它会回绕到0,但页地址不变。
我曾经在一个项目中,需要快速刷新屏幕上的某个区域。如果用页地址模式,我只需要切换页地址,然后从指定列开始写数据,效率非常高。
设置页地址的命令很简单:
// 设置页地址为 Page 2
// 命令格式:0xB0 | PageNum
send_command(0xB2); // 0xB0 | 0x02 = 0xB2
// 设置列起始地址为 32
// 需要两个命令:低4位和高4位
send_command(0x20); // 设置列起始地址低4位
send_command(0x02); // 列起始地址 = 32 (0x20)
// 然后就可以连续写入数据了
for (int i = 0; i < 64; i++) {
send_data(buffer[i]); // 列地址自动从32递增到95
}
小技巧:页地址模式适合逐行扫描式的显示更新。如果你要更新整个屏幕,通常的做法是:从Page0开始,写满128字节,然后切换到Page1,再写128字节……直到Page7。这样正好1024字节,覆盖整个GDDRAM。
4.3 列地址范围:别让数据跑偏了
列地址范围,决定了你的数据写到了屏幕的哪个水平位置。SSD1306的列地址范围是0~127,对应SEG0~SEG127。
但有个容易踩的坑:列地址的映射方向。
驱动芯片允许你通过命令反转列地址的映射顺序。也就是说,你可以让SEG0对应最左边,也可以让它对应最右边。这个功能由命令0xA0(列地址重映射)控制。
// 正常映射:SEG0 对应最左边
send_command(0xA0);
// 反转映射:SEG127 对应最左边
send_command(0xA1);
我记得有一次,帮同事调试一块屏幕,发现显示的内容是镜像的。查了半天,就是因为他初始化时用了0xA1,而他的数据是从左往右画的。改回0xA0,问题立刻解决。
注意:列地址范围还受显示偏移(Display Offset)命令影响。如果你设置了偏移,实际的列地址和物理像素位置可能不对应。调试时如果发现画面整体偏移,先检查这两个寄存器。
4.4 水平/垂直寻址模式:两种不同的“刷屏”姿势
除了页地址模式,SSD1306还支持水平寻址模式和垂直寻址模式。这两种模式,说白了就是改变了地址递增的方向。
| 寻址模式 | 地址递增方式 | 适用场景 |
|---|---|---|
| 页地址模式 | 列递增 → 列回绕 → 页不变 | 局部刷新、逐页操作 |
| 水平寻址模式 | 列递增 → 列回绕 → 页递增 → 页回绕 | 全屏刷新、连续帧缓冲 |
| 垂直寻址模式 | 页递增 → 页回绕 → 列递增 → 列回绕 | 竖屏滚动、特殊效果 |
水平寻址模式:你从(Page0, Col0)开始写数据,列地址一直递增到127,然后自动跳到下一页的Col0。这样连续写1024个字节,就能完整填充整个GDDRAM。我个人觉得,如果你要一次性刷新全屏,这个模式最省事。
垂直寻址模式:这个模式比较冷门。它的递增顺序是先页后列。也就是说,你先写满所有页的Col0,然后才跳到Col1。我在做竖屏滚动字幕的时候用过一次,效果还行,但平时基本用不上。
设置寻址模式的命令:
// 设置寻址模式
// 命令:0x20,后面跟一个参数
send_command(0x20);
// 参数:
// 0x00 = 水平寻址模式
// 0x01 = 垂直寻址模式
// 0x02 = 页地址模式(默认)
// 例如,切换到水平寻址模式
send_command(0x20);
send_command(0x00);
4.5 三种寻址模式对比:一张图看懂
下面这张SVG图,展示了三种寻址模式下,数据写入GDDRAM的路径。你可以直观地看到地址是怎么“走”的。
4.6 实战建议:选哪种模式?
嗯,这里我直接给结论吧,省得你纠结。
- 默认用页地址模式:兼容性最好,代码逻辑清晰。适合大部分场景。
- 全屏刷新用水平寻址模式:一次设置,连续写完1024字节,代码最简洁。
- 垂直寻址模式:除非你做竖屏滚动或者特殊效果,否则别碰它。我用了这么多年,也就用过两次。
避坑指南:我曾经在切换寻址模式后,忘记重新设置列起始地址,结果数据写到了错误的位置。记住:切换寻址模式后,一定要重新设置页地址和列地址,否则驱动芯片会沿用之前的地址指针,导致数据错位。
好了,GDDRAM的结构和寻址模式就讲到这里。你只要记住:页决定哪8行,列决定哪一列,寻址模式决定地址怎么走。把这三点理清楚,显存操作这块就稳了。