Skip to content

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