/** * @file oled.c * @brief SSD1315 OLED 128x64 I2C driver implementation */ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/i2c.h" #include "esp_log.h" #include "oled.h" static const char *TAG = "oled"; /* I2C configuration */ #define I2C_PORT I2C_NUM_0 #define OLED_ADDR 0x3C #define I2C_CLOCK_HZ 400000 /* Display buffer (128x64 / 8 = 1024 bytes) */ static uint8_t display_buffer[OLED_WIDTH * OLED_PAGES]; /* Simple 8x16 font (ASCII 0x20-0x7F) */ /* Using a basic 5x7 font doubled to 8x16 by row replication */ static const uint8_t font_8x8[][8] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* space */ {0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00}, /* ! */ {0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x00}, /* " */ {0x14,0x7F,0x14,0x7F,0x14,0x00,0x00,0x00}, /* # */ {0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00,0x00}, /* $ */ {0x23,0x13,0x08,0x64,0x62,0x00,0x00,0x00}, /* % */ {0x36,0x49,0x55,0x22,0x50,0x00,0x00,0x00}, /* & */ {0x00,0x05,0x03,0x00,0x00,0x00,0x00,0x00}, /* ' */ {0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00}, /* ( */ {0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00}, /* ) */ {0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00,0x00}, /* * */ {0x08,0x08,0x3E,0x08,0x08,0x00,0x00,0x00}, /* + */ {0x00,0x50,0x30,0x00,0x00,0x00,0x00,0x00}, /* , */ {0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00}, /* - */ {0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00}, /* . */ {0x20,0x10,0x08,0x04,0x02,0x00,0x00,0x00}, /* / */ {0x3E,0x51,0x49,0x45,0x3E,0x00,0x00,0x00}, /* 0 */ {0x00,0x42,0x7F,0x40,0x00,0x00,0x00,0x00}, /* 1 */ {0x42,0x61,0x51,0x49,0x46,0x00,0x00,0x00}, /* 2 */ {0x21,0x41,0x45,0x4B,0x31,0x00,0x00,0x00}, /* 3 */ {0x18,0x14,0x12,0x7F,0x10,0x00,0x00,0x00}, /* 4 */ {0x27,0x45,0x45,0x45,0x39,0x00,0x00,0x00}, /* 5 */ {0x3C,0x4A,0x49,0x49,0x30,0x00,0x00,0x00}, /* 6 */ {0x01,0x71,0x09,0x05,0x03,0x00,0x00,0x00}, /* 7 */ {0x36,0x49,0x49,0x49,0x36,0x00,0x00,0x00}, /* 8 */ {0x06,0x49,0x49,0x29,0x1E,0x00,0x00,0x00}, /* 9 */ {0x00,0x36,0x36,0x00,0x00,0x00,0x00,0x00}, /* : */ {0x00,0x56,0x36,0x00,0x00,0x00,0x00,0x00}, /* ; */ {0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00}, /* < */ {0x14,0x14,0x14,0x14,0x14,0x00,0x00,0x00}, /* = */ {0x41,0x22,0x14,0x08,0x00,0x00,0x00,0x00}, /* > */ {0x02,0x01,0x51,0x09,0x06,0x00,0x00,0x00}, /* ? */ {0x32,0x49,0x79,0x41,0x3E,0x00,0x00,0x00}, /* @ */ {0x7E,0x11,0x11,0x11,0x7E,0x00,0x00,0x00}, /* A */ {0x7F,0x49,0x49,0x49,0x36,0x00,0x00,0x00}, /* B */ {0x3E,0x41,0x41,0x41,0x22,0x00,0x00,0x00}, /* C */ {0x7F,0x41,0x41,0x22,0x1C,0x00,0x00,0x00}, /* D */ {0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00}, /* E */ {0x7F,0x09,0x09,0x01,0x01,0x00,0x00,0x00}, /* F */ {0x3E,0x41,0x41,0x51,0x32,0x00,0x00,0x00}, /* G */ {0x7F,0x08,0x08,0x08,0x7F,0x00,0x00,0x00}, /* H */ {0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00}, /* I */ {0x20,0x40,0x41,0x3F,0x01,0x00,0x00,0x00}, /* J */ {0x7F,0x08,0x14,0x22,0x41,0x00,0x00,0x00}, /* K */ {0x7F,0x40,0x40,0x40,0x40,0x00,0x00,0x00}, /* L */ {0x7F,0x02,0x04,0x02,0x7F,0x00,0x00,0x00}, /* M */ {0x7F,0x04,0x08,0x10,0x7F,0x00,0x00,0x00}, /* N */ {0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00}, /* O */ {0x7F,0x09,0x09,0x09,0x06,0x00,0x00,0x00}, /* P */ {0x3E,0x41,0x51,0x21,0x5E,0x00,0x00,0x00}, /* Q */ {0x7F,0x09,0x19,0x29,0x46,0x00,0x00,0x00}, /* R */ {0x46,0x49,0x49,0x49,0x31,0x00,0x00,0x00}, /* S */ {0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00}, /* T */ {0x3F,0x40,0x40,0x40,0x3F,0x00,0x00,0x00}, /* U */ {0x1F,0x20,0x40,0x20,0x1F,0x00,0x00,0x00}, /* V */ {0x7F,0x20,0x18,0x20,0x7F,0x00,0x00,0x00}, /* W */ {0x63,0x14,0x08,0x14,0x63,0x00,0x00,0x00}, /* X */ {0x03,0x04,0x78,0x04,0x03,0x00,0x00,0x00}, /* Y */ {0x61,0x51,0x49,0x45,0x43,0x00,0x00,0x00}, /* Z */ }; /* I2C write command */ static esp_err_t oled_write_cmd(uint8_t cmd) { i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); i2c_master_start(i2c_cmd); i2c_master_write_byte(i2c_cmd, (OLED_ADDR << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(i2c_cmd, 0x00, true); /* Co=0, D/C#=0 (command) */ i2c_master_write_byte(i2c_cmd, cmd, true); i2c_master_stop(i2c_cmd); esp_err_t ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, pdMS_TO_TICKS(100)); i2c_cmd_link_delete(i2c_cmd); return ret; } /* I2C write data */ static esp_err_t oled_write_data(const uint8_t *data, size_t len) { i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); i2c_master_start(i2c_cmd); i2c_master_write_byte(i2c_cmd, (OLED_ADDR << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(i2c_cmd, 0x40, true); /* Co=0, D/C#=1 (data) */ i2c_master_write(i2c_cmd, data, len, true); i2c_master_stop(i2c_cmd); esp_err_t ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, pdMS_TO_TICKS(100)); i2c_cmd_link_delete(i2c_cmd); return ret; } esp_err_t oled_init(int sda, int scl) { /* Initialize I2C */ i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = sda, .scl_io_num = scl, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = I2C_CLOCK_HZ, }; ESP_ERROR_CHECK(i2c_param_config(I2C_PORT, &conf)); ESP_ERROR_CHECK(i2c_driver_install(I2C_PORT, conf.mode, 0, 0, 0)); vTaskDelay(pdMS_TO_TICKS(100)); /* SSD1315 initialization sequence */ oled_write_cmd(0xAE); /* Display off */ oled_write_cmd(0xD5); /* Set display clock divide ratio */ oled_write_cmd(0x80); oled_write_cmd(0xA8); /* Set multiplex ratio */ oled_write_cmd(0x3F); /* 64 lines */ oled_write_cmd(0xD3); /* Set display offset */ oled_write_cmd(0x00); oled_write_cmd(0x40); /* Set display start line */ oled_write_cmd(0x8D); /* Charge pump setting */ oled_write_cmd(0x14); /* Enable */ oled_write_cmd(0x20); /* Memory addressing mode */ oled_write_cmd(0x00); /* Horizontal */ oled_write_cmd(0xA1); /* Segment remap (column 127 mapped to SEG0) */ oled_write_cmd(0xC8); /* COM output scan direction */ oled_write_cmd(0xDA); /* COM pins hardware configuration */ oled_write_cmd(0x12); /* Alternative pin configuration */ oled_write_cmd(0x81); /* Set contrast */ oled_write_cmd(0x7F); oled_write_cmd(0xD9); /* Set pre-charge period */ oled_write_cmd(0xF1); oled_write_cmd(0xDB); /* Set VCOMH deselect level */ oled_write_cmd(0x40); oled_write_cmd(0xA4); /* Display on (resume) */ oled_write_cmd(0xA6); /* Normal display (not inverted) */ oled_write_cmd(0xAF); /* Display on */ /* Clear display buffer */ memset(display_buffer, 0, sizeof(display_buffer)); oled_update(); ESP_LOGI(TAG, "OLED initialized"); return ESP_OK; } void oled_clear(void) { memset(display_buffer, 0, sizeof(display_buffer)); } void oled_clear_area(uint8_t x, uint8_t y, uint8_t w, uint8_t h) { for (uint8_t i = y; i < y + h; i++) { for (uint8_t j = x; j < x + w; j++) { if (j < OLED_WIDTH && i < OLED_HEIGHT) { uint16_t idx = (i / 8) * OLED_WIDTH + j; if (idx < sizeof(display_buffer)) { display_buffer[idx] &= ~(1 << (i % 8)); } } } } } void oled_draw_pixel(uint8_t x, uint8_t y, uint8_t color) { if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return; uint16_t idx = (y / 8) * OLED_WIDTH + x; if (idx >= sizeof(display_buffer)) return; if (color) { display_buffer[idx] |= (1 << (y % 8)); } else { display_buffer[idx] &= ~(1 << (y % 8)); } } void oled_draw_char(uint8_t x, uint8_t y, char c, uint8_t color) { if (c < 0x20 || c > 0x5A) c = 0x20; /* Space for non-printable */ c -= 0x20; for (uint8_t i = 0; i < 8; i++) { uint8_t line = font_8x8[(uint8_t)c][i]; for (uint8_t j = 0; j < 8; j++) { if (line & (0x80 >> j)) { oled_draw_pixel(x + j, y + i, color); } else { oled_draw_pixel(x + j, y + i, !color); } } } } void oled_draw_string(uint8_t x, uint8_t y, const char *str, uint8_t color) { while (*str) { oled_draw_char(x, y, *str, color); x += 8; if (x + 8 > OLED_WIDTH) { x = 0; y += 8; } str++; } } void oled_draw_top_string(const char *str, uint8_t color) { oled_draw_string(0, 0, str, color); } void oled_draw_hline(uint8_t x, uint8_t y, uint8_t w, uint8_t color) { for (uint8_t i = 0; i < w; i++) { oled_draw_pixel(x + i, y, color); } } void oled_draw_vline(uint8_t x, uint8_t y, uint8_t h, uint8_t color) { for (uint8_t i = 0; i < h; i++) { oled_draw_pixel(x, y + i, color); } } void oled_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color) { for (uint8_t i = y; i < y + h; i++) { for (uint8_t j = x; j < x + w; j++) { oled_draw_pixel(j, i, color); } } } void oled_update(void) { /* Set column address range */ oled_write_cmd(0x21); oled_write_cmd(0); oled_write_cmd(127); /* Set page address range */ oled_write_cmd(0x22); oled_write_cmd(0); oled_write_cmd(7); /* Write buffer */ oled_write_data(display_buffer, sizeof(display_buffer)); } void oled_display_on(void) { oled_write_cmd(0xAF); } void oled_display_off(void) { oled_write_cmd(0xAE); } void oled_set_contrast(uint8_t value) { oled_write_cmd(0x81); oled_write_cmd(value); }