news 2026/8/2 13:42:11

Grove OLED屏SH1107驱动指南:SPI/I2C双模通信与U8g2图形库实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Grove OLED屏SH1107驱动指南:SPI/I2C双模通信与U8g2图形库实战

1. 项目概述:一块能说会道的“小窗口”

如果你玩过单片机或者树莓派,大概率会对那些能显示点文字、图案的小屏幕感兴趣。今天要聊的这块Grove - OLED 显示屏 1.12 (SH1107) V3.0,就是这样一位“多面手”。它不像手机屏幕那样色彩斑斓,但在嵌入式开发的世界里,这块单色、高对比度的OLED屏,却是调试信息、状态显示、甚至小型UI界面的绝佳载体。我手头这块是V3.0版本,最大的亮点是同时支持SPII2C两种通信协议,并且兼容3.3V5V供电,这意味着它几乎可以无缝接入你手边任何一款主控板,无论是Arduino Uno(5V)、ESP32(3.3V)还是STM32。

为什么这块屏幕值得单独拿出来说?因为在项目开发中,一个可靠的显示输出往往能极大提升调试效率和用户体验。想象一下,你的智能温湿度计不再需要连接电脑串口才能读数,数据直接显示在屏幕上;或者你的迷你游戏机,需要一个低功耗的显示核心。这块OLED屏就是为这些场景而生的。它的核心驱动芯片是SH1107,这是一款非常经典的128x128像素OLED驱动IC,控制逻辑清晰,社区资源丰富。接下来,我会带你从硬件接口、通信协议选择、到软件驱动编写,完整地走一遍使用流程,并分享一些我实际项目中积累的、数据手册上不会写的经验和“坑”。

2. 硬件接口与通信协议深度解析

拿到这块屏幕,首先得搞清楚怎么把它和你的主板连起来。Grove接口的便利性在于它用了一个4针的标准化连接器,但里面藏着选择通信方式的玄机。

2.1 引脚定义与电源兼容性设计

先看四个引脚:VCCGNDSDASCL。如果你熟悉I2C,会立刻发现这就是标准的I2C四线接法。但这款屏幕的SPI功能是如何实现的呢?奥秘在于板载的跳线帽(或焊点选择)。

  • I2C模式:这是出厂默认模式。SDA和SCL就是I2C的数据线和时钟线。此时,屏幕的I2C地址通常是0x3C(这是SH1107的常见地址,但务必以实际扫描为准)。
  • SPI模式:需要通过改变硬件连接来启用。通常需要短接板子上标记为“SPI”的焊点,或者改变跳线帽的位置。在SPI模式下,SDA引脚变身为SPI的数据线(MOSI/SDI)SCL引脚则变为SPI的时钟线(SCK)。除此之外,SPI还需要额外的片选(CS)数据/命令选择(DC)引脚,这些信号是通过Grove接口旁边的单独排针引出的。所以,使用SPI模式时,你需要连接6根线(VCC, GND, SCK, MOSI, CS, DC),而不是简单的4线Grove接口。

关于3.3V/5V兼容,这是V3.0版本一个非常实用的特性。屏幕内部大概率集成了一颗电平转换芯片或采用了兼容两种电压的逻辑器件。这意味着,无论你的主控板逻辑电平是3.3V(如ESP32、STM32F4)还是5V(如Arduino Uno、AVR),都可以直接连接VCC和信号线,无需担心电平不匹配烧毁芯片或通信失败。这大大简化了硬件设计。不过,一个经验之谈是:如果你的系统是3.3V,尽量使用3.3V为屏幕供电,可以降低整体功耗和噪声。

2.2 SPI vs I2C:如何做出你的选择?

这是新手最常困惑的问题。两种协议没有绝对的好坏,只有适合与否。

I2C协议的优势与局限:

  • 优点:接线极其简单,仅需2根信号线(SDA, SCL),节省IO口。支持总线挂载多个设备(地址不同即可)。协议本身包含应答机制,通信相对可靠。
  • 缺点:速度较慢(标准模式100kbps,快速模式400kbps)。在需要频繁刷新全屏画面或显示动态效果时,可能会成为瓶颈。通信距离短,抗干扰能力相对SPI弱。

SPI协议的优势与局限:

  • 优点:全双工,通信速度极高(轻松达到10Mbps以上)。对于OLED这种需要持续刷新显存(GRAM)的设备,SPI可以带来极其流畅的刷新体验,尤其是在实现动画、滚动效果时。时序简单,由硬件直接控制,CPU开销小。
  • 缺点:需要至少4根线(SCK, MOSI, CS, DC),如果还要读回数据(MISO),则需要5根。每个设备需要独立的CS片选线,挂载多个设备时会占用大量IO。没有硬件应答机制,主设备无法直接确认从设备是否收到数据(通常靠延时或软件模拟)。

我的选择建议:对于大多数显示静态文本、简单图表或更新不频繁的数据(如温度每小时更新一次)的应用,I2C模式足矣,它能最大程度保持接线的简洁。但如果你要制作一个需要平滑滚动菜单、显示动态波形图或小型游戏的设备,那么SPI模式是必须的,否则你会看到明显的闪烁和拖影。我个人的项目里,数据监控仪表盘用I2C,而手持游戏机则毫不犹豫地选择SPI。

2.3 SH1107驱动芯片关键特性

理解驱动芯片,才能更好地驾驭屏幕。SH1107是一款132x132像素内存映射的OLED驱动IC,但我们常用的显示区域是128x128。它的显存(GRAM)结构是“页-列”式。整个屏幕在垂直方向被分成16个“页”(Page),每页8行像素(即高度为8)。每个页有132列(实际用128列)。当你发送数据时,需要先指定页地址(0~15)和列地址(0~131)。

这种结构影响了我们的编程逻辑:当你想要在某个坐标(x, y)画点时,需要先计算出该点位于第几页(Page = y / 8),以及在该页的第几列(Col = x),还要计算出在该列字节中的具体位(Bit = y % 8)。许多现成的图形库(如U8g2, Adafruit GFX)已经帮我们封装好了这些底层操作。

另一个重要特性是SH1107需要电荷泵电压。OLED像素点需要较高的电压(通常十几伏)才能发光,而我们的系统只有3.3V或5V。SH1107内部集成了电荷泵电路,我们只需要通过命令(0xAD, 0x8B, 0x30等序列)将其开启即可。几乎所有驱动库的初始化函数里都包含了这部分配置。

3. 软件驱动与图形库实战

硬件连接妥当后,下一步就是让屏幕亮起来并显示内容。我们通常不会直接去操作SH1107的寄存器,而是借助成熟的驱动库。

3.1 初始化流程与通信底层实现

无论使用SPI还是I2C,初始化流程大同小异,核心是向SH1107发送一系列初始化命令。以下是一个典型的初始化序列概要:

  1. 关闭显示(0xAE):在配置过程中避免屏幕上出现乱码。
  2. 设置显示时钟分频和振荡频率(0xD5, 0x80):调整驱动IC的内部工作频率。
  3. 设置多路复用率(0xA8, 0x7F):对于128x128屏幕,设置为127(0x7F)。
  4. 设置显示偏移(0xD3, 0x00):将显示起始行设置为0。
  5. 设置显示起始行(0x40)。
  6. 开启内部电荷泵(0xAD, 0x8B, 0x30等):这是点亮屏幕的关键步骤。
  7. 设置内存地址模式(0x20, 0x00):通常使用页地址模式(Page Addressing Mode),便于逐页写入数据。
  8. 设置列地址范围(0x21, 0x00, 0x7F):设置列起始和结束地址为0~127。
  9. 设置页地址范围(0x22, 0x00, 0x0F):设置页起始和结束地址为0~15。
  10. 整个显示开启(0xA4):不采用全亮模式。
  11. 设置正常显示(0xA6):非反相显示。
  12. 设置COM扫描方向(0xC8):通常为反向扫描。
  13. 设置对比度(0x81, 0x7F):默认对比度值。
  14. 开启显示(0xAF):配置完成,点亮屏幕。

在代码层面,你需要实现两个最基础的函数:写命令函数writeCommand(uint8_t cmd)写数据函数writeData(uint8_t data)。这两个函数的内部实现,就是SPI和I2C的区别所在。

  • I2C实现:每次传输以一个控制字节开始。通常,控制字节为0x00表示后续字节是命令,0x40表示后续字节是数据。所以你的一次I2C传输可能是:Start -> 写设备地址(0x3C) -> 写控制字节(0x00) -> 写命令字节 -> Stop
  • SPI实现:通过DC引脚的高低电平来区分命令和数据。DC拉低时,接下来通过MOSI发送的是命令;DC拉高时,发送的是数据。CS引脚则在每次传输前拉低,传输后拉高。

3.2 U8g2库:跨平台的图形解决方案

对于绝大多数应用,我强烈推荐使用U8g2库。它是一个功能极其强大的单色图形库,支持上百种显示控制器(包括SH1107),以及几乎所有的硬件平台(Arduino, ESP32, STM32 HAL, Linux等)和通信方式(I2C, SPI, 并行8080等)。

在Arduino IDE中安装U8g2库后,针对这块屏幕,你可以这样初始化:

对于I2C连接:

#include <U8g2lib.h> // 使用硬件I2C,并指定正确的引脚和地址 U8G2_SH1107_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // U8G2_R0表示旋转0度,U8X8_PIN_NONE表示没有硬件复位引脚 void setup() { u8g2.begin(); // 初始化通信并执行初始化序列 }

对于SPI连接:

#include <U8g2lib.h> // 假设CS接引脚10,DC接引脚9 U8G2_SH1107_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ U8X8_PIN_NONE); void setup() { u8g2.begin(); // 初始化 }

初始化完成后,你就可以使用一套统一的API进行绘图了。U8g2采用“页面缓冲器”或“全屏缓冲器”机制。对于SH1107,使用全屏缓冲(_F_在构造函数中)会占用128 * 128 / 8 = 2048字节的内存,但绘制和刷新非常简单:

void loop() { u8g2.clearBuffer(); // 清除内部缓冲区 u8g2.setFont(u8g2_font_ncenB08_tr); // 设置字体 u8g2.drawStr(0, 20, "Hello World!"); // 在坐标(0,20)绘制字符串 u8g2.drawCircle(64, 64, 30, U8G2_DRAW_ALL); // 画一个圆 u8g2.sendBuffer(); // 将缓冲区内容发送到屏幕显示 delay(1000); }

实操心得:在内存紧张的MCU(如Arduino Uno只有2KB RAM)上,使用全屏缓冲(2048字节)可能会吃紧。这时可以考虑使用“页面缓冲”模式(构造函数中不带_F_),它一次只处理一页(128字节)的数据,内存占用小,但编程模型稍复杂,需要你自己管理分页绘制逻辑。对于ESP32、STM32等资源丰富的平台,直接上全屏缓冲,体验最好。

3.3 自定义图形与动画技巧

U8g2提供了丰富的绘图函数:点、线、矩形、圆、三角形、位图等。制作动画的原理就是“清除->绘制->发送”的循环。

实现平滑动画的关键技巧:

  1. 避免全局清屏clearBuffer()会清除整个缓冲区,如果每帧都全清全画,在复杂图形下可能导致闪烁。可以尝试只重绘发生变化的部分区域(使用setDrawColor设置为背景色进行局部覆盖)。
  2. 双缓冲(如果支持):一些高级平台或库支持双缓冲,即在后台缓冲区绘制完成后再一次性交换到前台显示,可以完全消除闪烁。U8g2本身不直接支持硬件双缓冲,但你可以自己维护两个软件缓冲区。
  3. 使用sendBuffer()而非refreshDisplay()sendBuffer()是U8g2推荐的更新方式,它高效地将整个缓冲区发送到屏幕。避免在循环中调用初始化或重置函数。
  4. 精简绘图操作:计算和绘图是耗时的。对于固定不变的UI框架(如边框、标题栏),只需在初始化时绘制一次,之后只更新数据变化的部分。

例如,制作一个移动的小球:

int ballX = 10; int ballY = 64; int ballSpeedX = 2; void loop() { u8g2.clearBuffer(); // 绘制静态背景(如果复杂,可以只画一次) // u8g2.drawFrame(0,0,128,128); // 更新小球位置 ballX += ballSpeedX; if(ballX > 118 || ballX < 10) { ballSpeedX = -ballSpeedX; } // 绘制小球 u8g2.drawDisc(ballX, ballY, 10); u8g2.sendBuffer(); delay(16); // 约60FPS }

4. 典型应用场景与进阶玩法

这块屏幕的应用远不止显示几行文字。结合不同的主控,它能玩出很多花样。

4.1 嵌入式系统信息仪表盘

这是最直接的应用。你可以用ESP32读取网络时间(NTP)、传感器数据(温湿度、气压),然后美观地呈现在OLED上。

实现要点:

  • 布局设计:在纸上或绘图软件中先规划好各个数据块的位置,避免显示杂乱。
  • 字体选择:U8g2内置多种字体,大小各异。对于数字,可以选择等宽字体(如u8g2_font_profont22_mr)便于对齐。混合使用大字体显示关键数据,小字体显示标签。
  • 数据更新策略:传感器数据无需每秒刷新多次。可以设置一个定时器,每5秒或10秒读取并更新一次传感器数据和屏幕,其余时间MCU可以进入低功耗模式,这对于电池供电设备至关重要。
  • 图标使用:可以使用U8g2的drawXBM()函数显示自定义的位图图标,比如温度计图标、Wi-Fi信号图标,让界面更友好。

4.2 结合ESP32-S3实现网络时钟

结合最新的网络热词“esp32s3获取时间并显示在oled屏幕”,这是一个完美的实践。ESP32-S3内置Wi-Fi,可以轻松连接网络获取NTP时间。

核心步骤:

  1. 硬件连接:将OLED屏幕通过I2C或SPI连接到ESP32-S3。注意ESP32-S3的默认I2C引脚是GPIO8(SDA)和GPIO9(SCL),但你可以通过Wire.begin(SDA_PIN, SCL_PIN)自定义。
  2. 软件配置
    • 引入U8g2库和NTPClient库。
    • 编写连接Wi-Fi的函数。
    • 初始化NTP客户端,设置时区。
  3. 主循环逻辑
    void loop() { if (WiFi.status() == WL_CONNECTED) { timeClient.update(); // 从NTP服务器更新时间 formattedTime = timeClient.getFormattedTime(); formattedDate = timeClient.getFormattedDate(); } u8g2.clearBuffer(); u8g2.setFont(u8g2_font_logisoso32_tn); // 大号数字字体 u8g2.drawStr(centerX(formattedTime.substring(0,5)), 50, formattedTime.substring(0,5).c_str()); // 显示时:分 u8g2.setFont(u8g2_font_helvB10_tr); u8g2.drawStr(centerX(formattedDate), 80, formattedDate.c_str()); // 显示日期 u8g2.sendBuffer(); // 每分钟同步一次NTP即可,减少网络请求 static unsigned long lastSync = 0; if (millis() - lastSync > 60000) { timeClient.forceUpdate(); lastSync = millis(); } delay(100); // 控制刷新率 }

4.3 低功耗设备的状态显示器

得益于OLED屏幕像素自发光、黑色不耗电的特性,它非常适合低功耗设备。在显示静态内容时,功耗可以非常低。

省电技巧:

  1. 降低刷新率:如果不是必须,不要以最高频率刷新屏幕。可以仅在数据变化时更新。
  2. 使用深色背景:OLED显示黑色像素时,该像素点不工作。因此,设计UI时尽量采用黑色背景,亮色文字和图形。
  3. 利用驱动IC的睡眠命令:在设备长时间待机时,可以通过发送命令(0xAE)完全关闭显示,将屏幕功耗降到几乎为零。需要显示时再发送0xAF开启。
  4. 动态调整对比度:在环境光较暗时,可以适当降低对比度(0x81, value),也能节省少量功耗。

5. 常见问题排查与调试心得

即使按照教程操作,也难免会遇到屏幕不亮、显示乱码、通信失败等问题。这里总结几个我踩过的“坑”和解决方法。

5.1 屏幕完全不亮(无显示)

这是最常见的问题,排查思路如下:

  1. 检查电源和接线:这是第一步,也是最容易出错的一步。用万用表测量VCC和GND之间是否为稳定的3.3V或5V。确认所有连接线接触牢固,特别是Grove连接器是否插紧。
  2. 确认通信协议模式:你用的是I2C还是SPI?板子上的跳线帽或焊点设置是否正确?如果你写的是I2C程序,但硬件跳到了SPI模式,肯定无法通信。
  3. 检查I2C地址:使用一个I2C扫描程序(Arduino IDE有示例)扫描总线,确认屏幕上显示的地址。SH1107常见地址是0x3C,但也可能是0x3D。U8g2初始化时需要传入正确的地址。
  4. 检查复位引脚:有些驱动库或硬件需要复位信号。如果屏幕有RST引脚,确保它被正确初始化(通常先拉低再拉高)。如果未使用,在代码中设置为U8X8_PIN_NONE
  5. 初始化序列是否完整:特别是开启电荷泵的命令序列是否成功发送。可以尝试在初始化后,手动发送一个写全屏亮的命令(0xA5)测试屏幕硬件是否正常。如果全屏能亮,说明硬件和基础通信是好的,问题在初始化或GRAM操作。

5.2 显示乱码、花屏或内容错位

出现显示内容不正常,通常与数据发送有关。

  1. SPI模式下的DC引脚:在SPI模式下,DC引脚必须在发送命令前拉低,发送数据前拉高。这是一个非常容易疏忽的时序问题。用逻辑分析仪或示波器抓取CS、DC、SCK、MOSI的波形,是最直接的调试方法。
  2. 通信速度过快:特别是对于I2C,如果主控MCU的I2C时钟设置过快(比如超过400kHz),而屏幕或线材质量不佳,可能导致数据出错。尝试降低I2C时钟速度(例如Wire.setClock(100000))。
  3. 电源噪声:OLED屏在刷新时电流会有波动,如果电源纹波过大,可能导致驱动IC工作异常。在VCC和GND之间并联一个10uF~100uF的电解电容和一个0.1uF的陶瓷电容,可以显著改善。
  4. 软件库配置错误:确认U8g2构造函数选择的设备型号完全正确。U8G2_SH1107_128X128_F_HW_I2CU8G2_SH1106_128X64_...是不同的驱动芯片,初始化序列不同,混用必然导致花屏。
  5. 内存溢出:如果使用了全屏缓冲且进行了复杂的图形操作,可能导致栈溢出或内存混乱,从而影响显示。可以尝试简化程序,或切换到页面缓冲模式测试。

5.3 I2C通信不稳定或无法识别

  1. 上拉电阻:I2C总线需要上拉电阻(通常4.7kΩ~10kΩ)到VCC。虽然很多主控板(如Arduino Uno、ESP32开发板)和模块内部可能已经集成,但如果通信距离稍长或设备多,外接上拉电阻会大大增加稳定性。这是解决I2C通信问题最有效的措施之一。
  2. 总线冲突:总线上是否有其他I2C设备地址冲突?用扫描程序检查。
  3. 电平兼容:虽然屏幕兼容3.3V/5V,但要确保主控的I2C引脚电平与VCC匹配。一个5V的Arduino与一个工作在3.3V逻辑的屏幕直接连接,长期可能损坏屏幕。最好使用电平转换器,或者确保屏幕由5V供电(此时其信号电平也预期为5V)。

5.4 刷新速度慢或有闪烁

  1. 协议瓶颈:如前所述,I2C的刷新率有上限。计算一下:全屏128x128像素,1位色深,共2048字节。I2C每字节传输需要9个时钟周期(8位数据+1位ACK),在100kHz时钟下,传输一屏数据理论最快需要2048 * 9 / 100000 ≈ 0.184秒,即约5.4 FPS。这还不算命令开销和协议本身的开销。因此,I2C不适合高速刷新。解决方案:换用SPI接口。
  2. 软件瓶颈clearBuffer()和复杂的draw函数调用非常耗时。优化绘图逻辑,只更新变化区域。对于固定UI,使用setDrawColor进行局部擦除而非全屏清除。
  3. 缓冲区管理:确保使用的是sendBuffer(),并且不要在循环中频繁初始化库。

调试这类问题,最有力的工具是逻辑分析仪。一个几十块钱的简易逻辑分析仪(配合PulseView软件)就能清晰地看到I2C或SPI总线上的每一个命令和数据,对照SH1107的数据手册,可以精准定位是哪个命令没发、哪个数据发错了,效率远超盲目猜测。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/2 13:36:32

如何掌握LTX-2视频生成:ComfyUI-LTXVideo核心技术解析与实践指南

如何掌握LTX-2视频生成&#xff1a;ComfyUI-LTXVideo核心技术解析与实践指南 【免费下载链接】ComfyUI-LTXVideo LTX-Video Support for ComfyUI 项目地址: https://gitcode.com/GitHub_Trending/co/ComfyUI-LTXVideo ComfyUI-LTXVideo是LTX-2视频生成模型在ComfyUI环境…

作者头像 李华
网站建设 2026/8/2 13:33:10

乐高Studio 2.0虚拟设计:从零件库到搭建说明书的完整指南

1. 乐高Studio 2.0&#xff1a;从虚拟砖块到实体模型的数字桥梁 如果你和我一样&#xff0c;是个乐高迷&#xff0c;同时又对数字设计充满热情&#xff0c;那么乐高Studio 2.0绝对是你绕不开的一个宝藏工具。它远不止是一个简单的“搭积木”软件&#xff0c;而是一个功能强大的…

作者头像 李华
网站建设 2026/8/2 13:32:34

04Java学习day(4)

数组的定义 数组的定义和变量的定义类似。 public class Main{public static void main(String[] args){int a[] new int[10];float[] f new float[33];double[] d new double[123];char[] c new char[21];//这里的方括号也可以像c int a[]} }数组的初始化 数组初始化其实就…

作者头像 李华
网站建设 2026/8/2 13:30:07

基于YOLO与FastAPI的花卉检测识别系统全栈开发实战

1. 项目概述&#xff1a;当计算机视觉遇上花卉之美 在公园里散步&#xff0c;看到一片花海却叫不出名字&#xff1b;在园艺工作中&#xff0c;需要快速统计不同花卉的品种和数量&#xff1b;甚至是在电商平台&#xff0c;用户上传一张花卉图片就能自动识别并推荐养护知识——这…

作者头像 李华
网站建设 2026/8/2 13:28:25

177、TinyML实战项目:农业传感器数据预测

TinyML实战项目:农业传感器数据预测 一、从一次田间调试说起 去年夏天在河北某智慧农业基地,我蹲在玉米地里盯着LoRa网关的串口输出,温度传感器读数在35℃到42℃之间疯狂跳变。现场工程师说传感器是新的,但数据明显异常。我掏出示波器一看,电源纹波高达800mV——太阳能板…

作者头像 李华