Skip to content

ssd1306芯片的OLED显示屏

一.SSD1306 芯片简介

SSD1306 与 SSD1312芯片其驱动逻辑都是一样的,只是数据总线不同,SSD1306 芯片的数据总线是I2C,SSD1312芯片的数据也是I2C.
请基本逻辑请参考:SSD1312 芯片

二. 关于SSD1306 的寻址方式

在初始化代码中,有一个

c
    0x20, // 设置内存地址模式
    0x02, //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;

三种寻址模式

SSD1312有三种内存寻址模式,由0x20命令控制:

  • 水平寻址模式​ (0x20, 0x00) - 你想要的方式 设置起始坐标(0,0)后,写入的数据会自动从左到右、从上到下连续填充,到达行尾后自动跳到下一行开头。这是最符合“一次性写入全屏”需求的模式。
  • 垂直寻址模式​ (0x20, 0x01) 数据从上到下、从左到右填充(不常用)。
  • 页寻址模式​ (0x20, 0x02) - 你当前代码的模式(图片中未显示但很可能被设置) 数据只在当前页内从左到右填充,不会自动跨页,这就是为什么你需要手动切换页地址。

当使用水平寻址模式时,屏幕的刷新的速度会更快,因为数据是连续的,不需要每次都切换页地址。但是,如果需要显示的内容跨越多页,那么使用页寻址模式会更方便。

三.驱动代码

其驱动代码与 SSD1312 的代码基本上是一样的,除了初始化代码不一样外,其它的代码基本上都是一样的.

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[25] = {
    /*0xae,0X00,0X10,0x40,0X81,0XCF,0xff,0xa1,0xa4,
    0xA6,0xc8,0xa8,0x3F,0xd5,0x80,0xd3,0x00,0XDA,0X12,
    0x8d,0x14,0xdb,0x40,0X20,0X02,0xd9,0xf1,0xAF*/
    0xAE, // 关闭显示
    0xD5, // 设置时钟分频因子,震荡频率
    0x80, //[3:0],分频因子;[7:4],震荡频率

    0xA8, // 设置驱动路数
    0X3F, // 默认0X3F(1/64)
    0xD3, // 设置显示偏移
    0X00, // 默认为0
    0x40, // 设置显示开始行 [5:0],行数.
    0x8D, // 电荷泵设置
    0x14, // bit2,开启/关闭
    0x20, // 设置内存地址模式
    0x02, //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
    0xA1, // 段重定义设置,bit0:0,0->0;1,0->127;
    0xC8, // 设置COM扫描方向;bit3:0,普通模式;1,重定义模式
          // COM[N-1]->COM0;N:驱动路数
    0xDA, // 设置COM硬件引脚配置
    0x12, //[5:4]配置
    0x81, // 对比度设置
    0xEF, // 1~255;默认0X7F (亮度设置,越大越亮)
    0xD9, // 设置预充电周期
    0xf1, //[3:0],PHASE 1;[7:4],PHASE 2;
    0xDB, // 设置VCOMH 电压倍率
    0x30, //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
    0xA4, // 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
    0xA6, // 设置显示方式;bit0:1,反相显示;0,正常显示
    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 128
#define OLED_HEIGHT 64

#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