欢迎光临
我们一直在努力

STM32 OLED 显示屏使用详解:从硬件连接到代码实战

摘要:本文从零讲解 STM32 驱动 OLED 屏的完整流程,涵盖硬件接线(I2C/SPI)、SSD1306 驱动代码编写(初始化、显示字符/字符串/汉字)及 U8g2 等第三方库推荐,并附完整示例。

目录

  • 1. 引言
  • 2. OLED 显示屏基础知识
    • 2.1 常见 OLED 屏规格
    • 2.2 驱动芯片 SSD1306 简介
    • 2.3 显示原理
  • 3. 硬件接线
    • 3.1 I2C 接口接线(推荐)
    • 3.2 SPI 接口接线
  • 4. 开发环境准备
    • 4.1 使用 STM32CubeMX 配置 I2C
  • 5. OLED 驱动代码编写
    • 5.1 底层 I2C 通信函数
    • 5.2 SSD1306 初始化
    • 5.3 显示单个字符
    • 5.4 显示字符串
    • 5.5 显示汉字
  • 6. 完整示例:显示传感器数据
  • 7. 常用第三方库推荐
    • 7.1 U8g2 库

1. 引言

OLED(Organic Light-Emitting Diode,有机发光二极管)显示屏凭借其自发光、高对比度、低功耗、超薄等优势,在嵌入式开发中得到了广泛应用。在 STM32 项目中,OLED 屏常用于显示传感器数据、系统状态、菜单界面等信息,是调试和交互的重要窗口。

本文将带你从零开始,掌握 STM32 驱动 OLED 屏的完整流程,包括硬件接线、驱动芯片选择、I2C/SPI 通信协议、常用库的使用,以及完整的代码编写示例。

2. OLED 显示屏基础知识

2.1 常见 OLED 屏规格

参数常见规格
尺寸 0.96 英寸、1.3 英寸、1.54 英寸等
分辨率 128×64、128×32、96×64 等
颜色 单色(蓝/白/黄蓝双色)、RGB 彩色
驱动芯片 SSD1306(0.96 寸)、SH1106(1.3 寸)等
通信接口 I2C、SPI、并行 8080/6800

其中,0.96 英寸 128×64 分辨率、SSD1306 驱动芯片、I2C 接口的 OLED 屏最为常见,也是本文讲解的重点。

2.2 驱动芯片 SSD1306 简介

SSD1306 是一款单芯片 CMOS OLED/PLED 驱动器,内置 128×64 点阵的显示 RAM(GDDRAM)。它支持:

  • I2C 接口(地址通常为 0x78 或 0x7A)
  • SPI 接口(4 线或 3 线)
  • 128×64 像素点阵显示
  • 内部电荷泵,无需外部高压电源

2.3 显示原理

OLED 屏的每个像素点对应 GDDRAM 中的一个位(bit)。当某位为 1 时,对应像素点亮;为 0 时熄灭。SSD1306 将 128×64 的显示区域划分为 8 页(Page),每页 8 行像素,共 128 列。

页结构示意:
Page 0: 行 0-7
Page 1: 行 8-15
…
Page 7: 行 56-63

3. 硬件接线

3.1 I2C 接口接线(推荐)

I2C 接口只需两根数据线,占用引脚少,接线简单。

OLED 引脚STM32 引脚说明
VCC 3.3V 电源正极
GND GND 电源负极
SCL PB6(I2C1_SCL) 时钟线
SDA PB7(I2C1_SDA) 数据线

注意:部分 OLED 模块板载稳压电路,可接 5V;若没有稳压,务必接 3.3V,否则可能烧毁屏幕。

3.2 SPI 接口接线

SPI 接口传输速度更快,但需要更多引脚。

OLED 引脚STM32 引脚说明
VCC 3.3V 电源正极
GND GND 电源负极
SCK PB13(SPI2_SCK) 时钟线
MOSI PB15(SPI2_MOSI) 数据线
CS PB12 片选信号
DC PB11 数据/命令选择
RES PB10 复位信号

下面是 SPI 接口的接线示意图,箭头方向表示信号流向:

#mermaid-svg-k1gItr4hzZPumKIa{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-k1gItr4hzZPumKIa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-k1gItr4hzZPumKIa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-k1gItr4hzZPumKIa .error-icon{fill:#552222;}#mermaid-svg-k1gItr4hzZPumKIa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-k1gItr4hzZPumKIa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-k1gItr4hzZPumKIa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-k1gItr4hzZPumKIa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-k1gItr4hzZPumKIa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-k1gItr4hzZPumKIa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-k1gItr4hzZPumKIa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-k1gItr4hzZPumKIa .marker.cross{stroke:#333333;}#mermaid-svg-k1gItr4hzZPumKIa svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-k1gItr4hzZPumKIa p{margin:0;}#mermaid-svg-k1gItr4hzZPumKIa .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster-label text{fill:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster-label span{color:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster-label span p{background-color:transparent;}#mermaid-svg-k1gItr4hzZPumKIa .label text,#mermaid-svg-k1gItr4hzZPumKIa span{fill:#333;color:#333;}#mermaid-svg-k1gItr4hzZPumKIa .node rect,#mermaid-svg-k1gItr4hzZPumKIa .node circle,#mermaid-svg-k1gItr4hzZPumKIa .node ellipse,#mermaid-svg-k1gItr4hzZPumKIa .node polygon,#mermaid-svg-k1gItr4hzZPumKIa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .rough-node .label text,#mermaid-svg-k1gItr4hzZPumKIa .node .label text,#mermaid-svg-k1gItr4hzZPumKIa .image-shape .label,#mermaid-svg-k1gItr4hzZPumKIa .icon-shape .label{text-anchor:middle;}#mermaid-svg-k1gItr4hzZPumKIa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .rough-node .label,#mermaid-svg-k1gItr4hzZPumKIa .node .label,#mermaid-svg-k1gItr4hzZPumKIa .image-shape .label,#mermaid-svg-k1gItr4hzZPumKIa .icon-shape .label{text-align:center;}#mermaid-svg-k1gItr4hzZPumKIa .node.clickable{cursor:pointer;}#mermaid-svg-k1gItr4hzZPumKIa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-k1gItr4hzZPumKIa .arrowheadPath{fill:#333333;}#mermaid-svg-k1gItr4hzZPumKIa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-k1gItr4hzZPumKIa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-k1gItr4hzZPumKIa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k1gItr4hzZPumKIa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-k1gItr4hzZPumKIa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k1gItr4hzZPumKIa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-k1gItr4hzZPumKIa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .cluster text{fill:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster span{color:#333;}#mermaid-svg-k1gItr4hzZPumKIa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-k1gItr4hzZPumKIa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-k1gItr4hzZPumKIa rect.text{fill:none;stroke-width:0;}#mermaid-svg-k1gItr4hzZPumKIa .icon-shape,#mermaid-svg-k1gItr4hzZPumKIa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k1gItr4hzZPumKIa .icon-shape p,#mermaid-svg-k1gItr4hzZPumKIa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-k1gItr4hzZPumKIa .icon-shape .label rect,#mermaid-svg-k1gItr4hzZPumKIa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k1gItr4hzZPumKIa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-k1gItr4hzZPumKIa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-k1gItr4hzZPumKIa :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

OLED 模块 (SSD1306)

STM32F103C8T6

时钟信号

数据信号

片选信号

数据/命令选择

复位信号

PB13 (SPI2_SCK)

PB15 (SPI2_MOSI)

PB12 (CS)

PB11 (DC)

PB10 (RES)

SCK

MOSI

CS

DC

RES

说明:SPI 为单向主从通信,STM32 作为主机(Master)向 OLED 从机(Slave)发送时钟与数据;DC 引脚用于区分当前传输的是命令还是显示数据,RES 用于复位 OLED 控制器。

3.3 SPI 驱动代码示例

SPI 模式与 I2C 模式在代码结构上保持一致,主要差异在于底层通信函数:SPI 需要额外控制 CS(片选)、DC(数据/命令选择)和 RES(复位)三个 GPIO 引脚,且写命令/写数据时不再需要 I2C 的控制字节(0x00/0x40),而是通过 DC 引脚电平来区分命令与数据。

/* oled_spi.h */
#ifndef OLED_SPI_H
#define OLED_SPI_H

#include "stm32f1xx_hal.h"

/* SPI 片选、数据/命令选择、复位引脚定义(按 3.2 节接线) */
#define OLED_CS_GPIO_Port GPIOB
#define OLED_CS_Pin GPIO_PIN_12
#define OLED_DC_GPIO_Port GPIOB
#define OLED_DC_Pin GPIO_PIN_11
#define OLED_RES_GPIO_Port GPIOB
#define OLED_RES_Pin GPIO_PIN_10

void OLED_WriteCmd(uint8_t cmd);
void OLED_WriteData(uint8_t data);
void OLED_Reset(void);

#endif

/* oled_spi.c */
#include "oled_spi.h"

extern SPI_HandleTypeDef hspi2;

/* 片选拉低,选中 OLED */
static void OLED_Select(void)
{
HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_RESET);
}

/* 片选拉高,释放 OLED */
static void OLED_Unselect(void)
{
HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_SET);
}

/* 写命令:DC 置低表示命令 */
void OLED_WriteCmd(uint8_t cmd)
{
OLED_Select();
HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi2, &cmd, 1, 100);
OLED_Unselect();
}

/* 写数据:DC 置高表示数据 */
void OLED_WriteData(uint8_t data)
{
OLED_Select();
HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, GPIO_PIN_SET);
HAL_SPI_Transmit(&hspi2, &data, 1, 100);
OLED_Unselect();
}

/* 复位:RES 拉低至少 10us 再拉高 */
void OLED_Reset(void)
{
HAL_GPIO_WritePin(OLED_RES_GPIO_Port, OLED_RES_Pin, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(OLED_RES_GPIO_Port, OLED_RES_Pin, GPIO_PIN_SET);
HAL_Delay(100);
}

SPI 模式的 SSD1306 初始化命令序列与 I2C 模式完全相同,只需在 OLED_Init() 开头调用 OLED_Reset() 完成硬件复位,其余命令照搬即可。与 I2C 模式相比,SPI 模式的主要差异可总结为:

  • 通信函数:I2C 通过 HAL_I2C_Master_Transmit 发送带控制字节的数据包;SPI 通过 HAL_SPI_Transmit 直接发送单字节,并用 DC 引脚区分命令/数据。
  • 引脚控制:SPI 模式额外管理 CS、DC、RES 三个 GPIO,I2C 模式无需这些引脚。
  • 初始化流程:SPI 模式在发送命令序列前需先执行 OLED_Reset() 复位;I2C 模式仅需上电延时。
  • 传输速度:SPI 时钟可达数 MHz,明显快于 I2C 的 100kHz/400kHz,适合刷新较频繁的界面。

4. 开发环境准备

本文使用以下开发环境:

  • 芯片型号:STM32F103C8T6(蓝板)
  • 开发方式:STM32 HAL 库 + Keil MDK 或 STM32CubeIDE
  • OLED 模块:0.96 英寸 I2C 接口 SSD1306

4.1 使用 STM32CubeMX 配置 I2C

  • 打开 STM32CubeMX,选择芯片 STM32F103C8T6。
  • 在 Pinout & Configuration 中,将 PB6 配置为 I2C1_SCL,PB7 配置为 I2C1_SDA。
  • 在 Connectivity → I2C1 中,将 Mode 设置为 I2C,速度设为 Standard Mode(100kHz)或 Fast Mode(400kHz)。
  • 生成代码,并在 main.c 中调用 MX_I2C1_Init()。
  • 5. OLED 驱动代码编写

    5.1 底层 I2C 通信函数

    首先编写 OLED 与 STM32 之间的底层通信函数,包括写命令和写数据。

    /* oled_i2c.h */
    #ifndef OLED_I2C_H
    #define OLED_I2C_H

    #include "stm32f1xx_hal.h"

    #define OLED_ADDR 0x78 // I2C 设备地址(7 位地址 0x3C 左移一位)

    void OLED_WriteCmd(uint8_t cmd);
    void OLED_WriteData(uint8_t data);

    #endif

    /* oled_i2c.c */
    #include "oled_i2c.h"

    extern I2C_HandleTypeDef hi2c1;

    void OLED_WriteCmd(uint8_t cmd)
    {
    uint8_t buf[2];
    buf[0] = 0x00; // 控制字节:0x00 表示后续为命令
    buf[1] = cmd;
    HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100);
    }

    void OLED_WriteData(uint8_t data)
    {
    uint8_t buf[2];
    buf[0] = 0x40; // 控制字节:0x40 表示后续为数据
    buf[1] = data;
    HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100);
    }

    5.2 SSD1306 初始化

    SSD1306 上电后需要发送初始化命令序列,配置显示参数。

    /* oled_ssd1306.h */
    #ifndef OLED_SSD1306_H
    #define OLED_SSD1306_H

    #include "oled_i2c.h"

    void OLED_Init(void);
    void OLED_Clear(void);
    void OLED_DisplayOn(void);
    void OLED_DisplayOff(void);

    #endif

    /* oled_ssd1306.c */
    #include "oled_ssd1306.h"

    void OLED_Init(void)
    {
    HAL_Delay(100); // 上电等待

    OLED_WriteCmd(0xAE); // 关闭显示
    OLED_WriteCmd(0x20); // 设置内存寻址模式
    OLED_WriteCmd(0x02); // 页寻址模式
    OLED_WriteCmd(0xB0); // 设置页起始地址(Page 0)
    OLED_WriteCmd(0xC8); // 扫描方向:从上到下
    OLED_WriteCmd(0x00); // 设置列地址低 4 位
    OLED_WriteCmd(0x10); // 设置列地址高 4 位
    OLED_WriteCmd(0x40); // 设置显示起始行
    OLED_WriteCmd(0x81); // 设置对比度
    OLED_WriteCmd(0x7F); // 对比度值
    OLED_WriteCmd(0xA1); // 段重映射:列地址 0 映射到 SEG0
    OLED_WriteCmd(0xA6); // 正常显示(非反显)
    OLED_WriteCmd(0xA8); // 设置多路复用比
    OLED_WriteCmd(0x3F); // 64 路
    OLED_WriteCmd(0xA4); // 关闭整个显示(使用 RAM 内容)
    OLED_WriteCmd(0xD3); // 设置显示偏移
    OLED_WriteCmd(0x00); // 偏移 0
    OLED_WriteCmd(0xD5); // 设置时钟分频
    OLED_WriteCmd(0x80); // 推荐值
    OLED_WriteCmd(0xD9); // 设置预充电周期
    OLED_WriteCmd(0xF1); // 推荐值
    OLED_WriteCmd(0xDA); // 设置 COM 引脚硬件配置
    OLED_WriteCmd(0x12); // 推荐值
    OLED_WriteCmd(0xDB); // 设置 VCOMH 电压
    OLED_WriteCmd(0x40); // 推荐值
    OLED_WriteCmd(0x8D); // 设置电荷泵
    OLED_WriteCmd(0x14); // 开启电荷泵
    OLED_WriteCmd(0xAF); // 开启显示

    OLED_Clear();
    }

    void OLED_Clear(void)
    {
    uint8_t page, col;
    for (page = 0; page < 8; page++)
    {
    OLED_WriteCmd(0xB0 + page); // 设置页地址
    OLED_WriteCmd(0x00); // 列低地址
    OLED_WriteCmd(0x10); // 列高地址
    for (col = 0; col < 128; col++)
    {
    OLED_WriteData(0x00); // 清空该像素
    }
    }
    }

    void OLED_DisplayOn(void)
    {
    OLED_WriteCmd(0xAF);
    }

    void OLED_DisplayOff(void)
    {
    OLED_WriteCmd(0xAE);
    }

    5.3 显示单个字符

    SSD1306 本身不包含字库,需要自行准备 ASCII 字符的点阵数据。下面以 8×16 字体为例。

    /* oled_font.h */
    #ifndef OLED_FONT_H
    #define OLED_FONT_H

    extern const uint8_t F8x16[][16];

    #endif

    /* oled_font.c */
    #include "oled_font.h"

    /* 8×16 字体,每个字符 16 字节,每字节表示一列 8 个像素 */
    const uint8_t F8x16[][16] =
    {
    /* 字符 'A' (0x41) */
    {
    0x00, 0x00, 0x00, 0x10, 0x28, 0x44, 0x82, 0xFE,
    0x82, 0x82, 0x82, 0x82, 0x82, 0x00, 0x00, 0x00
    },
    /* 字符 'B' (0x42) */
    {
    0x00, 0x00, 0x00, 0xFC, 0x82, 0x82, 0x82, 0xFC,
    0x82, 0x82, 0x82, 0x82, 0xFC, 0x00, 0x00, 0x00
    },
    /* 更多字符请按 ASCII 码顺序补充 */
    };

    提示:完整的 8×16 字库文件较长,通常作为独立头文件引入。也可以使用取模软件(如 PCtoLCD2002)生成任意字符的点阵数据。

    5.4 显示字符串

    /* oled_show.h */
    #ifndef OLED_SHOW_H
    #define OLED_SHOW_H

    #include "oled_ssd1306.h"

    void OLED_ShowChar(uint8_t x, uint8_t y, char ch);
    void OLED_ShowString(uint8_t x, uint8_t y, const char *str);
    void OLED_SetPos(uint8_t x, uint8_t y);

    #endif

    /* oled_show.c */
    #include "oled_show.h"
    #include "oled_font.h"

    /* 设置显示位置:x 为列(0-127),y 为页(0-7) */
    void OLED_SetPos(uint8_t x, uint8_t y)
    {
    OLED_WriteCmd(0xB0 + y); // 设置页地址
    OLED_WriteCmd(((x & 0xF0) >> 4) | 0x10); // 列高 4 位
    OLED_WriteCmd(x & 0x0F); // 列低 4 位
    }

    /* 显示单个字符(8×16 字体,占 2 页) */
    void OLED_ShowChar(uint8_t x, uint8_t y, char ch)
    {
    uint8_t i;
    if (ch < 0x20 || ch > 0x7E) // 仅支持可打印 ASCII
    ch = ' ';

    /* 上半部分(8 像素高) */
    OLED_SetPos(x, y);
    for (i = 0; i < 8; i++)
    {
    OLED_WriteData(F8x16[(ch – 0x20) * 16 + i]);
    }

    /* 下半部分(8 像素高) */
    OLED_SetPos(x, y + 1);
    for (i = 8; i < 16; i++)
    {
    OLED_WriteData(F8x16[(ch – 0x20) * 16 + i]);
    }
    }

    /* 显示字符串 */
    void OLED_ShowString(uint8_t x, uint8_t y, const char *str)
    {
    while (*str)
    {
    OLED_ShowChar(x, y, *str);
    x += 8; // 每个字符占 8 列
    if (x > 120) // 超出屏幕宽度则换行
    {
    x = 0;
    y += 2;
    }
    str++;
    }
    }

    5.5 显示汉字

    汉字为 16×16 点阵,需要 2 页 × 16 列。同样使用取模软件生成点阵数据。

    /* 16×16 汉字点阵示例:'中' */
    const uint8_t HZ16[][32] =
    {
    {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    }
    /* 实际数据请用取模软件生成 */
    };

    void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t index)
    {
    uint8_t i;
    OLED_SetPos(x, y);
    for (i = 0; i < 16; i++)
    {
    OLED_WriteData(HZ16[index][i]);
    }
    OLED_SetPos(x, y + 1);
    for (i = 16; i < 32; i++)
    {
    OLED_WriteData(HZ16[index][i]);
    }
    }

    6. 完整示例:显示传感器数据

    下面是一个综合示例,演示如何在 OLED 上显示 ADC 采集的电压值。

    /* main.c 中的主循环示例 */
    #include "oled_show.h"
    #include "adc.h"
    #include <stdio.h>

    extern ADC_HandleTypeDef hadc1;

    char buf[32];

    int main(void)
    {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_ADC1_Init();

    OLED_Init();
    OLED_ShowString(0, 0, "STM32 OLED Demo");
    OLED_ShowString(0, 2, "ADC Voltage:");

    while (1)
    {
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 100);
    uint16_t adc_val = HAL_ADC_GetValue(&hadc1);
    HAL_ADC_Stop(&hadc1);

    float voltage = adc_val * 3.3f / 4095.0f;
    sprintf(buf, "%.2f V", voltage);

    /* 先清空旧数据区域 */
    OLED_SetPos(0, 4);
    for (int i = 0; i < 16; i++)
    OLED_WriteData(0x00);

    OLED_ShowString(0, 4, buf);
    HAL_Delay(500);
    }
    }

    7. 常用第三方库推荐

    除了手写驱动,也可以直接使用成熟的开源库,节省开发时间。

    7.1 U8g2 库

    U8g2 是目前最流行的嵌入式图形库之一,支持 SSD1306 等多种控制器,提供丰富的绘图 API。

    #include "u8g2.h"

    u8g2_t u8g2; // 全局 U8g2 对象,所有绘图操作都基于它

    /* 初始化 U8g2 并配置 SSD1306(I2C 接口,128×64) */
    void u8g2_setup(void)
    {
    /* 1. 绑定控制器与通信方式:SSD1306、I2C、128×64、非 RAM 缓冲模式 */
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(
    &u8g2, U8G2_R0, // 旋转方向:0 度
    u8x8_byte_sw_i2c, // 软件 I2C 字节发送函数
    u8x8_gpio_and_delay); // GPIO 与延时回调

    /* 2. 初始化显示控制器(发送初始化命令序列) */
    u8g2_InitDisplay(&u8g2);

    /* 3. 关闭省电模式,点亮屏幕 */
    u8g2_SetPowerSave(&u8g2, 0);
    }

    /* 常用绘图操作示例:字符串、矩形、图片 */
    void u8g2_demo(void)
    {
    /* 清空内部缓冲区,准备绘制新一帧 */
    u8g2_ClearBuffer(&u8g2);

    /* 设置字体(8×13 等宽字体) */
    u8g2_SetFont(&u8g2, u8g2_font_8x13_tr);

    /* 在 (0, 10) 处绘制字符串 */
    u8g2_DrawStr(&u8g2, 0, 10, "Hello STM32!");

    /* 绘制一个空心矩形:左上角 (10, 20),宽 60,高 30 */
    u8g2_DrawFrame(&u8g2, 10, 20, 60, 30);

    /* 绘制一个实心矩形:左上角 (80, 20),宽 40,高 30 */
    u8g2_DrawBox(&u8g2, 80, 20, 40, 30);

    /* 绘制一条直线:从 (10, 55) 到 (120, 55) */
    u8g2_DrawLine(&u8g2, 10, 55, 120, 55);

    /* 绘制一张 16×16 的位图(XBM 格式),放在 (0, 40) 处 */
    /* 位图数据可用取模软件生成,这里以全 0 占位 */
    static const uint8_t bitmap[32] = { 0 };
    u8g2_DrawXBM(&u8g2, 0, 40, 16, 16, bitmap);

    /* 将缓冲区内容一次性发送到 OLED 显示 */
    u8g2_SendBuffer(&u8g2);
    }

    8. 常见问题与排查

    在实际开发中,OLED 屏不亮、花屏、乱码等问题非常常见。下面针对典型故障给出原因分析与排查步骤。

    8.1 OLED 不亮

    可能原因:

    • 供电不足或电压错误(未接 3.3V 而接了 5V,或电源电流不够)。
    • 接线错误,VCC/GND 接反。
    • 初始化命令序列未执行,或 OLED_Init() 未被调用。
    • 电荷泵未开启(缺少 0x8D, 0x14 命令)。
    • 对比度设置过低(0x81 后的值太小)。

    排查步骤:

  • 用万用表测量 OLED 的 VCC 与 GND 之间电压,确认在 3.3V 左右。
  • 检查接线是否与 3.1/3.2 节表格一致,重点核对 VCC/GND 极性。
  • 确认 main() 中已调用 OLED_Init(),且初始化命令完整(尤其 0x8D, 0x14 开启电荷泵)。
  • 将对比度命令 0x81 后的值调大(如 0xFF)再测试。
  • 代码修复建议:

    /* 确保初始化中包含开启电荷泵的命令 */
    OLED_WriteCmd(0x8D); // 设置电荷泵
    OLED_WriteCmd(0x14); // 开启电荷泵(0x10 为关闭)
    OLED_WriteCmd(0xAF); // 开启显示

    8.2 花屏 / 显示异常

    可能原因:

    • 初始化命令序列不完整或顺序错误。
    • 显示缓冲区未清空,残留旧数据。
    • 页地址或列地址设置错误,导致数据写入位置错乱。
    • 电源纹波过大,或 SPI 模式下时钟极性/相位配置错误。

    排查步骤:

  • 对照 5.2 节核对初始化命令序列,确保每条命令及参数完整、顺序一致。
  • 在显示新内容前调用 OLED_Clear() 清屏。
  • 检查 OLED_SetPos() 中页地址(0xB0 + y)与列地址的计算是否正确。
  • SPI 模式下检查 CubeMX 中 SPI 的 CPOL/CPHA 是否与 OLED 模块要求一致(通常为 Mode 0 或 Mode 3)。
  • 代码修复建议:

    /* 显示新内容前先清屏,避免残留 */
    OLED_Clear();

    /* 设置正确的显示位置后再写入数据 */
    OLED_SetPos(0, 0);
    OLED_ShowString(0, 0, "Hello");

    8.3 显示乱码

    可能原因:

    • 字库数据与字符编码不匹配(如使用 GB2312 编码却按 ASCII 解析)。
    • 取模方式错误(列行式/行列式、阴码/阳码不匹配)。
    • 汉字点阵数据未正确生成或索引越界。
    • 字符串编码与编译器字符集不一致。

    排查步骤:

  • 确认 ASCII 字符使用 8×16 字库,且 OLED_ShowChar() 中索引计算 (ch – 0x20) * 16 正确。
  • 汉字使用 16×16 点阵,确认取模软件设置为「列行式、阴码、逐列式」。
  • 检查 OLED_ShowChinese() 传入的 index 是否在字库数组范围内。
  • 若使用 U8g2,确认字体选择正确(如 u8g2_font_unifont_t_chinese2 支持中文)。
  • 代码修复建议:

    /* 使用 U8g2 显示中文时,需选择支持中文的字体 */
    u8g2_SetFont(&u8g2, u8g2_font_unifont_t_chinese2);
    u8g2_DrawUTF8(&u8g2, 0, 20, "你好 STM32");

    8.4 I2C 通信失败

    可能原因:

    • I2C 地址错误(应为 0x78 或 0x7A,取决于 SA0 引脚电平)。
    • SCL/SDA 上拉电阻缺失或阻值不当。
    • 引脚复用配置错误(PB6/PB7 未配置为 I2C1 功能)。
    • 总线被其他设备占用,或时序不满足要求。

    排查步骤:

  • 用逻辑分析仪或示波器观察 SCL/SDA 波形,确认有正常的起始/停止位。
  • 核对 OLED_ADDR 宏定义:7 位地址 0x3C 左移一位为 0x78;若模块 SA0 接高电平则为 0x7A。
  • 确认 CubeMX 中 PB6/PB7 已配置为 I2C1_SCL/I2C1_SDA,且开启内部上拉或外接 4.7kΩ 上拉电阻。
  • 检查 HAL_I2C_Master_Transmit 返回值,若非 HAL_OK 则打印错误码定位。
  • 代码修复建议:

    /* 检查 I2C 发送返回值,便于定位通信问题 */
    HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100);
    if (status != HAL_OK)
    {
    /* 通信失败:检查地址、上拉电阻与引脚配置 */
    Error_Handler();
    }

    8.5 常见问题对比表

    症状可能原因解决方案
    OLED 完全不亮 供电不足/接线错误 检查 VCC/GND 电压与极性,确保 3.3V 供电
    OLED 完全不亮 初始化未执行或电荷泵未开启 调用 OLED_Init(),确保包含 0x8D, 0x14 命令
    花屏/显示错乱 初始化命令不完整或顺序错误 对照 5.2 节核对命令序列
    花屏/显示错乱 未清屏导致残留 显示前调用 OLED_Clear()
    显示乱码 字库/取模方式不匹配 重新取模,核对阴码/阳码与列行式
    显示乱码 汉字索引越界 检查 OLED_ShowChinese() 的 index 参数
    I2C 通信失败 设备地址错误 核对 OLED_ADDR 为 0x78 或 0x7A
    I2C 通信失败 上拉电阻缺失 外接 4.7kΩ 上拉或开启内部上拉
    I2C 通信失败 引脚复用配置错误 在 CubeMX 中确认 PB6/PB7 配置为 I2C1
    赞(0)
    未经允许不得转载:171主机测评 » STM32 OLED 显示屏使用详解:从硬件连接到代码实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址