第二章 开发环境搭建与初始化:从选型到点亮第一个像素

做嵌入式显示开发,第一步不是写代码,而是把手上的硬件和软件环境理清楚。我见过不少新手,买了个OLED屏回来,折腾半天点不亮,最后发现是I2C地址搞错了——这种坑,咱们今天一次性避开。

2.1 硬件选型:0.96寸 vs 1.3寸

市面上最常见的OLED屏,就这两种尺寸。它们驱动芯片不同,接口时序也有差异。我个人的习惯是:先看项目需求,再定屏幕。

参数 0.96寸 OLED 1.3寸 OLED
分辨率 128×64 128×64
驱动芯片 SSD1306 SH1106
通信接口 I2C / SPI I2C / SPI
像素大小 较小,细腻 较大,视觉冲击强
功耗 约20mA 约25mA
价格 较低 略高
我的经验:如果你做的是手表、手环这类小设备,0.96寸更合适,像素密度高,显示细腻。如果是做桌面仪表、智能家居面板,1.3寸的视觉冲击力更强,而且SH1106的驱动时序比SSD1306更宽松,抗干扰能力好一些。

2.2 主控平台选择:STM32 vs ESP32

这两个平台我都用过。STM32稳,ESP32快。怎么选?看你的网络需求。

  • STM32F103C8T6:72MHz主频,64KB Flash,20KB RAM。适合纯本地显示,不联网。我最早的项目就是用这个,驱动0.96寸OLED,跑一个简单的菜单系统,稳如老狗。
  • ESP32:240MHz双核,520KB SRAM,自带Wi-Fi/蓝牙。适合需要联网、OTA升级、远程控制的场景。说白了,你要做物联网屏显,ESP32是首选。
核心结论:STM32适合工业级、低功耗、高可靠性场景;ESP32适合联网、快速原型、功能丰富的场景。两者都能驱动OLED,只是库的移植方式略有不同。

2.3 库文件移植与初始化流程

不管用哪个平台,驱动OLED的核心就三步:初始化通信接口、配置驱动芯片寄存器、写显存数据。嗯,这里要注意,SSD1306和SH1106的初始化序列不一样,千万别混用。

2.3.1 STM32平台(HAL库 + I2C)

我习惯用STM32CubeMX生成初始化代码,然后手动移植SSD1306的驱动库。具体步骤:

  1. 在CubeMX中配置I2C1,速率400kHz,引脚PB6(SCL)、PB7(SDA)。
  2. 生成代码后,将ssd1306.h和ssd1306.c添加到工程。
  3. 修改ssd1306.h中的I2C地址:0x3C(或0x3D,取决于你的模块)。
  4. 在main.c中调用SSD1306_Init()。
// ssd1306.h 中的关键配置
#define SSD1306_I2C_ADDR        (0x3C << 1)  // 左移1位,适配HAL库
#define SSD1306_WIDTH           128
#define SSD1306_HEIGHT          64

// main.c 中的初始化
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_I2C1_Init();
    
    SSD1306_Init();           // 初始化OLED
    SSD1306_Fill(Black);      // 清屏
    SSD1306_UpdateScreen();   // 刷新显存到屏幕
    
    while (1)
    {
        // 你的显示逻辑
    }
}
我曾经踩过的坑:I2C地址搞错是最常见的。0.96寸模块默认地址是0x3C,但有些模块的地址引脚接了高电平,变成0x3D。用逻辑分析仪抓一下,或者用I2C扫描代码确认,比猜靠谱。

2.3.2 ESP32平台(Arduino框架 + Wire库)

ESP32用Arduino框架开发,库移植更简单。我推荐用Adafruit的SSD1306库,但要注意,它依赖Adafruit GFX库。

  1. 在Arduino IDE中安装库:Adafruit SSD1306、Adafruit GFX。
  2. 选择开发板:ESP32 Dev Module。
  3. 接线:SDA→GPIO21,SCL→GPIO22。
  4. 写初始化代码。
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1  // 不使用复位引脚

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
    Serial.begin(115200);
    
    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
        Serial.println(F("SSD1306 allocation failed"));
        for(;;);  // 死循环,等待复位
    }
    
    display.clearDisplay();
    display.display();  // 刷新显存
}

void loop() {
    // 你的显示逻辑
}
小技巧:ESP32的I2C引脚可以任意映射,不一定要用默认的GPIO21/22。用Wire.begin(SDA, SCL)指定即可。我在一个项目中为了布线方便,把SDA放到了GPIO4,SCL放到了GPIO5,完全没问题。

2.4 点亮第一个像素点

好了,环境搭好了,库也移植了。现在咱们来点亮第一个像素。说白了,就是往显存里写一个点,然后刷新到屏幕。

2.4.1 STM32版本

// 在(10, 10)位置画一个白点
SSD1306_DrawPixel(10, 10, White);
SSD1306_UpdateScreen();

2.4.2 ESP32版本

// 在(10, 10)位置画一个白点
display.drawPixel(10, 10, WHITE);
display.display();

就这么简单。但为什么有时候点了没反应?我遇到过两种情况:

  • 显存没刷新:SSD1306的显存是双缓冲的,你写进去的数据在RAM里,必须调用UpdateScreen()或display()才能推到屏幕。
  • 坐标越界:OLED的坐标范围是(0,0)到(127,63),如果你写(128,64),它不会报错,但也不会显示。
验证方法:画一个十字线,从(0,32)到(127,32)画水平线,从(64,0)到(64,63)画垂直线。如果十字线完整显示,说明你的初始化、通信、显存刷新全部正常。

2.5 本章知识体系

下面这张图,把本章的核心流程串起来了。你可以把它当作一个检查清单,做项目时对着走,不容易漏。

开发环境搭建与初始化流程 开发环境搭建与初始化流程 硬件选型 主控平台选择 通信接口确定 驱动库移植 初始化序列配置 显存缓冲区建立 点亮第一个像素:DrawPixel + UpdateScreen 验证:画十字线 → 确认显示正常 提示:每一步都可用逻辑分析仪或串口打印辅助调试
我的建议:第一次做的时候,不要急着写复杂的UI。先点亮一个像素,再画一条线,再显示一个字符。每一步都确认没问题,再往下走。这样出了问题,你能快速定位是硬件还是软件的问题。