4、像素操作与画点:设置单个像素点、区域像素填充、反色与滚动效果
好,咱们进入第四讲。这一章可以说是整个OLED驱动里最基础、也最核心的部分。你想想看,屏幕上显示的任何东西——文字、图标、波形、曲线——归根结底,都是由一个个像素点组成的。说白了,你学会了怎么控制一个像素点,就等于掌握了整个屏幕的「生杀大权」。
我个人习惯把像素操作比作「在网格纸上涂格子」。OLED屏幕就是一张密密麻麻的网格纸,每个格子要么亮(白色),要么不亮(黑色)。我们的任务,就是精确地告诉屏幕:哪个格子该亮,哪个不该亮。
4.1 像素点的坐标体系
先搞清楚坐标。我用的这块0.96寸OLED,分辨率是128×64。什么意思?横向128个像素点,纵向64个像素点。
坐标原点(0,0)在左上角。X轴向右增加,Y轴向下增加。这一点和很多图形库不一样,但OLED驱动芯片(比如SSD1306)就是这么定义的。我第一次用的时候也犯过嘀咕,后来习惯了反而觉得顺手。
| 方向 | 范围 | 说明 |
|---|---|---|
| X轴(列) | 0 ~ 127 | 从左到右 |
| Y轴(行) | 0 ~ 63 | 从上到下 |
4.2 设置单个像素点
这是最基础的操作。SSD1306内部把屏幕分成了8页(Page),每页8个像素点。为什么这么设计?因为它的数据总线是8位的,一次可以操作8个像素点。
画一个像素点的流程是这样的:
- 计算这个像素点属于哪一页(Page = Y / 8)
- 计算在这一页内的偏移位置(Bit = Y % 8)
- 读取当前页的整字节数据
- 修改对应的位(置1或清0)
- 把修改后的字节写回去
听起来有点绕?看代码就清楚了:
// 设置单个像素点
// x: 0-127, y: 0-63, color: 1=亮, 0=灭
void OLED_DrawPixel(uint8_t x, uint8_t y, uint8_t color) {
if (x >= 128 || y >= 64) return; // 超出范围,直接返回
uint8_t page = y / 8; // 计算页号
uint8_t bit = y % 8; // 计算位偏移
uint8_t byte_val;
// 读取当前页的字节
byte_val = OLED_Buffer[page][x];
if (color) {
byte_val |= (1 << bit); // 置1:点亮像素
} else {
byte_val &= ~(1 << bit); // 清0:熄灭像素
}
OLED_Buffer[page][x] = byte_val;
}
这里有个关键点:我用了缓冲区(OLED_Buffer)。为什么不直接写屏幕?因为SSD1306的读写速度有限,频繁操作屏幕会拖慢系统。我的做法是先在内存里画好,然后一次性刷新到屏幕。这个技巧在嵌入式开发里很常用,叫「双缓冲」。
4.3 区域像素填充
单个像素点画起来太慢了。实际项目中,我们经常需要填充一个矩形区域。比如血压计里的状态栏背景、数值显示框等。
区域填充的思路很简单:循环遍历指定范围内的所有像素点,逐个设置颜色。但要注意效率。
// 填充矩形区域
// x1,y1: 左上角坐标, x2,y2: 右下角坐标
void OLED_FillRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color) {
uint8_t x, y;
// 边界检查
if (x1 > x2) swap(&x1, &x2);
if (y1 > y2) swap(&y1, &y2);
for (y = y1; y <= y2; y++) {
for (x = x1; x <= x2; x++) {
OLED_DrawPixel(x, y, color);
}
}
}
嗯,这里要注意。上面的代码虽然能用,但效率不高。为什么?因为每个像素点都要做一次页计算和位操作。我优化过一版,按页来填充,一次操作8个像素点,速度能快8倍。
4.4 反色效果
反色,就是把屏幕上的颜色反转。白的变黑,黑的变白。这个效果在血压计里很有用——比如高亮显示当前测量的数值,或者做闪烁提示。
实现反色有两种方式:
- 全局反色: 直接操作SSD1306的寄存器,整个屏幕反转。速度快,但不够灵活。
- 局部反色: 对缓冲区中的指定区域进行取反操作。灵活,但需要自己实现。
我一般用局部反色,因为可以精确控制哪些区域反色:
// 局部反色:对指定矩形区域取反
void OLED_InvertRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
uint8_t x, y;
for (y = y1; y <= y2; y++) {
for (x = x1; x <= x2; x++) {
uint8_t page = y / 8;
uint8_t bit = y % 8;
// 取反操作:异或1
OLED_Buffer[page][x] ^= (1 << bit);
}
}
}
看到那个 ^= 了吗?异或操作是反色的精髓。1变0,0变1,干净利落。
4.5 滚动效果
滚动效果在血压计里很常见——比如测量结果太长,一屏显示不下,就需要上下滚动查看。
SSD1306内置了硬件滚动功能,但我个人不太喜欢用。为什么?因为硬件滚动的方向和速度是固定的,不够灵活。我更喜欢用软件滚动,想怎么滚就怎么滚。
软件滚动的核心思想:移动缓冲区中的数据。比如向上滚动一行,就是把第1行的数据复制到第0行,第2行复制到第1行,以此类推,最后一行清空。
// 向上滚动一行
void OLED_ScrollUp(void) {
uint8_t page, x;
// 从第1页开始,把每一页的数据复制到上一页
for (page = 1; page < 8; page++) {
for (x = 0; x < 128; x++) {
OLED_Buffer[page - 1][x] = OLED_Buffer[page][x];
}
}
// 最后一行清空
for (x = 0; x < 128; x++) {
OLED_Buffer[7][x] = 0x00;
}
// 刷新屏幕
OLED_Refresh();
}
你看,代码其实不复杂。但要注意一点:滚动后要立即刷新屏幕,否则用户看不到变化。
4.6 综合应用:血压计数值闪烁提示
最后,咱们把今天学的知识串起来,做一个血压计里常用的效果——数值闪烁提示。比如当测量值超过正常范围时,让数值闪烁,提醒用户注意。
// 数值闪烁效果
void OLED_FlashValue(uint8_t x, uint8_t y, uint16_t value, uint8_t flash_count) {
uint8_t i;
for (i = 0; i < flash_count; i++) {
// 显示数值(正常颜色)
OLED_ShowNum(x, y, value, 3);
OLED_Refresh();
delay_ms(300);
// 反色区域(让数值闪烁)
OLED_InvertRect(x, y, x + 24, y + 16);
OLED_Refresh();
delay_ms(300);
// 再反色回来
OLED_InvertRect(x, y, x + 24, y + 16);
}
}
这个例子里,我们用到了画点(显示数字本质就是画点)、区域填充(数字的背景)、反色(闪烁效果)。你看,基础操作组合起来,就能做出实用的功能。
好了,这一章的内容就到这里。像素操作是OLED驱动的基石,一定要亲手写代码试试。下一章,我们会讲如何用这些基础操作来绘制字符和汉字——这可是做血压计界面的关键技能。