// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "driver/gpio.h" #include "screen_driver.h" #include "screen_utility.h" #include "ssd1306.h" static const char *TAG = "lcd ssd1306"; #define LCD_CHECK(a, str, ret) if(!(a)) { \ ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ return (ret); \ } #define LCD_NAME "OLED SSD1306" #define LCD_BPP 1 // Some fundamental define for screen controller #define SSD1306_PAGES 8 #define SSD1306_HEIGHT 64 #define SSD1306_COLUMNS 128 // Control byte #define SSD1306_CONTROL_BYTE_CMD_SINGLE 0x80 #define SSD1306_CONTROL_BYTE_CMD_STREAM 0x00 #define SSD1306_CONTROL_BYTE_DATA_STREAM 0x40 // Fundamental commands (pg.28) #define SSD1306_CMD_SET_CONTRAST 0x81 // follow with 0x7F #define SSD1306_CMD_DISPLAY_RAM 0xA4 #define SSD1306_CMD_DISPLAY_ALLON 0xA5 #define SSD1306_CMD_DISPLAY_NORMAL 0xA6 #define SSD1306_CMD_DISPLAY_INVERTED 0xA7 #define SSD1306_CMD_DISPLAY_OFF 0xAE #define SSD1306_CMD_DISPLAY_ON 0xAF // Display Scrolling Parameters #define SSD1306_CMD_RIGHT_HORIZONTAL_SCROLL 0x26 // Init rt scroll #define SSD1306_CMD_LEFT_HORIZONTAL_SCROLL 0x27 // Init left scroll #define SSD1306_CMD_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 // Init diag scroll #define SSD1306_CMD_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A // Init diag scroll #define SSD1306_CMD_DEACTIVATE_SCROLL 0x2E // Stop scroll #define SSD1306_CMD_ACTIVATE_SCROLL 0x2F // Start scroll #define SSD1306_CMD_SET_VERTICAL_SCROLL_AREA 0xA3 // Set scroll range // Addressing Command Table (pg.30) #define SSD1306_CMD_SET_LOWER_COLUMN_ADDR 0x00 // Set Lower Column Start Address for Page Addressing Mode, using X[3:0] #define SSD1306_CMD_SET_HIGHER_COLUMN_ADDR 0x10 // Set Higher Column Start Address for Page Addressing Mode, using X[3:0] #define SSD1306_CMD_SET_MEMORY_ADDR_MODE 0x20 // follow with 00b= Horizontal Addressing Mode; 01b=Vertical Addressing Mode; 10b= Page Addressing Mode (RESET) #define SSD1306_CMD_SET_COLUMN_RANGE 0x21 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x7F = COL127 #define SSD1306_CMD_SET_PAGE_RANGE 0x22 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x07 = PAGE7 #define SSD1306_CMD_SET_PAGE_ADDR 0xB0 // Hardware Config (pg.31) #define SSD1306_CMD_SET_DISPLAY_START_LINE 0x40 #define SSD1306_CMD_SET_SEGMENT_REMAP 0xA1 #define SSD1306_CMD_SET_MUX_RATIO 0xA8 // follow with 0x3F = 64 MUX #define SSD1306_CMD_SET_COM_SCAN_MODE_NORMAL 0xC0 #define SSD1306_CMD_SET_COM_SCAN_MODE_REMAP 0xC8 #define SSD1306_CMD_SET_DISPLAY_OFFSET 0xD3 // follow with 0x00 #define SSD1306_CMD_SET_COM_PIN_MAP 0xDA // follow with 0x12 #define SSD1306_CMD_NOP 0xE3 // NOP // Timing and Driving Scheme (pg.32) #define SSD1306_CMD_SET_DISPLAY_CLK_DIV 0xD5 // follow with 0x80 #define SSD1306_CMD_SET_PRECHARGE 0xD9 // follow with 0xF1 #define SSD1306_CMD_SET_VCOMH_DESELCT 0xDB // follow with 0x30 // Charge Pump (pg.62) #define OLED_CMD_SET_CHARGE_PUMP 0x8D // follow with 0x14 #define SSD1306_COLUMN_ADDR 0x00 #define SSD1306_LOWER_ADDRESS (SSD1306_CMD_SET_LOWER_COLUMN_ADDR + (SSD1306_COLUMN_ADDR&0x0f)) #define SSD1306_HIGHER_ADDRESS (SSD1306_CMD_SET_HIGHER_COLUMN_ADDR + ((SSD1306_COLUMN_ADDR&0xf0)>>4)) static scr_handle_t g_lcd_handle; /** * This header file is only used to redefine the function to facilitate the call. * It can only be placed in this position, not in the head of the file. */ #include "interface_drv_def.h" static esp_err_t lcd_ssd1306_write_ram_data(uint16_t color); scr_driver_t lcd_ssd1306_default_driver = { .init = lcd_ssd1306_init, .deinit = lcd_ssd1306_deinit, .set_direction = lcd_ssd1306_set_rotate, .set_window = lcd_ssd1306_set_window, .write_ram_data = lcd_ssd1306_write_ram_data, .draw_pixel = lcd_ssd1306_draw_pixel, .draw_bitmap = lcd_ssd1306_draw_bitmap, .get_info = lcd_ssd1306_get_info, }; esp_err_t lcd_ssd1306_init(const scr_controller_config_t *lcd_conf) { LCD_CHECK(lcd_conf->width <= SSD1306_COLUMNS, "Width greater than maximum", ESP_ERR_INVALID_ARG); LCD_CHECK(lcd_conf->height <= SSD1306_HEIGHT, "Height greater than maximum", ESP_ERR_INVALID_ARG); // Reset the display if (lcd_conf->pin_num_rst >= 0) { gpio_pad_select_gpio(lcd_conf->pin_num_rst); gpio_set_direction(lcd_conf->pin_num_rst, GPIO_MODE_OUTPUT); gpio_set_level(lcd_conf->pin_num_rst, (lcd_conf->rst_active_level) & 0x1); vTaskDelay(100 / portTICK_RATE_MS); gpio_set_level(lcd_conf->pin_num_rst, (~(lcd_conf->rst_active_level)) & 0x1); vTaskDelay(100 / portTICK_RATE_MS); } g_lcd_handle.interface_drv = lcd_conf->interface_drv; g_lcd_handle.original_width = lcd_conf->width; g_lcd_handle.original_height = lcd_conf->height; g_lcd_handle.offset_hor = lcd_conf->offset_hor; g_lcd_handle.offset_ver = lcd_conf->offset_ver; LCD_WRITE_CMD(0xAE); //--turn off oled panel LCD_WRITE_CMD(0x00); //---set low column address LCD_WRITE_CMD(0x10); //---set high column address LCD_WRITE_CMD(0x40); //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) LCD_WRITE_CMD(0x81); //--set contrast control register LCD_WRITE_CMD(0xCF); // Set SEG Output Current Brightness LCD_WRITE_CMD(0xA1); //--Set SEG/Column Mapping LCD_WRITE_CMD(0xC0); //Set COM/Row Scan Direction LCD_WRITE_CMD(0xA6); //--set normal display LCD_WRITE_CMD(0xA8); //--set multiplex ratio(1 to 64) LCD_WRITE_CMD(0x3f); //--1/64 duty LCD_WRITE_CMD(0xD3); //-set display offset Shift Mapping RAM Counter (0x00~0x3F) LCD_WRITE_CMD(0x00); //-not offset LCD_WRITE_CMD(0xd5); //--set display clock divide ratio/oscillator frequency LCD_WRITE_CMD(0x80); //--set divide ratio, Set Clock as 100 Frames/Sec LCD_WRITE_CMD(0xD9); //--set pre-charge period LCD_WRITE_CMD(0xF1); //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock LCD_WRITE_CMD(0xDA); //--set com pins hardware configuration LCD_WRITE_CMD(0x12); LCD_WRITE_CMD(0xDB); //--set vcomh LCD_WRITE_CMD(0x40); //Set VCOM Deselect Level LCD_WRITE_CMD(0x20); //-Set Page Addressing Mode (0x00/0x01/0x02) LCD_WRITE_CMD(0x02); LCD_WRITE_CMD(0x8D); //--set Charge Pump enable/disable LCD_WRITE_CMD(0x14); //--set(0x10) disable LCD_WRITE_CMD(0xA4); // Disable Entire Display On (0xa4/0xa5) LCD_WRITE_CMD(0xA6); // Disable Inverse Display On (0xa6/a7) LCD_WRITE_CMD(0xAF); //--turn on oled panel lcd_ssd1306_set_rotate(lcd_conf->rotate); return ESP_OK; } esp_err_t lcd_ssd1306_deinit(void) { memset(&g_lcd_handle, 0, sizeof(scr_handle_t)); return ESP_OK; } esp_err_t lcd_ssd1306_get_info(scr_info_t *info) { LCD_CHECK(NULL != info, "info pointer invalid", ESP_ERR_INVALID_ARG); info->width = g_lcd_handle.width; info->height = g_lcd_handle.height; info->dir = g_lcd_handle.dir; info->name = LCD_NAME; info->color_type = SCR_COLOR_TYPE_MONO; info->bpp = LCD_BPP; return ESP_OK; } esp_err_t lcd_ssd1306_set_rotate(scr_dir_t dir) { esp_err_t ret = ESP_OK; if (SCR_DIR_MAX < dir) { dir >>= 5; } LCD_CHECK(dir < 8, "Unsupport rotate direction", ESP_ERR_INVALID_ARG); switch (dir) { case SCR_DIR_LRTB: g_lcd_handle.width = g_lcd_handle.original_width; g_lcd_handle.height = g_lcd_handle.original_height; ret |= LCD_WRITE_CMD(0xA0); ret |= LCD_WRITE_CMD(0xC0); break; case SCR_DIR_LRBT: ret |= LCD_WRITE_CMD(0xA0); ret |= LCD_WRITE_CMD(0xC8); g_lcd_handle.width = g_lcd_handle.original_width; g_lcd_handle.height = g_lcd_handle.original_height; break; case SCR_DIR_RLTB: ret |= LCD_WRITE_CMD(0xA1); ret |= LCD_WRITE_CMD(0xC0); g_lcd_handle.width = g_lcd_handle.original_width; g_lcd_handle.height = g_lcd_handle.original_height; break; case SCR_DIR_RLBT: ret |= LCD_WRITE_CMD(0xA1); ret |= LCD_WRITE_CMD(0xC8); g_lcd_handle.width = g_lcd_handle.original_width; g_lcd_handle.height = g_lcd_handle.original_height; break; default: ESP_LOGE(TAG, "Unsupport rotate direction"); g_lcd_handle.width = g_lcd_handle.original_width; g_lcd_handle.height = g_lcd_handle.original_height; return ESP_ERR_INVALID_ARG; break; } LCD_CHECK(ESP_OK == ret, "Set screen rotate failed", ESP_FAIL); g_lcd_handle.dir = dir; return ESP_OK; } esp_err_t lcd_ssd1306_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { esp_err_t ret = ESP_OK; y1 += 1; LCD_CHECK((0 == (y0 % 8)), "y0 should be a multiple of 8", ESP_ERR_INVALID_ARG); LCD_CHECK((0 == (y1 % 8)), "(y1+1) should be a multiple of 8", ESP_ERR_INVALID_ARG); uint8_t row1 = y0 >> 3; uint8_t row2 = y1 / 8; ret |= LCD_WRITE_CMD(SSD1306_CMD_SET_MEMORY_ADDR_MODE); ret |= LCD_WRITE_CMD(0); /**< Set to Horizontal Addressing Mode */ ret |= LCD_WRITE_CMD(SSD1306_CMD_SET_COLUMN_RANGE); ret |= LCD_WRITE_CMD(x0); ret |= LCD_WRITE_CMD(x1 - 1); ret |= LCD_WRITE_CMD(SSD1306_CMD_SET_PAGE_RANGE); ret |= LCD_WRITE_CMD(row1); ret |= LCD_WRITE_CMD(row2 - 1); LCD_CHECK(ESP_OK == ret, "Set window failed", ESP_FAIL); return ESP_OK; } static esp_err_t lcd_ssd1306_write_ram_data(uint16_t color) { ESP_LOGW(TAG, "lcd ssd1306 unsupport write ram data"); return ESP_ERR_NOT_SUPPORTED; } esp_err_t lcd_ssd1306_draw_pixel(uint16_t x, uint16_t y, uint16_t color) { ESP_LOGW(TAG, "SSD1306 not support draw pixel without buffer"); return ESP_ERR_NOT_SUPPORTED; } esp_err_t lcd_ssd1306_draw_bitmap(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) { LCD_CHECK((x + w <= g_lcd_handle.width) && (y + h <= g_lcd_handle.height), "The set coordinates exceed the screen size", ESP_ERR_INVALID_ARG); esp_err_t ret = ESP_OK; uint8_t *p = (uint8_t *)bitmap; LCD_IFACE_ACQUIRE(); ret = lcd_ssd1306_set_window(x, y, x + w - 1, y + h - 1); if (ESP_OK != ret) { return ESP_FAIL; } ret = LCD_WRITE(p, w * LCD_BPP / 8 * h); LCD_IFACE_RELEASE(); LCD_CHECK(ESP_OK == ret, "Draw bitmap failed", ESP_FAIL); return ESP_OK; } esp_err_t lcd_ssd1306_display_on(void) { esp_err_t ret; ret = LCD_WRITE_CMD(OLED_CMD_SET_CHARGE_PUMP); // SET DCDC ret |= LCD_WRITE_CMD(0X14); // Enable charge pump during display on ret |= LCD_WRITE_CMD(SSD1306_CMD_DISPLAY_ON); // DISPLAY ON LCD_CHECK(ESP_OK == ret, "Set display ON failed", ESP_FAIL); return ESP_OK; } esp_err_t lcd_ssd1306_display_off(void) { esp_err_t ret; ret = LCD_WRITE_CMD(OLED_CMD_SET_CHARGE_PUMP); //SET DCDC ret |= LCD_WRITE_CMD(0X10); // Disable charge pump ret |= LCD_WRITE_CMD(SSD1306_CMD_DISPLAY_OFF); //DISPLAY OFF LCD_CHECK(ESP_OK == ret, "Set display OFF failed", ESP_FAIL); return ESP_OK; } esp_err_t lcd_ssd1306_set_contrast(uint8_t contrast) { esp_err_t ret; ret = LCD_WRITE_CMD(0x81); ret |= LCD_WRITE_CMD(contrast); LCD_CHECK(ESP_OK == ret, "Set contrast failed", ESP_FAIL); return ESP_OK; } esp_err_t lcd_ssd1306_start_horizontal_scroll(uint8_t dir, uint8_t start, uint8_t stop, uint8_t interval) { LCD_CHECK(start < SSD1306_PAGES, "Start page address invalid", ESP_ERR_INVALID_ARG); LCD_CHECK(stop < SSD1306_PAGES, "End page address invalid", ESP_ERR_INVALID_ARG); LCD_CHECK(interval < 8, "Time interval invalid", ESP_ERR_INVALID_ARG); uint8_t cmd = dir ? SSD1306_CMD_LEFT_HORIZONTAL_SCROLL : SSD1306_CMD_RIGHT_HORIZONTAL_SCROLL; esp_err_t ret = ESP_OK; ret |= LCD_WRITE_CMD(cmd); ret |= LCD_WRITE_CMD(0x00); ret |= LCD_WRITE_CMD(start); ret |= LCD_WRITE_CMD(interval); ret |= LCD_WRITE_CMD(stop); ret |= LCD_WRITE_CMD(0x00); ret |= LCD_WRITE_CMD(0xff); ret |= LCD_WRITE_CMD(SSD1306_CMD_ACTIVATE_SCROLL); LCD_CHECK(ESP_OK == ret, "Start horizontal scroll failed", ESP_FAIL); return ESP_OK; } esp_err_t lcd_ssd1306_start_vertical_scroll(uint8_t start, uint8_t stop) { LCD_CHECK(start < SSD1306_PAGES, "Start page address invalid", ESP_ERR_INVALID_ARG); LCD_CHECK(stop < SSD1306_PAGES, "End page address invalid", ESP_ERR_INVALID_ARG); esp_err_t ret = ESP_OK; ret |= LCD_WRITE_CMD(SSD1306_CMD_SET_VERTICAL_SCROLL_AREA); ret |= LCD_WRITE_CMD(start); ret |= LCD_WRITE_CMD(stop); ret |= LCD_WRITE_CMD(SSD1306_CMD_ACTIVATE_SCROLL); LCD_CHECK(ESP_OK == ret, "Start vertical scroll failed", ESP_FAIL); return ESP_OK; } esp_err_t lcd_ssd1306_start_scroll_diagRight(uint8_t start, uint8_t stop) { esp_err_t ret = ESP_OK; ret |= LCD_WRITE_CMD(SSD1306_CMD_SET_VERTICAL_SCROLL_AREA); ret |= LCD_WRITE_CMD(0x00); ret |= LCD_WRITE_CMD(32); ret |= LCD_WRITE_CMD(SSD1306_CMD_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL); ret |= LCD_WRITE_CMD(0x00); ret |= LCD_WRITE_CMD(start); ret |= LCD_WRITE_CMD(0x00); ret |= LCD_WRITE_CMD(stop); ret |= LCD_WRITE_CMD(0x01); ret |= LCD_WRITE_CMD(SSD1306_CMD_ACTIVATE_SCROLL); LCD_CHECK(ESP_OK == ret, "Start diagRight scroll failed", ESP_FAIL); return ESP_OK; } esp_err_t lcd_ssd1306_stop_scroll(void) { esp_err_t ret = ESP_OK; ret |= LCD_WRITE_CMD(SSD1306_CMD_DEACTIVATE_SCROLL); LCD_CHECK(ESP_OK == ret, "Stop scroll failed", ESP_FAIL); return ESP_OK; }