回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

Arduino 直接驱动ssd1306 OLED 简单

头像 zoologist 2026.07.25 5 0

最近入手了一个 0.91寸OLED ,主控是 SSD1306。

为了更方便的在项目中驱动这款 OLED 研究了一下直接使用代码驱动,不使用第三方库来进行显示。

image.png

代码
#include <Wire.h>
#include "Font/ssd1306_font5x8.h"

/*===============================C header===============================*/
#define SSD1306_ADDR 0x3C
#define SSD1306_WIDTH 128
#define SSD1306_PAGE_HEIGHT 8
#define SSD1306_MAX_SCALE 8

typedef struct {
  const uint8_t width;
  const uint8_t height;
  const uint8_t space;
  const uint8_t first_char;
  const uint8_t last_char;
  const uint8_t *data;
} font_t;

typedef enum {
  SSD1306_HEIGHT_16 = 16,
  SSD1306_HEIGHT_32 = 32,
  SSD1306_HEIGHT_64 = 64
} ssd1306_height_t;

typedef enum {
  SSD1306_MEMORY_ADDRESSING_MODE_HORIZONTAL = 0x00,
  SSD1306_MEMORY_ADDRESSING_MODE_VERTICAL = 0x01,
  SSD1306_MEMORY_ADDRESSING_MODE_PAGE = 0x02
} ssd1306_mam_t;

typedef enum {
  SSD1306_MULTIPLEX_RATIO_16 = 0x0F,
  SSD1306_MULTIPLEX_RATIO_32 = 0x1F,
  SSD1306_MULTIPLEX_RATIO_64 = 0x3F
} ssd1306_mux_t;

typedef enum {
  SSD1306_COM_PINS_SEQUENTIAL_NO_REMAP = 0x02,
  SSD1306_COM_PINS_ALTERNATIVE_NO_REMAP = 0x12,
  SSD1306_COM_PINS_SEQUENTIAL_REMAP = 0x22,
  SSD1306_COM_PINS_ALTERNATIVE_REMAP = 0x32
} ssd1306_com_pins_t;

typedef enum {
  SSD1306_VCOMH_065 = 0x00,
  SSD1306_VCOMH_077 = 0x20,
  SSD1306_VCOMH_083 = 0x30
} ssd1306_vcomh_t;

static uint8_t height = SSD1306_HEIGHT_64;
static uint8_t pages = SSD1306_HEIGHT_64 / SSD1306_PAGE_HEIGHT;

const font_t Font5x8 = { 5, 8, 1, 32, 126, (const uint8_t *)font5x8 };

static uint8_t cur_scale = 1;
static const font_t *cur_font = &Font5x8;

static uint16_t cmd_buf_index = 0;
static uint8_t cmd_buf[31];
static uint16_t data_buf_index = 0;
static uint16_t update_data_buf_index = 0;
static uint8_t *data_buf;

static uint8_t data_buf_16[SSD1306_WIDTH * SSD1306_HEIGHT_16 / SSD1306_PAGE_HEIGHT];
static uint8_t data_buf_32[SSD1306_WIDTH * SSD1306_HEIGHT_32 / SSD1306_PAGE_HEIGHT];
static uint8_t data_buf_64[SSD1306_WIDTH * SSD1306_HEIGHT_64 / SSD1306_PAGE_HEIGHT];

/*===============================C code===============================*/
void ssd1306_setup(ssd1306_height_t ssd1306_height) {
  height = ssd1306_height;
  pages = height / SSD1306_PAGE_HEIGHT;

  switch (ssd1306_height) {
    case 16:
      data_buf = data_buf_16;
      break;
    case 32:
      data_buf = data_buf_32;
      break;
    default:
      data_buf = data_buf_64;
      break;
  }
}
static void add_command(uint8_t cmd) {
  cmd_buf[cmd_buf_index] = cmd;
  cmd_buf_index++;
}

static void set_contrast_control(uint8_t percent) {
  if (percent > 100) {
    percent = 100;
  }
  add_command(0x81);
  add_command(percent * 255 / 100);
}

static void set_entire_display_on(bool on) {
  add_command(on ? 0xA5 : 0xA4);
}

static void set_normal_inverse_display(bool inverse) {
  add_command(inverse ? 0xA7 : 0xA6);
}

static void set_display_on_off(bool on) {
  add_command(on ? 0xAF : 0xAE);
}

static void set_memory_addressing_mode(ssd1306_mam_t mode) {
  add_command(0x20);
  add_command(mode);
}

static void set_display_start_line(uint8_t line) {
  if (line > height - 1) {
    line = height - 1;
  }
  add_command(0x40 | line);
}

// (RESET = false)
static void set_segment_remap(bool remap) {
  add_command(remap ? 0xA1 : 0xA0);
}

// (RESET = SSD1306_MULTIPLEX_RATIO_64)
static void set_multiplex_ratio(ssd1306_mux_t ratio) {
  add_command(0xA8);
  add_command(ratio);
}

static void set_com_scan_direction(bool remapped) {
  add_command(remapped ? 0xC8 : 0xC0);
}

static void set_display_offset(uint8_t offset) {
  if (offset > height - 1) {
    offset = height - 1;
  }
  add_command(0xD3);
  add_command(offset);
}

static void set_display_clock(uint8_t clock, uint8_t osc_freq) {
  if (clock > 15) {
    clock = 15;
  }
  if (osc_freq > 15) {
    osc_freq = 15;
  }
  add_command(0xD5);
  add_command((osc_freq << 4) | clock);
}

static void set_com_pins(ssd1306_com_pins_t com_pins) {
  add_command(0xDA);
  add_command(com_pins);
}

static void set_precharge_period(uint8_t period) {
  add_command(0xD9);
  add_command(period);
}

static void set_vcomh_deselect_level(ssd1306_vcomh_t level) {
  add_command(0xDB);
  add_command(level);
}

static void set_charge_pump_setting(bool enable) {
  add_command(0x8D);
  add_command(enable ? 0x14 : 0x10);
}

void ssd1306_i2c_write(uint8_t control, const uint8_t *data, uint16_t len) {
  uint16_t i = 0;
  while (i < len) {
    uint8_t chunkSize = min(len - i, 31);

    Wire.beginTransmission(SSD1306_ADDR);
    Wire.write(control);
    Wire.write(&data[i], chunkSize);
    uint8_t result = Wire.endTransmission();

    if (result != 0) {
      Serial.print(F("ssd1306_i2c_write error: "));
      Serial.println(result);
      return;
    }

    i += chunkSize;
  }
}
void ssd1306_init(void) {
  cmd_buf_index = 0;

  set_contrast_control(50);
  set_entire_display_on(false);
  set_normal_inverse_display(false);
  set_display_on_off(false);

  // set_column_start_address(0);
  set_memory_addressing_mode(SSD1306_MEMORY_ADDRESSING_MODE_HORIZONTAL);
  // set_column_address(0, SSD1306_WIDTH - 1);
  // set_page_address(0, pages - 1);
  // set_page_start_address(0);

  set_display_start_line(0);
  set_segment_remap(false);
  ssd1306_mux_t mux;
  ssd1306_com_pins_t com_pins;
  if (height == 16) {
    mux = SSD1306_MULTIPLEX_RATIO_16;
    com_pins = SSD1306_COM_PINS_SEQUENTIAL_NO_REMAP;
  } else if (height == 32) {
    mux = SSD1306_MULTIPLEX_RATIO_32;
    com_pins = SSD1306_COM_PINS_SEQUENTIAL_NO_REMAP;
  } else {
    mux = SSD1306_MULTIPLEX_RATIO_64;
    com_pins = SSD1306_COM_PINS_ALTERNATIVE_NO_REMAP;
  }
  set_multiplex_ratio(mux);
  set_com_scan_direction(false);
  set_display_offset(0);
  set_com_pins(com_pins);

  set_display_clock(0, 8);
  set_precharge_period(0x22);
  set_vcomh_deselect_level(SSD1306_VCOMH_077);

  set_charge_pump_setting(true);

  set_display_on_off(true);

  ssd1306_i2c_write(0x00, cmd_buf, cmd_buf_index + 1);
}

void ssd1306_clear(void) {
  memset(&data_buf[0], 0, SSD1306_WIDTH * pages);

  update_data_buf_index = SSD1306_WIDTH * pages - 1;
}

void ssd1306_set_caret(uint8_t col, uint8_t row) {
  if (col > SSD1306_WIDTH - 1) {
    col = SSD1306_WIDTH - 1;
  }
  if (row > height - 1) {
    row = height - 1;
  }

  data_buf_index = row * SSD1306_WIDTH + col;
}

static void scale_column(uint8_t col, uint8_t *out) {
  memset(out, 0, cur_scale);

  for (uint8_t i = 0; i < 8; i++) {
    uint8_t bit = (col >> i) & 0x01;
    if (!bit) {
      continue;
    }

    for (uint8_t j = 0; j < cur_scale; j++) {
      uint8_t dst_row = i * cur_scale + j;
      uint8_t byte_index = dst_row / 8;
      uint8_t bit_index = dst_row % 8;

      out[byte_index] |= (0x01 << bit_index);
    }
  }
}

void set_column_data(uint8_t col, uint8_t page, uint8_t page_offset, uint8_t col_data) {
  uint8_t scaled_bytes[cur_scale];
  if (cur_scale == 1) {
    scaled_bytes[0] = col_data;
  } else {
    scale_column(col_data, scaled_bytes);
  }
  for (uint8_t scale_h = 0; scale_h < cur_scale; scale_h++) {
    for (uint8_t scale_w = 0; scale_w < cur_scale; scale_w++) {
      uint8_t upper = scaled_bytes[scale_w] << page_offset;
      uint8_t lower = scaled_bytes[scale_w] >> (SSD1306_PAGE_HEIGHT - page_offset);

      uint16_t buf_index = (page + scale_w) * SSD1306_WIDTH + (col + scale_h);
      if (buf_index > SSD1306_WIDTH * pages - 1) {
        continue;
      }
      data_buf[buf_index] |= upper;
      if (lower) {
        buf_index = buf_index + SSD1306_WIDTH;
        data_buf[buf_index] |= lower;
      }

      if (buf_index > update_data_buf_index) {
        update_data_buf_index = buf_index;
      }
    }
  }
}

void ssd1306_print_char(char c) {
  if ((uint8_t)c < cur_font->first_char || (uint8_t)c > cur_font->last_char) {
    return;
  }

  uint8_t row = data_buf_index / SSD1306_WIDTH;
  uint8_t col = data_buf_index % SSD1306_WIDTH;
  if (col + cur_font->width * cur_scale > SSD1306_WIDTH) {
    col = 0;
    row += SSD1306_PAGE_HEIGHT * cur_scale;
    ssd1306_set_caret(col, row);
  }
  if (cur_font->height * cur_scale + row > height) {
    return;
  }

  uint16_t char_index = (uint8_t)c - cur_font->first_char;
  uint16_t char_offset = char_index * cur_font->width;
  const uint8_t *bitmap = &cur_font->data[char_offset];
  uint8_t page = row / SSD1306_PAGE_HEIGHT;
  uint8_t page_offset = row % SSD1306_PAGE_HEIGHT;

  for (uint8_t font_col = 0; font_col < cur_font->width; font_col++) {
    uint8_t col_data = pgm_read_byte(bitmap + font_col);
    set_column_data(col + font_col * cur_scale, page, page_offset, col_data);
  }

  col = col + cur_font->width * cur_scale + cur_font->space;

  ssd1306_set_caret(col, row);
}

void ssd1306_print(const char* str) {
  while (*str) {
    ssd1306_print_char(*str++);
  }
}

void ssd1306_set_scale(uint8_t scale) {
  if (scale == 0) {
    scale = 1;
  }
  if (scale > 8) {
    scale = 8;
  }
  cur_scale = scale;
}

static void set_column_address(uint8_t start, uint8_t end) {
  if (end > SSD1306_WIDTH - 1) {
    end = SSD1306_WIDTH - 1;
  }
  if (start > end) {
    start = end;
  }
  add_command(0x21);
  add_command(start);
  add_command(end);
}

static void set_page_address(uint8_t start, uint8_t end) {
  if (end > pages - 1) {
    end = pages - 1;
  }
  if (start > end) {
    start = end;
  }
  add_command(0x22);
  add_command(start);
  add_command(end);
}

void ssd1306_update(void) {
  cmd_buf_index = 0;
  set_column_address(0, SSD1306_WIDTH - 1);
  set_page_address(0, pages - 1);
  ssd1306_i2c_write(0x00, cmd_buf, cmd_buf_index + 1);

  ssd1306_i2c_write(0x40, data_buf, update_data_buf_index + 1);
  update_data_buf_index = 0;
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Wire.begin();
  ssd1306_setup(SSD1306_HEIGHT_64);
  ssd1306_init();
}

void loop() {
  ssd1306_clear();
  ssd1306_print_char('!');
  ssd1306_print("\"#$%&'()*+,-./");
  ssd1306_set_caret(10, 10);
  ssd1306_set_scale(2);
  ssd1306_print("0123456789");
  ssd1306_set_caret(40, 28);
  ssd1306_set_scale(1);
  ssd1306_print(":;<=>?@");
  ssd1306_set_caret(0, 40);
  ssd1306_print("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  ssd1306_print("[\\]^_`");
  ssd1306_print("abcdefghijklmnopqrstuvwxyz");
  ssd1306_print("{|}~");
  ssd1306_update();
}

最终运行效果如下

image.png

评论

user-avatar