Newer
Older
ESP32-RetroPlay / managed_components / espressif__ssd1306 / ssd1306.c
/*
 * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "driver/i2c.h"
#include "ssd1306.h"
#include "string.h" // for memset

#define SSD1306_WRITE_CMD           (0x00)
#define SSD1306_WRITE_DAT           (0x40)

#define COORDINATE_SWAP(x1, x2, y1, y2)  { int16_t temp = x1; x1 = x2, x2 = temp; \
                                                   temp = y1; y1 = y2; y2 = temp; }

typedef struct {
    i2c_port_t bus;
    uint16_t dev_addr;
    uint8_t s_chDisplayBuffer[128][8];
} ssd1306_dev_t;

static uint32_t _pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;
    while (n--) {
        result *= m;
    }
    return result;
}

static esp_err_t ssd1306_write_data(ssd1306_handle_t dev, const uint8_t *const data, const uint16_t data_len)
{
    ssd1306_dev_t *device = (ssd1306_dev_t *) dev;
    esp_err_t ret;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    ret = i2c_master_start(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, device->dev_addr | I2C_MASTER_WRITE, true);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, SSD1306_WRITE_DAT, true);
    assert(ESP_OK == ret);
    ret = i2c_master_write(cmd, data, data_len, true);
    assert(ESP_OK == ret);
    ret = i2c_master_stop(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_cmd_begin(device->bus, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);

    return ret;
}

static esp_err_t ssd1306_write_cmd(ssd1306_handle_t dev, const uint8_t *const data, const uint16_t data_len)
{
    ssd1306_dev_t *device = (ssd1306_dev_t *) dev;
    esp_err_t ret;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    ret = i2c_master_start(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, device->dev_addr | I2C_MASTER_WRITE, true);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, SSD1306_WRITE_CMD, true);
    assert(ESP_OK == ret);
    ret = i2c_master_write(cmd, data, data_len, true);
    assert(ESP_OK == ret);
    ret = i2c_master_stop(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_cmd_begin(device->bus, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);

    return ret;
}

static inline esp_err_t ssd1306_write_cmd_byte(ssd1306_handle_t dev, const uint8_t cmd)
{
    return ssd1306_write_cmd(dev, &cmd, 1);
}

void ssd1306_fill_rectangle(ssd1306_handle_t dev, uint8_t chXpos1,
                            uint8_t chYpos1, uint8_t chXpos2, uint8_t chYpos2, uint8_t chDot)
{
    uint8_t chXpos, chYpos;

    for (chXpos = chXpos1; chXpos <= chXpos2; chXpos++) {
        for (chYpos = chYpos1; chYpos <= chYpos2; chYpos++) {
            ssd1306_fill_point(dev, chXpos, chYpos, chDot);
        }
    }
}

void ssd1306_draw_num(ssd1306_handle_t dev, uint8_t chXpos, uint8_t chYpos,
                      uint32_t chNum, uint8_t chLen, uint8_t chSize)
{
    uint8_t i;
    uint8_t chTemp, chShow = 0;

    for (i = 0; i < chLen; i++) {
        chTemp = (chNum / _pow(10, chLen - i - 1)) % 10;
        if (chShow == 0 && i < (chLen - 1)) {
            if (chTemp == 0) {
                ssd1306_draw_char(dev, chXpos + (chSize / 2) * i, chYpos,
                                  ' ', chSize, 1);
                continue;
            } else {
                chShow = 1;
            }
        }
        ssd1306_draw_char(dev, chXpos + (chSize / 2) * i, chYpos,
                          chTemp + '0', chSize, 1);
    }
}

void ssd1306_draw_char(ssd1306_handle_t dev, uint8_t chXpos, uint8_t chYpos,
                       uint8_t chChr, uint8_t chSize, uint8_t chMode)
{
    uint8_t i, j;
    uint8_t chTemp, chYpos0 = chYpos;

    chChr = chChr - ' ';
    for (i = 0; i < chSize; i++) {
        if (chSize == 12) {
            if (chMode) {
                chTemp = c_chFont1206[chChr][i];
            } else {
                chTemp = ~c_chFont1206[chChr][i];
            }
        } else {
            if (chMode) {
                chTemp = c_chFont1608[chChr][i];
            } else {
                chTemp = ~c_chFont1608[chChr][i];
            }
        }

        for (j = 0; j < 8; j++) {
            if (chTemp & 0x80) {
                ssd1306_fill_point(dev, chXpos, chYpos, 1);
            } else {
                ssd1306_fill_point(dev, chXpos, chYpos, 0);
            }
            chTemp <<= 1;
            chYpos++;

            if ((chYpos - chYpos0) == chSize) {
                chYpos = chYpos0;
                chXpos++;
                break;
            }
        }
    }
}

void ssd1306_draw_string(ssd1306_handle_t dev, uint8_t chXpos, uint8_t chYpos,
                         const uint8_t *pchString, uint8_t chSize, uint8_t chMode)
{
    while (*pchString != '\0') {
        if (chXpos > (SSD1306_WIDTH - chSize / 2)) {
            chXpos = 0;
            chYpos += chSize;
            if (chYpos > (SSD1306_HEIGHT - chSize)) {
                chYpos = chXpos = 0;
                ssd1306_clear_screen(dev, 0x00);
            }
        }
        ssd1306_draw_char(dev, chXpos, chYpos, *pchString, chSize, chMode);
        chXpos += chSize / 2;
        pchString++;
    }
}

void ssd1306_fill_point(ssd1306_handle_t dev, uint8_t chXpos, uint8_t chYpos, uint8_t chPoint)
{
    ssd1306_dev_t *device = (ssd1306_dev_t *) dev;
    uint8_t chPos, chBx, chTemp = 0;

    if (chXpos > 127 || chYpos > 63) {
        return;
    }
    chPos = 7 - chYpos / 8;
    chBx = chYpos % 8;
    chTemp = 1 << (7 - chBx);

    if (chPoint) {
        device->s_chDisplayBuffer[chXpos][chPos] |= chTemp;
    } else {
        device->s_chDisplayBuffer[chXpos][chPos] &= ~chTemp;
    }
}

void ssd1306_draw_1616char(ssd1306_handle_t dev, uint8_t chXpos, uint8_t chYpos, uint8_t chChar)
{
    uint8_t i, j;
    uint8_t chTemp = 0, chYpos0 = chYpos, chMode = 0;

    for (i = 0; i < 32; i++) {
        chTemp = c_chFont1612[chChar - 0x30][i];
        for (j = 0; j < 8; j++) {
            chMode = chTemp & 0x80 ? 1 : 0;
            ssd1306_fill_point(dev, chXpos, chYpos, chMode);
            chTemp <<= 1;
            chYpos++;
            if ((chYpos - chYpos0) == 16) {
                chYpos = chYpos0;
                chXpos++;
                break;
            }
        }
    }
}

void ssd1306_draw_3216char(ssd1306_handle_t dev, uint8_t chXpos, uint8_t chYpos, uint8_t chChar)
{
    uint8_t i, j;
    uint8_t chTemp = 0, chYpos0 = chYpos, chMode = 0;

    for (i = 0; i < 64; i++) {
        chTemp = c_chFont3216[chChar - 0x30][i];
        for (j = 0; j < 8; j++) {
            chMode = chTemp & 0x80 ? 1 : 0;
            ssd1306_fill_point(dev, chXpos, chYpos, chMode);
            chTemp <<= 1;
            chYpos++;
            if ((chYpos - chYpos0) == 32) {
                chYpos = chYpos0;
                chXpos++;
                break;
            }
        }
    }
}

void ssd1306_draw_bitmap(ssd1306_handle_t dev, uint8_t chXpos, uint8_t chYpos,
                         const uint8_t *pchBmp, uint8_t chWidth, uint8_t chHeight)
{
    uint16_t i, j, byteWidth = (chWidth + 7) / 8;

    for (j = 0; j < chHeight; j++) {
        for (i = 0; i < chWidth; i++) {
            if (*(pchBmp + j * byteWidth + i / 8) & (128 >> (i & 7))) {
                ssd1306_fill_point(dev, chXpos + i, chYpos + j, 1);
            }
        }
    }
}

void ssd1306_draw_line(ssd1306_handle_t dev, int16_t chXpos1, int16_t chYpos1, int16_t chXpos2, int16_t chYpos2)
{
    // 16-bit variables allowing a display overflow effect
    int16_t x_len = abs(chXpos1 - chXpos2);
    int16_t y_len = abs(chYpos1 - chYpos2);

    if (y_len < x_len) {
        if (chXpos1 > chXpos2) {
            COORDINATE_SWAP(chXpos1, chXpos2, chYpos1, chYpos2);
        }
        int16_t len = x_len;
        int16_t diff = y_len;

        do {
            if (diff >= x_len) {
                diff -= x_len;
                if (chYpos1 < chYpos2) {
                    chYpos1++;
                } else {
                    chYpos1--;
                }
            }

            diff += y_len;
            ssd1306_fill_point(dev, chXpos1++, chYpos1, 1);
        } while (len--);
    }

    else {
        if (chYpos1 > chYpos2) {
            COORDINATE_SWAP(chXpos1, chXpos2, chYpos1, chYpos2);
        }
        int16_t len = y_len;
        int16_t diff = x_len;

        do {
            if (diff >= y_len) {
                diff -= y_len;
                if (chXpos1 < chXpos2) {
                    chXpos1++;
                } else {
                    chXpos1--;
                }
            }

            diff += x_len;
            ssd1306_fill_point(dev, chXpos1, chYpos1++, 1);
        } while (len--);
    }
}

esp_err_t ssd1306_init(ssd1306_handle_t dev)
{
    esp_err_t ret;

    ssd1306_write_cmd_byte(dev, 0xAE); //--turn off oled panel
    ssd1306_write_cmd_byte(dev, 0x40); //--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    ssd1306_write_cmd_byte(dev, 0x81); //--set contrast control register
    ssd1306_write_cmd_byte(dev, 0xCF); // Set SEG Output Current Brightness
    ssd1306_write_cmd_byte(dev, 0xA1); //--Set SEG/Column Mapping
    ssd1306_write_cmd_byte(dev, 0xC0); //Set COM/Row Scan Direction
    ssd1306_write_cmd_byte(dev, 0xA6); //--set normal display
    ssd1306_write_cmd_byte(dev, 0xA8); //--set multiplex ratio(1 to 64)
    ssd1306_write_cmd_byte(dev, 0x3f); //--1/64 duty
    ssd1306_write_cmd_byte(dev, 0xd5); //--set display clock divide ratio/oscillator frequency
    ssd1306_write_cmd_byte(dev, 0x80); //--set divide ratio, Set Clock as 100 Frames/Sec
    ssd1306_write_cmd_byte(dev, 0xD9); //--set pre-charge period
    ssd1306_write_cmd_byte(dev, 0xF1); //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    ssd1306_write_cmd_byte(dev, 0xDA); //--set com pins hardware configuration
    ssd1306_write_cmd_byte(dev, 0xDB); //--set vcomh
    ssd1306_write_cmd_byte(dev, 0x40); //Set VCOM Deselect Level
    ssd1306_write_cmd_byte(dev, 0x8D); //--set Charge Pump enable/disable
    ssd1306_write_cmd_byte(dev, 0x14); //--set(0x10) disable
    ssd1306_write_cmd_byte(dev, 0xA4); // Disable Entire Display On (0xa4/0xa5)
    ssd1306_write_cmd_byte(dev, 0xA6); // Disable Inverse Display On (0xa6/a7)

    const uint8_t cmd[2] = {0x20, 1}; //-- set vertical adressing mode
    ssd1306_write_cmd(dev, cmd, sizeof(cmd));

    uint8_t cmd2[3] = {0x21, 0, 127};
    ssd1306_write_cmd(dev, cmd2, sizeof(cmd2)); //--set column address to zero
    cmd2[0] = 0x22;
    cmd2[2] = 7;
    ssd1306_write_cmd(dev, cmd2, sizeof(cmd2)); //--set row address to zero

    ret = ssd1306_write_cmd_byte(dev, 0xAF); //--turn on oled panel

    ssd1306_clear_screen(dev, 0x00);
    return ret;
}

ssd1306_handle_t ssd1306_create(i2c_port_t bus, uint16_t dev_addr)
{
    ssd1306_dev_t *dev = (ssd1306_dev_t *) calloc(1, sizeof(ssd1306_dev_t));
    dev->bus = bus;
    dev->dev_addr = dev_addr << 1;
    ssd1306_init((ssd1306_handle_t) dev);
    return (ssd1306_handle_t) dev;
}

void ssd1306_delete(ssd1306_handle_t dev)
{
    ssd1306_dev_t *device = (ssd1306_dev_t *) dev;
    free(device);
}

esp_err_t ssd1306_refresh_gram(ssd1306_handle_t dev)
{
    ssd1306_dev_t *device = (ssd1306_dev_t *) dev;
    return ssd1306_write_data(dev, &device->s_chDisplayBuffer[0][0], sizeof(device->s_chDisplayBuffer));
}

void ssd1306_clear_screen(ssd1306_handle_t dev, uint8_t chFill)
{
    ssd1306_dev_t *device = (ssd1306_dev_t *) dev;
    memset(device->s_chDisplayBuffer, chFill, sizeof(device->s_chDisplayBuffer));
}