2、硬件连接与初始化:树莓派/ESP32与OLED接线图、电源去耦电容布局、初始化序列代码实现

好,咱们进入第二章。这一章是真正动手前的最后一道理论关。说白了,就是要把屏幕和主控芯片「焊」在一起,然后让它「亮」起来。

我个人习惯,每次做新项目,第一步不是写代码,而是先画接线图。为什么?因为硬件一旦焊错,轻则屏幕不亮,重则烧芯片。我当年刚入行时,就曾把OLED的VCC和GND接反了,屏幕当场冒烟——嗯,那味道至今难忘。

2.1 树莓派与OLED接线图

树莓派用的是3.3V逻辑电平,而大部分0.96寸OLED模块也是3.3V供电。所以两者可以直接对接,不需要电平转换。这一点很省心。

我常用的接线方案是这样的(以树莓派4B为例):

OLED引脚 树莓派引脚(BCM编号) 物理引脚位置 说明
VCC 3.3V Pin 1 供电,千万别接5V
GND GND Pin 6 共地
SCL SCL (GPIO 3) Pin 5 I2C时钟线
SDA SDA (GPIO 2) Pin 3 I2C数据线
小提示: 如果你用的是树莓派Zero或更老的版本,I2C引脚位置可能不同。建议先用 gpio readall 命令确认一下。我每次换板子都会跑一遍这个命令,避免低级错误。

2.2 ESP32与OLED接线图

ESP32的情况稍微复杂一点。它也是3.3V逻辑,但有些开发板的I2C引脚默认不是GPIO 21和22。你想想看,如果买了个便宜板子,引脚定义和标准版不一样,那就尴尬了。

我建议用以下标准配置:

OLED引脚 ESP32引脚 说明
VCC 3.3V 供电
GND GND 共地
SCL GPIO 22 I2C时钟
SDA GPIO 21 I2C数据
注意: 有些ESP32开发板(比如ESP32-C3、ESP32-S3)的I2C引脚可能不同。我曾经在ESP32-C3上踩过坑,它的默认I2C引脚是GPIO 8和GPIO 9。所以,拿到板子第一件事——看原理图!

2.3 电源去耦电容布局

这个知识点,很多初学者会忽略。但我要说,它真的很重要。

OLED屏幕在刷新时,电流会瞬间变化。如果电源纹波太大,屏幕会出现闪烁、花屏,甚至初始化失败。我遇到过最离谱的一次,屏幕死活不亮,查了半天,最后发现是电源线太长,寄生电感太大导致的。

去耦电容的布局原则很简单:

  • 电容要靠近OLED的VCC和GND引脚,越近越好
  • 典型值:10µF电解电容 + 0.1µF陶瓷电容并联
  • 电解电容滤低频纹波,陶瓷电容滤高频噪声

如果你用的是面包板,建议在面包板上的VCC和GND之间直接插一个10µF电容。如果是自己画PCB,那就在OLED模块的背面焊两个电容,紧贴引脚。

我的经验: 在ESP32项目中,我习惯在OLED的VCC引脚旁边再加一个100µF的钽电容。因为ESP32的WiFi模块启动时,电流会突然增大,这个电容能有效防止屏幕闪一下。

2.4 初始化序列代码实现

好了,硬件接好了,电容也焊上了。接下来就是让屏幕「说话」。

OLED的初始化,说白了就是给它发一串命令。这些命令告诉屏幕:怎么扫描、亮度多少、要不要翻转等等。不同的OLED驱动芯片(SSD1306、SH1106等),初始化序列略有不同。

我以最常用的SSD1306为例,给出初始化代码。这段代码我用了好几年,在树莓派和ESP32上都验证过。

2.4.1 树莓派(Python)初始化代码

import smbus
import time

# I2C地址,通常是0x3C或0x3D
OLED_ADDR = 0x3C

# 初始化I2C总线
bus = smbus.SMBus(1)  # 树莓派3B+/4B用总线1

def write_cmd(cmd):
    """发送命令到OLED"""
    bus.write_byte_data(OLED_ADDR, 0x00, cmd)

def write_data(data):
    """发送数据到OLED"""
    bus.write_byte_data(OLED_ADDR, 0x40, data)

def oled_init():
    """SSD1306初始化序列"""
    # 关闭显示
    write_cmd(0xAE)
    
    # 设置显示偏移
    write_cmd(0xD3)
    write_cmd(0x00)
    
    # 设置起始行地址
    write_cmd(0x40)
    
    # 设置段重映射(左右翻转)
    write_cmd(0xA1)
    
    # 设置COM扫描方向(上下翻转)
    write_cmd(0xC8)
    
    # 设置COM引脚硬件配置
    write_cmd(0xDA)
    write_cmd(0x12)
    
    # 设置对比度
    write_cmd(0x81)
    write_cmd(0x7F)
    
    # 关闭整个显示(维持RAM内容)
    write_cmd(0xA4)
    
    # 设置显示模式(正常)
    write_cmd(0xA6)
    
    # 设置振荡频率
    write_cmd(0xD5)
    write_cmd(0x80)
    
    # 设置预充电周期
    write_cmd(0xD9)
    write_cmd(0xF1)
    
    # 设置VCOMH电压
    write_cmd(0xDB)
    write_cmd(0x40)
    
    # 开启电荷泵
    write_cmd(0x8D)
    write_cmd(0x14)
    
    # 开启显示
    write_cmd(0xAF)
    
    print("OLED初始化完成!")

# 调用初始化
oled_init()
避坑指南: 我曾经在树莓派上遇到一个问题——初始化后屏幕依然不亮。后来发现是I2C地址搞错了。0x3C和0x3D都有可能,取决于OLED模块背面的电阻配置。建议先用 i2cdetect -y 1 扫描一下,看看地址到底是哪个。

2.4.2 ESP32(MicroPython)初始化代码

from machine import Pin, I2C
import time

# 初始化I2C
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)

# OLED地址
OLED_ADDR = 0x3C

def write_cmd(cmd):
    """发送命令"""
    i2c.writeto(OLED_ADDR, bytearray([0x00, cmd]))

def write_data(data):
    """发送数据"""
    i2c.writeto(OLED_ADDR, bytearray([0x40, data]))

def oled_init():
    """SSD1306初始化序列(和树莓派版本一致)"""
    write_cmd(0xAE)  # 关闭显示
    write_cmd(0xD3)  # 设置显示偏移
    write_cmd(0x00)
    write_cmd(0x40)  # 设置起始行
    write_cmd(0xA1)  # 段重映射
    write_cmd(0xC8)  # COM扫描方向
    write_cmd(0xDA)  # COM引脚配置
    write_cmd(0x12)
    write_cmd(0x81)  # 对比度
    write_cmd(0x7F)
    write_cmd(0xA4)  # 全局显示
    write_cmd(0xA6)  # 正常显示
    write_cmd(0xD5)  # 振荡频率
    write_cmd(0x80)
    write_cmd(0xD9)  # 预充电
    write_cmd(0xF1)
    write_cmd(0xDB)  # VCOMH
    write_cmd(0x40)
    write_cmd(0x8D)  # 电荷泵
    write_cmd(0x14)
    write_cmd(0xAF)  # 开启显示
    print("ESP32 OLED初始化完成!")

oled_init()

2.5 初始化序列的几点说明

你可能会问:为什么初始化序列要发这么多命令?少发几个行不行?

嗯,理论上可以。但你会发现,少发几个命令后,屏幕可能显示不正常。比如,如果不设置电荷泵(0x8D, 0x14),屏幕根本不会亮。如果不设置对比度,显示可能很暗。

我个人习惯,把初始化序列封装成一个函数,每次上电都调用一次。这样即使屏幕因为干扰而「死机」,也能通过重新初始化来恢复。

核心要点: 初始化序列的顺序不能乱。SSD1306的数据手册里明确规定了每个命令的先后关系。比如,开启电荷泵必须在开启显示之前。我曾经因为调换了这两个命令的顺序,导致屏幕初始化后一直闪烁,查了半天才发现是顺序问题。

2.6 验证初始化是否成功

代码写完了,怎么知道屏幕初始化成功了呢?

最简单的方法:在初始化之后,往屏幕的某个位置写一个像素点。如果那个点亮了,说明初始化成功。

# 在屏幕左上角画一个像素点
def draw_pixel(x, y):
    # 计算页地址和列地址
    page = y // 8
    col = x
    # 设置页地址
    write_cmd(0xB0 + page)
    # 设置列地址(低4位和高4位)
    write_cmd(0x00 + (col & 0x0F))
    write_cmd(0x10 + ((col >> 4) & 0x0F))
    # 写入数据(点亮该列的第一个像素)
    write_data(0x01)

# 在(0,0)位置画一个点
draw_pixel(0, 0)

如果屏幕左上角出现了一个亮点,恭喜你,硬件连接和初始化都成功了!

如果没亮,别急。先检查电源,再检查I2C地址,最后检查接线。我遇到过最奇葩的问题,是OLED模块的I2C地址被焊盘上的锡短路改变了。嗯,有时候硬件问题就是这么玄学。

好了,这一章的内容就到这里。下一章,我们会真正开始写UI——让屏幕显示文字、数字,甚至画一个血压计的波形图。