最近入手了一个 0.91寸OLED ,主控是 SSD1306。
为了更方便的在项目中驱动这款 OLED 研究了一下直接使用代码驱动,不使用第三方库来进行显示。

代码
#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();
}最终运行效果如下


返回首页
回到顶部


评论