diff --git a/board_single/main/CMakeLists.txt b/board_single/main/CMakeLists.txt index 6a31384..d0ac056 100644 --- a/board_single/main/CMakeLists.txt +++ b/board_single/main/CMakeLists.txt @@ -6,6 +6,7 @@ idf_component_register( "button.cpp" "fan.cpp" "auto.cpp" + "display.cpp" INCLUDE_DIRS "." ) diff --git a/board_single/main/display.cpp b/board_single/main/display.cpp new file mode 100644 index 0000000..29ae86c --- /dev/null +++ b/board_single/main/display.cpp @@ -0,0 +1,207 @@ +#include "display.hpp" +extern "C"{ +#include +} + + +//# +//# SSD1306 Configuration +//# +#define GPIO_RANGE_MAX 33 +#define I2C_INTERFACE y +//# SPI_INTERFACE is not set +//# SSD1306_128x32 is not set +#define SSD1306_128x64 y +#define OFFSETX 0 +//# FLIP is not set +#define SCL_GPIO 22 +#define SDA_GPIO 23 +#define RESET_GPIO 15 //FIXME remove this +#define I2C_PORT_0 y +//# I2C_PORT_1 is not set +//# end of SSD1306 Configuration + + + + + +//-------------------------- +//------- getVoltage ------- +//-------------------------- +//local function to get average voltage from adc +float getVoltage1(adc1_channel_t adc, uint32_t samples){ + //measure voltage + int measure = 0; + for (int j=0; j0x30;font--) { + // memset(image, 0, sizeof(image)); + // ssd1306_display_image(&dev, top+1, (7*8-1), image, 8); + // memcpy(image, font8x8_basic_tr[font], 8); + // if (dev._flip) ssd1306_flip(image, 8); + // ssd1306_display_image(&dev, top+1, (7*8-1), image, 8); + // vTaskDelay(1000 / portTICK_PERIOD_MS); + //} + // + //// Scroll Up + //ssd1306_clear_screen(&dev, false); + //ssd1306_contrast(&dev, 0xff); + //ssd1306_display_text(&dev, 0, "---Scroll UP---", 16, true); + ////ssd1306_software_scroll(&dev, 7, 1); + //ssd1306_software_scroll(&dev, (dev._pages - 1), 1); + //for (int line=0;line0;contrast=contrast-0x20) { + ssd1306_contrast(&dev, contrast); + vTaskDelay(40); + } +#endif + +} + diff --git a/board_single/main/display.hpp b/board_single/main/display.hpp new file mode 100644 index 0000000..4aea0cf --- /dev/null +++ b/board_single/main/display.hpp @@ -0,0 +1,14 @@ +extern "C" { +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +#include "ssd1306.h" +#include "font8x8_basic.h" +} + + +void startDisplayTest(); diff --git a/board_single/main/main.cpp b/board_single/main/main.cpp index 229f2fc..7ed2781 100644 --- a/board_single/main/main.cpp +++ b/board_single/main/main.cpp @@ -28,6 +28,8 @@ extern "C" #include "uart_common.hpp" +#include "display.hpp" + //tag for logging static const char * TAG = "main"; @@ -232,6 +234,10 @@ extern "C" void app_main(void) { //control.changeMode(controlMode_t::HTTP); + + //========== display test ============ + startDisplayTest(); + //--- main loop --- //does nothing except for testing things while(1){ diff --git a/components/ssd1306/CMakeLists.txt b/components/ssd1306/CMakeLists.txt new file mode 100644 index 0000000..ac32f0a --- /dev/null +++ b/components/ssd1306/CMakeLists.txt @@ -0,0 +1,5 @@ +set(component_srcs "ssd1306.c" "ssd1306_i2c.c" "ssd1306_spi.c") + +idf_component_register(SRCS "${component_srcs}" + PRIV_REQUIRES driver + INCLUDE_DIRS ".") diff --git a/components/ssd1306/Kconfig.projbuild b/components/ssd1306/Kconfig.projbuild new file mode 100644 index 0000000..97af991 --- /dev/null +++ b/components/ssd1306/Kconfig.projbuild @@ -0,0 +1,183 @@ +menu "SSD1306 Configuration" + + config GPIO_RANGE_MAX + int + default 33 if IDF_TARGET_ESP32 + default 46 if IDF_TARGET_ESP32S2 + default 48 if IDF_TARGET_ESP32S3 + default 18 if IDF_TARGET_ESP32C2 + default 19 if IDF_TARGET_ESP32C3 + default 30 if IDF_TARGET_ESP32C6 + + choice INTERFACE + prompt "Interface" + default I2C_INTERFACE + help + Select Interface. + config I2C_INTERFACE + bool "I2C Interface" + help + I2C Interface. + config SPI_INTERFACE + bool "SPI Interface" + help + SPI Interface. + endchoice + + choice PANEL + prompt "Panel Type" + default SSD1306_128x64 + help + Select Panel Type. + config SSD1306_128x32 + bool "128x32 Panel" + help + Panel is 128x32. + config SSD1306_128x64 + bool "128x64 Panel" + help + Panel is 128x64. + endchoice + + config OFFSETX + int "GRAM X OFFSET" + range 0 99 + default 0 + help + When your TFT have offset(X), set it. + + config FLIP + bool "Flip upside down" + default false + help + Flip upside down. + + config SCL_GPIO + depends on I2C_INTERFACE + int "SCL GPIO number" + range 0 GPIO_RANGE_MAX + default 22 if IDF_TARGET_ESP32 + default 12 if IDF_TARGET_ESP32S2 + default 12 if IDF_TARGET_ESP32S3 + default 6 # C3 and others + help + GPIO number (IOxx) to I2C SCL. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to I2C. + GPIOs 35-39 are input-only so cannot be used as outputs. + + config SDA_GPIO + depends on I2C_INTERFACE + int "SDA GPIO number" + range 0 GPIO_RANGE_MAX + default 21 if IDF_TARGET_ESP32 + default 11 if IDF_TARGET_ESP32S2 + default 11 if IDF_TARGET_ESP32S3 + default 5 # C3 and others + help + GPIO number (IOxx) to I2C SDA. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to I2C. + GPIOs 35-39 are input-only so cannot be used as outputs. + + config MOSI_GPIO + depends on SPI_INTERFACE + int "MOSI GPIO number" + range 0 GPIO_RANGE_MAX + default 23 if IDF_TARGET_ESP32 + default 35 if IDF_TARGET_ESP32S2 + default 35 if IDF_TARGET_ESP32S3 + default 1 # C3 and others + help + GPIO number (IOxx) to SPI MOSI. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to MOSI. + On the ESP32, GPIOs 35-39 are input-only so cannot be used as outputs. + On the ESP32-S2, GPIO 46 is input-only so cannot be used as outputs. + + config SCLK_GPIO + depends on SPI_INTERFACE + int "SCLK GPIO number" + range 0 GPIO_RANGE_MAX + default 18 if IDF_TARGET_ESP32 + default 36 if IDF_TARGET_ESP32S2 + default 36 if IDF_TARGET_ESP32S3 + default 2 # C3 and others + help + GPIO number (IOxx) to SPI SCLK. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to SCLK. + On the ESP32, GPIOs 35-39 are input-only so cannot be used as outputs. + On the ESP32-S2, GPIO 46 is input-only so cannot be used as outputs. + + config CS_GPIO + depends on SPI_INTERFACE + int "CS GPIO number" + range 0 GPIO_RANGE_MAX + default 5 if IDF_TARGET_ESP32 + default 34 if IDF_TARGET_ESP32S2 + default 34 if IDF_TARGET_ESP32S3 + default 10 # C3 and others + help + GPIO number (IOxx) to SPI CS. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to CS. + GPIOs 35-39 are input-only so cannot be used as outputs. + + config DC_GPIO + depends on SPI_INTERFACE + int "DC GPIO number" + range 0 GPIO_RANGE_MAX + default 4 if IDF_TARGET_ESP32 + default 37 if IDF_TARGET_ESP32S2 + default 37 if IDF_TARGET_ESP32S3 + default 3 # C3 and others + help + GPIO number (IOxx) to SPI DC. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to DC. + GPIOs 35-39 are input-only so cannot be used as outputs. + + config RESET_GPIO + int "RESET GPIO number" + range -1 GPIO_RANGE_MAX + default 15 if IDF_TARGET_ESP32 + default 38 if IDF_TARGET_ESP32S2 + default 38 if IDF_TARGET_ESP32S3 + default 4 # C3 and others + help + GPIO number (IOxx) to RESET. + When it is -1, RESET isn't performed. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to RESET. + GPIOs 35-39 are input-only so cannot be used as outputs. + + choice I2C_PORT + depends on I2C_INTERFACE + prompt "I2C port that controls this bus" + default I2C_PORT_0 + help + Select I2C port that controls this bus. + config I2C_PORT_0 + bool "I2C_PORT_0" + help + Use I2C_PORT_0. + config I2C_PORT_1 + depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + bool "I2C_PORT_1" + help + Use I2C_PORT_1. + endchoice + + choice SPI_HOST + depends on SPI_INTERFACE + prompt "SPI peripheral that controls this bus" + default SPI2_HOST + help + Select SPI peripheral that controls this bus. + config SPI2_HOST + bool "SPI2_HOST" + help + Use SPI2_HOST. This is also called HSPI_HOST. + config SPI3_HOST + depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + bool "SPI3_HOST" + help + USE SPI3_HOST. This is also called VSPI_HOST + endchoice + +endmenu + diff --git a/components/ssd1306/font8x8_basic.h b/components/ssd1306/font8x8_basic.h new file mode 100644 index 0000000..413825e --- /dev/null +++ b/components/ssd1306/font8x8_basic.h @@ -0,0 +1,174 @@ +/* + * font8x8_basic.h + * + * Created on: 2017/05/03 + * Author: yanbe + */ + +#ifndef MAIN_FONT8X8_BASIC_H_ +#define MAIN_FONT8X8_BASIC_H_ + +/* + Constant: font8x8_basic_tr + Contains an 90 digree transposed 8x8 font map for unicode points + U+0000 - U+007F (basic latin) + + To make it easy to use with SSD1306's GDDRAM mapping and API, + this constant is an 90 degree transposed. + The original version written by Marcel Sondaar is availble at: + https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h + + Conversion is done via following procedure: + + for (int code = 0; code < 128; code++) { + uint8_t trans[8]; + for (int w = 0; w < 8; w++) { + trans[w] = 0x00; + for (int b = 0; b < 8; b++) { + trans[w] |= ((font8x8_basic[code][b] & (1 << w)) >> w) << b; + } + } + + for (int w = 0; w < 8; w++) { + if (w == 0) { printf(" { "); } + printf("0x%.2X", trans[w]); + if (w < 7) { printf(", "); } + if (w == 7) { printf(" }, // U+00%.2X (%c)\n", code, code); } + } + } +*/ + +static uint8_t font8x8_basic_tr[128][8] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0000 (nul) + { 0x00, 0x04, 0x02, 0xFF, 0x02, 0x04, 0x00, 0x00 }, // U+0001 (Up Allow) + { 0x00, 0x20, 0x40, 0xFF, 0x40, 0x20, 0x00, 0x00 }, // U+0002 (Down Allow) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0003 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0004 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0005 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0006 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0007 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0008 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0009 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0010 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0011 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0012 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0013 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0014 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0015 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0016 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0017 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0018 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0019 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0020 (space) + { 0x00, 0x00, 0x06, 0x5F, 0x5F, 0x06, 0x00, 0x00 }, // U+0021 (!) + { 0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00 }, // U+0022 (") + { 0x14, 0x7F, 0x7F, 0x14, 0x7F, 0x7F, 0x14, 0x00 }, // U+0023 (#) + { 0x24, 0x2E, 0x6B, 0x6B, 0x3A, 0x12, 0x00, 0x00 }, // U+0024 ($) + { 0x46, 0x66, 0x30, 0x18, 0x0C, 0x66, 0x62, 0x00 }, // U+0025 (%) + { 0x30, 0x7A, 0x4F, 0x5D, 0x37, 0x7A, 0x48, 0x00 }, // U+0026 (&) + { 0x04, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0027 (') + { 0x00, 0x1C, 0x3E, 0x63, 0x41, 0x00, 0x00, 0x00 }, // U+0028 (() + { 0x00, 0x41, 0x63, 0x3E, 0x1C, 0x00, 0x00, 0x00 }, // U+0029 ()) + { 0x08, 0x2A, 0x3E, 0x1C, 0x1C, 0x3E, 0x2A, 0x08 }, // U+002A (*) + { 0x08, 0x08, 0x3E, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // U+002B (+) + { 0x00, 0x80, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x00 }, // U+002C (,) + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // U+002D (-) + { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00 }, // U+002E (.) + { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00 }, // U+002F (/) + { 0x3E, 0x7F, 0x71, 0x59, 0x4D, 0x7F, 0x3E, 0x00 }, // U+0030 (0) + { 0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, 0x00 }, // U+0031 (1) + { 0x62, 0x73, 0x59, 0x49, 0x6F, 0x66, 0x00, 0x00 }, // U+0032 (2) + { 0x22, 0x63, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00 }, // U+0033 (3) + { 0x18, 0x1C, 0x16, 0x53, 0x7F, 0x7F, 0x50, 0x00 }, // U+0034 (4) + { 0x27, 0x67, 0x45, 0x45, 0x7D, 0x39, 0x00, 0x00 }, // U+0035 (5) + { 0x3C, 0x7E, 0x4B, 0x49, 0x79, 0x30, 0x00, 0x00 }, // U+0036 (6) + { 0x03, 0x03, 0x71, 0x79, 0x0F, 0x07, 0x00, 0x00 }, // U+0037 (7) + { 0x36, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00 }, // U+0038 (8) + { 0x06, 0x4F, 0x49, 0x69, 0x3F, 0x1E, 0x00, 0x00 }, // U+0039 (9) + { 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003A (:) + { 0x00, 0x80, 0xE6, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003B (;) + { 0x08, 0x1C, 0x36, 0x63, 0x41, 0x00, 0x00, 0x00 }, // U+003C (<) + { 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x00 }, // U+003D (=) + { 0x00, 0x41, 0x63, 0x36, 0x1C, 0x08, 0x00, 0x00 }, // U+003E (>) + { 0x02, 0x03, 0x51, 0x59, 0x0F, 0x06, 0x00, 0x00 }, // U+003F (?) + { 0x3E, 0x7F, 0x41, 0x5D, 0x5D, 0x1F, 0x1E, 0x00 }, // U+0040 (@) + { 0x7C, 0x7E, 0x13, 0x13, 0x7E, 0x7C, 0x00, 0x00 }, // U+0041 (A) + { 0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00 }, // U+0042 (B) + { 0x1C, 0x3E, 0x63, 0x41, 0x41, 0x63, 0x22, 0x00 }, // U+0043 (C) + { 0x41, 0x7F, 0x7F, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+0044 (D) + { 0x41, 0x7F, 0x7F, 0x49, 0x5D, 0x41, 0x63, 0x00 }, // U+0045 (E) + { 0x41, 0x7F, 0x7F, 0x49, 0x1D, 0x01, 0x03, 0x00 }, // U+0046 (F) + { 0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00 }, // U+0047 (G) + { 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00 }, // U+0048 (H) + { 0x00, 0x41, 0x7F, 0x7F, 0x41, 0x00, 0x00, 0x00 }, // U+0049 (I) + { 0x30, 0x70, 0x40, 0x41, 0x7F, 0x3F, 0x01, 0x00 }, // U+004A (J) + { 0x41, 0x7F, 0x7F, 0x08, 0x1C, 0x77, 0x63, 0x00 }, // U+004B (K) + { 0x41, 0x7F, 0x7F, 0x41, 0x40, 0x60, 0x70, 0x00 }, // U+004C (L) + { 0x7F, 0x7F, 0x0E, 0x1C, 0x0E, 0x7F, 0x7F, 0x00 }, // U+004D (M) + { 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00 }, // U+004E (N) + { 0x1C, 0x3E, 0x63, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+004F (O) + { 0x41, 0x7F, 0x7F, 0x49, 0x09, 0x0F, 0x06, 0x00 }, // U+0050 (P) + { 0x1E, 0x3F, 0x21, 0x71, 0x7F, 0x5E, 0x00, 0x00 }, // U+0051 (Q) + { 0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00 }, // U+0052 (R) + { 0x26, 0x6F, 0x4D, 0x59, 0x73, 0x32, 0x00, 0x00 }, // U+0053 (S) + { 0x03, 0x41, 0x7F, 0x7F, 0x41, 0x03, 0x00, 0x00 }, // U+0054 (T) + { 0x7F, 0x7F, 0x40, 0x40, 0x7F, 0x7F, 0x00, 0x00 }, // U+0055 (U) + { 0x1F, 0x3F, 0x60, 0x60, 0x3F, 0x1F, 0x00, 0x00 }, // U+0056 (V) + { 0x7F, 0x7F, 0x30, 0x18, 0x30, 0x7F, 0x7F, 0x00 }, // U+0057 (W) + { 0x43, 0x67, 0x3C, 0x18, 0x3C, 0x67, 0x43, 0x00 }, // U+0058 (X) + { 0x07, 0x4F, 0x78, 0x78, 0x4F, 0x07, 0x00, 0x00 }, // U+0059 (Y) + { 0x47, 0x63, 0x71, 0x59, 0x4D, 0x67, 0x73, 0x00 }, // U+005A (Z) + { 0x00, 0x7F, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00 }, // U+005B ([) + { 0x01, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00 }, // U+005C (\) + { 0x00, 0x41, 0x41, 0x7F, 0x7F, 0x00, 0x00, 0x00 }, // U+005D (]) + { 0x08, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x08, 0x00 }, // U+005E (^) + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, // U+005F (_) + { 0x00, 0x00, 0x03, 0x07, 0x04, 0x00, 0x00, 0x00 }, // U+0060 (`) + { 0x20, 0x74, 0x54, 0x54, 0x3C, 0x78, 0x40, 0x00 }, // U+0061 (a) + { 0x41, 0x7F, 0x3F, 0x48, 0x48, 0x78, 0x30, 0x00 }, // U+0062 (b) + { 0x38, 0x7C, 0x44, 0x44, 0x6C, 0x28, 0x00, 0x00 }, // U+0063 (c) + { 0x30, 0x78, 0x48, 0x49, 0x3F, 0x7F, 0x40, 0x00 }, // U+0064 (d) + { 0x38, 0x7C, 0x54, 0x54, 0x5C, 0x18, 0x00, 0x00 }, // U+0065 (e) + { 0x48, 0x7E, 0x7F, 0x49, 0x03, 0x02, 0x00, 0x00 }, // U+0066 (f) + { 0x98, 0xBC, 0xA4, 0xA4, 0xF8, 0x7C, 0x04, 0x00 }, // U+0067 (g) + { 0x41, 0x7F, 0x7F, 0x08, 0x04, 0x7C, 0x78, 0x00 }, // U+0068 (h) + { 0x00, 0x44, 0x7D, 0x7D, 0x40, 0x00, 0x00, 0x00 }, // U+0069 (i) + { 0x60, 0xE0, 0x80, 0x80, 0xFD, 0x7D, 0x00, 0x00 }, // U+006A (j) + { 0x41, 0x7F, 0x7F, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+006B (k) + { 0x00, 0x41, 0x7F, 0x7F, 0x40, 0x00, 0x00, 0x00 }, // U+006C (l) + { 0x7C, 0x7C, 0x18, 0x38, 0x1C, 0x7C, 0x78, 0x00 }, // U+006D (m) + { 0x7C, 0x7C, 0x04, 0x04, 0x7C, 0x78, 0x00, 0x00 }, // U+006E (n) + { 0x38, 0x7C, 0x44, 0x44, 0x7C, 0x38, 0x00, 0x00 }, // U+006F (o) + { 0x84, 0xFC, 0xF8, 0xA4, 0x24, 0x3C, 0x18, 0x00 }, // U+0070 (p) + { 0x18, 0x3C, 0x24, 0xA4, 0xF8, 0xFC, 0x84, 0x00 }, // U+0071 (q) + { 0x44, 0x7C, 0x78, 0x4C, 0x04, 0x1C, 0x18, 0x00 }, // U+0072 (r) + { 0x48, 0x5C, 0x54, 0x54, 0x74, 0x24, 0x00, 0x00 }, // U+0073 (s) + { 0x00, 0x04, 0x3E, 0x7F, 0x44, 0x24, 0x00, 0x00 }, // U+0074 (t) + { 0x3C, 0x7C, 0x40, 0x40, 0x3C, 0x7C, 0x40, 0x00 }, // U+0075 (u) + { 0x1C, 0x3C, 0x60, 0x60, 0x3C, 0x1C, 0x00, 0x00 }, // U+0076 (v) + { 0x3C, 0x7C, 0x70, 0x38, 0x70, 0x7C, 0x3C, 0x00 }, // U+0077 (w) + { 0x44, 0x6C, 0x38, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+0078 (x) + { 0x9C, 0xBC, 0xA0, 0xA0, 0xFC, 0x7C, 0x00, 0x00 }, // U+0079 (y) + { 0x4C, 0x64, 0x74, 0x5C, 0x4C, 0x64, 0x00, 0x00 }, // U+007A (z) + { 0x08, 0x08, 0x3E, 0x77, 0x41, 0x41, 0x00, 0x00 }, // U+007B ({) + { 0x00, 0x00, 0x00, 0x77, 0x77, 0x00, 0x00, 0x00 }, // U+007C (|) + { 0x41, 0x41, 0x77, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // U+007D (}) + { 0x02, 0x03, 0x01, 0x03, 0x02, 0x03, 0x01, 0x00 }, // U+007E (~) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // U+007F +}; + +#endif /* MAIN_FONT8X8_BASIC_H_ */ + + diff --git a/components/ssd1306/ssd1306.c b/components/ssd1306/ssd1306.c new file mode 100644 index 0000000..f5d97f3 --- /dev/null +++ b/components/ssd1306/ssd1306.c @@ -0,0 +1,619 @@ +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" + +#include "ssd1306.h" +#include "font8x8_basic.h" + +#define TAG "SSD1306" + +#define PACK8 __attribute__((aligned( __alignof__( uint8_t ) ), packed )) + +typedef union out_column_t { + uint32_t u32; + uint8_t u8[4]; +} PACK8 out_column_t; + +void ssd1306_init(SSD1306_t * dev, int width, int height) +{ + if (dev->_address == SPIAddress) { + spi_init(dev, width, height); + } else { + i2c_init(dev, width, height); + } + // Initialize internal buffer + for (int i=0;i_pages;i++) { + memset(dev->_page[i]._segs, 0, 128); + } +} + +int ssd1306_get_width(SSD1306_t * dev) +{ + return dev->_width; +} + +int ssd1306_get_height(SSD1306_t * dev) +{ + return dev->_height; +} + +int ssd1306_get_pages(SSD1306_t * dev) +{ + return dev->_pages; +} + +void ssd1306_show_buffer(SSD1306_t * dev) +{ + if (dev->_address == SPIAddress) { + for (int page=0; page_pages;page++) { + spi_display_image(dev, page, 0, dev->_page[page]._segs, dev->_width); + } + } else { + for (int page=0; page_pages;page++) { + i2c_display_image(dev, page, 0, dev->_page[page]._segs, dev->_width); + } + } +} + +void ssd1306_set_buffer(SSD1306_t * dev, uint8_t * buffer) +{ + int index = 0; + for (int page=0; page_pages;page++) { + memcpy(&dev->_page[page]._segs, &buffer[index], 128); + index = index + 128; + } +} + +void ssd1306_get_buffer(SSD1306_t * dev, uint8_t * buffer) +{ + int index = 0; + for (int page=0; page_pages;page++) { + memcpy(&buffer[index], &dev->_page[page]._segs, 128); + index = index + 128; + } +} + +void ssd1306_display_image(SSD1306_t * dev, int page, int seg, uint8_t * images, int width) +{ + if (dev->_address == SPIAddress) { + spi_display_image(dev, page, seg, images, width); + } else { + i2c_display_image(dev, page, seg, images, width); + } + // Set to internal buffer + memcpy(&dev->_page[page]._segs[seg], images, width); +} + +void ssd1306_display_text(SSD1306_t * dev, int page, char * text, int text_len, bool invert) +{ + if (page >= dev->_pages) return; + int _text_len = text_len; + if (_text_len > 16) _text_len = 16; + + uint8_t seg = 0; + uint8_t image[8]; + for (uint8_t i = 0; i < _text_len; i++) { + memcpy(image, font8x8_basic_tr[(uint8_t)text[i]], 8); + if (invert) ssd1306_invert(image, 8); + if (dev->_flip) ssd1306_flip(image, 8); + ssd1306_display_image(dev, page, seg, image, 8); +#if 0 + if (dev->_address == SPIAddress) { + spi_display_image(dev, page, seg, image, 8); + } else { + i2c_display_image(dev, page, seg, image, 8); + } +#endif + seg = seg + 8; + } +} + +// by Coert Vonk +void +ssd1306_display_text_x3(SSD1306_t * dev, int page, char * text, int text_len, bool invert) +{ + if (page >= dev->_pages) return; + int _text_len = text_len; + if (_text_len > 5) _text_len = 5; + + uint8_t seg = 0; + + for (uint8_t nn = 0; nn < _text_len; nn++) { + + uint8_t const * const in_columns = font8x8_basic_tr[(uint8_t)text[nn]]; + + // make the character 3x as high + out_column_t out_columns[8]; + memset(out_columns, 0, sizeof(out_columns)); + + for (uint8_t xx = 0; xx < 8; xx++) { // for each column (x-direction) + + uint32_t in_bitmask = 0b1; + uint32_t out_bitmask = 0b111; + + for (uint8_t yy = 0; yy < 8; yy++) { // for pixel (y-direction) + if (in_columns[xx] & in_bitmask) { + out_columns[xx].u32 |= out_bitmask; + } + in_bitmask <<= 1; + out_bitmask <<= 3; + } + } + + // render character in 8 column high pieces, making them 3x as wide + for (uint8_t yy = 0; yy < 3; yy++) { // for each group of 8 pixels high (y-direction) + + uint8_t image[24]; + for (uint8_t xx = 0; xx < 8; xx++) { // for each column (x-direction) + image[xx*3+0] = + image[xx*3+1] = + image[xx*3+2] = out_columns[xx].u8[yy]; + } + if (invert) ssd1306_invert(image, 24); + if (dev->_flip) ssd1306_flip(image, 24); + if (dev->_address == SPIAddress) { + spi_display_image(dev, page+yy, seg, image, 24); + } else { + i2c_display_image(dev, page+yy, seg, image, 24); + } + memcpy(&dev->_page[page+yy]._segs[seg], image, 24); + } + seg = seg + 24; + } +} + +void ssd1306_clear_screen(SSD1306_t * dev, bool invert) +{ + char space[16]; + memset(space, 0x00, sizeof(space)); + for (int page = 0; page < dev->_pages; page++) { + ssd1306_display_text(dev, page, space, sizeof(space), invert); + } +} + +void ssd1306_clear_line(SSD1306_t * dev, int page, bool invert) +{ + char space[16]; + memset(space, 0x00, sizeof(space)); + ssd1306_display_text(dev, page, space, sizeof(space), invert); +} + +void ssd1306_contrast(SSD1306_t * dev, int contrast) +{ + if (dev->_address == SPIAddress) { + spi_contrast(dev, contrast); + } else { + i2c_contrast(dev, contrast); + } +} + +void ssd1306_software_scroll(SSD1306_t * dev, int start, int end) +{ + ESP_LOGD(TAG, "software_scroll start=%d end=%d _pages=%d", start, end, dev->_pages); + if (start < 0 || end < 0) { + dev->_scEnable = false; + } else if (start >= dev->_pages || end >= dev->_pages) { + dev->_scEnable = false; + } else { + dev->_scEnable = true; + dev->_scStart = start; + dev->_scEnd = end; + dev->_scDirection = 1; + if (start > end ) dev->_scDirection = -1; + } +} + + +void ssd1306_scroll_text(SSD1306_t * dev, char * text, int text_len, bool invert) +{ + ESP_LOGD(TAG, "dev->_scEnable=%d", dev->_scEnable); + if (dev->_scEnable == false) return; + + void (*func)(SSD1306_t * dev, int page, int seg, uint8_t * images, int width); + if (dev->_address == SPIAddress) { + func = spi_display_image; + } else { + func = i2c_display_image; + } + + int srcIndex = dev->_scEnd - dev->_scDirection; + while(1) { + int dstIndex = srcIndex + dev->_scDirection; + ESP_LOGD(TAG, "srcIndex=%d dstIndex=%d", srcIndex,dstIndex); + for(int seg = 0; seg < dev->_width; seg++) { + dev->_page[dstIndex]._segs[seg] = dev->_page[srcIndex]._segs[seg]; + } + (*func)(dev, dstIndex, 0, dev->_page[dstIndex]._segs, sizeof(dev->_page[dstIndex]._segs)); + if (srcIndex == dev->_scStart) break; + srcIndex = srcIndex - dev->_scDirection; + } + + int _text_len = text_len; + if (_text_len > 16) _text_len = 16; + + ssd1306_display_text(dev, srcIndex, text, text_len, invert); +} + +void ssd1306_scroll_clear(SSD1306_t * dev) +{ + ESP_LOGD(TAG, "dev->_scEnable=%d", dev->_scEnable); + if (dev->_scEnable == false) return; + + int srcIndex = dev->_scEnd - dev->_scDirection; + while(1) { + int dstIndex = srcIndex + dev->_scDirection; + ESP_LOGD(TAG, "srcIndex=%d dstIndex=%d", srcIndex,dstIndex); + ssd1306_clear_line(dev, dstIndex, false); + if (dstIndex == dev->_scStart) break; + srcIndex = srcIndex - dev->_scDirection; + } +} + + +void ssd1306_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll) +{ + if (dev->_address == SPIAddress) { + spi_hardware_scroll(dev, scroll); + } else { + i2c_hardware_scroll(dev, scroll); + } +} + +// delay = 0 : display with no wait +// delay > 0 : display with wait +// delay < 0 : no display +void ssd1306_wrap_arround(SSD1306_t * dev, ssd1306_scroll_type_t scroll, int start, int end, int8_t delay) +{ + if (scroll == SCROLL_RIGHT) { + int _start = start; // 0 to 7 + int _end = end; // 0 to 7 + if (_end >= dev->_pages) _end = dev->_pages - 1; + uint8_t wk; + //for (int page=0;page_pages;page++) { + for (int page=_start;page<=_end;page++) { + wk = dev->_page[page]._segs[127]; + for (int seg=127;seg>0;seg--) { + dev->_page[page]._segs[seg] = dev->_page[page]._segs[seg-1]; + } + dev->_page[page]._segs[0] = wk; + } + + } else if (scroll == SCROLL_LEFT) { + int _start = start; // 0 to 7 + int _end = end; // 0 to 7 + if (_end >= dev->_pages) _end = dev->_pages - 1; + uint8_t wk; + //for (int page=0;page_pages;page++) { + for (int page=_start;page<=_end;page++) { + wk = dev->_page[page]._segs[0]; + for (int seg=0;seg<127;seg++) { + dev->_page[page]._segs[seg] = dev->_page[page]._segs[seg+1]; + } + dev->_page[page]._segs[127] = wk; + } + + } else if (scroll == SCROLL_UP) { + int _start = start; // 0 to {width-1} + int _end = end; // 0 to {width-1} + if (_end >= dev->_width) _end = dev->_width - 1; + uint8_t wk0; + uint8_t wk1; + uint8_t wk2; + uint8_t save[128]; + // Save pages 0 + for (int seg=0;seg<128;seg++) { + save[seg] = dev->_page[0]._segs[seg]; + } + // Page0 to Page6 + for (int page=0;page_pages-1;page++) { + //for (int seg=0;seg<128;seg++) { + for (int seg=_start;seg<=_end;seg++) { + wk0 = dev->_page[page]._segs[seg]; + wk1 = dev->_page[page+1]._segs[seg]; + if (dev->_flip) wk0 = ssd1306_rotate_byte(wk0); + if (dev->_flip) wk1 = ssd1306_rotate_byte(wk1); + if (seg == 0) { + ESP_LOGD(TAG, "b page=%d wk0=%02x wk1=%02x", page, wk0, wk1); + } + wk0 = wk0 >> 1; + wk1 = wk1 & 0x01; + wk1 = wk1 << 7; + wk2 = wk0 | wk1; + if (seg == 0) { + ESP_LOGD(TAG, "a page=%d wk0=%02x wk1=%02x wk2=%02x", page, wk0, wk1, wk2); + } + if (dev->_flip) wk2 = ssd1306_rotate_byte(wk2); + dev->_page[page]._segs[seg] = wk2; + } + } + // Page7 + int pages = dev->_pages-1; + //for (int seg=0;seg<128;seg++) { + for (int seg=_start;seg<=_end;seg++) { + wk0 = dev->_page[pages]._segs[seg]; + wk1 = save[seg]; + if (dev->_flip) wk0 = ssd1306_rotate_byte(wk0); + if (dev->_flip) wk1 = ssd1306_rotate_byte(wk1); + wk0 = wk0 >> 1; + wk1 = wk1 & 0x01; + wk1 = wk1 << 7; + wk2 = wk0 | wk1; + if (dev->_flip) wk2 = ssd1306_rotate_byte(wk2); + dev->_page[pages]._segs[seg] = wk2; + } + + } else if (scroll == SCROLL_DOWN) { + int _start = start; // 0 to {width-1} + int _end = end; // 0 to {width-1} + if (_end >= dev->_width) _end = dev->_width - 1; + uint8_t wk0; + uint8_t wk1; + uint8_t wk2; + uint8_t save[128]; + // Save pages 7 + int pages = dev->_pages-1; + for (int seg=0;seg<128;seg++) { + save[seg] = dev->_page[pages]._segs[seg]; + } + // Page7 to Page1 + for (int page=pages;page>0;page--) { + //for (int seg=0;seg<128;seg++) { + for (int seg=_start;seg<=_end;seg++) { + wk0 = dev->_page[page]._segs[seg]; + wk1 = dev->_page[page-1]._segs[seg]; + if (dev->_flip) wk0 = ssd1306_rotate_byte(wk0); + if (dev->_flip) wk1 = ssd1306_rotate_byte(wk1); + if (seg == 0) { + ESP_LOGD(TAG, "b page=%d wk0=%02x wk1=%02x", page, wk0, wk1); + } + wk0 = wk0 << 1; + wk1 = wk1 & 0x80; + wk1 = wk1 >> 7; + wk2 = wk0 | wk1; + if (seg == 0) { + ESP_LOGD(TAG, "a page=%d wk0=%02x wk1=%02x wk2=%02x", page, wk0, wk1, wk2); + } + if (dev->_flip) wk2 = ssd1306_rotate_byte(wk2); + dev->_page[page]._segs[seg] = wk2; + } + } + // Page0 + //for (int seg=0;seg<128;seg++) { + for (int seg=_start;seg<=_end;seg++) { + wk0 = dev->_page[0]._segs[seg]; + wk1 = save[seg]; + if (dev->_flip) wk0 = ssd1306_rotate_byte(wk0); + if (dev->_flip) wk1 = ssd1306_rotate_byte(wk1); + wk0 = wk0 << 1; + wk1 = wk1 & 0x80; + wk1 = wk1 >> 7; + wk2 = wk0 | wk1; + if (dev->_flip) wk2 = ssd1306_rotate_byte(wk2); + dev->_page[0]._segs[seg] = wk2; + } + + } + + if (delay >= 0) { + for (int page=0;page_pages;page++) { + if (dev->_address == SPIAddress) { + spi_display_image(dev, page, 0, dev->_page[page]._segs, 128); + } else { + i2c_display_image(dev, page, 0, dev->_page[page]._segs, 128); + } + if (delay) vTaskDelay(delay); + } + } + +} + +void ssd1306_bitmaps(SSD1306_t * dev, int xpos, int ypos, uint8_t * bitmap, int width, int height, bool invert) +{ + if ( (width % 8) != 0) { + ESP_LOGE(TAG, "width must be a multiple of 8"); + return; + } + int _width = width / 8; + uint8_t wk0; + uint8_t wk1; + uint8_t wk2; + uint8_t page = (ypos / 8); + uint8_t _seg = xpos; + uint8_t dstBits = (ypos % 8); + ESP_LOGD(TAG, "ypos=%d page=%d dstBits=%d", ypos, page, dstBits); + int offset = 0; + for(int _height=0;_height=0; srcBits--) { + wk0 = dev->_page[page]._segs[_seg]; + if (dev->_flip) wk0 = ssd1306_rotate_byte(wk0); + + wk1 = bitmap[index+offset]; + if (invert) wk1 = ~wk1; + + //wk2 = ssd1306_copy_bit(bitmap[index+offset], srcBits, wk0, dstBits); + wk2 = ssd1306_copy_bit(wk1, srcBits, wk0, dstBits); + if (dev->_flip) wk2 = ssd1306_rotate_byte(wk2); + + ESP_LOGD(TAG, "index=%d offset=%d page=%d _seg=%d, wk2=%02x", index, offset, page, _seg, wk2); + dev->_page[page]._segs[_seg] = wk2; + _seg++; + } + } + vTaskDelay(1); + offset = offset + _width; + dstBits++; + _seg = xpos; + if (dstBits == 8) { + page++; + dstBits=0; + } + } + +#if 0 + for (int _seg=ypos;_seg_page[_page]._segs[_seg]; + uint8_t wk1 = 1 << _bits; + ESP_LOGD(TAG, "ypos=%d _page=%d _bits=%d wk0=0x%02x wk1=0x%02x", ypos, _page, _bits, wk0, wk1); + if (invert) { + wk0 = wk0 & ~wk1; + } else { + wk0 = wk0 | wk1; + } + if (dev->_flip) wk0 = ssd1306_rotate_byte(wk0); + ESP_LOGD(TAG, "wk0=0x%02x wk1=0x%02x", wk0, wk1); + dev->_page[_page]._segs[_seg] = wk0; +} + +// Set line to internal buffer. Not show it. +void _ssd1306_line(SSD1306_t * dev, int x1, int y1, int x2, int y2, bool invert) +{ + int i; + int dx,dy; + int sx,sy; + int E; + + /* distance between two points */ + dx = ( x2 > x1 ) ? x2 - x1 : x1 - x2; + dy = ( y2 > y1 ) ? y2 - y1 : y1 - y2; + + /* direction of two point */ + sx = ( x2 > x1 ) ? 1 : -1; + sy = ( y2 > y1 ) ? 1 : -1; + + /* inclination < 1 */ + if ( dx > dy ) { + E = -dx; + for ( i = 0 ; i <= dx ; i++ ) { + _ssd1306_pixel(dev, x1, y1, invert); + x1 += sx; + E += 2 * dy; + if ( E >= 0 ) { + y1 += sy; + E -= 2 * dx; + } + } + + /* inclination >= 1 */ + } else { + E = -dy; + for ( i = 0 ; i <= dy ; i++ ) { + _ssd1306_pixel(dev, x1, y1, invert); + y1 += sy; + E += 2 * dx; + if ( E >= 0 ) { + x1 += sx; + E -= 2 * dy; + } + } + } +} + +void ssd1306_invert(uint8_t *buf, size_t blen) +{ + uint8_t wk; + for(int i=0; i0x48 +uint8_t ssd1306_rotate_byte(uint8_t ch1) { + uint8_t ch2 = 0; + for (int j=0;j<8;j++) { + ch2 = (ch2 << 1) + (ch1 & 0x01); + ch1 = ch1 >> 1; + } + return ch2; +} + + +void ssd1306_fadeout(SSD1306_t * dev) +{ + void (*func)(SSD1306_t * dev, int page, int seg, uint8_t * images, int width); + if (dev->_address == SPIAddress) { + func = spi_display_image; + } else { + func = i2c_display_image; + } + + uint8_t image[1]; + for(int page=0; page_pages; page++) { + image[0] = 0xFF; + for(int line=0; line<8; line++) { + if (dev->_flip) { + image[0] = image[0] >> 1; + } else { + image[0] = image[0] << 1; + } + for(int seg=0; seg<128; seg++) { + (*func)(dev, page, seg, image, 1); + dev->_page[page]._segs[seg] = image[0]; + } + } + } +} + +void ssd1306_dump(SSD1306_t dev) +{ + printf("_address=%x\n",dev._address); + printf("_width=%x\n",dev._width); + printf("_height=%x\n",dev._height); + printf("_pages=%x\n",dev._pages); +} + +void ssd1306_dump_page(SSD1306_t * dev, int page, int seg) +{ + ESP_LOGI(TAG, "dev->_page[%d]._segs[%d]=%02x", page, seg, dev->_page[page]._segs[seg]); +} + diff --git a/components/ssd1306/ssd1306.h b/components/ssd1306/ssd1306.h new file mode 100644 index 0000000..120315d --- /dev/null +++ b/components/ssd1306/ssd1306.h @@ -0,0 +1,157 @@ +#ifndef MAIN_SSD1306_H_ +#define MAIN_SSD1306_H_ + +#include "driver/spi_master.h" + +// Following definitions are bollowed from +// http://robotcantalk.blogspot.com/2015/03/interfacing-arduino-with-ssd1306-driven.html + +/* Control byte for i2c +Co : bit 8 : Continuation Bit + * 1 = no-continuation (only one byte to follow) + * 0 = the controller should expect a stream of bytes. +D/C# : bit 7 : Data/Command Select bit + * 1 = the next byte or byte stream will be Data. + * 0 = a Command byte or byte stream will be coming up next. + Bits 6-0 will be all zeros. +Usage: +0x80 : Single Command byte +0x00 : Command Stream +0xC0 : Single Data byte +0x40 : Data Stream +*/ +#define OLED_CONTROL_BYTE_CMD_SINGLE 0x80 +#define OLED_CONTROL_BYTE_CMD_STREAM 0x00 +#define OLED_CONTROL_BYTE_DATA_SINGLE 0xC0 +#define OLED_CONTROL_BYTE_DATA_STREAM 0x40 + +// Fundamental commands (pg.28) +#define OLED_CMD_SET_CONTRAST 0x81 // follow with 0x7F +#define OLED_CMD_DISPLAY_RAM 0xA4 +#define OLED_CMD_DISPLAY_ALLON 0xA5 +#define OLED_CMD_DISPLAY_NORMAL 0xA6 +#define OLED_CMD_DISPLAY_INVERTED 0xA7 +#define OLED_CMD_DISPLAY_OFF 0xAE +#define OLED_CMD_DISPLAY_ON 0xAF + +// Addressing Command Table (pg.30) +#define OLED_CMD_SET_MEMORY_ADDR_MODE 0x20 +#define OLED_CMD_SET_HORI_ADDR_MODE 0x00 // Horizontal Addressing Mode +#define OLED_CMD_SET_VERT_ADDR_MODE 0x01 // Vertical Addressing Mode +#define OLED_CMD_SET_PAGE_ADDR_MODE 0x02 // Page Addressing Mode +#define OLED_CMD_SET_COLUMN_RANGE 0x21 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x7F = COL127 +#define OLED_CMD_SET_PAGE_RANGE 0x22 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x07 = PAGE7 + +// Hardware Config (pg.31) +#define OLED_CMD_SET_DISPLAY_START_LINE 0x40 +#define OLED_CMD_SET_SEGMENT_REMAP_0 0xA0 +#define OLED_CMD_SET_SEGMENT_REMAP_1 0xA1 +#define OLED_CMD_SET_MUX_RATIO 0xA8 // follow with 0x3F = 64 MUX +#define OLED_CMD_SET_COM_SCAN_MODE 0xC8 +#define OLED_CMD_SET_DISPLAY_OFFSET 0xD3 // follow with 0x00 +#define OLED_CMD_SET_COM_PIN_MAP 0xDA // follow with 0x12 +#define OLED_CMD_NOP 0xE3 // NOP + +// Timing and Driving Scheme (pg.32) +#define OLED_CMD_SET_DISPLAY_CLK_DIV 0xD5 // follow with 0x80 +#define OLED_CMD_SET_PRECHARGE 0xD9 // follow with 0xF1 +#define OLED_CMD_SET_VCOMH_DESELCT 0xDB // follow with 0x30 + +// Charge Pump (pg.62) +#define OLED_CMD_SET_CHARGE_PUMP 0x8D // follow with 0x14 + +// Scrolling Command +#define OLED_CMD_HORIZONTAL_RIGHT 0x26 +#define OLED_CMD_HORIZONTAL_LEFT 0x27 +#define OLED_CMD_CONTINUOUS_SCROLL 0x29 +#define OLED_CMD_DEACTIVE_SCROLL 0x2E +#define OLED_CMD_ACTIVE_SCROLL 0x2F +#define OLED_CMD_VERTICAL 0xA3 + +#define I2CAddress 0x3C +#define SPIAddress 0xFF + +typedef enum { + SCROLL_RIGHT = 1, + SCROLL_LEFT = 2, + SCROLL_DOWN = 3, + SCROLL_UP = 4, + SCROLL_STOP = 5 +} ssd1306_scroll_type_t; + +typedef struct { + bool _valid; // Not using it anymore + int _segLen; // Not using it anymore + uint8_t _segs[128]; +} PAGE_t; + +typedef struct { + int _address; + int _width; + int _height; + int _pages; + int _dc; + spi_device_handle_t _SPIHandle; + bool _scEnable; + int _scStart; + int _scEnd; + int _scDirection; + PAGE_t _page[8]; + bool _flip; +} SSD1306_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +void ssd1306_init(SSD1306_t * dev, int width, int height); +int ssd1306_get_width(SSD1306_t * dev); +int ssd1306_get_height(SSD1306_t * dev); +int ssd1306_get_pages(SSD1306_t * dev); +void ssd1306_show_buffer(SSD1306_t * dev); +void ssd1306_set_buffer(SSD1306_t * dev, uint8_t * buffer); +void ssd1306_get_buffer(SSD1306_t * dev, uint8_t * buffer); +void ssd1306_display_image(SSD1306_t * dev, int page, int seg, uint8_t * images, int width); +void ssd1306_display_text(SSD1306_t * dev, int page, char * text, int text_len, bool invert); +void ssd1306_display_text_x3(SSD1306_t * dev, int page, char * text, int text_len, bool invert); +void ssd1306_clear_screen(SSD1306_t * dev, bool invert); +void ssd1306_clear_line(SSD1306_t * dev, int page, bool invert); +void ssd1306_contrast(SSD1306_t * dev, int contrast); +void ssd1306_software_scroll(SSD1306_t * dev, int start, int end); +void ssd1306_scroll_text(SSD1306_t * dev, char * text, int text_len, bool invert); +void ssd1306_scroll_clear(SSD1306_t * dev); +void ssd1306_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll); +void ssd1306_wrap_arround(SSD1306_t * dev, ssd1306_scroll_type_t scroll, int start, int end, int8_t delay); +void ssd1306_bitmaps(SSD1306_t * dev, int xpos, int ypos, uint8_t * bitmap, int width, int height, bool invert); +void _ssd1306_pixel(SSD1306_t * dev, int xpos, int ypos, bool invert); +void _ssd1306_line(SSD1306_t * dev, int x1, int y1, int x2, int y2, bool invert); +void ssd1306_invert(uint8_t *buf, size_t blen); +void ssd1306_flip(uint8_t *buf, size_t blen); +uint8_t ssd1306_copy_bit(uint8_t src, int srcBits, uint8_t dst, int dstBits); +uint8_t ssd1306_rotate_byte(uint8_t ch1); +void ssd1306_fadeout(SSD1306_t * dev); +void ssd1306_dump(SSD1306_t dev); +void ssd1306_dump_page(SSD1306_t * dev, int page, int seg); + +void i2c_master_init(SSD1306_t * dev, int16_t sda, int16_t scl, int16_t reset); +void i2c_init(SSD1306_t * dev, int width, int height); +void i2c_display_image(SSD1306_t * dev, int page, int seg, uint8_t * images, int width); +void i2c_contrast(SSD1306_t * dev, int contrast); +void i2c_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll); + +void spi_master_init(SSD1306_t * dev, int16_t GPIO_MOSI, int16_t GPIO_SCLK, int16_t GPIO_CS, int16_t GPIO_DC, int16_t GPIO_RESET); +bool spi_master_write_byte(spi_device_handle_t SPIHandle, const uint8_t* Data, size_t DataLength ); +bool spi_master_write_command(SSD1306_t * dev, uint8_t Command ); +bool spi_master_write_data(SSD1306_t * dev, const uint8_t* Data, size_t DataLength ); +void spi_init(SSD1306_t * dev, int width, int height); +void spi_display_image(SSD1306_t * dev, int page, int seg, uint8_t * images, int width); +void spi_contrast(SSD1306_t * dev, int contrast); +void spi_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll); + +#ifdef __cplusplus +} +#endif + +#endif /* MAIN_SSD1306_H_ */ + diff --git a/components/ssd1306/ssd1306_i2c.c b/components/ssd1306/ssd1306_i2c.c new file mode 100644 index 0000000..fe2d821 --- /dev/null +++ b/components/ssd1306/ssd1306_i2c.c @@ -0,0 +1,253 @@ +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "driver/i2c.h" +#include "esp_log.h" + +#include "ssd1306.h" + +#define tag "SSD1306" + +#if CONFIG_I2C_PORT_0 +#define I2C_NUM I2C_NUM_0 +#elif CONFIG_I2C_PORT_1 +#define I2C_NUM I2C_NUM_1 +#else +#define I2C_NUM I2C_NUM_0 // if spi is selected +#endif + +#define I2C_MASTER_FREQ_HZ 400000 /*!< I2C clock of SSD1306 can run at 400 kHz max. */ + +void i2c_master_init(SSD1306_t * dev, int16_t sda, int16_t scl, int16_t reset) +{ + i2c_config_t i2c_config = { + .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_MASTER_FREQ_HZ + }; + ESP_ERROR_CHECK(i2c_param_config(I2C_NUM, &i2c_config)); + ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0)); + + if (reset >= 0) { + //gpio_pad_select_gpio(reset); + gpio_reset_pin(reset); + gpio_set_direction(reset, GPIO_MODE_OUTPUT); + gpio_set_level(reset, 0); + vTaskDelay(50 / portTICK_PERIOD_MS); + gpio_set_level(reset, 1); + } + dev->_address = I2CAddress; + dev->_flip = false; +} + +void i2c_init(SSD1306_t * dev, int width, int height) { + dev->_width = width; + dev->_height = height; + dev->_pages = 8; + if (dev->_height == 32) dev->_pages = 4; + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (dev->_address << 1) | I2C_MASTER_WRITE, true); + i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true); + i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_OFF, true); // AE + i2c_master_write_byte(cmd, OLED_CMD_SET_MUX_RATIO, true); // A8 + if (dev->_height == 64) i2c_master_write_byte(cmd, 0x3F, true); + if (dev->_height == 32) i2c_master_write_byte(cmd, 0x1F, true); + i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_OFFSET, true); // D3 + i2c_master_write_byte(cmd, 0x00, true); + //i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true); // 40 + i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_START_LINE, true); // 40 + //i2c_master_write_byte(cmd, OLED_CMD_SET_SEGMENT_REMAP, true); // A1 + if (dev->_flip) { + i2c_master_write_byte(cmd, OLED_CMD_SET_SEGMENT_REMAP_0, true); // A0 + } else { + i2c_master_write_byte(cmd, OLED_CMD_SET_SEGMENT_REMAP_1, true); // A1 + } + i2c_master_write_byte(cmd, OLED_CMD_SET_COM_SCAN_MODE, true); // C8 + i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_CLK_DIV, true); // D5 + i2c_master_write_byte(cmd, 0x80, true); + i2c_master_write_byte(cmd, OLED_CMD_SET_COM_PIN_MAP, true); // DA + if (dev->_height == 64) i2c_master_write_byte(cmd, 0x12, true); + if (dev->_height == 32) i2c_master_write_byte(cmd, 0x02, true); + i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true); // 81 + i2c_master_write_byte(cmd, 0xFF, true); + i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_RAM, true); // A4 + i2c_master_write_byte(cmd, OLED_CMD_SET_VCOMH_DESELCT, true); // DB + i2c_master_write_byte(cmd, 0x40, true); + i2c_master_write_byte(cmd, OLED_CMD_SET_MEMORY_ADDR_MODE, true); // 20 + //i2c_master_write_byte(cmd, OLED_CMD_SET_HORI_ADDR_MODE, true); // 00 + i2c_master_write_byte(cmd, OLED_CMD_SET_PAGE_ADDR_MODE, true); // 02 + // Set Lower Column Start Address for Page Addressing Mode + i2c_master_write_byte(cmd, 0x00, true); + // Set Higher Column Start Address for Page Addressing Mode + i2c_master_write_byte(cmd, 0x10, true); + i2c_master_write_byte(cmd, OLED_CMD_SET_CHARGE_PUMP, true); // 8D + i2c_master_write_byte(cmd, 0x14, true); + i2c_master_write_byte(cmd, OLED_CMD_DEACTIVE_SCROLL, true); // 2E + i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_NORMAL, true); // A6 + i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_ON, true); // AF + + i2c_master_stop(cmd); + + esp_err_t espRc = i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS); + if (espRc == ESP_OK) { + ESP_LOGI(tag, "OLED configured successfully"); + } else { + ESP_LOGE(tag, "OLED configuration failed. code: 0x%.2X", espRc); + } + i2c_cmd_link_delete(cmd); +} + + +void i2c_display_image(SSD1306_t * dev, int page, int seg, uint8_t * images, int width) { + i2c_cmd_handle_t cmd; + + if (page >= dev->_pages) return; + if (seg >= dev->_width) return; + + int _seg = seg + CONFIG_OFFSETX; + uint8_t columLow = _seg & 0x0F; + uint8_t columHigh = (_seg >> 4) & 0x0F; + + int _page = page; + if (dev->_flip) { + _page = (dev->_pages - page) - 1; + } + + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (dev->_address << 1) | I2C_MASTER_WRITE, true); + + i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true); + // Set Lower Column Start Address for Page Addressing Mode + i2c_master_write_byte(cmd, (0x00 + columLow), true); + // Set Higher Column Start Address for Page Addressing Mode + i2c_master_write_byte(cmd, (0x10 + columHigh), true); + // Set Page Start Address for Page Addressing Mode + i2c_master_write_byte(cmd, 0xB0 | _page, true); + + i2c_master_stop(cmd); + i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (dev->_address << 1) | I2C_MASTER_WRITE, true); + + i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true); + i2c_master_write(cmd, images, width, true); + + i2c_master_stop(cmd); + i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); +} + +void i2c_contrast(SSD1306_t * dev, int contrast) { + i2c_cmd_handle_t cmd; + int _contrast = contrast; + if (contrast < 0x0) _contrast = 0; + if (contrast > 0xFF) _contrast = 0xFF; + + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (dev->_address << 1) | I2C_MASTER_WRITE, true); + i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true); + i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true); // 81 + i2c_master_write_byte(cmd, _contrast, true); + i2c_master_stop(cmd); + i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); +} + + +void i2c_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll) { + esp_err_t espRc; + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + + i2c_master_write_byte(cmd, (dev->_address << 1) | I2C_MASTER_WRITE, true); + i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true); + + if (scroll == SCROLL_RIGHT) { + i2c_master_write_byte(cmd, OLED_CMD_HORIZONTAL_RIGHT, true); // 26 + i2c_master_write_byte(cmd, 0x00, true); // Dummy byte + i2c_master_write_byte(cmd, 0x00, true); // Define start page address + i2c_master_write_byte(cmd, 0x07, true); // Frame frequency + i2c_master_write_byte(cmd, 0x07, true); // Define end page address + i2c_master_write_byte(cmd, 0x00, true); // + i2c_master_write_byte(cmd, 0xFF, true); // + i2c_master_write_byte(cmd, OLED_CMD_ACTIVE_SCROLL, true); // 2F + } + + if (scroll == SCROLL_LEFT) { + i2c_master_write_byte(cmd, OLED_CMD_HORIZONTAL_LEFT, true); // 27 + i2c_master_write_byte(cmd, 0x00, true); // Dummy byte + i2c_master_write_byte(cmd, 0x00, true); // Define start page address + i2c_master_write_byte(cmd, 0x07, true); // Frame frequency + i2c_master_write_byte(cmd, 0x07, true); // Define end page address + i2c_master_write_byte(cmd, 0x00, true); // + i2c_master_write_byte(cmd, 0xFF, true); // + i2c_master_write_byte(cmd, OLED_CMD_ACTIVE_SCROLL, true); // 2F + } + + if (scroll == SCROLL_DOWN) { + i2c_master_write_byte(cmd, OLED_CMD_CONTINUOUS_SCROLL, true); // 29 + i2c_master_write_byte(cmd, 0x00, true); // Dummy byte + i2c_master_write_byte(cmd, 0x00, true); // Define start page address + i2c_master_write_byte(cmd, 0x07, true); // Frame frequency + //i2c_master_write_byte(cmd, 0x01, true); // Define end page address + i2c_master_write_byte(cmd, 0x00, true); // Define end page address + i2c_master_write_byte(cmd, 0x3F, true); // Vertical scrolling offset + + i2c_master_write_byte(cmd, OLED_CMD_VERTICAL, true); // A3 + i2c_master_write_byte(cmd, 0x00, true); + if (dev->_height == 64) + //i2c_master_write_byte(cmd, 0x7F, true); + i2c_master_write_byte(cmd, 0x40, true); + if (dev->_height == 32) + i2c_master_write_byte(cmd, 0x20, true); + i2c_master_write_byte(cmd, OLED_CMD_ACTIVE_SCROLL, true); // 2F + } + + if (scroll == SCROLL_UP) { + i2c_master_write_byte(cmd, OLED_CMD_CONTINUOUS_SCROLL, true); // 29 + i2c_master_write_byte(cmd, 0x00, true); // Dummy byte + i2c_master_write_byte(cmd, 0x00, true); // Define start page address + i2c_master_write_byte(cmd, 0x07, true); // Frame frequency + //i2c_master_write_byte(cmd, 0x01, true); // Define end page address + i2c_master_write_byte(cmd, 0x00, true); // Define end page address + i2c_master_write_byte(cmd, 0x01, true); // Vertical scrolling offset + + i2c_master_write_byte(cmd, OLED_CMD_VERTICAL, true); // A3 + i2c_master_write_byte(cmd, 0x00, true); + if (dev->_height == 64) + //i2c_master_write_byte(cmd, 0x7F, true); + i2c_master_write_byte(cmd, 0x40, true); + if (dev->_height == 32) + i2c_master_write_byte(cmd, 0x20, true); + i2c_master_write_byte(cmd, OLED_CMD_ACTIVE_SCROLL, true); // 2F + } + + if (scroll == SCROLL_STOP) { + i2c_master_write_byte(cmd, OLED_CMD_DEACTIVE_SCROLL, true); // 2E + } + + i2c_master_stop(cmd); + espRc = i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS); + if (espRc == ESP_OK) { + ESP_LOGD(tag, "Scroll command succeeded"); + } else { + ESP_LOGE(tag, "Scroll command failed. code: 0x%.2X", espRc); + } + + i2c_cmd_link_delete(cmd); +} + diff --git a/components/ssd1306/ssd1306_spi.c b/components/ssd1306/ssd1306_spi.c new file mode 100644 index 0000000..fce2b7f --- /dev/null +++ b/components/ssd1306/ssd1306_spi.c @@ -0,0 +1,254 @@ +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "esp_log.h" + +#include "ssd1306.h" + +#define TAG "SSD1306" + +#if CONFIG_SPI2_HOST +#define HOST_ID SPI2_HOST +#elif CONFIG_SPI3_HOST +#define HOST_ID SPI3_HOST +#else +#define HOST_ID SPI2_HOST // If i2c is selected +#endif + +static const int SPI_Command_Mode = 0; +static const int SPI_Data_Mode = 1; +static const int SPI_Frequency = 1000000; // 1MHz + +void spi_master_init(SSD1306_t * dev, int16_t GPIO_MOSI, int16_t GPIO_SCLK, int16_t GPIO_CS, int16_t GPIO_DC, int16_t GPIO_RESET) +{ + esp_err_t ret; + + //gpio_pad_select_gpio( GPIO_CS ); + gpio_reset_pin( GPIO_CS ); + gpio_set_direction( GPIO_CS, GPIO_MODE_OUTPUT ); + gpio_set_level( GPIO_CS, 0 ); + + //gpio_pad_select_gpio( GPIO_DC ); + gpio_reset_pin( GPIO_DC ); + gpio_set_direction( GPIO_DC, GPIO_MODE_OUTPUT ); + gpio_set_level( GPIO_DC, 0 ); + + if ( GPIO_RESET >= 0 ) { + //gpio_pad_select_gpio( GPIO_RESET ); + gpio_reset_pin( GPIO_RESET ); + gpio_set_direction( GPIO_RESET, GPIO_MODE_OUTPUT ); + gpio_set_level( GPIO_RESET, 0 ); + vTaskDelay( pdMS_TO_TICKS( 100 ) ); + gpio_set_level( GPIO_RESET, 1 ); + } + + spi_bus_config_t spi_bus_config = { + .mosi_io_num = GPIO_MOSI, + .miso_io_num = -1, + .sclk_io_num = GPIO_SCLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = 0, + .flags = 0 + }; + + ESP_LOGI(TAG, "SPI HOST_ID=%d", HOST_ID); + ret = spi_bus_initialize( HOST_ID, &spi_bus_config, SPI_DMA_CH_AUTO ); + ESP_LOGI(TAG, "spi_bus_initialize=%d",ret); + assert(ret==ESP_OK); + + spi_device_interface_config_t devcfg; + memset( &devcfg, 0, sizeof( spi_device_interface_config_t ) ); + devcfg.clock_speed_hz = SPI_Frequency; + devcfg.spics_io_num = GPIO_CS; + devcfg.queue_size = 1; + + spi_device_handle_t handle; + ret = spi_bus_add_device( HOST_ID, &devcfg, &handle); + ESP_LOGI(TAG, "spi_bus_add_device=%d",ret); + assert(ret==ESP_OK); + dev->_dc = GPIO_DC; + dev->_SPIHandle = handle; + dev->_address = SPIAddress; + dev->_flip = false; +} + + +bool spi_master_write_byte(spi_device_handle_t SPIHandle, const uint8_t* Data, size_t DataLength ) +{ + spi_transaction_t SPITransaction; + + if ( DataLength > 0 ) { + memset( &SPITransaction, 0, sizeof( spi_transaction_t ) ); + SPITransaction.length = DataLength * 8; + SPITransaction.tx_buffer = Data; + spi_device_transmit( SPIHandle, &SPITransaction ); + } + + return true; +} + +bool spi_master_write_command(SSD1306_t * dev, uint8_t Command ) +{ + static uint8_t CommandByte = 0; + CommandByte = Command; + gpio_set_level( dev->_dc, SPI_Command_Mode ); + return spi_master_write_byte( dev->_SPIHandle, &CommandByte, 1 ); +} + +bool spi_master_write_data(SSD1306_t * dev, const uint8_t* Data, size_t DataLength ) +{ + gpio_set_level( dev->_dc, SPI_Data_Mode ); + return spi_master_write_byte( dev->_SPIHandle, Data, DataLength ); +} + + +void spi_init(SSD1306_t * dev, int width, int height) +{ + dev->_width = width; + dev->_height = height; + dev->_pages = 8; + if (dev->_height == 32) dev->_pages = 4; + + spi_master_write_command(dev, OLED_CMD_DISPLAY_OFF); // AE + spi_master_write_command(dev, OLED_CMD_SET_MUX_RATIO); // A8 + if (dev->_height == 64) spi_master_write_command(dev, 0x3F); + if (dev->_height == 32) spi_master_write_command(dev, 0x1F); + spi_master_write_command(dev, OLED_CMD_SET_DISPLAY_OFFSET); // D3 + spi_master_write_command(dev, 0x00); + spi_master_write_command(dev, OLED_CONTROL_BYTE_DATA_STREAM); // 40 + if (dev->_flip) { + spi_master_write_command(dev, OLED_CMD_SET_SEGMENT_REMAP_0); // A0 + } else { + spi_master_write_command(dev, OLED_CMD_SET_SEGMENT_REMAP_1); // A1 + } + //spi_master_write_command(dev, OLED_CMD_SET_SEGMENT_REMAP); // A1 + spi_master_write_command(dev, OLED_CMD_SET_COM_SCAN_MODE); // C8 + spi_master_write_command(dev, OLED_CMD_SET_DISPLAY_CLK_DIV); // D5 + spi_master_write_command(dev, 0x80); + spi_master_write_command(dev, OLED_CMD_SET_COM_PIN_MAP); // DA + if (dev->_height == 64) spi_master_write_command(dev, 0x12); + if (dev->_height == 32) spi_master_write_command(dev, 0x02); + spi_master_write_command(dev, OLED_CMD_SET_CONTRAST); // 81 + spi_master_write_command(dev, 0xFF); + spi_master_write_command(dev, OLED_CMD_DISPLAY_RAM); // A4 + spi_master_write_command(dev, OLED_CMD_SET_VCOMH_DESELCT); // DB + spi_master_write_command(dev, 0x40); + spi_master_write_command(dev, OLED_CMD_SET_MEMORY_ADDR_MODE); // 20 + //spi_master_write_command(dev, OLED_CMD_SET_HORI_ADDR_MODE); // 00 + spi_master_write_command(dev, OLED_CMD_SET_PAGE_ADDR_MODE); // 02 + // Set Lower Column Start Address for Page Addressing Mode + spi_master_write_command(dev, 0x00); + // Set Higher Column Start Address for Page Addressing Mode + spi_master_write_command(dev, 0x10); + spi_master_write_command(dev, OLED_CMD_SET_CHARGE_PUMP); // 8D + spi_master_write_command(dev, 0x14); + spi_master_write_command(dev, OLED_CMD_DEACTIVE_SCROLL); // 2E + spi_master_write_command(dev, OLED_CMD_DISPLAY_NORMAL); // A6 + spi_master_write_command(dev, OLED_CMD_DISPLAY_ON); // AF +} + + +void spi_display_image(SSD1306_t * dev, int page, int seg, uint8_t * images, int width) +{ + if (page >= dev->_pages) return; + if (seg >= dev->_width) return; + + int _seg = seg + CONFIG_OFFSETX; + uint8_t columLow = _seg & 0x0F; + uint8_t columHigh = (_seg >> 4) & 0x0F; + + int _page = page; + if (dev->_flip) { + _page = (dev->_pages - page) - 1; + } + + // Set Lower Column Start Address for Page Addressing Mode + spi_master_write_command(dev, (0x00 + columLow)); + // Set Higher Column Start Address for Page Addressing Mode + spi_master_write_command(dev, (0x10 + columHigh)); + // Set Page Start Address for Page Addressing Mode + spi_master_write_command(dev, 0xB0 | _page); + + spi_master_write_data(dev, images, width); + +} + +void spi_contrast(SSD1306_t * dev, int contrast) { + int _contrast = contrast; + if (contrast < 0x0) _contrast = 0; + if (contrast > 0xFF) _contrast = 0xFF; + + spi_master_write_command(dev, OLED_CMD_SET_CONTRAST); // 81 + spi_master_write_command(dev, _contrast); +} + +void spi_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll) +{ + + if (scroll == SCROLL_RIGHT) { + spi_master_write_command(dev, OLED_CMD_HORIZONTAL_RIGHT); // 26 + spi_master_write_command(dev, 0x00); // Dummy byte + spi_master_write_command(dev, 0x00); // Define start page address + spi_master_write_command(dev, 0x07); // Frame frequency + spi_master_write_command(dev, 0x07); // Define end page address + spi_master_write_command(dev, 0x00); // + spi_master_write_command(dev, 0xFF); // + spi_master_write_command(dev, OLED_CMD_ACTIVE_SCROLL); // 2F + } + + if (scroll == SCROLL_LEFT) { + spi_master_write_command(dev, OLED_CMD_HORIZONTAL_LEFT); // 27 + spi_master_write_command(dev, 0x00); // Dummy byte + spi_master_write_command(dev, 0x00); // Define start page address + spi_master_write_command(dev, 0x07); // Frame frequency + spi_master_write_command(dev, 0x07); // Define end page address + spi_master_write_command(dev, 0x00); // + spi_master_write_command(dev, 0xFF); // + spi_master_write_command(dev, OLED_CMD_ACTIVE_SCROLL); // 2F + } + + if (scroll == SCROLL_DOWN) { + spi_master_write_command(dev, OLED_CMD_CONTINUOUS_SCROLL); // 29 + spi_master_write_command(dev, 0x00); // Dummy byte + spi_master_write_command(dev, 0x00); // Define start page address + spi_master_write_command(dev, 0x07); // Frame frequency + //spi_master_write_command(dev, 0x01); // Define end page address + spi_master_write_command(dev, 0x00); // Define end page address + spi_master_write_command(dev, 0x3F); // Vertical scrolling offset + + spi_master_write_command(dev, OLED_CMD_VERTICAL); // A3 + spi_master_write_command(dev, 0x00); + if (dev->_height == 64) + spi_master_write_command(dev, 0x40); + if (dev->_height == 32) + spi_master_write_command(dev, 0x20); + spi_master_write_command(dev, OLED_CMD_ACTIVE_SCROLL); // 2F + } + + if (scroll == SCROLL_UP) { + spi_master_write_command(dev, OLED_CMD_CONTINUOUS_SCROLL); // 29 + spi_master_write_command(dev, 0x00); // Dummy byte + spi_master_write_command(dev, 0x00); // Define start page address + spi_master_write_command(dev, 0x07); // Frame frequency + //spi_master_write_command(dev, 0x01); // Define end page address + spi_master_write_command(dev, 0x00); // Define end page address + spi_master_write_command(dev, 0x01); // Vertical scrolling offset + + spi_master_write_command(dev, OLED_CMD_VERTICAL); // A3 + spi_master_write_command(dev, 0x00); + if (dev->_height == 64) + spi_master_write_command(dev, 0x40); + if (dev->_height == 32) + spi_master_write_command(dev, 0x20); + spi_master_write_command(dev, OLED_CMD_ACTIVE_SCROLL); // 2F + } + + if (scroll == SCROLL_STOP) { + spi_master_write_command(dev, OLED_CMD_DEACTIVE_SCROLL); // 2E + } +}