/* * 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)); }