From 3a51aff8b6dfdcd336c9b6f9cd4fb9886a3e7feb Mon Sep 17 00:00:00 2001 From: Rick Astley Date: Thu, 9 Jan 2025 18:05:51 +0100 Subject: [PATCH] Added heltecv3 support thus added ssd1306.h handler --- .../BeamStalker/boards/sdkconfig.HeltecV3 | 23 + Firmware/BeamStalker/build.sh | 2 +- .../components/ssd1306/CMakeLists.txt | 16 + .../components/ssd1306/Kconfig.projbuild | 186 ++++ .../components/ssd1306/font8x8_basic.h | 174 ++++ .../BeamStalker/components/ssd1306/ssd1306.c | 813 ++++++++++++++++++ .../BeamStalker/components/ssd1306/ssd1306.h | 179 ++++ .../components/ssd1306/ssd1306_i2c_legacy.c | 290 +++++++ .../components/ssd1306/ssd1306_i2c_new.c | 291 +++++++ .../components/ssd1306/ssd1306_spi.c | 315 +++++++ .../main/BeamStalker-cardputer.cpp | 6 +- Firmware/BeamStalker/main/Kconfig.projbuild | 14 +- Firmware/BeamStalker/main/firmware/helper.h | 2 +- .../main/firmware/includes/wifi_sniffer.cpp | 8 +- .../BeamStalker/main/firmware/interface.cpp | 34 +- .../BeamStalker/main/firmware/interface.h | 24 +- Firmware/BeamStalker/main/firmware/menu.cpp | 48 +- Firmware/BeamStalker/main/firmware/menu.h | 6 - 18 files changed, 2392 insertions(+), 39 deletions(-) create mode 100644 Firmware/BeamStalker/boards/sdkconfig.HeltecV3 create mode 100644 Firmware/BeamStalker/components/ssd1306/CMakeLists.txt create mode 100644 Firmware/BeamStalker/components/ssd1306/Kconfig.projbuild create mode 100644 Firmware/BeamStalker/components/ssd1306/font8x8_basic.h create mode 100644 Firmware/BeamStalker/components/ssd1306/ssd1306.c create mode 100644 Firmware/BeamStalker/components/ssd1306/ssd1306.h create mode 100644 Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_legacy.c create mode 100644 Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_new.c create mode 100644 Firmware/BeamStalker/components/ssd1306/ssd1306_spi.c diff --git a/Firmware/BeamStalker/boards/sdkconfig.HeltecV3 b/Firmware/BeamStalker/boards/sdkconfig.HeltecV3 new file mode 100644 index 0000000..4468a2b --- /dev/null +++ b/Firmware/BeamStalker/boards/sdkconfig.HeltecV3 @@ -0,0 +1,23 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MB.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-8MB.csv" +CONFIG_BT_ENABLED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y + +CONFIG_HELTEC_BOARD=y +CONFIG_HAS_SSD1306_DISPLAY=y + + +# SSD1306 specific config +CONFIG_GPIO_RANGE_MAX=48 +CONFIG_I2C_INTERFACE=y +CONFIG_SSD1306_128x64=y +CONFIG_OFFSETX=0 +CONFIG_SCL_GPIO=17 +CONFIG_SDA_GPIO=18 +CONFIG_RESET_GPIO=21 +CONFIG_I2C_PORT_0=y + diff --git a/Firmware/BeamStalker/build.sh b/Firmware/BeamStalker/build.sh index 4a73390..0f14ac1 100755 --- a/Firmware/BeamStalker/build.sh +++ b/Firmware/BeamStalker/build.sh @@ -25,6 +25,6 @@ for board in $(ls ./boards); idf.py build mv ./build/BeamStalker.bin ./bin/BeamStalker-$version-$board_name.bin idf.py fullclean - + rm sdkconfig sdkconfig.defaults done diff --git a/Firmware/BeamStalker/components/ssd1306/CMakeLists.txt b/Firmware/BeamStalker/components/ssd1306/CMakeLists.txt new file mode 100644 index 0000000..b73951d --- /dev/null +++ b/Firmware/BeamStalker/components/ssd1306/CMakeLists.txt @@ -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 ".") diff --git a/Firmware/BeamStalker/components/ssd1306/Kconfig.projbuild b/Firmware/BeamStalker/components/ssd1306/Kconfig.projbuild new file mode 100644 index 0000000..9af6448 --- /dev/null +++ b/Firmware/BeamStalker/components/ssd1306/Kconfig.projbuild @@ -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 + diff --git a/Firmware/BeamStalker/components/ssd1306/font8x8_basic.h b/Firmware/BeamStalker/components/ssd1306/font8x8_basic.h new file mode 100644 index 0000000..413825e --- /dev/null +++ b/Firmware/BeamStalker/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/Firmware/BeamStalker/components/ssd1306/ssd1306.c b/Firmware/BeamStalker/components/ssd1306/ssd1306.c new file mode 100644 index 0000000..e310ac3 --- /dev/null +++ b/Firmware/BeamStalker/components/ssd1306/ssd1306.c @@ -0,0 +1,813 @@ +#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 == SPI_ADDRESS) { + 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 == SPI_ADDRESS) { + 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 == 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; + + int seg = 0; + uint8_t image[8]; + for (int 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); + seg = seg + 8; + } +} + +void ssd1306_display_text_box1(SSD1306_t * dev, int page, int seg, char * text, int box_width, int text_len, bool invert, int delay) +{ + if (page >= dev->_pages) return; + int text_box_pixel = box_width * 8; + if (seg + text_box_pixel > dev->_width) return; + + int _seg = seg; + uint8_t image[8]; + for (int i = 0; i < box_width; 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); + _seg = _seg + 8; + } + vTaskDelay(delay); + + // Horizontally scroll inside the box + for (int _text=box_width;_text_flip) ssd1306_flip(image, 8); + for (int _bit=0;_bit<8;_bit++) { + for (int _pixel=0;_pixel_page[page]._segs[_pixel+seg] = dev->_page[page]._segs[_pixel+seg+1]; + } + dev->_page[page]._segs[seg+text_box_pixel-1] = image[_bit]; + ssd1306_display_image(dev, page, seg, &dev->_page[page]._segs[seg], text_box_pixel); + vTaskDelay(delay); + } + } +} + +void ssd1306_display_text_box2(SSD1306_t * dev, int page, int seg, char * text, int box_width, int text_len, bool invert, int delay) +{ + if (page >= dev->_pages) return; + int text_box_pixel = box_width * 8; + if (seg + text_box_pixel > dev->_width) return; + + int _seg = seg; + uint8_t image[8]; + + // Fill the text box with blanks + for (int i = 0; i < box_width; i++) { + //memcpy(image, font8x8_basic_tr[(uint8_t)text[i]], 8); + memcpy(image, font8x8_basic_tr[0x20], 8); + if (invert) ssd1306_invert(image, 8); + if (dev->_flip) ssd1306_flip(image, 8); + ssd1306_display_image(dev, page, _seg, image, 8); + _seg = _seg + 8; + } + vTaskDelay(delay); + + // Horizontally scroll inside the box + for (int _text=0;_text_flip) ssd1306_flip(image, 8); + for (int _bit=0;_bit<8;_bit++) { + for (int _pixel=0;_pixel_page[page]._segs[_pixel+seg] = dev->_page[page]._segs[_pixel+seg+1]; + } + dev->_page[page]._segs[seg+text_box_pixel-1] = image[_bit]; + ssd1306_display_image(dev, page, seg, &dev->_page[page]._segs[seg], text_box_pixel); + vTaskDelay(delay); + } + } + + // Horizontally scroll inside the box + for (int _text=0;_text_flip) ssd1306_flip(image, 8); + for (int _bit=0;_bit<8;_bit++) { + for (int _pixel=0;_pixel_page[page]._segs[_pixel+seg] = dev->_page[page]._segs[_pixel+seg+1]; + } + dev->_page[page]._segs[seg+text_box_pixel-1] = image[_bit]; + ssd1306_display_image(dev, page, seg, &dev->_page[page]._segs[seg], text_box_pixel); + vTaskDelay(delay); + } + } +} + +// 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; + + int seg = 0; + + for (int 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 (int xx = 0; xx < 8; xx++) { // for each column (x-direction) + + uint32_t in_bitmask = 0b1; + uint32_t out_bitmask = 0b111; + + for (int 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 (int yy = 0; yy < 3; yy++) { // for each group of 8 pixels high (y-direction) + + uint8_t image[24]; + for (int 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_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; + } + + } 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_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_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, "_width=%d ypos=%d page=%d dstBits=%d", _width, 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 wk1=0x%x page=%d _seg=%d, wk2=%02x", index, offset, wk1, 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; + } + } + } +} + +// 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; 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 == SPI_ADDRESS) { + 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]; + } + } + } +} + +// 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]); +} + diff --git a/Firmware/BeamStalker/components/ssd1306/ssd1306.h b/Firmware/BeamStalker/components/ssd1306/ssd1306.h new file mode 100644 index 0000000..1d9226c --- /dev/null +++ b/Firmware/BeamStalker/components/ssd1306/ssd1306.h @@ -0,0 +1,179 @@ +#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_bus_handle_t _i2c_bus_handle; + 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_box1(SSD1306_t * dev, int page, int seg, char * text, int box_width, int text_len, bool invert, int delay); +void ssd1306_display_text_box2(SSD1306_t * dev, int page, int seg, char * text, int box_width, int text_len, bool invert, int delay); +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, uint16_t i2c_address); +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_ */ + diff --git a/Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_legacy.c b/Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_legacy.c new file mode 100644 index 0000000..6ea50a8 --- /dev/null +++ b/Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_legacy.c @@ -0,0 +1,290 @@ +#include + +#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, uint16_t i2c_address) +{ + 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); +} + diff --git a/Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_new.c b/Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_new.c new file mode 100644 index 0000000..ae15be8 --- /dev/null +++ b/Firmware/BeamStalker/components/ssd1306/ssd1306_i2c_new.c @@ -0,0 +1,291 @@ +#include + +#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 i2c_bus_handle; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &i2c_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(i2c_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_bus_handle = i2c_bus_handle; + dev->_i2c_dev_handle = i2c_dev_handle; +} + +void i2c_device_add(SSD1306_t * dev, i2c_port_t i2c_num, int16_t reset, uint16_t i2c_address) +{ + 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 i2c_bus_handle; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &i2c_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(dev->_i2c_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 < 4 ? 4 : 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)); +} + diff --git a/Firmware/BeamStalker/components/ssd1306/ssd1306_spi.c b/Firmware/BeamStalker/components/ssd1306/ssd1306_spi.c new file mode 100644 index 0000000..9fb059f --- /dev/null +++ b/Firmware/BeamStalker/components/ssd1306/ssd1306_spi.c @@ -0,0 +1,315 @@ +#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 + +#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 + } +} diff --git a/Firmware/BeamStalker/main/BeamStalker-cardputer.cpp b/Firmware/BeamStalker/main/BeamStalker-cardputer.cpp index fbdca8e..1689821 100644 --- a/Firmware/BeamStalker/main/BeamStalker-cardputer.cpp +++ b/Firmware/BeamStalker/main/BeamStalker-cardputer.cpp @@ -125,14 +125,14 @@ extern "C" void app_main(void) { vTaskDelay(pdMS_TO_TICKS(200)); - displayText(0, 70, name); - displayText(0, 90, VERSION); + displayText(0, 4, name); + displayText(0, 5, VERSION); printf("%s %s\n", name, VERSION); vTaskDelay(pdMS_TO_TICKS(500)); - displayText(0, 110, "Press to boot..."); + displayText(0, 7, "Press to boot..."); int loop = 1; diff --git a/Firmware/BeamStalker/main/Kconfig.projbuild b/Firmware/BeamStalker/main/Kconfig.projbuild index ae63527..06a3407 100644 --- a/Firmware/BeamStalker/main/Kconfig.projbuild +++ b/Firmware/BeamStalker/main/Kconfig.projbuild @@ -1,9 +1,21 @@ menu "BeamStalker Build Configuration" config M5_BOARD - bool "is M5Stack hardware" + bool "Is M5Stack hardware" default n help Specify if board is using M5Stack libs. +config HELTEC_BOARD + bool "Is Heltec hardware" + default n + help + Specify if board is using Heltec libs. + +config HAS_SSD1306_DISPLAY + bool "Has ssd1306 display" + default n + help + Specify if board is using SSD1306 libs. + endmenu \ No newline at end of file diff --git a/Firmware/BeamStalker/main/firmware/helper.h b/Firmware/BeamStalker/main/firmware/helper.h index e560536..661f783 100644 --- a/Firmware/BeamStalker/main/firmware/helper.h +++ b/Firmware/BeamStalker/main/firmware/helper.h @@ -1,7 +1,7 @@ #ifndef HELPER_H #define HELPER_H -#define VERSION "v0.2.7.16" +#define VERSION "v0.2.7.17" #define ROW_SIZE 7 #define COLUMN_SIZE 7 #define SCREEN_WIDTH 128 diff --git a/Firmware/BeamStalker/main/firmware/includes/wifi_sniffer.cpp b/Firmware/BeamStalker/main/firmware/includes/wifi_sniffer.cpp index dca13a5..e00a42a 100644 --- a/Firmware/BeamStalker/main/firmware/includes/wifi_sniffer.cpp +++ b/Firmware/BeamStalker/main/firmware/includes/wifi_sniffer.cpp @@ -7,10 +7,10 @@ void sniff_pps_timer_callback(TimerHandle_t xTimer) { snprintf(ch_buffer, sizeof(ch_buffer), "Channel: %d", channel); clearScreen(); - displayText(0, 0*charsize, "Sniffing for 1000s", TFT_WHITE); - displayText(0, 2*charsize, pc_buffer, TFT_WHITE); - displayText(0, 3*charsize, ch_buffer, TFT_WHITE); - displayText(0, 7*charsize, "Press any key to exit...", TFT_WHITE); + displayText(0, 0, "Sniffing for 1000s", TFT_WHITE); + displayText(0, 2, pc_buffer, TFT_WHITE); + displayText(0, 3, ch_buffer, TFT_WHITE); + displayText(0, 7, "Press any key to exit...", TFT_WHITE); } void init_sniff_pps_timer() { diff --git a/Firmware/BeamStalker/main/firmware/interface.cpp b/Firmware/BeamStalker/main/firmware/interface.cpp index a28c4ea..3094ded 100644 --- a/Firmware/BeamStalker/main/firmware/interface.cpp +++ b/Firmware/BeamStalker/main/firmware/interface.cpp @@ -150,10 +150,14 @@ void initBoard() { #ifdef CONFIG_M5_BOARD M5Cardputer.begin(true); M5.Power.begin(); - M5.Display.setTextSize(charsize_multiplier); M5.Display.setTextColor(TFT_WHITE, TFT_BLACK); M5.Display.setFont(&fonts::FreeMonoBold18pt7b); + #elif CONFIG_HELTEC_BOARD + i2c_master_init(&display, CONFIG_SCL_GPIO, CONFIG_SDA_GPIO, CONFIG_RESET_GPIO); + ssd1306_init(&display, 128, 64); + ssd1306_contrast(&display, 0xff); + ssd1306_clear_screen(&display, false); #endif } @@ -183,15 +187,19 @@ int isCharging() { void displayText(int x, int y, const char* text, uint32_t color) { #ifdef CONFIG_M5_BOARD - M5.Display.setCursor(x, y); + M5.Display.setCursor(x, y*charsize); M5.Display.setTextColor(color); M5.Display.print(text); + #elif CONFIG_HELTEC_BOARD + ssd1306_display_text(&display, y, (char*)text, strlen(text), false); #endif } void clearScreen(uint32_t color) { #ifdef CONFIG_M5_BOARD M5.Display.fillScreen(color); + #elif CONFIG_HELTEC_BOARD + ssd1306_clear_screen(&display, false); #endif } @@ -205,4 +213,24 @@ void drawFillRect(int x, int y, int end_x, int end_y, const unsigned int &color) #ifdef CONFIG_M5_BOARD M5.Display.fillRect(x, y, end_x, end_y, color); #endif -} \ No newline at end of file +} + +int LogError(const std::string& message) { + clearScreen(); + displayText(0, 1, message.c_str(), TFT_RED); + vTaskDelay(pdMS_TO_TICKS(5000)); + return 0; +} + +void drawBitmap(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *bitmap, uint32_t color) { + #ifdef CONFIG_HELTEC_BOARD + ssd1306_bitmaps(&display, x, y, (uint8_t*)bitmap, width, height, false); + #else + for (int16_t i = 0; i < height; i++) { + for (int16_t j = 0; j < width; j++) { + uint8_t bit = (bitmap[i * (width / 8) + (j / 8)] >> (7 - (j % 8))) & 1; + drawPixel(x + j, y + i, bit ? color : TFT_BLACK); + } + } + #endif +} diff --git a/Firmware/BeamStalker/main/firmware/interface.h b/Firmware/BeamStalker/main/firmware/interface.h index 00219f6..483165c 100644 --- a/Firmware/BeamStalker/main/firmware/interface.h +++ b/Firmware/BeamStalker/main/firmware/interface.h @@ -1,7 +1,6 @@ #ifndef INTERFACE_H #define INTERFACE_H - #include "esp_event.h" #include "esp_task_wdt.h" #include "freertos/FreeRTOS.h" @@ -22,6 +21,10 @@ #include #endif +#ifdef CONFIG_HELTEC_BOARD +#include "ssd1306.h" +#endif + // Temp fix for missing M5Cardputer.h #ifndef CONFIG_M5_BOARD #undef TFT_WHITE @@ -52,15 +55,34 @@ void displayText(int x, int y, const char* text, uint32_t color = TFT_WHITE); void clearScreen(uint32_t color = TFT_BLACK); void drawPixel(int32_t x, int32_t y, const unsigned int &color = TFT_WHITE); void drawFillRect(int x, int y, int end_x, int end_y, const unsigned int &color = TFT_WHITE); +int LogError(const std::string& message); +void drawBitmap(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *bitmap, uint32_t color); #ifdef CONFIG_M5_BOARD M5GFX display; M5Canvas canvas(&display); #define DISPLAY_WIDTH M5.Display.width() #define DISPLAY_HEIGHT M5.Display.height() +#elif CONFIG_HELTEC_BOARD +SSD1306_t display; +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 #else #define DISPLAY_WIDTH 13 #define DISPLAY_HEIGHT 37 #endif +#ifdef CONFIG_M5_BOARD +int font_size = 18; +float charsize_multiplier = 0.5; +#elif CONFIG_HELTEC_BOARD +int font_size = 8; +float charsize_multiplier = 1; +#else +int font_size = 1; +float charsize_multiplier = 1; +#endif + +int charsize = (int)(font_size*charsize_multiplier)+((40*font_size)/100); + #endif \ No newline at end of file diff --git a/Firmware/BeamStalker/main/firmware/menu.cpp b/Firmware/BeamStalker/main/firmware/menu.cpp index d91c1b1..e233c90 100644 --- a/Firmware/BeamStalker/main/firmware/menu.cpp +++ b/Firmware/BeamStalker/main/firmware/menu.cpp @@ -11,7 +11,13 @@ int intChecker(int value, int length) { } char *createHeaderLine(const char *menu_name) { + #ifdef CONFIG_M5_BOARD const int max_menu_name = 10 + 7 - 6; + #elif CONFIG_HELTEC_BOARD + const int max_menu_name = 10 + 5; + #else + const int max_menu_name = 10 + 5; + #endif static char final[50]; char cropped_menu_name[max_menu_name + 1]; @@ -80,7 +86,11 @@ void drawMenu(struct menu Menu, int selector) { clearScreen(); + #ifdef CONFIG_M5_BOARD drawFillRect(0, 0, DISPLAY_WIDTH, charsize, TFT_WHITE); + #elif CONFIG_HELTEC_BOARD + drawFillRect(0, 0, DISPLAY_WIDTH, charsize, TFT_BLACK); + #endif displayText(0, 0, fullMenuName, TFT_BLACK); int j = 1; @@ -90,8 +100,17 @@ void drawMenu(struct menu Menu, int selector) { struct item element = Menu.elements[intChecker(selector+i, Menu.length)]; if (element.type == 0) { + #ifdef CONFIG_M5_BOARD const int max_element = 14; const int max_value = 10-4; + #elif CONFIG_HELTEC_BOARD + const int max_element = 10; + const int max_value = 2; + #else + const int max_element = 10; + const int max_value = 2; + #endif + char cropped_element[max_element + 1]; if (strlen(element.name) > max_element) { @@ -112,26 +131,17 @@ void drawMenu(struct menu Menu, int selector) { } if (i == 0) { - displayText(0, j*charsize, element_str, TFT_CYAN); + #ifdef CONFIG_M5_BOARD + displayText(0, j, element_str, TFT_CYAN); + #elif CONFIG_HELTEC_BOARD + element_str[0] = '>'; + displayText(0, j, element_str, TFT_CYAN); + #else + displayText(0, j, element_str, TFT_CYAN); + #endif } else { - displayText(0, j*charsize, element_str, TFT_WHITE); + displayText(0, j, element_str, TFT_WHITE); } j++; } -} - -int LogError(const std::string& message) { - clearScreen(); - displayText(0, 1 * charsize, message.c_str(), TFT_RED); - vTaskDelay(pdMS_TO_TICKS(5000)); - return 0; -} - -void drawBitmap(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *bitmap, uint32_t color) { - for (int16_t i = 0; i < height; i++) { - for (int16_t j = 0; j < width; j++) { - uint8_t bit = (bitmap[i * (width / 8) + (j / 8)] >> (7 - (j % 8))) & 1; - drawPixel(x + j, y + i, bit ? color : TFT_BLACK); - } - } -} +} \ No newline at end of file diff --git a/Firmware/BeamStalker/main/firmware/menu.h b/Firmware/BeamStalker/main/firmware/menu.h index b8c6987..fd22f57 100644 --- a/Firmware/BeamStalker/main/firmware/menu.h +++ b/Firmware/BeamStalker/main/firmware/menu.h @@ -21,13 +21,7 @@ struct menu { int length; }; -float charsize_multiplier = 0.5; -int font_size = 18; -int charsize = (int)(font_size*charsize_multiplier)+((40*font_size)/100); - int intChecker(int value, int length); char *createHeaderLine(const char *menu_name); void drawMenu(struct menu Menu, int selector); -int LogError(const std::string& message); -void drawBitmap(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *bitmap, uint32_t color); #endif \ No newline at end of file