OLED显示屏学习笔记

文章目录

  • 前言
  • 一、控制原理
    • [1.1 像素底层逻辑](#1.1 像素底层逻辑)
    • [1.2 驱动OLED屏幕](#1.2 驱动OLED屏幕)
  • 二、单片机控制OLED
    • [2.1 通信原理](#2.1 通信原理)
    • [2.2 代码实现](#2.2 代码实现)
      • [2.2.1 写命令](#2.2.1 写命令)
      • [2.2.2 OLED初始化代码](#2.2.2 OLED初始化代码)
      • [2.2.3 测试程序](#2.2.3 测试程序)
      • [2.2.4 画点函数](#2.2.4 画点函数)
  • 三、移植Keysking的代码显示字模
    • [3.1 代码移植](#3.1 代码移植)
    • [3.2 注意事项](#3.2 注意事项)
  • 总结

前言

单片机开发中我们经常会用到OLED显示屏,这篇文章对OLED的控制原理、如何与单片机通信,以及如何移植别人的代码,来实现自己想要的显示效果做出介绍,主要是为了复习查看,参考 B站Keysking 的 OLED教学视频 所写。


一、控制原理

1.1 像素底层逻辑

OLED,即有机发光二极管(Organic Light Emitting Diode)。我们以中景园电子的OLED为例进行说明。

OLED的分辨率是 128 × 64 ,也就是说屏幕上总共有 8192 个会发光的小点(像素)。如果你每次只控制一个点,不仅写代码累死,屏幕刷新也会非常慢,我们的单片机也没有这么多的控制引脚!

因此,就需要用到屏幕驱动芯片,下面以SSD1306屏幕驱动芯片 为例,进行说明。

为了高效地控制这8192个像素点,SSD1306芯片在硬件设计和软件驱动上,采用了一种 "降维打击"和"分层抽象" 的策略。

1.2 驱动OLED屏幕

SSD1306芯片并不支持你"随心所欲地控制任意一个单独的点"。

在最常用的工作模式下,它是按 "页(Page)" 来管理的。

  1. 分块 : 屏幕宽128,高64。它把高度切成了 8个横条,每个横条称为一"页"(Page 0 到 Page 7)。
  2. 1个字节控制一列: 每一"页"的高度刚好是 8个像素。
  3. 关键点 : 当STM32通过I2C给OLED发送 1个字节(8位二进制数,比如 0x01,即 0000 0001) 时,OLED会在当前位置竖着点亮对应的8个像素。

硬件层面上,你每次操作的最小单位不是1个点,而是竖排的8个点(1个字节)

设置完一字节的8个像素后,列地址会自动+1.

二、单片机控制OLED

2.1 通信原理

SSD1306 是一个非常强大的多面手,它支持多达 5种 不同的通信接口,具体用哪种接口,是由芯片上的三个硬件引脚(BS0, BS1, BS2)的电平(接高电平还是低电平)来决定的。

我买的模块已经固定死了,也就是大家常见的 4针脚 I2C 总线通信的那款,需要两根线:SCL(时钟线,控制节奏) 和 SDA(数据线,传输内容)。

如上图所示,I2C通信的流程和它的数据帧格式是相对应的由起始位,寻址位,数据传输和停止位构成。

(1)第一句先喊"名字"(设备地址) :OLED模块通常有一个固定的I2C地址 ,大部分是 0x78(也有部分是0x7A,看模块背面的电阻怎么焊的)。每次通信,STM32必须先发 0x78,OLED听到叫自己,才会搭理你。

(2)第二句说明"意图"(控制字) :OLED芯片有两个截然不同的"大脑":一个是设置寄存器 (管亮度、对比度、在哪一页画图),另一个是显存GRAM(管哪个灯亮)。

  • 如果STM32接着发 0x00 ,OLED就知道:"哦,接下来你要发的是命令(Command),比如让我清屏或者调节亮度。"
  • 如果STM32接着发 0x40 ,OLED就知道:"哦,接下来你要发的是数据(Data),让我按顺序点亮屏幕上的灯。"

(3)第三句发送"内容"(具体的字节) :根据第二步的意图,发送具体的命令代码或像素数据。

2.2 代码实现

用HAL库实现,采用CubeMX + Keil 的方式,这里选择硬件I2C,使用单片机的I2C1外设,对引脚进行初始化。

在现代STM32开发中,我们不需要自己手动去拉高拉低引脚电平来模拟I2C时序,ST官方提供的 HAL库 中的 HAL_I2C_Master_Transmit 函数已经帮我们搞定了最底层的时序。

2.2.1 写命令

我们只需要一层一层往上写代码,先写一个函数用于向OLED发送指令

c 复制代码
// OLED器件地址
#define OLED_ADDRESS 0x78

/**
 * @brief 向OLED发送指令
 */
void OLED_SendCmd(uint8_t cmd){
	uint8_t sendBuffer[2];
	sendBuffer[0] = 0x00;
	sendBuffer[1] = cmd;
	HAL_I2C_Master_Transmit(&hi2c1,OLED_ADDRESS,sendBuffer,2,HAL_MAX_DELAY);
}

2.2.2 OLED初始化代码

SSD1306刚上电时是懵的,你必须发一长串特定的命令给它,配置它的时钟、电荷泵、扫描方向等。这串代码在所有的OLED驱动里都是固定的,直接抄就行(这就是查数据手册得来的)

参考数据手册中的命令,对OLED进行初始化操作

c 复制代码
// ========================== OLED驱动函数 ==========================

/**
 * @brief 初始化OLED (SSD1306)
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他驱动芯片时应根据实际情况修改此函数
 */
void OLED_Init(void){
	OLED_SendCmd(0xAE); /*关闭显示 display off*/

  OLED_SendCmd(0x20);
  OLED_SendCmd(0x10);

  OLED_SendCmd(0xB0);

  OLED_SendCmd(0xC8);

  OLED_SendCmd(0x00);
  OLED_SendCmd(0x10);

  OLED_SendCmd(0x40);

  OLED_SendCmd(0x81);

  OLED_SendCmd(0xDF);
  OLED_SendCmd(0xA1);

  OLED_SendCmd(0xA6);
  OLED_SendCmd(0xA8);

  OLED_SendCmd(0x3F);

  OLED_SendCmd(0xA4);

  OLED_SendCmd(0xD3);
  OLED_SendCmd(0x00);

  OLED_SendCmd(0xD5);
  OLED_SendCmd(0xF0);

  OLED_SendCmd(0xD9);
  OLED_SendCmd(0x22);

  OLED_SendCmd(0xDA);
  OLED_SendCmd(0x12);

  OLED_SendCmd(0xDB);
  OLED_SendCmd(0x20);

  OLED_SendCmd(0x8D);
  OLED_SendCmd(0x14);

  OLED_SendCmd(0xAF); /*开启显示 display ON*/
}

2.2.3 测试程序

我们对OLED进行了初始化配置,那么接下来写一段测试程序,测试它的显示效果。

这里我们从第0页,第0列开始绘图,观察实验现象,之前说过驱动芯片设置完一字节的像素后,列地址会自动+1。

这里,我们将要发送的数据,填入一个数组,方便滚屏操作。

类似这样,将页地址递增8次,就可以完成整个屏幕的设置。

c 复制代码
void OLED_Test(void){
	OLED_SendCmd(0xB0);//页地址,第0页
	OLED_SendCmd(0x00);//列地址(0x00),第0列的低四位
	OLED_SendCmd(0x10);//第0列高四位
	
	uint8_t sendBuffer[] = {0x40,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA};//
	HAL_I2C_Master_Transmit(&hi2c1,OLED_ADDRESS,sendBuffer,sizeof(sendBuffer),HAL_MAX_DELAY);
}

在main函数中,调用初始化函数,然后在 while 中调用 OLED_Test() 函数,你将看到下面左图的显示效果。

得到这样一个花的屏幕,是因为刚启动的SSD1306中的数据是随机的,这样数据为1亮,数据为0灭,就是这样的效果。

而我们实际想要的是右图的效果,因此,我们需要对OLED显示屏清屏。

具体做法是在单片机内存里建一个数组,模拟屏幕,将128*64个像素全部设置为0;然后把内存中的数组一次性"倒"入OLED芯片里.

OLED_NewFrame 函数实现清屏操作,OLED_ShowFrame的作用是将显存显示到屏幕。

c 复制代码
// OLED参数
#define OLED_PAGE 8            // OLED页数
#define OLED_ROW 8 * OLED_PAGE // OLED行数
#define OLED_COLUMN 128        // OLED列数

// 显存
uint8_t OLED_GRAM[OLED_PAGE][OLED_COLUMN];

/**
 * @brief 清空显存 绘制新的一帧
 */
void OLED_NewFrame(void){
	for(int i=0;i<8;i++){
		for(int j=0;i<128;j++){
			OLED_GRAM[i][j] = 0;
		}
	}
}

/**
 * @brief 将当前显存显示到屏幕上
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他驱动芯片时应根据实际情况修改此函数
 */
void OLED_ShowFrame(void){
	static uint8_t sendBuffer[129];
  sendBuffer[0] = 0x40;
	for(uint8_t i=0;i<8;i++){
		for(uint8_t j=0;j<128;j++){
				sendBuffer[j+1] = OLED_GRAM[i][j];
		}
		OLED_SendCmd(0xB0+i);//设置页地址
		OLED_SendCmd(0x00);//设置列地址低4位
		OLED_SendCmd(0x10);//设置列地址高四位
		HAL_I2C_Master_Transmit(&hi2c1,OLED_ADDRESS,sendBuffer,sizeof(sendBuffer),HAL_MAX_DELAY);
	}
}

2.2.4 画点函数

我们想要绘制一个像素点该怎么处理,下面编写画点函数。

把OLED想象成一块画布,假设你要在坐标 (x, y) 画一个点:

  1. 算它在哪一页(Y坐标除以8):page = y / 8;
  2. 算它在这一页的第几位(Y坐标对8取余):bit = y % 8;
  3. 修改STM32里的数组(把这一位置1,其他位不动):OLED_GRAM[page][x] |= (1 << bit);

所有的字符、汉字显示,底层都在调用类似这样的改写内存的逻辑。

c 复制代码
/**
 * @brief 设置一个像素点
 * @param x 横坐标
 * @param y 纵坐标
 * @param color 颜色
 */
void OLED_SetPixel(uint8_t x,uint8_t y){
	//判断X或Y是否越界
	if(x>=128 || y>=64) return;
	
	OLED_GRAM[y/8][x] |= 0x01 << (y % 8);
}

刷新屏幕 :当你把想画的点、想写的字都在STM32的 OLED_GRAM 数组里修改好之后,调用一个 OLED_ShowFrame() 函数。这个函数会以极快的速度,把这1024个字节一次性打包扔给OLED屏幕。屏幕瞬间显示出你画的内容!

至此,OLED的基础配置完成。

当有一天,想换一个SPI接口的屏幕,或者想把屏幕移植到另一款单片机(比如ESP32、51单片机)上时,你只需要修改最底层的 OLED_SendCmd 函数。只要搞定了发命令和发数据的方式,上面几层的画点、画字、刷新代码,完全不用改,直接照搬! 这就是理解底层驱动逻辑的最大意义。

三、移植Keysking的代码显示字模

3.1 代码移植

B站 UP Keysking 做了详细的OLED显示屏教学,并且在他的开源网站 波特律动文档站 给了丰富的驱动文件,因此我们就无需自己再去实现字模和图像的函数了,直接从网站下载移植即可。

并且他制作的取模软件,不仅可以取字模,还可以实现图片的效果!

3.2 注意事项

移植后的代码,一般包含4个文件,OLED.c 和 OLED.h 是OLED显示屏的驱动文件;而 font.c 和 font.h 是字体库,我们取模后要将字模或者图模存放在这里面,如果是图片则需要在front.h文件中进行声明

看看自己的驱动芯片通信方式和引脚是什么,然后修改引脚和通信方式。

如果使用Keil,编码方式要改为UTF-8,否则无法显示中文

图片显示效果如下


总结

以上为所有内容,有了这个驱动文件之后,我们的OLED可以有更加丰富的显示效果!

相关推荐
智者知已应修善业2 小时前
【51单片机1,左边4个LED灯先闪烁2次后,右边4个LED灯再闪烁2次:2,接着所用灯一起闪烁3次,接着重复步骤1,如此循环。】2023-5-19
c++·经验分享·笔记·算法·51单片机
zhangrelay2 小时前
蓝桥云课一分钟-绚丽贪吃蛇-后续-cmake
笔记·学习
承渊政道2 小时前
【优选算法】(实战攻坚BFS之FloodFill、最短路径问题、多源BFS以及解决拓扑排序)
数据结构·c++·笔记·学习·算法·leetcode·宽度优先
_李小白2 小时前
【OSG学习笔记】Day 39: NodeCallback(帧回调机制)
java·笔记·学习
Z文的博客3 小时前
嵌入式 ARM 设备交叉编译 mosquitto 2.0.20 (完整 TLS 支持) 详细教程 TRAE全程辅助,没敲一行代码
qt·mqtt·嵌入式·ai编程·mosquitto·嵌入式linux·trae
小陈phd3 小时前
CCPD数据集全解析:中文车牌识别的“双黄金标准“
笔记·学习·生成对抗网络
吃着火锅x唱着歌3 小时前
深度探索C++对象模型 学习笔记 第三章 Data语意学(2)
c++·笔记·学习
_李小白3 小时前
【OSG学习笔记】Day 35: Material(材质)
笔记·学习·材质
ZhiqianXia3 小时前
Pytorch 学习笔记(21) : PyTorch Profiler
pytorch·笔记·学习