第二章 开发环境搭建与初始化:从选型到点亮第一个像素
做嵌入式显示开发,第一步不是写代码,而是把手上的硬件和软件环境理清楚。我见过不少新手,买了个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的驱动库。具体步骤:
- 在CubeMX中配置I2C1,速率400kHz,引脚PB6(SCL)、PB7(SDA)。
- 生成代码后,将ssd1306.h和ssd1306.c添加到工程。
- 修改ssd1306.h中的I2C地址:0x3C(或0x3D,取决于你的模块)。
- 在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库。
- 在Arduino IDE中安装库:Adafruit SSD1306、Adafruit GFX。
- 选择开发板:ESP32 Dev Module。
- 接线:SDA→GPIO21,SCL→GPIO22。
- 写初始化代码。
#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 本章知识体系
下面这张图,把本章的核心流程串起来了。你可以把它当作一个检查清单,做项目时对着走,不容易漏。
我的建议:第一次做的时候,不要急着写复杂的UI。先点亮一个像素,再画一条线,再显示一个字符。每一步都确认没问题,再往下走。这样出了问题,你能快速定位是硬件还是软件的问题。