288 lines
9.5 KiB
C
288 lines
9.5 KiB
C
/**
|
|
* @file oled.c
|
|
* @brief SSD1315 OLED 128x64 I2C driver implementation
|
|
*/
|
|
#include <string.h>
|
|
#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);
|
|
}
|