SSD1312 显示屏驱动
SSD1312 简介
SSD1312 是一款常用的 OLED 显示屏驱动芯片,广泛应用于各种嵌入式系统中。它支持 I2C 和 SPI 接口,具有低功耗、高对比度和长寿命等特点。本文将介绍 SSD1312 的基本原理、接口定义和驱动方法。
SSD1312 芯片的I2C 的默认地址是0x3C,这是一个7位的地址,实际写入数据时, 左移1位, 0x3C 变为 0x78.
十六进制转换工具

SSD1312 显示的基本原理
- SSD1312的显示屏的列的概念是 OLED 水平方向上每个像素从上到下为一列,假如屏幕分辨率为128x64,那么OLED的分辨率为128列,64行.
- SSD1312的显示屏会分成页. 页的概念是 OLED在竖直方向上,每8个行像素为一页. 这里8是固定值,都是以8行像素为一个页. 如果屏幕分辨率为128x64,那么OLED的分辨率为128列,64行, 64/8 = 8页.
- 无论屏幕是横屏还是竖屏,数据写入的格式都是相同的,分页的方法也是完全的相同的.
- 我们在屏幕上显示的数据,是竖着扫描的.


SSD1312 初始化
只要是 SSD1312 显示屏,初始化过程基本相同。
c
const unsigned char OLED_init_cmd[27] = {
0xAE,//关闭显示
0x00,
0x10,
0xB0,
0x81, //设置对比度
0x5f, //值越大,像素点亮的电流越大
0x20, //设置内存寻址模式,
0x09, //如果90度,就改为0x09,默认0x02
0xA0, //列地址从0开始(左)增加到127(右)。即从左到右画。
0xC8, //行地址从63开始(下)减少到0(上)。即从下到上画。
0xA4,
0xA6,
0xA8, //设置复用率
0x3F,
0xD3,//设置显示偏移
0x00,
0xD5,//设置振荡器分频/频率
0x00,
0xD9,//设置预充电周期
0x22,
0xDA,//设置SEG引脚硬件配置
0x10,
0xDB,//设置VCOMH电压
0x30,
0x8D, //设置电荷泵使能/DC-DC控制
0x72,//这条命令(0x10)是启用它
0xAF//打开显示
};
void OLED_Init(void) {
HAL_Delay(500); // 上电延时,等待OLED稳定
uint8_t len= sizeof(OLED_init_cmd);
for (uint8_t i = 0; i < len; i++) {
OLED_WriteCmd(OLED_init_cmd[i]);
HAL_Delay(10); // 每条命令后添加短延时
}
HAL_Delay(500); // 初始化完成后添加延时
}初始化的函数,加了一些延时,保证OLED稳定. 函数中定义了一个初始化命令数组,数组中存放了OLED的初始化命令.函数中通过循环遍历数组,依次发送命令给OLED,完成初始化.
SSD1312 填充数据
c
void OLED_Clear(void) {
uint8_t i;
uint8_t blank[OLED_WIDTH]; // 一行的缓冲区
// 清空缓冲区
for (i = 0; i < OLED_WIDTH; i++) {
blank[i] = 0;
}
// 逐页清空,每页使用批量写入
for (i = 0; i < OLED_PAGE_NUM; i++) {
OLED_SetCursor(0, i * 8);
OLED_WriteData_Array(blank, OLED_WIDTH); // 一次写入OLED_WIDTH字节
}
}
void OLED_Fill(uint8_t fill) {
uint16_t i, j;
uint8_t buffer[OLED_WIDTH]; // 一行的缓冲区
// 填充缓冲区
for (j = 0; j < OLED_WIDTH; j++) {
buffer[j] = fill;
}
// 逐页填充,每页使用批量写入
for (i = 0; i < OLED_PAGE_NUM; i++) {
OLED_SetCursor(0, i * 8);
OLED_WriteData_Array(buffer, OLED_WIDTH); // 一次写入OLED_WIDTH字节
}
}SSD1312 写入数据的方法
- 指定写入的位置:
OLED_SetCursor(x, y); - 写入数据: 最多写入OLED_WIDTH字节的数据,可以使用
OLED_WriteData_Array(data, length);方法.
相关代码:
oled.c 文件的内容
c
#include "oled.h"
#include "i2c.h"
#include "stm32f1xx_hal.h"
#include "stm32f1xx_hal_i2c.h"
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
extern I2C_HandleTypeDef hi2c1;
const unsigned char OLED_init_cmd[27] = {
0xAE,//关闭显示
0x00,
0x10,
0xB0,
0x81, //设置对比度
0x5f, //值越大,像素点亮的电流越大
0x20, //设置内存寻址模式,
0x09, //如果90度,就改为0x09,默认0x02
0xA0, //列地址从0开始(左)增加到127(右)。即从左到右画。
0xC8, //行地址从63开始(下)减少到0(上)。即从下到上画。
0xA4,
0xA6,
0xA8, //设置复用率
0x3F,
0xD3,//设置显示偏移
0x00,
0xD5,//设置振荡器分频/频率
0x00,
0xD9,//设置预充电周期
0x22,
0xDA,//设置SEG引脚硬件配置
0x10,
0xDB,//设置VCOMH电压
0x30,
0x8D, //设置电荷泵使能/DC-DC控制
0x72,//这条命令(0x10)是启用它
0xAF//打开显示
};
void OLED_Init(void) {
HAL_Delay(500); // 上电延时,等待OLED稳定
uint8_t len= sizeof(OLED_init_cmd);
for (uint8_t i = 0; i < len; i++) {
OLED_WriteCmd(OLED_init_cmd[i]);
HAL_Delay(10); // 每条命令后添加短延时
}
HAL_Delay(500); // 初始化完成后添加延时
}
void OLED_WriteCmd(uint8_t cmd) {
uint8_t data[2];
data[0] = OLED_CMD;
data[1] = cmd;
uint8_t addr = OLED_ADDRESS << 1;
HAL_StatusTypeDef status =
HAL_I2C_Master_Transmit(&hi2c1, addr, data, 2, 1000);
if (status != HAL_OK) {
// 通信失败,重试一次
HAL_Delay(10);
HAL_I2C_Master_Transmit(&hi2c1, addr, data, 2, 1000);
}
}
void OLED_WriteData(uint8_t data) {
uint8_t data1[2];
data1[0] = OLED_DATA;
data1[1] = data;
uint8_t addr = OLED_ADDRESS << 1;
HAL_StatusTypeDef status =
HAL_I2C_Master_Transmit(&hi2c1, addr, data1, 2, 1000);
if (status != HAL_OK) {
// 通信失败,重试一次
HAL_Delay(10);
HAL_I2C_Master_Transmit(&hi2c1, addr, data1, 2, 1000);
}
}
void OLED_SetCursor(uint8_t x, uint8_t y) {
if (x >= OLED_WIDTH || y >= OLED_HEIGHT)
return; // 边界检查
uint8_t page = y >> 3; // y/8,确定在第几页
OLED_WriteCmd(0xB0 + page); // 设置页地址
OLED_WriteCmd(((x & 0xF0) >> 4) | 0x10); // 设置列地址高4位
OLED_WriteCmd((x & 0x0F) | 0x00); // 设置列地址低4位
}
void OLED_WriteData_Array(uint8_t *pData, uint16_t size) {
// 创建缓冲区:控制字节 + 数据(最大128字节)
uint8_t data[OLED_WIDTH+1];
data[0] = OLED_DATA; // 控制字节
for (uint16_t i = 0; i < size && i < 128; i++) {
data[i + 1] = pData[i]; // 复制数据
}
uint8_t addr = OLED_ADDRESS << 1;
// 一次性发送控制字节和数据
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(
&hi2c1, addr, data, (size > OLED_WIDTH ? OLED_WIDTH+1 : size + 1), 1000);
if (status != HAL_OK) {
// 通信失败,可以添加错误处理代码
HAL_Delay(10);
HAL_I2C_Master_Transmit(&hi2c1, addr, data, (size > OLED_WIDTH ? OLED_WIDTH+1 : size + 1),
1000);
}
}
void OLED_Clear(void) {
uint8_t i;
uint8_t blank[OLED_WIDTH]; // 一行的缓冲区
// 清空缓冲区
for (i = 0; i < OLED_WIDTH; i++) {
blank[i] = 0;
}
// 逐页清空,每页使用批量写入
for (i = 0; i < OLED_PAGE_NUM; i++) {
OLED_SetCursor(0, i * 8);
OLED_WriteData_Array(blank, OLED_WIDTH); // 一次写入OLED_WIDTH字节
}
}
void OLED_Fill(uint8_t fill) {
uint16_t i, j;
uint8_t buffer[OLED_WIDTH]; // 一行的缓冲区
// 填充缓冲区
for (j = 0; j < OLED_WIDTH; j++) {
buffer[j] = fill;
}
// 逐页填充,每页使用批量写入
for (i = 0; i < OLED_PAGE_NUM; i++) {
OLED_SetCursor(0, i * 8);
OLED_WriteData_Array(buffer, OLED_WIDTH); // 一次写入OLED_WIDTH字节
}
}
void OLED_DrawPoint(uint8_t x, uint8_t y) {
if (x >= OLED_WIDTH || y >= OLED_HEIGHT)
return; // 边界检查
uint8_t bit_pos = y & 0x07; // y%8,确定在页内的第几位
OLED_SetCursor(x, y);
// 读取当前列当前页的数据,修改指定位,然后写回
// 注意:由于I2C无法直接读取,通常需要维护一个显存缓冲区
// 简化版本:直接写入数据(会覆盖整个字节)
OLED_WriteData(1 << bit_pos); // 只点亮指定的位
}
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
int16_t dx = abs(x2 - x1);
int16_t dy = abs(y2 - y1);
int16_t sx = (x1 < x2) ? 1 : -1;
int16_t sy = (y1 < y2) ? 1 : -1;
int16_t err = dx - dy;
while (1) {
OLED_DrawPoint(x1, y1);
if (x1 == x2 && y1 == y2)
break;
int16_t e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x1 += sx;
}
if (e2 < dx) {
err += dx;
y1 += sy;
}
}
}
void OLED_DrawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2,
uint8_t fill) {
// 确保x1 <= x2, y1 <= y2
if (x1 > x2) {
uint8_t temp = x1;
x1 = x2;
x2 = temp;
}
if (y1 > y2) {
uint8_t temp = y1;
y1 = y2;
y2 = temp;
}
if (fill) {
// 填充矩形
for (uint8_t y = y1; y <= y2; y++) {
OLED_DrawLine(x1, y, x2, y);
}
} else {
// 只画矩形边框
OLED_DrawLine(x1, y1, x2, y1); // 上边
OLED_DrawLine(x1, y2, x2, y2); // 下边
OLED_DrawLine(x1, y1, x1, y2); // 左边
OLED_DrawLine(x2, y1, x2, y2); // 右边
}
}
void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r, uint8_t fill) {
int16_t a = 0, b = r;
int16_t di = 3 - 2 * r;
while (a <= b) {
if (fill) {
// 填充圆
OLED_DrawLine(x - a, y - b, x + a, y - b);
OLED_DrawLine(x - a, y + b, x + a, y + b);
OLED_DrawLine(x - b, y - a, x + b, y - a);
OLED_DrawLine(x - b, y + a, x + b, y + a);
} else {
// 只画圆周
OLED_DrawPoint(x + a, y - b);
OLED_DrawPoint(x + b, y - a);
OLED_DrawPoint(x + b, y + a);
OLED_DrawPoint(x + a, y + b);
OLED_DrawPoint(x - a, y + b);
OLED_DrawPoint(x - b, y + a);
OLED_DrawPoint(x - b, y - a);
OLED_DrawPoint(x - a, y - b);
}
a++;
if (di < 0) {
di += 4 * a + 6;
} else {
di += 10 - 4 * (b--);
}
}
}oled.h 文件内容
c
#ifndef __I2C_OLED_H__
#define __I2C_OLED_H__
#include "stm32f1xx_hal.h"
#include "i2c.h"
// 显示模块,芯片是SSD1312,使用I2C通信,使用I2C驱动库
#define OLED_ADDRESS 0x3C // 0x78是8位地址,HAL库使用7位地址,所以右移一位
#define OLED_WIDTH 64
#define OLED_HEIGHT 128
#define OLED_CMD 0x00
#define OLED_DATA 0x40
#define OLED_PAGE_ADDR_CMD 0xB0 // Page Address Command (B0h to B7h for pages 0-7)
#define OLED_COLUMN_ADDR_CMD 0x00 // Lower Column Address (0x00-0x0F)
#define OLED_COLUMN_ADDR_MSB 0x10 // Higher Column Address (0x10-0x1F)
#define OLED_PAGE_NUM OLED_HEIGHT/8 // 64像素高 / 8 = 8页
#define OLED_COLUMN_NUM OLED_WIDTH // OLED_WIDTH列
void OLED_Init(void);
void OLED_WriteCmd(uint8_t cmd);
void OLED_WriteData(uint8_t data);
void OLED_WriteData_Array(uint8_t *pData, uint16_t size);
void OLED_Clear(void);
void OLED_Fill(uint8_t fill);
void OLED_DrawPoint(uint8_t x, uint8_t y);
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
void OLED_DrawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t fill);
void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r, uint8_t fill);
#endif