First upload to repository

This commit is contained in:
2024-11-15 22:29:50 +01:00
parent 9252f8896b
commit 9bb6b8c99a
2620 changed files with 306264 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
set(component_srcs "ssd1306.c" "ssd1306_spi.c")
# get IDF version for comparison
set(idf_version "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}")
if(idf_version VERSION_GREATER_EQUAL "5.2")
if(CONFIG_LEGACY_DRIVER)
list(APPEND component_srcs "ssd1306_i2c_legacy.c")
else()
list(APPEND component_srcs "ssd1306_i2c_new.c")
endif()
else()
list(APPEND component_srcs "ssd1306_i2c_legacy.c")
endif()
idf_component_register(SRCS "${component_srcs}" PRIV_REQUIRES driver INCLUDE_DIRS "include")

View File

@@ -0,0 +1,186 @@
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
default 27 if IDF_TARGET_ESP32H2
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 2 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 2 if IDF_TARGET_ESP32H2
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 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 1 if IDF_TARGET_ESP32H2
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 || 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 || 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 || 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 || 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 || 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_ARCH_XTENSA
bool "I2C_PORT_1"
help
Use I2C_PORT_1.
endchoice
config LEGACY_DRIVER
depends on I2C_INTERFACE
bool "Force legacy i2c driver"
default false
help
Force legacy i2c driver.
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_ARCH_XTENSA
bool "SPI3_HOST"
help
USE SPI3_HOST. This is also called VSPI_HOST
endchoice
endmenu

View File

@@ -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_ */

View File

@@ -0,0 +1,177 @@
#ifndef MAIN_SSD1306_H_
#define MAIN_SSD1306_H_
#include "driver/spi_master.h"
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0))
#include "driver/i2c_master.h"
#else
#include "driver/i2c.h"
#endif
// 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 I2C_ADDRESS 0x3C
#define SPI_ADDRESS 0xFF
typedef enum {
SCROLL_RIGHT = 1,
SCROLL_LEFT = 2,
SCROLL_DOWN = 3,
SCROLL_UP = 4,
PAGE_SCROLL_DOWN = 5,
PAGE_SCROLL_UP = 6,
SCROLL_STOP = 7
} 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;
bool _scEnable;
int _scStart;
int _scEnd;
int _scDirection;
PAGE_t _page[8];
bool _flip;
i2c_port_t _i2c_num;
spi_device_handle_t _spi_device_handle;
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0))
i2c_master_dev_handle_t _i2c_dev_handle;
#endif
} 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_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_circle(SSD1306_t * dev, int x0, int y0, int r, bool invert);
void _ssd1306_cursor(SSD1306_t * dev, int x0, int y0, int r, 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_rotate_image(uint8_t *image, bool flip);
void ssd1306_display_rotate_text(SSD1306_t * dev, int seg, char * text, int text_len, bool invert);
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_device_add(SSD1306_t * dev, i2c_port_t i2c_num, int16_t reset);
void i2c_bus_add(SSD1306_t * dev, i2c_master_bus_handle_t bus_handle, i2c_port_t i2c_num, 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_clock_speed(int speed);
void spi_master_init(SSD1306_t * dev, int16_t mosi, int16_t sclk, int16_t cs, int16_t dc, int16_t reset);
void spi_device_add(SSD1306_t * dev, int16_t cs, int16_t dc, int16_t 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_ */

View File

@@ -0,0 +1,733 @@
#include <string.h>
#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 == SPI_ADDRESS) {
spi_init(dev, width, height);
} else {
i2c_init(dev, width, height);
}
// Initialize internal buffer
for (int i=0;i<dev->_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 == SPI_ADDRESS) {
for (int page=0; page<dev->_pages;page++) {
spi_display_image(dev, page, 0, dev->_page[page]._segs, dev->_width);
}
} else {
for (int page=0; page<dev->_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<dev->_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<dev->_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 == SPI_ADDRESS) {
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 == SPI_ADDRESS) {
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 == SPI_ADDRESS) {
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 == SPI_ADDRESS) {
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 == SPI_ADDRESS) {
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 == SPI_ADDRESS) {
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<dev->_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<dev->_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<dev->_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;
}
} else if (scroll == PAGE_SCROLL_DOWN) {
uint8_t save[128];
// Save pages 7
for (int seg=0;seg<128;seg++) {
save[seg] = dev->_page[dev->_pages-1]._segs[seg];
}
// Page7 to Page1
for (int page=dev->_pages-1;page>0;page--) {
for (int seg=0;seg<128;seg++) {
dev->_page[page]._segs[seg] = dev->_page[page-1]._segs[seg];
}
}
// Store pages 0
for (int seg=0;seg<128;seg++) {
dev->_page[0]._segs[seg] = save[seg];
}
} else if (scroll == PAGE_SCROLL_UP) {
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<dev->_pages-1;page++) {
for (int seg=0;seg<128;seg++) {
dev->_page[page]._segs[seg] = dev->_page[page+1]._segs[seg];
}
}
// Store pages 7
for (int seg=0;seg<128;seg++) {
dev->_page[dev->_pages-1]._segs[seg] = save[seg];
}
}
if (delay >= 0) {
for (int page=0;page<dev->_pages;page++) {
if (dev->_address == SPI_ADDRESS) {
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<height;_height++) {
for (int index=0;index<_width;index++) {
for (int srcBits=7; srcBits>=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<ypos+width;_seg++) {
ssd1306_dump_page(dev, page-1, _seg);
}
for (int _seg=ypos;_seg<ypos+width;_seg++) {
ssd1306_dump_page(dev, page, _seg);
}
#endif
//ssd1306_show_buffer(dev);
}
void ssd1306_bitmaps(SSD1306_t * dev, int xpos, int ypos, uint8_t * bitmap, int width, int height, bool invert)
{
_ssd1306_bitmaps(dev, xpos, ypos, bitmap, width, height, invert);
ssd1306_show_buffer(dev);
}
// Set pixel to internal buffer. Not show it.
void _ssd1306_pixel(SSD1306_t * dev, int xpos, int ypos, bool invert)
{
uint8_t _page = (ypos / 8);
uint8_t _bits = (ypos % 8);
uint8_t _seg = xpos;
uint8_t wk0 = dev->_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;
}
}
}
}
// Draw circle
void _ssd1306_circle(SSD1306_t * dev, int x0, int y0, int r, bool invert)
{
int x;
int y;
int err;
int old_err;
x=0;
y=-r;
err=2-2*r;
do{
_ssd1306_pixel(dev, x0-x, y0+y, invert);
_ssd1306_pixel(dev, x0-y, y0-x, invert);
_ssd1306_pixel(dev, x0+x, y0-y, invert);
_ssd1306_pixel(dev, x0+y, y0+x, invert);
if ((old_err=err)<=x) err+=++x*2+1;
if (old_err>y || err>x) err+=++y*2+1;
} while(y<0);
}
// Draw cursor
void _ssd1306_cursor(SSD1306_t * dev, int x0, int y0, int r, bool invert)
{
_ssd1306_line(dev, x0-r, y0, x0+r, y0, invert);
_ssd1306_line(dev, x0, y0-r, x0, y0+r, invert);
}
void ssd1306_invert(uint8_t *buf, size_t blen)
{
uint8_t wk;
for(int i=0; i<blen; i++){
wk = buf[i];
buf[i] = ~wk;
}
}
// Flip upside down
void ssd1306_flip(uint8_t *buf, size_t blen)
{
for(int i=0; i<blen; i++){
buf[i] = ssd1306_rotate_byte(buf[i]);
}
}
uint8_t ssd1306_copy_bit(uint8_t src, int srcBits, uint8_t dst, int dstBits)
{
ESP_LOGD(TAG, "src=%02x srcBits=%d dst=%02x dstBits=%d", src, srcBits, dst, dstBits);
uint8_t smask = 0x01 << srcBits;
uint8_t dmask = 0x01 << dstBits;
uint8_t _src = src & smask;
#if 0
if (_src != 0) _src = 1;
uint8_t _wk = _src << dstBits;
uint8_t _dst = dst | _wk;
#endif
uint8_t _dst;
if (_src != 0) {
_dst = dst | dmask; // set bit
} else {
_dst = dst & ~(dmask); // clear bit
}
return _dst;
}
// Rotate 8-bit data
// 0x12-->0x48
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 == SPI_ADDRESS) {
func = spi_display_image;
} else {
func = i2c_display_image;
}
uint8_t image[1];
for(int page=0; page<dev->_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];
}
}
}
}
// Rotate character image
// Only valid for 8 dots x 8 dots
void ssd1306_rotate_image(uint8_t *image, bool flip) {
uint8_t _image[8];
uint8_t _smask = 0x01;
for (int i=0;i<8;i++) {
uint8_t _dmask = 0x80;
_image[i] = 0;
for (int j=0;j<8;j++) {
uint8_t _wk = image[j] & _smask;
ESP_LOGD(TAG, "image[%d]=0x%x _smask=0x%x _wk=0x%x", j, image[j], _smask, _wk);
if (_wk != 0) {
_image[i] = _image[i] + _dmask;
}
_dmask = _dmask >> 1;
}
_smask = _smask << 1;
}
for (int i=0;i<8;i++) {
image[i] = _image[i];
}
if (flip) ssd1306_flip(image, 8);
#if 0
for (int i=0;i<8;i++) {
ESP_LOGI(TAG, "image[%d]=0x%x", i, image[i]);
}
#endif
}
void ssd1306_display_rotate_text(SSD1306_t * dev, int seg, char * text, int text_len, bool invert) {
int _text_len = text_len;
if (_text_len > 8) _text_len = 8;
uint8_t image[8];
int _page = dev->_pages-1;
for (uint8_t i = 0; i < _text_len; i++) {
memcpy(image, font8x8_basic_tr[(uint8_t)text[i]], 8);
ssd1306_rotate_image(image, dev->_flip);
ESP_LOGD(TAG, "_page=%d seg=%d", _page, seg);
if (invert) ssd1306_invert(image, 8);
ssd1306_display_image(dev, _page, seg, image, 8);
_page--;
if (_page < 0) return;
}
}
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]);
}

View File

@@ -0,0 +1,290 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.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.
#define I2C_TICKS_TO_WAIT 100 // Maximum ticks to wait before issuing a timeout.
void i2c_master_init(SSD1306_t * dev, int16_t sda, int16_t scl, int16_t reset)
{
ESP_LOGI(TAG, "Legacy i2c driver is used");
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 = I2C_ADDRESS;
dev->_flip = false;
dev->_i2c_num = I2C_NUM;
}
void i2c_device_add(SSD1306_t * dev, i2c_port_t i2c_num, int16_t reset)
{
ESP_LOGI(TAG, "Legacy i2c driver is used");
ESP_LOGW(TAG, "Will not install i2c master driver");
#if 0
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));
#endif
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 = I2C_ADDRESS;
dev->_flip = false;
dev->_i2c_num = i2c_num;
}
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 res = i2c_master_cmd_begin(dev->_i2c_num, cmd, I2C_TICKS_TO_WAIT);
if (res == ESP_OK) {
ESP_LOGI(TAG, "OLED configured successfully");
} else {
ESP_LOGE(TAG, "OLED configuration failed. code: 0x%.2X", res);
}
i2c_cmd_link_delete(cmd);
}
void i2c_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;
}
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);
// 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);
esp_err_t res = i2c_master_cmd_begin(dev->_i2c_num, cmd, I2C_TICKS_TO_WAIT);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Image command failed. code: 0x%.2X", res);
}
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);
res = i2c_master_cmd_begin(dev->_i2c_num, cmd, I2C_TICKS_TO_WAIT);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Image command failed. code: 0x%.2X", res);
}
i2c_cmd_link_delete(cmd);
}
void i2c_contrast(SSD1306_t * dev, int contrast) {
int _contrast = contrast;
if (contrast < 0x0) _contrast = 0;
if (contrast > 0xFF) _contrast = 0xFF;
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); // 00
i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true); // 81
i2c_master_write_byte(cmd, _contrast, true);
i2c_master_stop(cmd);
esp_err_t res = i2c_master_cmd_begin(dev->_i2c_num, cmd, I2C_TICKS_TO_WAIT);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Contrast command failed. code: 0x%.2X", res);
}
i2c_cmd_link_delete(cmd);
}
void i2c_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll) {
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); // 00
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);
esp_err_t res = i2c_master_cmd_begin(dev->_i2c_num, cmd, I2C_TICKS_TO_WAIT);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Scroll command failed. code: 0x%.2X", res);
}
i2c_cmd_link_delete(cmd);
}

View File

@@ -0,0 +1,290 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c_master.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.
#define I2C_TICKS_TO_WAIT 100 // Maximum ticks to wait before issuing a timeout.
void i2c_master_init(SSD1306_t * dev, int16_t sda, int16_t scl, int16_t reset)
{
ESP_LOGI(TAG, "New i2c driver is used");
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.i2c_port = I2C_NUM,
.scl_io_num = scl,
.sda_io_num = sda,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = I2C_ADDRESS,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
i2c_master_dev_handle_t i2c_dev_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &i2c_dev_handle));
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 = I2C_ADDRESS;
dev->_flip = false;
dev->_i2c_num = I2C_NUM;
dev->_i2c_dev_handle = i2c_dev_handle;
}
void i2c_bus_add(SSD1306_t * dev, i2c_master_bus_handle_t bus_handle, i2c_port_t i2c_num, int16_t reset)
{
ESP_LOGI(TAG, "New i2c driver is used");
ESP_LOGW(TAG, "Will not install i2c master driver");
#if 0
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.i2c_port = I2C_NUM,
.scl_io_num = scl,
.sda_io_num = sda,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
#endif
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = I2C_ADDRESS,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
i2c_master_dev_handle_t i2c_dev_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &i2c_dev_handle));
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 = I2C_ADDRESS;
dev->_flip = false;
dev->_i2c_num = i2c_num;
dev->_i2c_dev_handle = i2c_dev_handle;
}
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;
uint8_t out_buf[27];
int out_index = 0;
out_buf[out_index++] = OLED_CONTROL_BYTE_CMD_STREAM;
out_buf[out_index++] = OLED_CMD_DISPLAY_OFF; // AE
out_buf[out_index++] = OLED_CMD_SET_MUX_RATIO; // A8
if (dev->_height == 64) out_buf[out_index++] = 0x3F;
if (dev->_height == 32) out_buf[out_index++] = 0x1F;
out_buf[out_index++] = OLED_CMD_SET_DISPLAY_OFFSET; // D3
out_buf[out_index++] = 0x00;
//out_buf[out_index++] = OLED_CONTROL_BYTE_DATA_STREAM; // 40
out_buf[out_index++] = OLED_CMD_SET_DISPLAY_START_LINE; // 40
//out_buf[out_index++] = OLED_CMD_SET_SEGMENT_REMAP; // A1
if (dev->_flip) {
out_buf[out_index++] = OLED_CMD_SET_SEGMENT_REMAP_0; // A0
} else {
out_buf[out_index++] = OLED_CMD_SET_SEGMENT_REMAP_1; // A1
}
out_buf[out_index++] = OLED_CMD_SET_COM_SCAN_MODE; // C8
out_buf[out_index++] = OLED_CMD_SET_DISPLAY_CLK_DIV; // D5
out_buf[out_index++] = 0x80;
out_buf[out_index++] = OLED_CMD_SET_COM_PIN_MAP; // DA
if (dev->_height == 64) out_buf[out_index++] = 0x12;
if (dev->_height == 32) out_buf[out_index++] = 0x02;
out_buf[out_index++] = OLED_CMD_SET_CONTRAST; // 81
out_buf[out_index++] = 0xFF;
out_buf[out_index++] = OLED_CMD_DISPLAY_RAM; // A4
out_buf[out_index++] = OLED_CMD_SET_VCOMH_DESELCT; // DB
out_buf[out_index++] = 0x40;
out_buf[out_index++] = OLED_CMD_SET_MEMORY_ADDR_MODE; // 20
//out_buf[out_index++] = OLED_CMD_SET_HORI_ADDR_MODE; // 00
out_buf[out_index++] = OLED_CMD_SET_PAGE_ADDR_MODE; // 02
// Set Lower Column Start Address for Page Addressing Mode
out_buf[out_index++] = 0x00;
// Set Higher Column Start Address for Page Addressing Mode
out_buf[out_index++] = 0x10;
out_buf[out_index++] = OLED_CMD_SET_CHARGE_PUMP; // 8D
out_buf[out_index++] = 0x14;
out_buf[out_index++] = OLED_CMD_DEACTIVE_SCROLL; // 2E
out_buf[out_index++] = OLED_CMD_DISPLAY_NORMAL; // A6
out_buf[out_index++] = OLED_CMD_DISPLAY_ON; // AF
esp_err_t res;
res = i2c_master_transmit(dev->_i2c_dev_handle, out_buf, out_index, I2C_TICKS_TO_WAIT);
if (res == ESP_OK) {
ESP_LOGI(TAG, "OLED configured successfully");
} else {
ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->_address, dev->_i2c_num, res, esp_err_to_name(res));
}
}
void i2c_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;
}
uint8_t *out_buf;
out_buf = malloc(width + 1);
if (out_buf == NULL) {
ESP_LOGE(TAG, "malloc fail");
return;
}
int out_index = 0;
out_buf[out_index++] = OLED_CONTROL_BYTE_CMD_STREAM;
// Set Lower Column Start Address for Page Addressing Mode
out_buf[out_index++] = (0x00 + columLow);
// Set Higher Column Start Address for Page Addressing Mode
out_buf[out_index++] = (0x10 + columHigh);
// Set Page Start Address for Page Addressing Mode
out_buf[out_index++] = 0xB0 | _page;
esp_err_t res;
res = i2c_master_transmit(dev->_i2c_dev_handle, out_buf, out_index, I2C_TICKS_TO_WAIT);
if (res != ESP_OK)
ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->_address, dev->_i2c_num, res, esp_err_to_name(res));
out_buf[0] = OLED_CONTROL_BYTE_DATA_STREAM;
memcpy(&out_buf[1], images, width);
res = i2c_master_transmit(dev->_i2c_dev_handle, out_buf, width + 1, I2C_TICKS_TO_WAIT);
if (res != ESP_OK)
ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->_address, dev->_i2c_num, res, esp_err_to_name(res));
free(out_buf);
}
void i2c_contrast(SSD1306_t * dev, int contrast) {
uint8_t _contrast = contrast;
if (contrast < 0x0) _contrast = 0;
if (contrast > 0xFF) _contrast = 0xFF;
uint8_t out_buf[3];
int out_index = 0;
out_buf[out_index++] = OLED_CONTROL_BYTE_CMD_STREAM; // 00
out_buf[out_index++] = OLED_CMD_SET_CONTRAST; // 81
out_buf[out_index++] = _contrast;
esp_err_t res = i2c_master_transmit(dev->_i2c_dev_handle, out_buf, 3, I2C_TICKS_TO_WAIT);
if (res != ESP_OK)
ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->_address, dev->_i2c_num, res, esp_err_to_name(res));
}
void i2c_hardware_scroll(SSD1306_t * dev, ssd1306_scroll_type_t scroll) {
uint8_t out_buf[11];
int out_index = 0;
out_buf[out_index++] = OLED_CONTROL_BYTE_CMD_STREAM; // 00
if (scroll == SCROLL_RIGHT) {
out_buf[out_index++] = OLED_CMD_HORIZONTAL_RIGHT; // 26
out_buf[out_index++] = 0x00; // Dummy byte
out_buf[out_index++] = 0x00; // Define start page address
out_buf[out_index++] = 0x07; // Frame frequency
out_buf[out_index++] = 0x07; // Define end page address
out_buf[out_index++] = 0x00; //
out_buf[out_index++] = 0xFF; //
out_buf[out_index++] = OLED_CMD_ACTIVE_SCROLL; // 2F
}
if (scroll == SCROLL_LEFT) {
out_buf[out_index++] = OLED_CMD_HORIZONTAL_LEFT; // 27
out_buf[out_index++] = 0x00; // Dummy byte
out_buf[out_index++] = 0x00; // Define start page address
out_buf[out_index++] = 0x07; // Frame frequency
out_buf[out_index++] = 0x07; // Define end page address
out_buf[out_index++] = 0x00; //
out_buf[out_index++] = 0xFF; //
out_buf[out_index++] = OLED_CMD_ACTIVE_SCROLL; // 2F
}
if (scroll == SCROLL_DOWN) {
out_buf[out_index++] = OLED_CMD_CONTINUOUS_SCROLL; // 29
out_buf[out_index++] = 0x00; // Dummy byte
out_buf[out_index++] = 0x00; // Define start page address
out_buf[out_index++] = 0x07; // Frame frequency
//out_buf[out_index++] = 0x01; // Define end page address
out_buf[out_index++] = 0x00; // Define end page address
out_buf[out_index++] = 0x3F; // Vertical scrolling offset
out_buf[out_index++] = OLED_CMD_VERTICAL; // A3
out_buf[out_index++] = 0x00;
if (dev->_height == 64)
//out_buf[out_index++] = 0x7F;
out_buf[out_index++] = 0x40;
if (dev->_height == 32)
out_buf[out_index++] = 0x20;
out_buf[out_index++] = OLED_CMD_ACTIVE_SCROLL; // 2F
}
if (scroll == SCROLL_UP) {
out_buf[out_index++] = OLED_CMD_CONTINUOUS_SCROLL; // 29
out_buf[out_index++] = 0x00; // Dummy byte
out_buf[out_index++] = 0x00; // Define start page address
out_buf[out_index++] = 0x07; // Frame frequency
//out_buf[out_index++] = 0x01; // Define end page address
out_buf[out_index++] = 0x00; // Define end page address
out_buf[out_index++] = 0x01; // Vertical scrolling offset
out_buf[out_index++] = OLED_CMD_VERTICAL; // A3
out_buf[out_index++] = 0x00;
if (dev->_height == 64)
//out_buf[out_index++] = 0x7F;
out_buf[out_index++] = 0x40;
if (dev->_height == 32)
out_buf[out_index++] = 0x20;
out_buf[out_index++] = OLED_CMD_ACTIVE_SCROLL; // 2F
}
if (scroll == SCROLL_STOP) {
out_buf[out_index++] = OLED_CMD_DEACTIVE_SCROLL; // 2E
}
esp_err_t res = i2c_master_transmit(dev->_i2c_dev_handle, out_buf, out_index, I2C_TICKS_TO_WAIT);
if (res != ESP_OK)
ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->_address, dev->_i2c_num, res, esp_err_to_name(res));
}

View File

@@ -0,0 +1,315 @@
#include <string.h>
#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
#define SPI_COMMAND_MODE 0
#define SPI_DATA_MODE 1
#define SPI_DEFAULT_FREQUENCY 1000000; // 1MHz
int clock_speed_hz = SPI_DEFAULT_FREQUENCY;
void spi_clock_speed(int speed) {
ESP_LOGI(TAG, "SPI clock speed=%d MHz", speed/1000000);
clock_speed_hz = speed;
}
void spi_master_init(SSD1306_t * dev, int16_t mosi, int16_t sclk, int16_t cs, int16_t dc, int16_t reset)
{
esp_err_t ret;
gpio_reset_pin( cs );
gpio_set_direction( cs, GPIO_MODE_OUTPUT );
gpio_set_level( cs, 0 );
gpio_reset_pin( dc );
gpio_set_direction( dc, GPIO_MODE_OUTPUT );
gpio_set_level( dc, 0 );
if ( reset >= 0 ) {
gpio_reset_pin( reset );
gpio_set_direction( reset, GPIO_MODE_OUTPUT );
gpio_set_level( reset, 0 );
vTaskDelay( pdMS_TO_TICKS( 100 ) );
gpio_set_level( reset, 1 );
}
spi_bus_config_t spi_bus_config = {
.mosi_io_num = mosi,
.miso_io_num = -1,
.sclk_io_num = 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_DEFAULT_FREQUENCY;
devcfg.clock_speed_hz = clock_speed_hz;
devcfg.spics_io_num = cs;
devcfg.queue_size = 1;
spi_device_handle_t spi_device_handle;
ret = spi_bus_add_device( HOST_ID, &devcfg, &spi_device_handle);
ESP_LOGI(TAG, "spi_bus_add_device=%d",ret);
assert(ret==ESP_OK);
dev->_dc = dc;
dev->_address = SPI_ADDRESS;
dev->_flip = false;
dev->_spi_device_handle = spi_device_handle;
}
void spi_device_add(SSD1306_t * dev, int16_t cs, int16_t dc, int16_t reset)
{
ESP_LOGW(TAG, "Will not install spi master driver");
esp_err_t ret;
gpio_reset_pin( cs );
gpio_set_direction( cs, GPIO_MODE_OUTPUT );
gpio_set_level( cs, 0 );
gpio_reset_pin( dc );
gpio_set_direction( dc, GPIO_MODE_OUTPUT );
gpio_set_level( dc, 0 );
if ( reset >= 0 ) {
gpio_reset_pin( reset );
gpio_set_direction( reset, GPIO_MODE_OUTPUT );
gpio_set_level( reset, 0 );
vTaskDelay( pdMS_TO_TICKS( 100 ) );
gpio_set_level( reset, 1 );
}
#if 0
spi_bus_config_t spi_bus_config = {
.mosi_io_num = mosi,
.miso_io_num = -1,
.sclk_io_num = 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);
#endif
spi_device_interface_config_t devcfg;
memset( &devcfg, 0, sizeof( spi_device_interface_config_t ) );
//devcfg.clock_speed_hz = SPI_DEFAULT_FREQUENCY;
devcfg.clock_speed_hz = clock_speed_hz;
devcfg.spics_io_num = cs;
devcfg.queue_size = 1;
spi_device_handle_t spi_device_handle;
ret = spi_bus_add_device( HOST_ID, &devcfg, &spi_device_handle);
ESP_LOGI(TAG, "spi_bus_add_device=%d",ret);
assert(ret==ESP_OK);
dev->_dc = dc;
dev->_address = SPI_ADDRESS;
dev->_flip = false;
dev->_spi_device_handle = spi_device_handle;
}
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->_spi_device_handle, &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->_spi_device_handle, 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
}
}