STM32F103C8T6 驱动 0.96 寸 OLED (SSD1306, I2C 接口) 教程

本教程详细介绍如何使用 STM32F103C8T6 微控制器(常见为 “蓝色飞线板” Blue Pill)通过 I2C 接口驱动 0.96 寸 OLED 显示屏(SSD1306 控制器),使用 STM32 HAL 库进行开发。内容从基础知识开始,一步步带领初学者完成 OLED 显示从初始化到显示文字、图形和图片的完整过程。

1. 基础知识

OLED 显示屏工作原理

OLED(Organic Light-Emitting Diode,有机发光二极管)显示屏通过有机材料在通电时自发光来显示图像。每个像素点本身就是一个微型发光二极管,因此无需背光,具有高对比度和宽视角等优点。0.96 寸 OLED 通常为单色显示(如白色、蓝色等),分辨率常见为 128×64 像素。这意味着屏幕上共有 128×64 = 8192 个像素点,每个像素可以单独点亮或熄灭。由于像素数量多,OLED 模块内部集成了专门的驱动控制器来管理这些像素的显示数据。

当需要显示图像时,主控(比如 STM32)将像素数据发送给 OLED 的驱动芯片,驱动芯片将数据存入显示内存,然后控制 OLED 面板上对应的位置发光或熄灭,从而形成图像或文字。因为OLED像素自发光,所以对比度极高,可以在黑暗环境下清晰显示,同时功耗相对低(点亮像素才耗电)。

SSD1306 控制器介绍

SSD1306 是一款常用的单色OLED显示屏驱动控制器。许多 0.96 寸 128×64 OLED 模块都使用 SSD1306 芯片。该芯片内置了显示所需的RAM(Graphic Display Data RAM, GDDRAM),大小为 128×64位(即 8192 位,大约 1KB),用于存储要显示的图案数据 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客) (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)。主控可以通过接口(I2C 或 SPI)读写这个显存,从而控制OLED上每个像素的明暗。SSD1306 支持三种地址模式:页地址模式水平地址模式垂直地址模式,可以灵活地控制数据在显存中的写入方式。但对于128×64的整屏更新,通常使用页模式或水平模式。页模式下显存被分成8个“页”(page,每页8行像素),每页有128列;水平模式下显存按行连续。 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)

SSD1306 支持 I2CSPI 等通信方式。本教程的OLED模块使用 I2C 接口(4针:VCC、GND、SCL、SDA)。SSD1306 芯片本身有一个 I2C 从机地址,通常根据模块的连线默认地址为 0x3C0x3D(7位地址)。在7位地址为0x3C的情况下,对应的8位读写地址为0x78/0x79 (How to interface OLED with STM32? - OLED/LCD Supplier) (How to interface OLED with STM32? - OLED/LCD Supplier)(0x78 用于写,0x79 用于读)。大部分OLED模块将地址脚(俗称 SA0 或 D/C# 引脚)接地,使地址为0x3C。本教程假定OLED地址为0x3C(写操作地址0x78)。需要注意的是,如果你的OLED模块地址脚接高电平,则地址将变为0x3D(写地址0x7A)。

SSD1306 没有独立的命令引脚,但通过 I2C 总线发送数据时可以使用一个控制字节(control byte)来区分命令数据。根据SSD1306数据手册:当控制字节的 D/C# 位为0时,表示后续字节为命令;为1时表示后续字节是显示数据。而控制字节的 Co 位用于指示是否还有后续控制字节,一般我们将 Co 设为0即可表示单次发送。 (How to interface OLED with STM32? - OLED/LCD Supplier) (How to interface OLED with STM32? - OLED/LCD Supplier)因此,通过I2C发送字节时:

I2C 通信协议基础

I2C(Inter-Integrated Circuit)是一种常用的串行通信总线,是双线半双工同步通信协议。有一条数据线 SDA 和一条时钟线 SCL,通过主从架构传输数据。STM32F103 作为主机(master),OLED 模块上的 SSD1306 控制器作为从机(slave)。I2C 通信基本要点:

  • 寻址:主机通过发送从设备地址来选择通信对象。地址通常为7位(还有1位表示读/写操作)。对于我们的OLED,从机地址7位是0x3C,发送时组合读写位形成8位地址0x78(写)或0x79(读)。
  • 起始与停止:当总线空闲时,SCL和SDA均为高电平。主机产生起始条件(Start)时,会在 SCL 高电平时拉低 SDA (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)。传输结束时,主机产生停止条件(Stop),即在 SCL高电平时拉高SDA (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)。
  • 数据有效性:在SCL为高期间,SDA上的电平若保持稳定即表示一个有效的比特位 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)。逻辑1通常表示 SDA高电平,逻辑0表示 SDA低电平(在SCL为高时采样判定)。
  • 读写:紧跟在起始之后,主机发送7位地址+1位读/写位。读位(1)表示主机想从从机读数据,写位(0)表示主机想写数据到从机 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)。发送完地址+读/写后,从设备需要回应一个 ACK(应答)位(拉低 SDA 表示应答)。如果没有应答,表示通信失败或无设备响应。
  • 数据传输:地址后就是数据传输阶段。在写操作中,主机持续发送数据字节,每字节后从机返回ACK。 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)在读操作中,由从机发送数据,主机在每字节后发送ACK表示继续读取,或发送NACK表示最后一个字节读取完成。
  • 控制字节与寄存器地址:某些I2C设备有寄存器地址概念,主机在地址后可能需要发送寄存器地址再发送数据。但对于SSD1306,使用控制字节区分命令/数据,可以把它理解为一种“寄存器地址”。HAL库提供的 HAL_I2C_Mem_Write 接口正是利用这一点,将控制字节作为“存储器地址”发送,然后再发送数据。

总的来说,与SSD1306通信的流程通常是:Start -> 设备地址(0x78) -> 控制字节(0x00或0x40) -> 数据 ... -> Stop。我们会利用HAL库简化这个过程,用户只需调用相应的函数,不必手动控制总线时序。

2. 硬件连接

OLED 模块与 STM32F103C8T6 的连线方式

0.96寸 OLED 模块(SSD1306驱动)通常有 4 根引脚,分别标注为:GND、VCC、SCL、SDA。如下面图片所示,它们的功能分别是:电源地(GND)、电源正极(VCC)、时钟线(SCL)、数据线(SDA)。 (SSD1306 OLED with STM32 Blue Pill using STM32CubeIDE)其中VCC工作电压范围通常为 3.3V~5V (SSD1306 OLED with STM32 Blue Pill using STM32CubeIDE)。使用 STM32F103 时推荐直接接 3.3V(与STM32逻辑电平匹配)。SCL和SDA将用于I2C通信,需要连接到STM32的 I2C 引脚。

(SSD1306 OLED with STM32 Blue Pill using STM32CubeIDE)上图显示了 OLED 模块与 STM32F103C8T6(蓝色开发板)的连接示意。其中:

上述连接中,PB6/PB7 对应 STM32 的硬件 I2C1 通道。这与我们稍后在 CubeMX 中配置I2C1接口相对应。请确保所有地线共地,即OLED模块的GND和STM32的GND相连,否则通信可能不稳定甚至无法工作。

硬件电路分析:I2C 总线是集电极开路输出,需要上拉电阻将线路拉至高电平。很多 OLED 模块板上已经内置了适当阻值(如4.7kΩ)的上拉电阻到VCC,如果模块没有内置上拉,则需要在SCL和SDA线上加上拉。STM32F103的硬件I2C引脚本身不提供内部上拉(即便可以配置,一般也使用外部上拉保证总线电气性能),因此务必确认线路有上拉电阻。在实际电路中,如果SCL或SDA线上没有上拉电阻,会导致总线始终读为低电平或通信失败。另外,由于OLED模块工作电压3.3~5V都可,用3.3V供电时I2C信号也是3.3V电平,完全兼容STM32F103的3.3V IO,不需要电平转换器。如果使用5V供电OLED模块,也通常接受3.3V的逻辑电平输入(因为很多模块的控制引脚通过FET或电阻兼容3.3V),但最好查阅具体模块资料以确保。

3. 开发环境配置

STM32CubeIDE 项目创建

使用 STM32CubeIDE 可以方便地创建 STM32F103C8T6 工程并自动生成 HAL 库初始化代码。基本步骤:

  1. 创建新项目:打开 STM32CubeIDE,选择 File -> New -> STM32 Project。在器件选型页面中,可以直接搜索 STM32F103C8T6 并选择它对应的 LQFP48 封装芯片(如果有 Blue Pill 开发板选项也可直接选板子)。然后点击 “Next”,填写项目名称,比如 “OLED_SSD1306_Demo”,选择 “STM32Cube”初始化方式,完成项目创建。
  2. CubeMX 引脚配置:新项目打开后,会出现 STM32CubeMX 配置界面。在Pinout & Configuration视图中展开左侧的 Connectivity 菜单,找到 I2C1 并启用它(Mode 选 I2C)。CubeMX 会自动将 I2C1 的引脚分配为 PB6(SCL) 和 PB7(SDA)。确认这两个引脚被标记为绿色的 I2C功能引脚 (SSD1306 OLED with STM32 Blue Pill using STM32CubeIDE)。如果需要使用其他 I2C 通道或引脚,也可以在图形界面中选择不同引脚,但本教程以默认的 I2C1 PB6/PB7 为例。
  3. I2C 参数设置:点击 I2C1 外设,在下方 Parameter Settings 中设置 I2C 的速度模式。将 “I2C Speed Mode” 改为 Fast Mode,并将时钟频率设置为 400kHz (SSD1306 OLED with STM32 Blue Pill using STM32CubeIDE)。Fast Mode (400kHz) 比标准100kHz更快,能提高屏幕刷新的速度,减少显示延迟。其他I2C参数(如地址模式7-bit、关闭时钟拉伸等)可以保持默认。
  4. 时钟配置:由于I2C时序受主频影响,我们确保 STM32 工作在最高频率 72MHz。切换到上方的 Clock Configuration 选项卡。在这里将 HCLK 设置为 72MHz(如果使用外部8MHz晶振,则 PLL倍频9得到72MHz)。如果你的 Blue Pill 板有8MHz外部晶振,在 RCC 配置中将 High Speed Clock (HSE) 源设置为 “Crystal/Ceramic Resonator” (SSD1306 OLED with STM32 Blue Pill using STM32CubeIDE)。CubeMX 会自动计算 PLL 参数使系统时钟 72MHz。如果没有外部晶振,也可以使用内部8MHz RC振荡器并配置PLL至72MHz。确认 I2C1 所在的 APB1 总线时钟为 36MHz(默认APB1预分频2),这样I2C模块才能正确产生400kHz时序。
  5. 生成代码:配置完成后,按 Ctrl+S 保存,会提示生成代码。点击 “Yes” 生成初始化代码并切换到开发视图。CubeIDE 会生成包含 HAL 库初始化的 C 源文件,如 main.cstm32f1xx_hal_msp.ci2c.c 等。其中 i2c.c 中的 MX_I2C1_Init() 函数已经根据我们设置的参数初始化好了 I2C1 外设,包括时钟频率等。我们在编写驱动时会用到 hi2c1 这个 I2C 句柄。

CubeMX 生成 I2C 初始化代码

CubeMX 自动生成的 I2C 初始化代码大致如下(位于 i2c.c):

I2C_HandleTypeDef hi2c1;

void MX_I2C1_Init(void) {
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 400000;              // 时钟速度 400kHz
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;      // 快速模式占空比
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    HAL_I2C_Init(&hi2c1);
}

可以看到,I2C1被配置为7位地址模式,时钟400kHz等。如果你的CubeMX版本有所不同,代码格式可能稍有差异,但核心参数需要与上述类似。注意:HAL 库的 I2C 初始化会配置GPIO为开漏模式,并在 HAL_I2C_Init 中使能I2C外设时钟等。确保在 main() 函数中正确调用 MX_I2C1_Init();HAL_Init();SystemClock_Config(); 等初始化代码。

完成以上工程配置后,我们就可以开始编写 SSD1306 OLED 的驱动代码了。

4. 驱动编写

下面我们将编写控制 SSD1306 OLED 的驱动程序,包括 I2C 通信读写操作封装、屏幕初始化以及清屏和填充功能。为了结构清晰,我们可以新建一个专门的源文件和头文件(例如 ssd1306.cssd1306.h)来存放OLED驱动代码。在其中我们会用到 CubeMX 生成的 hi2c1 句柄来进行 I2C 通信。

I2C 读写 SSD1306 寄存器的方法

由于 SSD1306 通过 I2C 接口接收命令和数据,我们先封装两个基础函数:发送命令发送数据到 OLED。利用 HAL 库的 HAL_I2C_Mem_Write 可以轻松地发送控制字节+数据。其函数原型:

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, 
                                    uint16_t DevAddress, 
                                    uint16_t MemAddress, 
                                    uint16_t MemAddSize, 
                                    uint8_t *pData, 
                                    uint16_t Size, uint32_t Timeout);
  • DevAddress 是从设备地址(注意:HAL 要求是左移1位后的8位地址,对于0x3C的7位地址,这里填0x78)。
  • MemAddress 是要写入的从设备内部寄存器地址,我们将其用于发送 控制字节 (0x00 或 0x40)。
  • MemAddSize 设为 I2C_MEMADD_SIZE_8BIT,表示寄存器地址大小为1字节。
  • pDataSize 则是实际要发送的数据缓冲区和长度。

基于此,我们实现两个辅助函数:一个发送单字节命令,一个发送单字节数据:

#include "i2c.h"    // 包含hi2c1的声明
#define OLED_I2C_ADDR    0x78    // OLED I2C写地址 (0x3C<<1)

void OLED_WriteCommand(uint8_t cmd) {
    // 发送一个命令字节 (控制字节=0x00)
    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x00, 
                      I2C_MEMADD_SIZE_8BIT, &cmd, 1, HAL_MAX_DELAY);
}

void OLED_WriteData(uint8_t data) {
    // 发送一个数据字节 (控制字节=0x40)
    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, 
                      I2C_MEMADD_SIZE_8BIT, &data, 1, HAL_MAX_DELAY);
}

上述 OLED_WriteCommand 会先发送设备地址0x78,然后发送控制字节0x00,接着发送 cmd 数据。例如,如果 cmd = 0xAE(关闭显示命令),总线上序列为:[0x78] [0x00] [0xAE]OLED_WriteData 类似,只是控制字节为0x40。我们以后也可以扩展这两个函数以发送多个字节的数据,比如连续显示数据。本教程后续代码中,我们也可能直接使用 HAL_I2C_Mem_Write 来一次发送多字节数据。

初始化 SSD1306 显示屏

OLED 屏上电后默认为关闭状态,我们需要按照SSD1306的数据手册发送一系列初始化命令来配置显示参数并点亮屏幕。典型的 SSD1306 初始化步骤如下:

  1. 关闭显示:发送命令 0xAE,让OLED进入休眠(关闭面板输出,防止初始化过程中出现杂乱像素)。
  2. 设置时钟分频和振荡频率:命令对 0xD5 后跟参数 0x800x80是默认设置,快速模式下可根据需要调整。
  3. 设置多路复用比:命令对 0xA8 后跟参数 0x3F。对于128×64 OLED,高度64像素,对应MUX比为0x3F(即63,表示驱动0-63行)。
  4. 设置显示偏移:命令对 0xD3 后跟参数 0x00。将显示起始行偏移设为0。
  5. 设置显示开始行:命令 0x40,将起始行号设置为0(0x40 | 0)。
  6. 启用电荷泵:命令对 0x8D 后跟 0x14。开启内部电荷泵以提供OLED所需电压(0x14表示在显示开启时启用)。如果使用外部供电OLED则此步骤不同,一般0x10关闭电荷泵。
  7. 设置内存地址模式:命令对 0x20 后跟 0x02。选择 页地址模式(Page Addressing Mode)。 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)页地址模式便于逐页更新显示。也可以选0x00(horizontal)模式一次性写满屏数据,这里选页模式便于理解控制。
  8. 列地址重映射:命令 0xA1,将列地址0映射到SEG127,引脚翻转左右镜像。大多数模块需要设置这个保证显示内容左右正确,否则可能水平翻转。
  9. 行扫描方向翻转:命令 0xC8,将行扫描从COM[N-1]到COM0(N为行数)翻转,从而上下颠倒扫描。与硬件连接有关,多数模块需要这样设置以使图像正向显示。
  10. 设置 COM 引脚硬件配置:命令对 0xDA 后跟 0x120x12适用于64行的OLED屏 (将COM配置为替代模式,开启COM左/right remap)。对于32行高的OLED,这个值应为0x02。
  11. 设置对比度:命令对 0x81 后跟 0x7F。0x7F是中等亮度,范围0x00~0xFF。可以根据需要设亮一些(0xCF)或暗一些。
  12. 设置预充电周期:命令对 0xD9 后跟 0xF1。在使用内部电荷泵时,0xF1是推荐值;若外部供电典型值为0x22。
  13. 设置 VCOMH 除数:命令对 0xDB 后跟 0x40。将 VCOMH 电平设置为0.77×Vcc,默认即可。
  14. 整个显示开启/关闭:命令 0xA4,取消整个显示开启(A5是Entire Display ON,会点亮所有像素,用于测试,A4则恢复根据RAM内容显示)。
  15. 设置正常/反相显示:命令 0xA6,选择正常显示模式(A7则是反相,像素bit=1显示灭,0显示亮)。
  16. 开启显示:命令 0xAF,点亮OLED面板,开始显示。

根据以上步骤,我们在代码中依次调用 OLED_WriteCommand()

void OLED_Init(void) {
    HAL_Delay(100);             // 上电延时,确保OLED电源稳定
    OLED_WriteCommand(0xAE);    // 1. 显示关闭
    OLED_WriteCommand(0xD5);    // 2. 设置显示时钟分频/振荡频率
    OLED_WriteCommand(0x80);    //    分频因子&振荡频率设置,0x80默认
    OLED_WriteCommand(0xA8);    // 3. 设置多路复用比
    OLED_WriteCommand(0x3F);    //    64行 (0x3F)
    OLED_WriteCommand(0xD3);    // 4. 设置显示偏移
    OLED_WriteCommand(0x00);    //    无偏移
    OLED_WriteCommand(0x40);    // 5. 设置显示开始行 (0)
    OLED_WriteCommand(0x8D);    // 6. 电荷泵设置
    OLED_WriteCommand(0x14);    //    开启电荷泵
    OLED_WriteCommand(0x20);    // 7. 内存地址模式
    OLED_WriteCommand(0x02);    //    页地址模式 (Page Mode)
    OLED_WriteCommand(0xA1);    // 8. 列地址重映射 (A0->A1 左右翻转)
    OLED_WriteCommand(0xC8);    // 9. 行扫描方向翻转 (上下翻转)
    OLED_WriteCommand(0xDA);    // 10. COM 引脚配置
    OLED_WriteCommand(0x12);    //     COM配置 (0x12 for 64行)
    OLED_WriteCommand(0x81);    // 11. 对比度设置
    OLED_WriteCommand(0x7F);    //     对比度值 (0x7F)
    OLED_WriteCommand(0xD9);    // 12. 预充电周期
    OLED_WriteCommand(0xF1);    //     设置为 0xF1
    OLED_WriteCommand(0xDB);    // 13. 设置 VCOMH 电平
    OLED_WriteCommand(0x40);    //     0x40 默认
    OLED_WriteCommand(0xA4);    // 14. 取消全显示,按照RAM内容显示
    OLED_WriteCommand(0xA6);    // 15. 正常显示 (非反相)
    OLED_WriteCommand(0xAF);    // 16. 开启显示
}

以上就是初始化所需的命令序列。调用 OLED_Init() 后,OLED屏应当被点亮(此时RAM内容不确定,可能会随机点亮一些像素)。接下来通常会清屏以熄灭残留像素。

提示:有些SSD1306模块的初始化命令略有不同,或者对比度等需要调整。上面的序列是常用的配置,能适用于大多数128×64 OLED。如果你的屏幕高度是32,则需要将多路复用比(0xA8)改为0x1F,COM引脚配置(0xDA)改为0x02等。

显存缓冲区、清屏与填充函数

为了方便地对OLED进行绘图和显示,我们通常维护一个与OLED显存对应的**帧缓冲(Framebuffer)**在STM32内存中。对于128×64的屏幕,显存大小为128×64/8 = 1024 字节。我们可以定义一个全局缓冲数组:

#define OLED_WIDTH 128
#define OLED_HEIGHT 64
static uint8_t OLED_Buffer[OLED_WIDTH * OLED_HEIGHT / 8];

这里每个字节对应OLED屏幕上的8个垂直像素(一页中的一列)。缓冲区的索引关系如下:假设像素坐标 (x, y),其中 x范围0127,y范围063。可以计算对应缓冲区索引:

  • 页号 page = y / 8 (取整除,每页8行像素)。
  • 在该页内的bit位 bit = y % 8 (y在该页中的第几行)。
  • 缓冲区索引 index = page * OLED_WIDTH + x
  • OLED_Buffer[index] 的某一位对应那个像素。例如将该字节的第 bit 位设1,则 (x,y) 像素亮,设0则灭。

有了软件缓冲区,我们的绘图操作都可以先在这个缓冲中完成,再统一刷新到OLED,减少闪烁和I2C通信次数。

清屏函数:将缓冲区全部清零,并更新到显示,使屏幕像素全灭。

void OLED_Clear(void) {
    memset(OLED_Buffer, 0x00, sizeof(OLED_Buffer));  // 缓冲设0
    // 刷新OLED显示
    for (uint8_t page = 0; page < 8; page++) {
        OLED_WriteCommand(0xB0 + page);  // 设置页地址(0xB0 = page0)
        OLED_WriteCommand(0x00);        // 设置列低4位为0
        OLED_WriteCommand(0x10);        // 设置列高4位为0 (列地址0)
        // 按当前页,将该页的缓冲区数据写出128字节
        HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT, 
                          &OLED_Buffer[page * OLED_WIDTH], OLED_WIDTH, HAL_MAX_DELAY);
    }
}

OLED_Clear() 利用一个循环逐页发送数据。每一页我们先发送3个命令:设置当前页地址,以及将列地址设为0(低4位和高4位命令分别是0x00和0x10)。然后通过 HAL_I2C_Mem_Write 发送整页的 128 字节显示数据(控制字节0x40自动加在前面)。这样8页数据依次发送完毕,就完成了全屏刷新。由于我们清屏时缓冲都为0x00,所以OLED上也全黑了。

填充屏幕函数:与清屏类似,可以填充亮点满屏。例如将缓冲区全置为0xFF并刷新,则屏幕所有像素点亮:

void OLED_Fill(uint8_t color) {
    // color=1 -> 全亮; color=0 -> 全黑
    uint8_t fill = (color ? 0xFF : 0x00);
    memset(OLED_Buffer, fill, sizeof(OLED_Buffer));
    // 刷新显示(与OLED_Clear类似)
    for (uint8_t page = 0; page < 8; page++) {
        OLED_WriteCommand(0xB0 + page);
        OLED_WriteCommand(0x00);
        OLED_WriteCommand(0x10);
        HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT,
                          &OLED_Buffer[page * OLED_WIDTH], OLED_WIDTH, HAL_MAX_DELAY);
    }
}

这样调用 OLED_Fill(1) 就能点亮全屏,OLED_Fill(0) 则清空全屏。实践中我们更常用的是清屏,将其用于初始化后的屏幕清空。

说明:上述清屏/填充函数中,我们每次刷新都逐页发送数据,采用的是页地址模式写入。如果在初始化时选择的是水平地址模式(0x20,0x00),也可以一次性连续发送1024字节刷新全屏,但代码略复杂一些。逐页更新虽然多发送了些命令,但逻辑清晰,适合初学者理解。如果追求极致性能,可考虑水平模式下一次性发送整屏数据甚至使用DMA,但初期不必纠结。

现在我们已经能够初始化OLED、清空和填充屏幕了。接下来实现文本和图形的绘制函数。

5. 字符显示

OLED 常用于显示字符和字符串。由于 SSD1306 是图形点阵屏,并不像字库液晶那样自带字模,我们需要在程序中存储字体点阵库。常见做法是预先准备好字模数据,例如ASCII字符的 5x7、6x8、8x16 等大小的点阵,并以数组形式存储。这里以常用的 6×8 和 8×16 英文字体为例说明。

字符库存储方案 (6×8、8×16 字体)

6×8 字体通常指每个字符占用 6 列、8 行点阵。其中宽度6包含了字符实际5列像素和1列间隔(或边框)空白。高度8正好在一页内(8像素高),适合在单页显示。我们可以设计这样一个字模数组:Font6x8[字符数][6],每个字符对应6个字节,每个字节的8位对应该字符的一列像素(从上到下8像素)。例如字符 'A' 的6字节可能表示从左到右每列的像素。ASCII字符从空格' ' (0x20)开始,我们可以令 Font6x8[0] 对应空格,Font6x8[1] 对应 '!',等等。这样字符的数组索引 = 字符ASCII码 - 0x20。

举例来说,假设字模中 'A' 7×8点阵(这里为说明简化),字模如下图形:

  ##  
 #  # 
 #  # 
 #### 
 #  # 
 #  # 
 #  # 
 (下面这一行空白)

将其列划分可以得到每列的8位二进制值,存入数组。例如 'A' 的字模数组可能定义为 {0x7E, 0x09, 0x09, 0x7E, 0x00, 0x00}(这里只是举例,并非实际值)。具体字模数据可通过字体生成工具或者手工定义。通常我们会准备好标准ASCII可见字符(0x20~0x7E,共95个)的数组。由于数据量较大,在此不一一列出,可在实际代码中包含字库头文件或自行生成。

8×16 字体指宽度约8像素、高度16像素的字符。高度16像素跨越OLED的两页(每页8像素),需要在垂直方向分成高8和低8位两部分来存储。常见做法是每个字符用16字节表示:前8字节对应字符上半部分每列点阵,下8字节对应下半部分。或者使用两个独立数组分别存储字符上半部分和下半部分字模。在显示时,需要在相邻的两页上叠加绘制同一个字符的上下部分。8×16字体适合在屏幕上显示大一点的字,在64像素高的屏幕上可以显示4行(16*4=64)。

本教程中我们以较简单的6×8字体演示,因为它易于实现且能显示较多字符。8×16字体实现原理类似,只是绘制函数需要处理两页。

显示字符串方法

要在OLED上显示字符,基本思路是:读取字符的点阵数据 -> 将对应像素写入显存缓冲 -> 最后刷新显示。我们可以实现以下函数:

  • OLED_DrawPixel(x, y, color) : 绘制单个像素到缓冲区。
  • OLED_DrawChar(x, y, char, font, color) : 在指定坐标绘制一个字符(利用某种字体点阵)。
  • OLED_ShowString(x, y, *str, font, color) : 从指定位置开始显示字符串。

其中 color 可以用1表示点亮(白色),0表示熄灭(黑色),以便支持反色显示等效果。

先实现画点函数:

void OLED_DrawPixel(uint8_t x, uint8_t y, uint8_t color) {
    if (x >= OLED_WIDTH || y >= OLED_HEIGHT) 
        return;  // 越界保护
    uint16_t index = (y / 8) * OLED_WIDTH + x;
    uint8_t bit = y % 8;
    if (color)  
        OLED_Buffer[index] |= (1 << bit);   // 置位
    else        
        OLED_Buffer[index] &= ~(1 << bit);  // 清位
}

OLED_DrawPixel 根据上面讲的映射计算出缓冲区索引,并设置相应位。这样我们可以单独控制缓冲里的每个像素位。注意这里只修改缓冲区,并不立即发送到屏幕,需等调用刷新函数才实际显示。

接着,实现绘制6×8字体字符的函数。假设我们有字体数组 Font6x8

extern const uint8_t Font6x8[][6];  // 声明外部字体表 (需自行定义并初始化)

void OLED_DrawChar(uint8_t x, uint8_t y, char chr, uint8_t color) {
    if (chr < 0x20 || chr > 0x7E) 
        chr = '?';  // 非可显示字符用 '?' 代替
    uint8_t index = chr - 0x20;
    // 每个字符宽6
    for (uint8_t col = 0; col < 6; col++) {
        uint8_t lineBits = Font6x8[index][col];
        // lineBits的每个位对应该列的像素,从字模取出
        for (uint8_t bit = 0; bit < 8; bit++) {
            uint8_t pixelColor = (lineBits >> bit) & 0x1;  // 取该位
            if (!color) pixelColor = !pixelColor;  // 如果color=0黑底白字,可取反
            OLED_DrawPixel(x + col, y + bit, pixelColor);
        }
    }
}

上述函数假设 y 是 8 的倍数(即从页开始的位置,如0, 8, 16,...),这样字符不会跨页,简化了处理。Font6x8[index][col] 返回字符的第col列的8个像素bit,最底下bit为第0行,顶部bit为第7行(具体取决于字模定义的位顺序,这里假设最低位对应字体上方像素)。然后我们逐位将对应位置的像素写入缓冲。我们加入了 if (!color) pixelColor = !pixelColor ,是为了支持 color=0 表示反色显示(黑底白字或白底黑字的切换)。如果只需要单一白字黑底,可以省略这个判定,直接使用字模位作为像素。

最后,实现显示字符串:

void OLED_ShowString(uint8_t x, uint8_t y, const char *str, uint8_t color) {
    while (*str) {
        OLED_DrawChar(x, y, *str, color);
        x += 6;  // 移动光标6列(字体宽度)
        if (x + 6 > OLED_WIDTH) {
            x = 0;        // 换行到头
            y += 8;       // 下移一页(8像素高)
        }
        if (y >= OLED_HEIGHT) {
            break;        // 超出屏幕则退出
        }
        str++;
    }
}

OLED_ShowString 会按照6像素宽递增 X 坐标。若一行快到边界,则换行到下一行(Y加8像素)。这个简单实现假定字符串不超过屏幕容纳范围。调用该函数后,同样需要调用刷新函数(如 OLED_UpdateScreen() 类似于我们前面的清屏函数中的刷新过程)才能将缓冲内容更新到OLED上。实际上,我们可以将刷新过程封装成 OLED_UpdateScreen() 函数,这里直接使用刚才清屏时写的逐页刷新的逻辑即可。

提示:字库数据建议存放在 Flash 常量区,例如使用 const uint8_t Font6x8[][6] PROGMEM = {...} 等(PROGMEM用于AVR,这里STM32直接const即可)。将字模放在程序存储器可以节省RAM。对于中文字符(16×16或更大)显示原理类似,但需要更大字库,可以根据需要拓展。

6. 图形绘制

除了文字,我们还可能需要在OLED上绘制基本图形,如点、线、矩形、圆形,甚至位图图片。利用我们已有的 OLED_DrawPixel 可以构建出更复杂的图形绘制函数。

画点、画线

画点已经由 OLED_DrawPixel 实现,可以直接使用。画线则需要在两个坐标间插入多个点。通用的画任意两点之间直线可以使用 Bresenham 算法,它能高效地计算一条近似直线的所有像素坐标。下面是 Bresenham 画线的一个实现:

void OLED_DrawLine(int x0, int y0, int x1, int y1, uint8_t color) {
    if (x0 == x1 && y0 == y1) {
        OLED_DrawPixel(x0, y0, color);
        return;
    }
    int dx = (x1 > x0 ? x1 - x0 : x0 - x1);
    int dy = (y1 > y0 ? y0 - y1 : y1 - y0);
    int sx = x0 < x1 ? 1 : -1;
    int sy = y0 < y1 ? 1 : -1;
    int err = dx + dy;  // 注意dy已取负
    while (1) {
        OLED_DrawPixel(x0, y0, color);
        if (x0 == x1 && y0 == y1) break;
        int e2 = 2 * err;
        if (e2 >= dy) { 
            err += dy; 
            x0 += sx; 
        }
        if (e2 <= dx) { 
            err += dx; 
            y0 += sy; 
        }
    }
}

这个函数能绘制任意倾斜的线段,包括水平和垂直线。对于水平或垂直的特殊情况,其实可以用更简单的方法(例如循环画点),但上面算法已经涵盖所有情况。调用如 OLED_DrawLine(0,0, 127,63, 1); 将画一条从屏幕左上角到右下角的对白线(需要刷新后可见)。

画矩形、画圆

矩形可以看作是绘制四条边的组合。如果要绘制空心矩形框,只需画上边、下边两条水平线和左边、右边两条垂直线;如果要填充矩形,可以在Y方向逐行绘制水平线填满区域。示例空心矩形代码:

void OLED_DrawRectangle(int x, int y, int width, int height, uint8_t color) {
    // 绘制矩形的四条边
    OLED_DrawLine(x, y, x + width - 1, y, color);               // 上边
    OLED_DrawLine(x, y + height - 1, x + width - 1, y + height - 1, color); // 下边
    OLED_DrawLine(x, y, x, y + height - 1, color);              // 左边
    OLED_DrawLine(x + width - 1, y, x + width - 1, y + height - 1, color); // 右边
}

需要注意坐标不要超出屏幕范围。填充矩形可以在上边和下边之间用一个嵌套循环调用 OLED_DrawPixel 或多次 OLED_DrawLine 实现,此处不再冗长展开。

圆形绘制可以使用 Bresenham 圆算法。画圆原理是利用圆的对称性,每计算出一个象限的像素,即可绘制出八个对称点。算法如下(绘制空心圆周):

void OLED_DrawCircle(int x0, int y0, int r, uint8_t color) {
    int a = 0;
    int b = r;
    int d = 1 - r;  // 判定参数
    while (a <= b) {
        // 八个对称点
        OLED_DrawPixel(x0 + a, y0 + b, color);
        OLED_DrawPixel(x0 - a, y0 + b, color);
        OLED_DrawPixel(x0 + a, y0 - b, color);
        OLED_DrawPixel(x0 - a, y0 - b, color);
        OLED_DrawPixel(x0 + b, y0 + a, color);
        OLED_DrawPixel(x0 - b, y0 + a, color);
        OLED_DrawPixel(x0 + b, y0 - a, color);
        OLED_DrawPixel(x0 - b, y0 - a, color);
        a++;
        if (d < 0) {
            d += 2 * a + 1;
        } else {
            b--;
            d += 2 * (a - b) + 1;
        }
    }
}

该算法从 (0, r) 开始逐步向圆周中间计算,当 a <= b 时绘制八个对称点。当 a > b 时完成。调用例如 OLED_DrawCircle(64, 32, 10, 1); 会以 (64,32) 为圆心绘制半径10的圆。若要实心填充圆,可以在每个y上绘制水平线连接左右两边的像素。

显示 BMP 图片方法

OLED 128×64 常用于显示简单的图标或图片。由于是单色显示,我们需要将图像转换为单色位图数据数组,然后通过程序将其发送给OLED。步骤如下:

  1. 图片转换:准备好所需显示的图片(例如 logo,大小不要超过128×64像素)。使用PC上的工具将图片转换为C语言数组。例如可以使用在线工具如 LCD AssistantImage2cpp,选择输出格式为单色位图,水平字节排列(对应SSD1306页模式)或直接选择SSD1306格式。一张128×64全屏图片会转换成 1024 字节数组。如果是部分区域的小图片,则字节数 = 宽 * 高 / 8(高度需是8的倍数或处理对齐)。工具通常会输出形如 {0xFF, 0x00, ...} 的十六进制初始器。我们可将此数组复制到程序中,例如:

    const uint8_t IMAGE_Logo[1024] = { 
        /* 128x64位图数据,例如0x00,0xFF,... 共1024字节 */
    };
    
  2. 显示图片:有几种方法将图像数据绘制出来:

    • 方法一:如果图像正好是全屏大小(128×64),可以直接将此数组的数据复制到OLED缓冲区然后刷新:
      memcpy(OLED_Buffer, IMAGE_Logo, 1024);
      OLED_UpdateScreen();  // 刷新OLED显示缓冲区内容
      
      其中 OLED_UpdateScreen() 的实现和前面清屏时的刷新过程类似(逐页发送缓冲数据)。这一方法简单粗暴,将整个屏幕内容替换为图片。
    • 方法二:如果只想在屏幕某个区域显示图片(例如一个小图标),可以编写函数将图像数组绘制到缓冲中的指定位置。该函数需要按照字节逐行或逐页拷贝数据,并正确地对齐目标缓冲。例如:
      void OLED_DrawBitmap(uint8_t x, uint8_t y, const uint8_t *bitmap, 
                           uint8_t w, uint8_t h) {
          // 假设h是8的倍数
          for (uint8_t j = 0; j < h/8; j++) {      // 对于每页
              for (uint8_t i = 0; i < w; i++) {    // 对于每列
                  OLED_Buffer[(y/8 + j) * OLED_WIDTH + x + i] = bitmap[j * w + i];
              }
          }
      }
      
      这样可以将一个宽w、高h(8的整数倍)的位图绘制到(x,y)起始的位置。但需要注意避免越界,以及如果y不是8的整数倍,高级处理需要将字节拆分,这里简化要求y对齐页。
    • 方法三:不用缓冲,直接一次性发送位图数据到OLED。这需要先发送设置地址范围的命令,然后发送数据数组。对于全屏图片,可以直接用与清屏类似的方法发送数组。对局部图片,则需设置起始页、结束页、起始列、结束列,再发送数据。因为较复杂,这里不细述。一般推荐使用缓冲方法方便统一管理屏幕内容。

总之,显示BMP图片的核心在于准备好正确格式的数组,然后将其内容送入OLED显示缓冲。在本教程提供的示例代码中,我们会演示加载全屏图片的方法。

7. 示例代码

下面通过几个示例来展示如何使用以上驱动函数实现实际效果。这些示例假设已经完成前面的初始化和函数实现。

首先,在 main.c 或合适的位置调用初始化函数点亮屏幕,并清屏:

OLED_Init();    // 初始化OLED屏幕
OLED_Clear();   // 清屏,所有像素熄灭

示例1:显示字符串 "Hello, OLED!"

利用我们编写的文本显示函数,在屏幕指定位置显示一行字符串:

OLED_Clear();  // 先清屏
OLED_ShowString(0, 0, "Hello, OLED!", 1);  // 在顶行(页0)显示字符串
OLED_UpdateScreen();  // 将缓冲内容更新到屏幕

调用 OLED_ShowString(0,0,...) 会把 "Hello, OLED!" 从左上角开始显示。由于使用6×8字体,字符串长度不会超过一行(128/6 ≈ 21字符,本例13字符)。OLED_UpdateScreen() 则执行实际的数据传输,将整个屏幕的显存数据发送给OLED。执行后应能看到 OLED 屏幕左上方显示出 Hello, OLED!。如果希望在不同位置显示,可以调整 x, y 参数(y需是8的倍数)。例如 OLED_ShowString(0, 8, "STM32 I2C OLED", 1) 会在第二行显示另一串字。别忘了每次修改缓冲后都需要更新屏幕才能反映变化。

示例2: 显示简单几何图形

在屏幕上绘制一些基本形状,如线条、矩形、圆:

OLED_Clear();  // 清屏
// 绘制对白线:从左上到右下
OLED_DrawLine(0, 0, OLED_WIDTH-1, OLED_HEIGHT-1, 1);
// 绘制矩形边框:位于(10,10),宽50高30
OLED_DrawRectangle(10, 10, 50, 30, 1);
// 绘制圆:圆心(64,32),半径20
OLED_DrawCircle(64, 32, 20, 1);
OLED_UpdateScreen();  // 刷新显示,呈现图形

执行后,屏幕上应出现一条对角线、一矩形和一个圆形轮廓。我们也可以尝试画实心矩形或改变参数观测效果。绘制图形的函数调用顺序可以随意,最终的 OLED_UpdateScreen() 会一次性把所有图形输出。由于我们在缓冲中绘制,它们可以重叠组合。如果画线和画圆有部分重叠,缓冲处理(这里全部用点设置为1)会使重叠区域保持亮点(也可能需要根据需要XOR等操作实现不同效果,这里均为简单绘制)。

示例3: 显示 BMP 图片

假设我们已经将一张 128×64 的单色图片转换为 C 数组 IMAGE_Logo[1024](替换为你的图片数据)。我们将其全屏显示:

// 假设 IMAGE_Logo 已包含128x64图片的数据
extern const uint8_t IMAGE_Logo[1024];  
// 将图片数据复制到缓冲区
memcpy(OLED_Buffer, IMAGE_Logo, 1024);
OLED_UpdateScreen();  // 刷新屏幕显示图片
HAL_Delay(5000);      // 延时5秒,让图片保持显示

这段代码会直接把图片数组拷贝到 OLED_Buffer,然后刷新屏幕。延时5秒以便肉眼看清(可选)。实际应用中,可以根据需要停留或在循环中显示不同画面。若想在不清屏的情况下叠加图片(比如logo和文字共存),就需要按位操作缓冲,这里演示的是替换整个屏幕内容。

以上三个示例展示了文本、图形和图像的显示方法。用户可以将它们结合,比如先显示文字,再显示形状,或者显示形状后在空白处显示文字等,关键在于正确更新缓冲区并刷新。

完整的代码应包含初始化、刷新和各种绘制函数以及字模数据。由于篇幅原因,这里没有给出所有内容的连续代码。不过,通过本节的示例,读者可以把各部分拼合到一起,做出一个功能完整的 OLED 驱动程序。

8. 调试与优化

在实际开发中,可能会遇到各种问题。下面总结一些常见问题的原因和解决思路,并提供优化性能的建议。

常见问题分析及解决方案

  • OLED 无显示/不工作

    • 电路连接问题:首先检查电源和地是否正确连接,3.3V 是否供到 OLED 模块;SCL/SDA 连线是否正确接到 PB6/PB7,有没有松动或接反。确认模块的 GND 和 STM32 的 GND 必须共地。
    • I2C 地址错误:确保代码里使用的 I2C 地址与实际模块一致。大部分 0.96寸 OLED 模块地址为0x3C(0x78),但也有可能是0x3D(0x7A)。如果初始化后完全黑屏,没有任何随机点亮,怀疑是不是地址不对,可尝试修改 OLED_I2C_ADDR 为0x7A 或使用I2C扫描程序探测地址。
    • 初始化序列问题:如果电路和地址都正确,但仍无显示,可能是初始化命令有误。检查是否执行了 OLED_Init() 且包含了 0xAF 开启显示命令。一些情况下如果省略了电荷泵开启(0x8D/0x14)或对比度设置不当,屏幕也可能一直不亮。可以在初始化后尝试调用 OLED_Fill(1) 全亮屏来测试OLED是否点亮,以区分是初始化问题还是后续刷新问题。
    • I2C 总线问题:如果程序卡在发送函数(例如 HAL_I2C_Mem_Write 阻塞)不往下执行,可能是I2C通信没有成功完成。检查CubeMX里是否启用了对应GPIO的复用以及 RCC 时钟。也可用示波器/逻辑分析仪观察 SCL/SDA 波形。必要时降低 I2C 时钟速率(如改回100kHz)以排除高速下信号完整性问题。STM32F1的I2C有时存在启动失败或总线繁忙问题,若总线一直处于忙状态,可尝试在调试时手动释放SCL/SDA或复位设备。
    • 代码逻辑错误:确认缓冲区更新和 OLED_UpdateScreen() 刷新调用顺序正确。例如调用 OLED_ShowString 后一定要刷屏才会看到结果。如果只调用了一次 OLED_Init() 而从未调用清屏或更新,屏幕可能仍旧停留在上电时的随机状态或上次显示的数据。
  • 显示异常/杂乱

    • 数据写入错位:如果屏幕上出现错位的像素列或行,可能是地址模式和刷新方法不匹配。例如我们用的是页模式刷新,但初始化时地址模式设置错了(比如仍是水平模式0x00),则写满一页后SSD1306会自动换行到下一行而不是下一页,导致混乱。解决方法:确保初始化选择的地址模式与刷新代码一致。我们在初始化用了页模式0x02,对应逐页写入是正确的。或者在刷新前发送 0x21(设置列地址范围)和 0x22(设置页地址范围)精确指定写入区域也可以避免错位。
    • 像素镜像翻转:如果发现显示出来的内容左右反了或上下颠倒了,可能是初始化的 0xA0/A10xC0/C8 设置与屏幕实际连接不符。可以尝试交换这些命令(A0<->A1,C0<->C8)来纠正。每款模块行列引脚可能连线不同,通过这些命令可以适配。
    • 部分残留或闪烁:如果清屏后仍有少量像素残留,可能是刷新不彻底或没有覆盖所有显存页面。确保刷新循环覆盖07页,列0127全部写入。如有遗漏会导致某些区域保持之前的数据。闪烁通常是因为反复清屏-全刷新的过程被肉眼捕捉到,可以考虑优化刷新方式减少不必要的全屏重绘(见下文优化)。
  • I2C通信错误

    • NACK错误:发送命令返回 HAL_ERROR 可能是没有收到ACK,常由地址不对或设备未初始化好引起。可在上电后延时一段时间再初始化OLED,或检查线是否接触不良。
    • 总线死锁:调试过程中如果意外中止程序,可能导致OLED正忙时退出,造成SDA或SCL拉低,导致下次运行时总线忙。可在初始化I2C前手工GPIO模拟产生几个时钟释放总线,或者简单粗暴地断电重启设备。

代码优化建议

  • 减少I2C刷新次数:尽量先在内存缓冲区完成所有绘制操作,然后调用一次 OLED_UpdateScreen() 刷新。不要在每画完一点或一条线后立刻刷屏,那样会产生大量I2C通信,降低效率并可能引起闪烁。如果需要实时刷新,也应在需要的区域更新,不必每次都全屏刷新。
  • 批量发送数据:我们在刷新时用了每页一次的 128字节发送。实际上可以在水平地址模式下用一条 I2C Write 发送全屏1024字节数据 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)。这只需发送一次设备地址和控制字节,速度更快。如果使用 HAL,可以调用 HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, ..., buffer, 1024, ...) 前提是先发送相应的设置列、页起始地址的命令并使用水平模式(或者 HAL_I2C_Master_Transmit 拼一个0x40在数据前面发送也可)。总之批量传输能显著提高刷新效率。
  • 使用DMA:STM32 的 I2C 外设支持 DMA 传输。我们可以配置 I2C1 的TX DMA通道,这样在发送大块数据(如屏幕缓冲)时,不用CPU逐字节等待传输完成。HAL库提供 HAL_I2C_Mem_Write_DMA 等函数,可以在更新屏幕时调用,让DMA控制器自动发送1024字节,同时CPU可以去处理其他任务,从而提高并行效率。需要注意DMA传输完成后通常通过回调函数通知,可以在回调里处理后续逻辑。
  • 优化绘图算法:基础绘图算法如 Bresenham 线算法已经较高效。但特殊情况下可以简化,例如水平线和垂直线可直接用 memset 或简单循环优化。填充矩形和圆形也可按Scanline填充以减少函数调用开销。
  • 字体和图像存储:将不常修改的字库或位图使用 const 限定放Flash,可以节省RAM。如果有大量图片,可以存储在外部Flash或SD卡,根据需要读取显示。
  • 分区域更新:如果屏幕大部分内容不变,仅小区域更新,可以只更新对应的页和列范围,而不必每次都刷新全屏1024字节。 (STM32入门HAL库-硬件I2C与0.96寸OLED_stm32 hal i2c oled-优快云博客)例如制作一个简单跑马灯,只刷新文字所在的页范围。这样减少I2C传输字节数,提升帧率。可以封装 OLED_UpdateArea(x, y, w, h) 按区域刷新。
  • 调低对比度节省功耗:OLED功耗与点亮像素和对比度设置有关。在不需要高亮时,可以用命令 0x81, contrast 动态调整亮度,或者在空闲时发送 0xAE 关闭显示以省电,适用于电池供电场景。

当掌握了基本方法后,读者可以尝试封装更高级的接口,例如打印数字、绘制进度条,甚至制作简单动画,不断提高开发技能。 (SSD1306 OLED with STM32 Blue Pill using STM32CubeIDE)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值