文章目录
-
- 每日一句正能量
- 引言:当128×64像素遇上嵌入式开发
- [一、双协议硬件架构:I2C vs SPI](#一、双协议硬件架构:I2C vs SPI)
-
- [1.1 硬件连接对比](#1.1 硬件连接对比)
- [1.2 协议选择决策](#1.2 协议选择决策)
- 二、显存架构:页寻址模式的奥秘
-
- [2.1 GDDRAM组织结构](#2.1 GDDRAM组织结构)
- [2.2 三种寻址模式](#2.2 三种寻址模式)
- [2.3 帧缓冲设计](#2.3 帧缓冲设计)
- 三、驱动初始化:16条命令的精密舞蹈
-
- [3.1 初始化命令序列](#3.1 初始化命令序列)
- [3.2 命令发送抽象层](#3.2 命令发送抽象层)
- 四、字库设计:从点阵到字符
-
- [4.1 5×7 ASCII字库](#4.1 5×7 ASCII字库)
- [4.2 中文点阵字库(GB2312)](#4.2 中文点阵字库(GB2312))
- 五、图形API:Bresenham算法与图形基元
-
- [5.1 图形API架构](#5.1 图形API架构)
- [5.2 Bresenham画线算法](#5.2 Bresenham画线算法)
- [5.3 中点画圆算法](#5.3 中点画圆算法)
- [5.4 矩形与位图](#5.4 矩形与位图)
- 六、完整驱动代码架构
-
- [6.1 头文件设计](#6.1 头文件设计)
- [6.2 使用示例](#6.2 使用示例)
- 七、性能优化与调试技巧
-
- [7.1 性能优化策略](#7.1 性能优化策略)
- [7.2 常见问题排查](#7.2 常见问题排查)
- [7.3 调试工具](#7.3 调试工具)
- 八、总结

每日一句正能量
但行好事,莫问前程,你只管播种善良,时间自会给出答案。
把注意力从"未来会怎样"收回到"此刻我该做什么"。善良不是交易,不等同于即时回报。但长期看,你散发的能量、结下的善缘、练就的品行,会以你意想不到的方式,在未来某个节点回到你身上。
引言:当128×64像素遇上嵌入式开发
SSD1306是嵌入式领域最经典的OLED控制器之一,驱动着从智能手表到工业仪表的无数显示屏。它支持128×64或128×32分辨率,通过I2C或SPI与主控通信,内置电荷泵将3.3V升压至驱动OLED所需的7.5V。
然而,SSD1306的驱动开发远不止"发送几个命令点亮屏幕"那么简单。从显存的页寻址架构、到字库的点阵设计、再到Bresenham图形算法,一个完整的驱动涉及硬件抽象 、内存管理 和计算机图形学三个层面的知识。本文将带你从零构建一个支持I2C/SPI双协议、具备完整图形API的SSD1306驱动。
一、双协议硬件架构:I2C vs SPI
1.1 硬件连接对比

| 特性 | I2C接口 | SPI接口 |
|---|---|---|
| 引脚数 | 4-pin (VCC, GND, SCL, SDA) | 7-pin (VCC, GND, SCK, MOSI, CS, DC, RES) |
| 上拉电阻 | 需要4.7kΩ上拉 | 不需要 |
| 额外GPIO | 无 | CS, DC, RES各需1个GPIO |
| 时钟速度 | 400kHz (Fast Mode) | 10MHz (SSD1306上限) |
| 全屏刷新时间 | ~3.5ms | ~0.8ms |
| 代码复杂度 | 中等(需处理ACK) | 简单 |
| 适用场景 | 引脚受限、低速UI | 动画、视频、高帧率 |
1.2 协议选择决策
c
// ssd1306_config.h - 编译时选择协议
#ifndef SSD1306_CONFIG_H
#define SSD1306_CONFIG_H
// 选择通信协议(二选一)
#define SSD1306_USE_I2C
// #define SSD1306_USE_SPI
#ifdef SSD1306_USE_I2C
#define SSD1306_I2C_ADDR 0x3C // SA0=GND时的地址
#define SSD1306_I2C_HANDLE hi2c1 // STM32 HAL I2C句柄
#endif
#ifdef SSD1306_USE_SPI
#define SSD1306_SPI_HANDLE hspi1 // STM32 HAL SPI句柄
#define SSD1306_CS_PORT GPIOA
#define SSD1306_CS_PIN GPIO_PIN_4
#define SSD1306_DC_PORT GPIOA
#define SSD1306_DC_PIN GPIO_PIN_3
#define SSD1306_RES_PORT GPIOA
#define SSD1306_RES_PIN GPIO_PIN_2
#endif
// 显示配置
#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 64
#define SSD1306_PAGES (SSD1306_HEIGHT / 8) // 8 pages
// 缓冲策略
#define SSD1306_USE_BUFFER 1 // 1=Full buffer, 0=Direct write
#endif
二、显存架构:页寻址模式的奥秘
2.1 GDDRAM组织结构

SSD1306的显存(GDDRAM)为128列×64行 ,但物理组织为8个Page,每个Page包含128字节。每个字节控制8个垂直像素(bit0=顶部,bit7=底部)。
GDDRAM物理布局:
Page 0: [Byte0][Byte1]...[Byte127] → 控制 Row 0~7
Page 1: [Byte0][Byte1]...[Byte127] → 控制 Row 8~15
...
Page 7: [Byte0][Byte1]...[Byte127] → 控制 Row 56~63
每个Byte的位映射:
Bit 7 → Row (page*8 + 7)
Bit 6 → Row (page*8 + 6)
...
Bit 0 → Row (page*8 + 0)
2.2 三种寻址模式
| 模式 | 命令 | 特点 | 适用场景 |
|---|---|---|---|
| 页寻址 | 0x20, 0x02 | 写完一列后列地址自动递增,页不变 | 文本显示、局部更新 |
| 水平寻址 | 0x20, 0x00 | 列和页都自动递增 | 全屏刷新、图像显示 |
| 垂直寻址 | 0x20, 0x01 | 列递增,页自动递增 | 特殊应用 |
c
// 设置寻址模式
void SSD1306_SetAddressingMode(uint8_t mode)
{
SSD1306_WriteCommand(0x20); // 设置寻址模式命令
SSD1306_WriteCommand(mode & 0x03); // 0x00=水平, 0x01=垂直, 0x02=页
}
// 设置页寻址模式的页和列起始地址
void SSD1306_SetPageAddress(uint8_t page, uint8_t col)
{
SSD1306_WriteCommand(0xB0 + page); // 设置页起始地址 (B0~B7)
SSD1306_WriteCommand(0x00 + (col & 0x0F)); // 设置列低4位
SSD1306_WriteCommand(0x10 + (col >> 4)); // 设置列高4位
}
2.3 帧缓冲设计
c
// ssd1306_buf.c
#if SSD1306_USE_BUFFER
// 完整帧缓冲: 128×64 / 8 = 1024 bytes
static uint8_t s_frame_buffer[SSD1306_PAGES][SSD1306_WIDTH];
// 脏页标记: 只更新修改过的页
static uint8_t s_dirty_pages[SSD1306_PAGES];
#endif
void SSD1306_DrawPixel(uint8_t x, uint8_t y, SSD1306_Color_t color)
{
if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) return;
#if SSD1306_USE_BUFFER
uint8_t page = y / 8;
uint8_t bit = y % 8;
if (color == SSD1306_COLOR_WHITE) {
s_frame_buffer[page][x] |= (1 << bit);
} else {
s_frame_buffer[page][x] &= ~(1 << bit);
}
s_dirty_pages[page] = 1; // 标记脏页
#else
// 直接写入模式(无缓冲)
uint8_t page = y / 8;
uint8_t bit = y % 8;
SSD1306_SetPageAddress(page, x);
// 需要先读取当前值,修改对应位,再写回
// 这是直接模式的缺点:需要读-修改-写操作
uint8_t current = SSD1306_ReadData(); // 如果支持读取
if (color == SSD1306_COLOR_WHITE) {
current |= (1 << bit);
} else {
current &= ~(1 << bit);
}
SSD1306_WriteData(current);
#endif
}
// 刷新显示(仅缓冲模式)
void SSD1306_Display(void)
{
#if SSD1306_USE_BUFFER
// 使用水平寻址模式一次性写入整个帧缓冲
SSD1306_WriteCommand(0x20); // 水平寻址
SSD1306_WriteCommand(0x00);
SSD1306_WriteCommand(0x21); // 设置列地址
SSD1306_WriteCommand(0x00); // 起始列
SSD1306_WriteCommand(0x7F); // 结束列 (127)
SSD1306_WriteCommand(0x22); // 设置页地址
SSD1306_WriteCommand(0x00); // 起始页
SSD1306_WriteCommand(0x07); // 结束页 (7)
// 突发写入1024字节
SSD1306_WriteDataMulti(&s_frame_buffer[0][0], SSD1306_WIDTH * SSD1306_PAGES);
#endif
}
三、驱动初始化:16条命令的精密舞蹈
3.1 初始化命令序列

c
// ssd1306.c - 初始化函数
bool SSD1306_Init(void)
{
// 1. 硬件复位(SPI模式需要,I2C模块通常已内置复位电路)
#ifdef SSD1306_USE_SPI
HAL_GPIO_WritePin(SSD1306_RES_PORT, SSD1306_RES_PIN, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(SSD1306_RES_PORT, SSD1306_RES_PIN, GPIO_PIN_SET);
HAL_Delay(10);
#endif
// 2. 发送初始化命令序列
static const uint8_t init_cmds[] = {
0xAE, // Display OFF (sleep mode)
0xD5, 0x80, // Set Display Clock Divide Ratio / Oscillator Frequency
// 0x80: divide ratio = 1, oscillator frequency = default
0xA8, 0x3F, // Set Multiplex Ratio (64 - 1 = 0x3F for 128×64)
0xD3, 0x00, // Set Display Offset (0)
0x40, // Set Display Start Line (0)
0x8D, 0x14, // Charge Pump Setting (0x14 = enable charge pump)
0x20, 0x00, // Set Memory Addressing Mode (0x00 = horizontal)
0xA1, // Set Segment Re-map (0xA1 = column 127 mapped to SEG0)
0xC8, // Set COM Output Scan Direction (0xC8 = remapped mode)
0xDA, 0x12, // Set COM Pins Hardware Configuration (0x12 = alternative, no remap)
0x81, 0x7F, // Set Contrast Control (0x7F = 127)
0xD9, 0xF1, // Set Pre-charge Period (0xF1 = phase 1: 15 DCLK, phase 2: 1 DCLK)
0xDB, 0x40, // Set VCOMH Deselect Level (0x40 = ~0.77×Vcc)
0xA4, // Entire Display ON (0xA4 = resume to RAM content display)
0xA6, // Set Normal/Inverse Display (0xA6 = normal)
0xAF, // Display ON (normal mode)
};
for (size_t i = 0; i < sizeof(init_cmds); i++) {
if (!SSD1306_WriteCommand(init_cmds[i])) {
return false;
}
}
// 3. 清屏
SSD1306_Clear();
SSD1306_Display();
return true;
}
3.2 命令发送抽象层
c
// ssd1306_i2c.c
#ifdef SSD1306_USE_I2C
#define SSD1306_I2C_CMD_MODE 0x00 // Co=0, D/C#=0 (命令)
#define SSD1306_I2C_DATA_MODE 0x40 // Co=0, D/C#=1 (数据)
bool SSD1306_WriteCommand(uint8_t cmd)
{
uint8_t buf[2] = {SSD1306_I2C_CMD_MODE, cmd};
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(
&SSD1306_I2C_HANDLE,
SSD1306_I2C_ADDR << 1,
buf, 2,
100
);
return (status == HAL_OK);
}
bool SSD1306_WriteData(uint8_t data)
{
uint8_t buf[2] = {SSD1306_I2C_DATA_MODE, data};
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(
&SSD1306_I2C_HANDLE,
SSD1306_I2C_ADDR << 1,
buf, 2,
100
);
return (status == HAL_OK);
}
// 突发写入(关键优化:减少I2C事务开销)
bool SSD1306_WriteDataMulti(const uint8_t *data, uint16_t len)
{
// 使用I2C突发模式:第一个字节是控制字节(0x40),后面全是数据
uint8_t *buf = malloc(len + 1);
if (!buf) return false;
buf[0] = SSD1306_I2C_DATA_MODE;
memcpy(buf + 1, data, len);
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(
&SSD1306_I2C_HANDLE,
SSD1306_I2C_ADDR << 1,
buf, len + 1,
1000
);
free(buf);
return (status == HAL_OK);
}
#endif
// ssd1306_spi.c
#ifdef SSD1306_USE_SPI
bool SSD1306_WriteCommand(uint8_t cmd)
{
HAL_GPIO_WritePin(SSD1306_DC_PORT, SSD1306_DC_PIN, GPIO_PIN_RESET); // DC=0 (command)
HAL_GPIO_WritePin(SSD1306_CS_PORT, SSD1306_CS_PIN, GPIO_PIN_RESET); // CS=0
HAL_StatusTypeDef status = HAL_SPI_Transmit(
&SSD1306_SPI_HANDLE,
&cmd, 1,
100
);
HAL_GPIO_WritePin(SSD1306_CS_PORT, SSD1306_CS_PIN, GPIO_PIN_SET); // CS=1
return (status == HAL_OK);
}
bool SSD1306_WriteData(uint8_t data)
{
HAL_GPIO_WritePin(SSD1306_DC_PORT, SSD1306_DC_PIN, GPIO_PIN_SET); // DC=1 (data)
HAL_GPIO_WritePin(SSD1306_CS_PORT, SSD1306_CS_PIN, GPIO_PIN_RESET); // CS=0
HAL_StatusTypeDef status = HAL_SPI_Transmit(
&SSD1306_SPI_HANDLE,
&data, 1,
100
);
HAL_GPIO_WritePin(SSD1306_CS_PORT, SSD1306_CS_PIN, GPIO_PIN_SET); // CS=1
return (status == HAL_OK);
}
// SPI突发写入(无需每字节切换DC,可优化为连续传输)
bool SSD1306_WriteDataMulti(const uint8_t *data, uint16_t len)
{
HAL_GPIO_WritePin(SSD1306_DC_PORT, SSD1306_DC_PIN, GPIO_PIN_SET); // DC=1
HAL_GPIO_WritePin(SSD1306_CS_PORT, SSD1306_CS_PIN, GPIO_PIN_RESET); // CS=0
HAL_StatusTypeDef status = HAL_SPI_Transmit(
&SSD1306_SPI_HANDLE,
(uint8_t *)data, len,
1000
);
HAL_GPIO_WritePin(SSD1306_CS_PORT, SSD1306_CS_PIN, GPIO_PIN_SET); // CS=1
return (status == HAL_OK);
}
#endif
四、字库设计:从点阵到字符
4.1 5×7 ASCII字库

c
// ssd1306_font.c
// 5×7 ASCII字库 (96个字符, 32-127)
// 每个字符 = 5 bytes, 每byte = 1列 (MSB = top pixel)
const uint8_t font_5x7[][5] = {
// Space (0x20)
{0x00, 0x00, 0x00, 0x00, 0x00},
// ! (0x21)
{0x00, 0x00, 0x5F, 0x00, 0x00},
// " (0x22)
{0x00, 0x07, 0x00, 0x07, 0x00},
// ... (省略中间字符)
// A (0x41)
{0x7E, 0x09, 0x09, 0x09, 0x7E},
// B (0x42)
{0x7F, 0x49, 0x49, 0x49, 0x36},
// C (0x43)
{0x3E, 0x41, 0x41, 0x41, 0x22},
// ... (省略)
// ~ (0x7E)
{0x40, 0x20, 0x10, 0x08, 0x04},
};
// 字库元数据
const FontDef_t font_5x7_def = {
.width = 5,
.height = 7,
.first_char = 32,
.last_char = 127,
.data = (const uint8_t *)font_5x7,
.bytes_per_char = 5
};
// 字符绘制函数
void SSD1306_DrawChar(uint8_t x, uint8_t y, char c, const FontDef_t *font, SSD1306_Color_t color)
{
if (c < font->first_char || c > font->last_char) return;
uint16_t offset = (c - font->first_char) * font->bytes_per_char;
const uint8_t *char_data = font->data + offset;
for (uint8_t col = 0; col < font->width; col++) {
uint8_t column_byte = char_data[col];
for (uint8_t row = 0; row < font->height; row++) {
if (column_byte & (1 << row)) {
SSD1306_DrawPixel(x + col, y + row, color);
}
}
}
}
void SSD1306_DrawString(uint8_t x, uint8_t y, const char *str, const FontDef_t *font, SSD1306_Color_t color)
{
while (*str) {
SSD1306_DrawChar(x, y, *str, font, color);
x += font->width + 1; // 字符间距1像素
str++;
if (x + font->width > SSD1306_WIDTH) break; // 换行或截断
}
}
4.2 中文点阵字库(GB2312)
c
// 中文16×16字库(仅常用汉字,使用索引查找)
// 每个汉字 = 32 bytes (16列 × 16行 / 8)
// 区位码查找表
typedef struct {
uint16_t unicode; // Unicode编码
const uint8_t *bitmap; // 32字节点阵数据
} ChineseChar_t;
// 示例:"温"字 16×16点阵
const uint8_t bitmap_wen[] = {
0x00, 0x40, 0x20, 0x40, 0x10, 0x40, 0x0C, 0x40,
0x07, 0x40, 0x00, 0x40, 0x00, 0x40, 0xF0, 0x7F,
0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40,
0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x00, 0x00
};
// 使用哈希或二分查找加速
const ChineseChar_t chinese_font[] = {
{0x6E29, bitmap_wen}, // 温
// ... 更多汉字
};
void SSD1306_DrawChinese(uint8_t x, uint8_t y, uint16_t unicode, SSD1306_Color_t color)
{
// 二分查找汉字点阵
const uint8_t *bitmap = FindChineseBitmap(unicode);
if (!bitmap) return;
for (uint8_t page = 0; page < 2; page++) { // 16行 = 2页
for (uint8_t col = 0; col < 16; col++) {
uint8_t byte_idx = page * 16 + col;
uint8_t byte_data = bitmap[byte_idx];
for (uint8_t bit = 0; bit < 8; bit++) {
if (byte_data & (1 << bit)) {
SSD1306_DrawPixel(x + col, y + page * 8 + bit, color);
}
}
}
}
}
五、图形API:Bresenham算法与图形基元
5.1 图形API架构

5.2 Bresenham画线算法

c
// ssd1306_gfx.c
// Bresenham画线算法(仅整数运算,适合无FPU的MCU)
void SSD1306_DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, SSD1306_Color_t color)
{
int16_t dx = abs(x1 - x0);
int16_t dy = abs(y1 - y0);
int16_t sx = (x0 < x1) ? 1 : -1;
int16_t sy = (y0 < y1) ? 1 : -1;
int16_t err = dx - dy;
while (1) {
SSD1306_DrawPixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int16_t e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
5.3 中点画圆算法
c
// 中点画圆算法(利用8对称性,只计算1/8圆弧)
void SSD1306_DrawCircle(uint8_t cx, uint8_t cy, uint8_t r, SSD1306_Color_t color)
{
int16_t x = 0;
int16_t y = r;
int16_t d = 1 - r; // 决策参数
while (x <= y) {
// 利用8对称性,一次绘制8个点
SSD1306_DrawPixel(cx + x, cy + y, color);
SSD1306_DrawPixel(cx + y, cy + x, color);
SSD1306_DrawPixel(cx + y, cy - x, color);
SSD1306_DrawPixel(cx + x, cy - y, color);
SSD1306_DrawPixel(cx - x, cy - y, color);
SSD1306_DrawPixel(cx - y, cy - x, color);
SSD1306_DrawPixel(cx - y, cy + x, color);
SSD1306_DrawPixel(cx - x, cy + y, color);
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
}
x++;
}
}
// 填充圆(使用水平线填充)
void SSD1306_FillCircle(uint8_t cx, uint8_t cy, uint8_t r, SSD1306_Color_t color)
{
int16_t x = 0;
int16_t y = r;
int16_t d = 1 - r;
while (x <= y) {
// 绘制水平线填充
SSD1306_DrawLine(cx - x, cy + y, cx + x, cy + y, color);
SSD1306_DrawLine(cx - y, cy + x, cx + y, cy + x, color);
SSD1306_DrawLine(cx - y, cy - x, cx + y, cy - x, color);
SSD1306_DrawLine(cx - x, cy - y, cx + x, cy - y, color);
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
}
x++;
}
}
5.4 矩形与位图
c
// 绘制矩形
void SSD1306_DrawRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, SSD1306_Color_t color)
{
SSD1306_DrawLine(x, y, x + w - 1, y, color); // 上边
SSD1306_DrawLine(x, y + h - 1, x + w - 1, y + h - 1, color); // 下边
SSD1306_DrawLine(x, y, x, y + h - 1, color); // 左边
SSD1306_DrawLine(x + w - 1, y, x + w - 1, y + h - 1, color); // 右边
}
// 填充矩形(利用页对齐优化)
void SSD1306_FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, SSD1306_Color_t color)
{
// 检查是否页对齐(y是8的倍数,h是8的倍数)
if ((y % 8 == 0) && (h % 8 == 0)) {
// 页对齐优化:直接操作字节,无需逐像素
uint8_t start_page = y / 8;
uint8_t pages = h / 8;
uint8_t fill_byte = (color == SSD1306_COLOR_WHITE) ? 0xFF : 0x00;
for (uint8_t p = 0; p < pages; p++) {
for (uint8_t col = x; col < x + w; col++) {
s_frame_buffer[start_page + p][col] = fill_byte;
}
s_dirty_pages[start_page + p] = 1;
}
} else {
// 非对齐:逐像素填充
for (uint8_t row = y; row < y + h; row++) {
for (uint8_t col = x; col < x + w; col++) {
SSD1306_DrawPixel(col, row, color);
}
}
}
}
// 绘制位图(XBM格式)
void SSD1306_DrawBitmap(uint8_t x, uint8_t y, const uint8_t *bitmap,
uint8_t w, uint8_t h, SSD1306_Color_t color)
{
for (uint8_t page = 0; page < (h + 7) / 8; page++) {
for (uint8_t col = 0; col < w; col++) {
uint8_t byte_data = bitmap[page * w + col];
for (uint8_t bit = 0; bit < 8; bit++) {
if (byte_data & (1 << bit)) {
SSD1306_DrawPixel(x + col, y + page * 8 + bit, color);
}
}
}
}
}
六、完整驱动代码架构

6.1 头文件设计
c
// ssd1306.h - 公共API
#ifndef SSD1306_H
#define SSD1306_H
#include <stdint.h>
#include <stdbool.h>
// 颜色定义
typedef enum {
SSD1306_COLOR_BLACK = 0,
SSD1306_COLOR_WHITE = 1
} SSD1306_Color_t;
// 字体定义
typedef struct {
uint8_t width;
uint8_t height;
uint8_t first_char;
uint8_t last_char;
const uint8_t *data;
uint8_t bytes_per_char;
} FontDef_t;
// 核心API
bool SSD1306_Init(void);
void SSD1306_DeInit(void);
void SSD1306_Display(void);
void SSD1306_Clear(void);
// 像素操作
void SSD1306_DrawPixel(uint8_t x, uint8_t y, SSD1306_Color_t color);
SSD1306_Color_t SSD1306_GetPixel(uint8_t x, uint8_t y);
// 图形基元
void SSD1306_DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, SSD1306_Color_t color);
void SSD1306_DrawRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, SSD1306_Color_t color);
void SSD1306_FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, SSD1306_Color_t color);
void SSD1306_DrawCircle(uint8_t cx, uint8_t cy, uint8_t r, SSD1306_Color_t color);
void SSD1306_FillCircle(uint8_t cx, uint8_t cy, uint8_t r, SSD1306_Color_t color);
void SSD1306_DrawBitmap(uint8_t x, uint8_t y, const uint8_t *bitmap, uint8_t w, uint8_t h, SSD1306_Color_t color);
// 文本
void SSD1306_DrawChar(uint8_t x, uint8_t y, char c, const FontDef_t *font, SSD1306_Color_t color);
void SSD1306_DrawString(uint8_t x, uint8_t y, const char *str, const FontDef_t *font, SSD1306_Color_t color);
void SSD1306_Printf(uint8_t x, uint8_t y, const FontDef_t *font, SSD1306_Color_t color, const char *fmt, ...);
// 高级功能
void SSD1306_SetContrast(uint8_t contrast);
void SSD1306_SetDisplayOn(bool on);
void SSD1306_InvertDisplay(bool invert);
void SSD1306_ScrollRight(uint8_t start_page, uint8_t end_page, uint8_t speed);
void SSD1306_ScrollLeft(uint8_t start_page, uint8_t end_page, uint8_t speed);
void SSD1306_StopScroll(void);
#endif
6.2 使用示例
c
// main.c
#include "ssd1306.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
// 初始化I2C/SPI
MX_I2C1_Init(); // 或 MX_SPI1_Init();
// 初始化OLED
if (!SSD1306_Init()) {
printf("OLED init failed!\n");
while (1);
}
// 清屏
SSD1306_Clear();
// 绘制文本
SSD1306_DrawString(0, 0, "Hello OLED!", &font_5x7_def, SSD1306_COLOR_WHITE);
SSD1306_DrawString(0, 10, "I2C + SPI Driver", &font_5x7_def, SSD1306_COLOR_WHITE);
// 绘制图形
SSD1306_DrawRectangle(0, 25, 60, 20, SSD1306_COLOR_WHITE);
SSD1306_FillRectangle(2, 27, 10, 16, SSD1306_COLOR_WHITE);
SSD1306_DrawCircle(90, 35, 10, SSD1306_COLOR_WHITE);
// 绘制进度条
SSD1306_DrawRectangle(0, 50, 100, 8, SSD1306_COLOR_WHITE);
SSD1306_FillRectangle(2, 52, 50, 4, SSD1306_COLOR_WHITE);
// 刷新显示
SSD1306_Display();
while (1) {
// 动态更新
static uint8_t progress = 0;
progress = (progress + 1) % 96;
SSD1306_FillRectangle(2, 52, 96, 4, SSD1306_COLOR_BLACK); // 清除
SSD1306_FillRectangle(2, 52, progress, 4, SSD1306_COLOR_WHITE); // 重绘
SSD1306_Display();
HAL_Delay(50);
}
}
七、性能优化与调试技巧
7.1 性能优化策略
| 优化点 | 方法 | 效果 |
|---|---|---|
| I2C突发写入 | 单次事务发送1024字节 | 减少90%的I2C开销 |
| 脏页追踪 | 只更新修改过的页 | 5-10倍速度提升 |
| 页对齐填充 | 矩形填充时直接操作字节 | 8倍速度提升 |
| DMA传输 | SPI/I2C使用DMA | CPU零等待 |
| 字体缓存 | 常用字符预渲染 | 减少重复计算 |
7.2 常见问题排查
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 屏幕全黑 | 未使能电荷泵 | 发送0x8D, 0x14 |
| 显示倒置 | 扫描方向错误 | 调整0xA0/0xA1和0xC0/0xC8 |
| 对比度低 | 对比度值太小 | 增加0x81后的值 |
| I2C无ACK | 地址错误或模块损坏 | 检查0x3C/0x3D,用示波器抓波形 |
| 显示花屏 | 显存数据错误 | 检查寻址模式,确保列/页地址正确 |
| 闪烁严重 | 刷新率太低 | 提高I2C速度或改用SPI |
7.3 调试工具
c
// 调试模式:显示帧缓冲内容到串口
void SSD1306_DumpBuffer(void)
{
printf("Frame Buffer Dump:\n");
for (uint8_t page = 0; page < SSD1306_PAGES; page++) {
printf("Page %d: ", page);
for (uint8_t col = 0; col < SSD1306_WIDTH; col++) {
printf("%02X ", s_frame_buffer[page][col]);
if ((col + 1) % 16 == 0) printf("\n");
}
}
}
// 测试模式:显示棋盘格
void SSD1306_TestPattern(void)
{
for (uint8_t y = 0; y < SSD1306_HEIGHT; y++) {
for (uint8_t x = 0; x < SSD1306_WIDTH; x++) {
SSD1306_DrawPixel(x, y, ((x / 8 + y / 8) % 2) ? SSD1306_COLOR_WHITE : SSD1306_COLOR_BLACK);
}
}
SSD1306_Display();
}
八、总结
SSD1306驱动开发是嵌入式图形编程的入门必修课。通过本文的讲解,你应该掌握了:
- 双协议抽象:I2C和SPI的硬件差异,以及如何通过编译时配置统一API
- 显存架构:理解页寻址模式,设计高效的帧缓冲和脏页追踪
- 初始化序列:16条精密命令背后的硬件原理
- 字库设计:点阵字模的存储格式和绘制方法
- 图形算法:Bresenham画线和中点画圆的整数算法
- 性能优化:从突发写入到DMA传输的完整优化链路
一个优秀的SSD1306驱动不仅是"能显示",更应该是可移植 (分离硬件抽象层)、可扩展 (支持多种字体和图形)、高性能(脏页追踪+DMA)的工程化代码。
转载自:https://blog.csdn.net/u014727709/article/details/162279123
欢迎 👍点赞✍评论⭐收藏,欢迎指正