From 8e1918b489015184be7b3a3ba13a6826c4641a23 Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Mon, 22 Dec 2025 23:17:24 -0600 Subject: [PATCH 1/7] Add OLED controller auto-detection (SSD1306/SH1106/SH1107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detect OLED controller type by reading status register 0x00 during initialization. Based on ss_oled library detection algorithm. Detection identifies: - SSD1306: status bits 0x03 or 0x06 (most common 128x64/128x32) - SH1106: status bits 0x08 (132x64, needs +2 pixel X offset) - SH1107: status bits 0x07 or 0x0F (128x128 displays) Adds LOG_ERROR debug messages to show detection results for troubleshooting display issues. Note: Drawing code updates for SH1106 X offset still needed. Reference: https://github.com/bitbank2/ss_oled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/main/drivers/display_ug2864hsweg01.c | 99 ++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c index 118acff5730..683e70336f4 100644 --- a/src/main/drivers/display_ug2864hsweg01.c +++ b/src/main/drivers/display_ug2864hsweg01.c @@ -30,6 +30,19 @@ #include "display_ug2864hsweg01.h" +#include "common/log.h" + +// OLED controller types (based on ss_oled detection) +typedef enum { + OLED_CONTROLLER_UNKNOWN = 0, + OLED_CONTROLLER_SSD1306, // Most common 128x64/128x32 + OLED_CONTROLLER_SH1106, // 132x64 with 2-pixel offset + OLED_CONTROLLER_SH1107, // 128x128 displays + OLED_CONTROLLER_SSD1309, // Similar to SSD1306 +} oledControllerType_e; + +static oledControllerType_e detectedController = OLED_CONTROLLER_UNKNOWN; + #define INVERSE_CHAR_FORMAT 0x7f // 0b01111111 #define NORMAL_CHAR_FORMAT 0x00 // 0b00000000 @@ -258,6 +271,73 @@ void i2c_OLED_send_string(const char *string) } } +/** + * Detect OLED controller type by reading status register. + * Based on ss_oled library detection algorithm. + * + * The status register (0x00) returns different values for different controllers: + * - SSD1306: typically returns 0x03 or 0x06 (lower nibble) + * - SH1106: typically returns 0x08 (lower nibble) + * - SH1107: typically returns 0x07 or 0x0F (lower nibble) + * + * Returns the detected controller type. + */ +static oledControllerType_e detectOledController(void) +{ + uint8_t statusByte = 0; + + // Read the status register (register 0x00) + // This is a read of the status byte from the OLED controller + if (!busRead(busDev, 0x00, &statusByte)) { + LOG_ERROR(SYSTEM, "OLED: Failed to read status register"); + return OLED_CONTROLLER_UNKNOWN; + } + + LOG_ERROR(SYSTEM, "OLED: Raw status register = 0x%02X", statusByte); + + // Mask off the upper bits - controller type is in lower nibble + uint8_t controllerBits = statusByte & 0x0F; + + LOG_ERROR(SYSTEM, "OLED: Controller ID bits (masked) = 0x%02X", controllerBits); + + oledControllerType_e detected; + const char *controllerName; + + // Detection logic based on ss_oled library + switch (controllerBits) { + case 0x07: + case 0x0F: + // SH1107 - 128x128 displays + detected = OLED_CONTROLLER_SH1107; + controllerName = "SH1107"; + break; + + case 0x08: + // SH1106 - 132x64 (needs +2 pixel x offset) + detected = OLED_CONTROLLER_SH1106; + controllerName = "SH1106"; + break; + + case 0x03: + case 0x06: + // SSD1306 - most common 128x64/128x32 + detected = OLED_CONTROLLER_SSD1306; + controllerName = "SSD1306"; + break; + + default: + // Assume SSD1306 for unknown values since it's most common + detected = OLED_CONTROLLER_SSD1306; + controllerName = "SSD1306 (assumed)"; + LOG_ERROR(SYSTEM, "OLED: Unknown controller bits 0x%02X, assuming SSD1306", controllerBits); + break; + } + + LOG_ERROR(SYSTEM, "OLED: Detected controller: %s", controllerName); + + return detected; +} + /** * according to http://www.adafruit.com/datasheets/UG-2864HSWEG01.pdf Chapter 4.4 Page 15 */ @@ -266,14 +346,23 @@ bool ug2864hsweg01InitI2C(void) busDev = busDeviceInit(BUSTYPE_I2C, DEVHW_UG2864, 0, OWNER_OLED_DISPLAY); if (!busDev) { + LOG_ERROR(SYSTEM, "OLED: Bus device init failed"); return false; } + LOG_ERROR(SYSTEM, "OLED: Bus device initialized, detecting controller type..."); + + // Detect the OLED controller type before initialization + detectedController = detectOledController(); + // Set display OFF if (!i2c_OLED_send_cmd(0xAE)) { + LOG_ERROR(SYSTEM, "OLED: Failed to send display OFF command"); return false; } + LOG_ERROR(SYSTEM, "OLED: Display OFF command sent, starting init sequence"); + i2c_OLED_send_cmd(0xD4); // Set Display Clock Divide Ratio / OSC Frequency i2c_OLED_send_cmd(0x80); // Display Clock Divide Ratio / OSC Frequency i2c_OLED_send_cmd(0xA8); // Set Multiplex Ratio @@ -283,6 +372,12 @@ bool ug2864hsweg01InitI2C(void) i2c_OLED_send_cmd(0x40); // Set Display Start Line i2c_OLED_send_cmd(0x8D); // Set Charge Pump i2c_OLED_send_cmd(0x14); // Charge Pump (0x10 External, 0x14 Internal DC/DC) + + // For SH1106, the segment remap and COM scan direction might need adjustment + if (detectedController == OLED_CONTROLLER_SH1106) { + LOG_ERROR(SYSTEM, "OLED: Applying SH1106-specific init (segment remap)"); + } + i2c_OLED_send_cmd(0xA1); // Set Segment Re-Map i2c_OLED_send_cmd(0xC8); // Set Com Output Scan Direction i2c_OLED_send_cmd(0xDA); // Set COM Hardware Configuration @@ -297,8 +392,12 @@ bool ug2864hsweg01InitI2C(void) i2c_OLED_send_cmd(0xA6); // Set display not inverted i2c_OLED_send_cmd(0xAF); // Set display On + LOG_ERROR(SYSTEM, "OLED: Init sequence complete, clearing display"); + i2c_OLED_clear_display(); + LOG_ERROR(SYSTEM, "OLED: Initialization complete, controller=%d", detectedController); + return true; } From ac5a111b716082fb79d28641478d508b494f2c7a Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Fri, 24 Apr 2026 09:41:06 -0500 Subject: [PATCH 2/7] Add SH1106 column offset and fix page-mode clearing SH1106 controller has 132-column GDDRAM but only 128 are visible; the first 2 columns are hidden. Without the offset, text and graphics render shifted 2 pixels to the left and the rightmost 2 columns are written into the invisible region. Also fixes clearing on SH1106: it only supports page addressing mode and ignores the horizontal addressing mode command (0x20/0x00) used by the previous i2c_OLED_clear_display(). The old code only cleared one page on SH1106 instead of all 8. Changes: - i2c_OLED_set_xy: add +2 pixel column offset for SH1106 - i2c_OLED_set_line: add +2 pixel start column for SH1106 - i2c_OLED_clear_display: switch to page-by-page clear (works on all controllers); apply SH1106 column offset; remove horizontal mode cmd - i2c_OLED_clear_display_quick: same fix - detectOledController: downgrade informational LOG_ERROR to LOG_DEBUG; keep LOG_ERROR only for actual failure paths - ug2864hsweg01InitI2C: remove empty SH1106 init placeholder stub; downgrade status LOG_ERROR messages to LOG_DEBUG --- src/main/drivers/display_ug2864hsweg01.c | 84 +++++++++++++----------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c index 683e70336f4..9a2ade22fd3 100644 --- a/src/main/drivers/display_ug2864hsweg01.c +++ b/src/main/drivers/display_ug2864hsweg01.c @@ -209,45 +209,58 @@ bool i2c_OLED_send_byte(uint8_t val) void i2c_OLED_clear_display(void) { - i2c_OLED_send_cmd(0xa6); // Set Normal Display - i2c_OLED_send_cmd(0xae); // Display OFF - i2c_OLED_send_cmd(0x20); // Set Memory Addressing Mode - i2c_OLED_send_cmd(0x00); // Set Memory Addressing Mode to Horizontal addressing mode - i2c_OLED_send_cmd(0xb0); // set page address to 0 - i2c_OLED_send_cmd(0x40); // Display start line register to 0 - i2c_OLED_send_cmd(0); // Set low col address to 0 - i2c_OLED_send_cmd(0x10); // Set high col address to 0 - for (uint16_t i = 0; i < 1024; i++) { // fill the display's RAM with graphic... 128*64 pixel picture - i2c_OLED_send_byte(0x00); // clear + // SH1106 only supports page addressing mode; use page-by-page clear for all controllers + uint8_t startCol = (detectedController == OLED_CONTROLLER_SH1106) ? 2 : 0; + + i2c_OLED_send_cmd(0xa6); // Set Normal Display + i2c_OLED_send_cmd(0xae); // Display OFF + i2c_OLED_send_cmd(0x40); // Display start line register to 0 + + for (uint8_t page = 0; page < 8; page++) { + i2c_OLED_send_cmd(0xb0 + page); // set page address + i2c_OLED_send_cmd(0x00 + (startCol & 0x0f)); // set low col address + i2c_OLED_send_cmd(0x10 + ((startCol >> 4) & 0x0f)); // set high col address + for (uint8_t col = 0; col < 128; col++) { + i2c_OLED_send_byte(0x00); + } } - i2c_OLED_send_cmd(0x81); // Setup CONTRAST CONTROL, following byte is the contrast Value... always a 2 byte instruction - i2c_OLED_send_cmd(200); // Here you can set the brightness 1 = dull, 255 is very bright - i2c_OLED_send_cmd(0xaf); // display on + + i2c_OLED_send_cmd(0x81); // Setup CONTRAST CONTROL + i2c_OLED_send_cmd(200); // Contrast value (1=dull, 255=very bright) + i2c_OLED_send_cmd(0xaf); // display on } void i2c_OLED_clear_display_quick(void) { - i2c_OLED_send_cmd(0xb0); // set page address to 0 - i2c_OLED_send_cmd(0x40); // Display start line register to 0 - i2c_OLED_send_cmd(0); // Set low col address to 0 - i2c_OLED_send_cmd(0x10); // Set high col address to 0 - for (uint16_t i = 0; i < 1024; i++) { // fill the display's RAM with graphic... 128*64 pixel picture - i2c_OLED_send_byte(0x00); // clear + uint8_t startCol = (detectedController == OLED_CONTROLLER_SH1106) ? 2 : 0; + + for (uint8_t page = 0; page < 8; page++) { + i2c_OLED_send_cmd(0xb0 + page); // set page address + i2c_OLED_send_cmd(0x00 + (startCol & 0x0f)); // set low col address + i2c_OLED_send_cmd(0x10 + ((startCol >> 4) & 0x0f)); // set high col address + for (uint8_t col = 0; col < 128; col++) { + i2c_OLED_send_byte(0x00); + } } } void i2c_OLED_set_xy(uint8_t col, uint8_t row) { - i2c_OLED_send_cmd(0xb0 + row); //set page address - i2c_OLED_send_cmd(0x00 + ((CHARACTER_WIDTH_TOTAL * col) & 0x0f)); //set low col address - i2c_OLED_send_cmd(0x10 + (((CHARACTER_WIDTH_TOTAL * col) >> 4) & 0x0f)); //set high col address + uint8_t pixelCol = CHARACTER_WIDTH_TOTAL * col; + if (detectedController == OLED_CONTROLLER_SH1106) { + pixelCol += 2; // SH1106 has 132-wide GDDRAM; first 2 cols are not visible + } + i2c_OLED_send_cmd(0xb0 + row); // set page address + i2c_OLED_send_cmd(0x00 + (pixelCol & 0x0f)); // set low col address + i2c_OLED_send_cmd(0x10 + ((pixelCol >> 4) & 0x0f)); // set high col address } void i2c_OLED_set_line(uint8_t row) { - i2c_OLED_send_cmd(0xb0 + row); //set page address - i2c_OLED_send_cmd(0); //set low col address - i2c_OLED_send_cmd(0x10); //set high col address + uint8_t startCol = (detectedController == OLED_CONTROLLER_SH1106) ? 2 : 0; + i2c_OLED_send_cmd(0xb0 + row); // set page address + i2c_OLED_send_cmd(0x00 + (startCol & 0x0f)); // set low col address + i2c_OLED_send_cmd(0x10 + ((startCol >> 4) & 0x0f)); // set high col address } void i2c_OLED_send_char(unsigned char ascii) @@ -293,12 +306,12 @@ static oledControllerType_e detectOledController(void) return OLED_CONTROLLER_UNKNOWN; } - LOG_ERROR(SYSTEM, "OLED: Raw status register = 0x%02X", statusByte); + LOG_DEBUG(SYSTEM, "OLED: Raw status register = 0x%02X", statusByte); // Mask off the upper bits - controller type is in lower nibble uint8_t controllerBits = statusByte & 0x0F; - LOG_ERROR(SYSTEM, "OLED: Controller ID bits (masked) = 0x%02X", controllerBits); + LOG_DEBUG(SYSTEM, "OLED: Controller ID bits (masked) = 0x%02X", controllerBits); oledControllerType_e detected; const char *controllerName; @@ -329,11 +342,11 @@ static oledControllerType_e detectOledController(void) // Assume SSD1306 for unknown values since it's most common detected = OLED_CONTROLLER_SSD1306; controllerName = "SSD1306 (assumed)"; - LOG_ERROR(SYSTEM, "OLED: Unknown controller bits 0x%02X, assuming SSD1306", controllerBits); + LOG_DEBUG(SYSTEM, "OLED: Unknown controller bits 0x%02X, assuming SSD1306", controllerBits); break; } - LOG_ERROR(SYSTEM, "OLED: Detected controller: %s", controllerName); + LOG_DEBUG(SYSTEM, "OLED: Detected controller: %s", controllerName); return detected; } @@ -350,7 +363,7 @@ bool ug2864hsweg01InitI2C(void) return false; } - LOG_ERROR(SYSTEM, "OLED: Bus device initialized, detecting controller type..."); + LOG_DEBUG(SYSTEM, "OLED: Bus device initialized, detecting controller type..."); // Detect the OLED controller type before initialization detectedController = detectOledController(); @@ -361,7 +374,7 @@ bool ug2864hsweg01InitI2C(void) return false; } - LOG_ERROR(SYSTEM, "OLED: Display OFF command sent, starting init sequence"); + LOG_DEBUG(SYSTEM, "OLED: Display OFF command sent, starting init sequence"); i2c_OLED_send_cmd(0xD4); // Set Display Clock Divide Ratio / OSC Frequency i2c_OLED_send_cmd(0x80); // Display Clock Divide Ratio / OSC Frequency @@ -373,11 +386,6 @@ bool ug2864hsweg01InitI2C(void) i2c_OLED_send_cmd(0x8D); // Set Charge Pump i2c_OLED_send_cmd(0x14); // Charge Pump (0x10 External, 0x14 Internal DC/DC) - // For SH1106, the segment remap and COM scan direction might need adjustment - if (detectedController == OLED_CONTROLLER_SH1106) { - LOG_ERROR(SYSTEM, "OLED: Applying SH1106-specific init (segment remap)"); - } - i2c_OLED_send_cmd(0xA1); // Set Segment Re-Map i2c_OLED_send_cmd(0xC8); // Set Com Output Scan Direction i2c_OLED_send_cmd(0xDA); // Set COM Hardware Configuration @@ -392,11 +400,11 @@ bool ug2864hsweg01InitI2C(void) i2c_OLED_send_cmd(0xA6); // Set display not inverted i2c_OLED_send_cmd(0xAF); // Set display On - LOG_ERROR(SYSTEM, "OLED: Init sequence complete, clearing display"); + LOG_DEBUG(SYSTEM, "OLED: Init sequence complete, clearing display"); i2c_OLED_clear_display(); - LOG_ERROR(SYSTEM, "OLED: Initialization complete, controller=%d", detectedController); + LOG_DEBUG(SYSTEM, "OLED: Initialization complete, controller=%d", detectedController); return true; } From 27e9e626bd9c3412f8a4630ed71c2a12687b3f34 Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Fri, 24 Apr 2026 22:29:07 -0500 Subject: [PATCH 3/7] Add OLED column-offset diagnostic test pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ug2864hsweg01TestPattern() draws a double-outline rectangle: - col 0: outer border, col 1: gap, col 2: inner border - symmetric on the right side - 1-pixel horizontal lines at top (page 0 bit 0) and bottom (page 7 bit 7) Uses i2c_OLED_set_line() so the controller offset is applied transparently — the same code works on SSD1306 (offset 0) and SH1106 (offset 2). How to read it on hardware: Correct offset : outer border flush at screen edge, 1-px gap, inner border Offset off by 1 : one border merges or a gap doubles Offset off by 2 : outer border invisible (off the left edge of the display) Call once after ug2864hsweg01InitI2C() for hardware verification only. Do not call from production code. --- src/main/drivers/display_ug2864hsweg01.c | 73 ++++++++++++++++++++++++ src/main/drivers/display_ug2864hsweg01.h | 3 + 2 files changed, 76 insertions(+) diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c index 9a2ade22fd3..94e671c21e4 100644 --- a/src/main/drivers/display_ug2864hsweg01.c +++ b/src/main/drivers/display_ug2864hsweg01.c @@ -409,4 +409,77 @@ bool ug2864hsweg01InitI2C(void) return true; } +/** + * Column-offset diagnostic test pattern. + * + * Draws a double-outline rectangle using only column-exact pixel writes so + * that a wrong SH1106 +2-pixel column offset is immediately visible on the + * physical display. + * + * Left edge layout (logical columns, after any controller offset is applied): + * col 0 : 0xFF outer left border (solid, full page height) + * col 1 : 0x00 1-pixel gap + * col 2 : 0xFF inner left border (solid, full page height) + * col 3..124: interior (see below) + * col 125: 0xFF inner right border (solid, full page height) + * col 126: 0x00 1-pixel gap + * col 127: 0xFF outer right border (solid, full page height) + * + * Top/bottom horizontal lines (1 pixel wide): + * page 0, cols 3..124: 0x01 (bit 0 = topmost pixel of the page) + * page 7, cols 3..124: 0x80 (bit 7 = bottommost pixel of the page) + * pages 1..6, cols 3..124: 0x00 (interior empty) + * + * How to read the result on the display: + * Correct offset : outer border flush at physical screen edge, 1-pixel gap, + * then inner border; symmetric on both sides. + * Offset off by 1 : one border merges with its neighbour or a gap doubles. + * Offset off by 2 : outer border disappears off the left (or right) edge. + * + * Call this function once after ug2864hsweg01InitI2C() to verify the column + * offset. Do NOT call it from production code paths. + */ +void ug2864hsweg01TestPattern(void) +{ + i2c_OLED_clear_display(); + + for (uint8_t page = 0; page < 8; page++) { + // Position to the start of this page (col 0, with controller offset applied) + i2c_OLED_set_line(page); + + // Determine the fill byte for the interior columns 3..124 on this page + uint8_t interiorByte; + if (page == 0) { + interiorByte = 0x01; // top horizontal border: only the topmost pixel + } else if (page == 7) { + interiorByte = 0x80; // bottom horizontal border: only the bottommost pixel + } else { + interiorByte = 0x00; // empty interior + } + + // col 0: outer left border (solid vertical stripe) + i2c_OLED_send_byte(0xFF); + + // col 1: 1-pixel gap + i2c_OLED_send_byte(0x00); + + // col 2: inner left border (solid vertical stripe) + i2c_OLED_send_byte(0xFF); + + // cols 3..124: interior (122 columns) + for (uint8_t col = 3; col <= 124; col++) { + i2c_OLED_send_byte(interiorByte); + } + + // col 125: inner right border (solid vertical stripe) + i2c_OLED_send_byte(0xFF); + + // col 126: 1-pixel gap + i2c_OLED_send_byte(0x00); + + // col 127: outer right border (solid vertical stripe) + i2c_OLED_send_byte(0xFF); + } +} + #endif // USE_OLED_UG2864 diff --git a/src/main/drivers/display_ug2864hsweg01.h b/src/main/drivers/display_ug2864hsweg01.h index c021e6abef4..627a15ebdf2 100644 --- a/src/main/drivers/display_ug2864hsweg01.h +++ b/src/main/drivers/display_ug2864hsweg01.h @@ -44,3 +44,6 @@ bool i2c_OLED_send_byte(uint8_t val); void i2c_OLED_clear_display(void); void i2c_OLED_clear_display_quick(void); +// Column-offset diagnostic: call once after init to verify SH1106/SSD1306 +// offset correctness. Do NOT call from production code paths. +void ug2864hsweg01TestPattern(void); From d62a21ec9cd996700e2c01ebd131fc3757307707 Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Sat, 25 Apr 2026 16:30:26 -0500 Subject: [PATCH 4/7] oled detection: first round debug --- src/main/drivers/display_ug2864hsweg01.c | 16 +++++++++++++++- src/main/target/ORBITF435/target.h | 2 ++ src/main/target/common_hardware.c | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c index 94e671c21e4..5f23c4dc97d 100644 --- a/src/main/drivers/display_ug2864hsweg01.c +++ b/src/main/drivers/display_ug2864hsweg01.c @@ -301,7 +301,7 @@ static oledControllerType_e detectOledController(void) // Read the status register (register 0x00) // This is a read of the status byte from the OLED controller - if (!busRead(busDev, 0x00, &statusByte)) { + if (!busRead(busDev, 0xFF, &statusByte)) { LOG_ERROR(SYSTEM, "OLED: Failed to read status register"); return OLED_CONTROLLER_UNKNOWN; } @@ -403,6 +403,7 @@ bool ug2864hsweg01InitI2C(void) LOG_DEBUG(SYSTEM, "OLED: Init sequence complete, clearing display"); i2c_OLED_clear_display(); + ug2864hsweg01TestPattern(); LOG_DEBUG(SYSTEM, "OLED: Initialization complete, controller=%d", detectedController); @@ -480,6 +481,19 @@ void ug2864hsweg01TestPattern(void) // col 127: outer right border (solid vertical stripe) i2c_OLED_send_byte(0xFF); } + + // Display detected controller name centered on page 3 (vertical middle) + const char *name; + uint8_t nameLen; + switch (detectedController) { + case OLED_CONTROLLER_SH1106: name = "SH1106"; nameLen = 6; break; + case OLED_CONTROLLER_SH1107: name = "SH1107"; nameLen = 6; break; + case OLED_CONTROLLER_SSD1309: name = "SSD1309"; nameLen = 7; break; + case OLED_CONTROLLER_SSD1306: + default: name = "SSD1306"; nameLen = 7; break; + } + i2c_OLED_set_xy((SCREEN_CHARACTER_COLUMN_COUNT - nameLen) / 2, 3); + i2c_OLED_send_string(name); } #endif // USE_OLED_UG2864 diff --git a/src/main/target/ORBITF435/target.h b/src/main/target/ORBITF435/target.h index a6456319523..d81e5908123 100644 --- a/src/main/target/ORBITF435/target.h +++ b/src/main/target/ORBITF435/target.h @@ -190,6 +190,8 @@ #define MAX_PWM_OUTPUT_PORTS 11 +#define USE_BOOTLOG 4096 + #define TARGET_IO_PORTA 0xffff #define TARGET_IO_PORTB 0xffff #define TARGET_IO_PORTC 0xffff diff --git a/src/main/target/common_hardware.c b/src/main/target/common_hardware.c index c8576429c50..bdb721ad209 100755 --- a/src/main/target/common_hardware.c +++ b/src/main/target/common_hardware.c @@ -456,7 +456,7 @@ #endif #endif - BUSDEV_REGISTER_I2C(busdev_ug2864, DEVHW_UG2864, UG2864_I2C_BUS, 0x3C, NONE, DEVFLAGS_NONE, 0); + BUSDEV_REGISTER_I2C(busdev_ug2864, DEVHW_UG2864, UG2864_I2C_BUS, 0x3C, NONE, DEVFLAGS_USE_RAW_REGISTERS, 0); #endif #if defined(USE_IRLOCK) && defined(USE_I2C) From 66b9ba3a3179d79f4a4aaa71bb15d75b48cbdc01 Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Mon, 27 Apr 2026 23:46:20 -0500 Subject: [PATCH 5/7] OLED debug: I2C address probe, BOOTLOG for AOCODARCH7DUAL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add I2C address probe in detectOledController() that reports which addresses ACK (0x3C and 0x3D) via LOG_DEBUG. Useful when diagnosing hardware issues without an I2C scanner — visible in bootlog after setting log_level=DEBUG and configuring a log serial port. Also: - AOCODARCH7DUAL: add USE_BOOTLOG 4096 for bootlog debugging - ORBITF435: USE_BOOTLOG 4096 already in previous commit - Revert "Bus device found" message back to LOG_DEBUG (was bumped to LOG_ERROR temporarily during hardware debugging session) - Mark ug2864hsweg01TestPattern() call in init as TODO: remove before PR Note: NAND flash (flash_w25n.c) had an unrelated local change that was intentionally excluded from this commit. --- src/main/drivers/display_ug2864hsweg01.c | 15 +++++++++++---- src/main/target/AOCODARCH7DUAL/target.h | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c index 5f23c4dc97d..ec7a0beba94 100644 --- a/src/main/drivers/display_ug2864hsweg01.c +++ b/src/main/drivers/display_ug2864hsweg01.c @@ -299,10 +299,17 @@ static oledControllerType_e detectOledController(void) { uint8_t statusByte = 0; - // Read the status register (register 0x00) - // This is a read of the status byte from the OLED controller + // Probe I2C addresses 0x3C and 0x3D — log which ones respond so the + // bootlog reveals address mismatches without needing an I2C scanner. + uint8_t probeByte = 0; + bool found3C = i2cRead(busDev->busdev.i2c.i2cBus, 0x3C, 0xFF, 1, &probeByte, true); + bool found3D = i2cRead(busDev->busdev.i2c.i2cBus, 0x3D, 0xFF, 1, &probeByte, true); + LOG_DEBUG(SYSTEM, "OLED: I2C probe — 0x3C:%s 0x3D:%s", + found3C ? "ACK" : "NAK", found3D ? "ACK" : "NAK"); + + // Raw status register read (0xFF = no register write phase before read) if (!busRead(busDev, 0xFF, &statusByte)) { - LOG_ERROR(SYSTEM, "OLED: Failed to read status register"); + LOG_ERROR(SYSTEM, "OLED: Failed to read status register at 0x3C"); return OLED_CONTROLLER_UNKNOWN; } @@ -403,7 +410,7 @@ bool ug2864hsweg01InitI2C(void) LOG_DEBUG(SYSTEM, "OLED: Init sequence complete, clearing display"); i2c_OLED_clear_display(); - ug2864hsweg01TestPattern(); + ug2864hsweg01TestPattern(); // TODO: remove before PR — hardware test only LOG_DEBUG(SYSTEM, "OLED: Initialization complete, controller=%d", detectedController); diff --git a/src/main/target/AOCODARCH7DUAL/target.h b/src/main/target/AOCODARCH7DUAL/target.h index 1c12f198a03..c7a5142e737 100644 --- a/src/main/target/AOCODARCH7DUAL/target.h +++ b/src/main/target/AOCODARCH7DUAL/target.h @@ -203,3 +203,5 @@ #define MAX_PWM_OUTPUT_PORTS 15 #define USE_DSHOT #define USE_ESC_SENSOR + +#define USE_BOOTLOG 4096 From 3859d296cedf226b60a566f7693d01986a321b01 Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Tue, 1 Sep 2026 23:48:56 -0500 Subject: [PATCH 6/7] Clean up OLED auto-detection debug scaffolding before PR Remove the hardware-test-only test pattern call from init (was drawing a diagnostic rectangle on every boot) and the two targets' temporary USE_BOOTLOG additions used only to debug I2C detection on hardware without a scanner. Also note that SH1107 detection is unverified on real 128x128 hardware, since the geometry code still assumes the 128x64/8-page layout used by SSD1306/SH1106. --- src/main/drivers/display_ug2864hsweg01.c | 6 ++++-- src/main/target/AOCODARCH7DUAL/target.h | 2 -- src/main/target/ORBITF435/target.h | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c index ec7a0beba94..af63da28c91 100644 --- a/src/main/drivers/display_ug2864hsweg01.c +++ b/src/main/drivers/display_ug2864hsweg01.c @@ -327,7 +327,10 @@ static oledControllerType_e detectOledController(void) switch (controllerBits) { case 0x07: case 0x0F: - // SH1107 - 128x128 displays + // SH1107 - 128x128 displays. Detection only: geometry code below + // still assumes the 128x64/8-page layout (SCREEN_HEIGHT is fixed + // at 64), so a true 128x128 panel will only get its top half + // addressed. Untested on real SH1107 hardware. detected = OLED_CONTROLLER_SH1107; controllerName = "SH1107"; break; @@ -410,7 +413,6 @@ bool ug2864hsweg01InitI2C(void) LOG_DEBUG(SYSTEM, "OLED: Init sequence complete, clearing display"); i2c_OLED_clear_display(); - ug2864hsweg01TestPattern(); // TODO: remove before PR — hardware test only LOG_DEBUG(SYSTEM, "OLED: Initialization complete, controller=%d", detectedController); diff --git a/src/main/target/AOCODARCH7DUAL/target.h b/src/main/target/AOCODARCH7DUAL/target.h index c7a5142e737..1c12f198a03 100644 --- a/src/main/target/AOCODARCH7DUAL/target.h +++ b/src/main/target/AOCODARCH7DUAL/target.h @@ -203,5 +203,3 @@ #define MAX_PWM_OUTPUT_PORTS 15 #define USE_DSHOT #define USE_ESC_SENSOR - -#define USE_BOOTLOG 4096 diff --git a/src/main/target/ORBITF435/target.h b/src/main/target/ORBITF435/target.h index d81e5908123..a6456319523 100644 --- a/src/main/target/ORBITF435/target.h +++ b/src/main/target/ORBITF435/target.h @@ -190,8 +190,6 @@ #define MAX_PWM_OUTPUT_PORTS 11 -#define USE_BOOTLOG 4096 - #define TARGET_IO_PORTA 0xffff #define TARGET_IO_PORTB 0xffff #define TARGET_IO_PORTC 0xffff From 738970bd6fab7448cbfd3684deaa373f799c7dbd Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Wed, 2 Sep 2026 00:00:10 -0500 Subject: [PATCH 7/7] Remove remaining debug scaffolding found in code review The prior cleanup commit removed the test-pattern call site but left the 85-line function itself dangling with no callers, plus a debug I2C probe (0x3C/0x3D) that ran on every boot and triggered a real bus reinit on the expected NAK, and several progress-narration log lines with no diagnostic value. Also collapses the four duplicated SH1106 column-offset checks into one helper. --- src/main/drivers/display_ug2864hsweg01.c | 126 +++-------------------- src/main/drivers/display_ug2864hsweg01.h | 4 - 2 files changed, 12 insertions(+), 118 deletions(-) diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c index af63da28c91..5e808576143 100644 --- a/src/main/drivers/display_ug2864hsweg01.c +++ b/src/main/drivers/display_ug2864hsweg01.c @@ -25,7 +25,6 @@ #ifdef USE_OLED_UG2864 #include "drivers/bus.h" -#include "drivers/bus_i2c.h" #include "drivers/time.h" #include "display_ug2864hsweg01.h" @@ -207,10 +206,18 @@ bool i2c_OLED_send_byte(uint8_t val) return busWrite(busDev, 0x40, val); } +// SH1106 has 132-wide GDDRAM but only 128 columns are visible; the first +// 2 columns are hidden, so writes must start 2 columns in. Other controllers +// use the full visible width and need no offset. +static uint8_t oledColumnOffset(void) +{ + return (detectedController == OLED_CONTROLLER_SH1106) ? 2 : 0; +} + void i2c_OLED_clear_display(void) { // SH1106 only supports page addressing mode; use page-by-page clear for all controllers - uint8_t startCol = (detectedController == OLED_CONTROLLER_SH1106) ? 2 : 0; + uint8_t startCol = oledColumnOffset(); i2c_OLED_send_cmd(0xa6); // Set Normal Display i2c_OLED_send_cmd(0xae); // Display OFF @@ -232,7 +239,7 @@ void i2c_OLED_clear_display(void) void i2c_OLED_clear_display_quick(void) { - uint8_t startCol = (detectedController == OLED_CONTROLLER_SH1106) ? 2 : 0; + uint8_t startCol = oledColumnOffset(); for (uint8_t page = 0; page < 8; page++) { i2c_OLED_send_cmd(0xb0 + page); // set page address @@ -246,10 +253,7 @@ void i2c_OLED_clear_display_quick(void) void i2c_OLED_set_xy(uint8_t col, uint8_t row) { - uint8_t pixelCol = CHARACTER_WIDTH_TOTAL * col; - if (detectedController == OLED_CONTROLLER_SH1106) { - pixelCol += 2; // SH1106 has 132-wide GDDRAM; first 2 cols are not visible - } + uint8_t pixelCol = CHARACTER_WIDTH_TOTAL * col + oledColumnOffset(); i2c_OLED_send_cmd(0xb0 + row); // set page address i2c_OLED_send_cmd(0x00 + (pixelCol & 0x0f)); // set low col address i2c_OLED_send_cmd(0x10 + ((pixelCol >> 4) & 0x0f)); // set high col address @@ -257,7 +261,7 @@ void i2c_OLED_set_xy(uint8_t col, uint8_t row) void i2c_OLED_set_line(uint8_t row) { - uint8_t startCol = (detectedController == OLED_CONTROLLER_SH1106) ? 2 : 0; + uint8_t startCol = oledColumnOffset(); i2c_OLED_send_cmd(0xb0 + row); // set page address i2c_OLED_send_cmd(0x00 + (startCol & 0x0f)); // set low col address i2c_OLED_send_cmd(0x10 + ((startCol >> 4) & 0x0f)); // set high col address @@ -299,27 +303,15 @@ static oledControllerType_e detectOledController(void) { uint8_t statusByte = 0; - // Probe I2C addresses 0x3C and 0x3D — log which ones respond so the - // bootlog reveals address mismatches without needing an I2C scanner. - uint8_t probeByte = 0; - bool found3C = i2cRead(busDev->busdev.i2c.i2cBus, 0x3C, 0xFF, 1, &probeByte, true); - bool found3D = i2cRead(busDev->busdev.i2c.i2cBus, 0x3D, 0xFF, 1, &probeByte, true); - LOG_DEBUG(SYSTEM, "OLED: I2C probe — 0x3C:%s 0x3D:%s", - found3C ? "ACK" : "NAK", found3D ? "ACK" : "NAK"); - // Raw status register read (0xFF = no register write phase before read) if (!busRead(busDev, 0xFF, &statusByte)) { LOG_ERROR(SYSTEM, "OLED: Failed to read status register at 0x3C"); return OLED_CONTROLLER_UNKNOWN; } - LOG_DEBUG(SYSTEM, "OLED: Raw status register = 0x%02X", statusByte); - // Mask off the upper bits - controller type is in lower nibble uint8_t controllerBits = statusByte & 0x0F; - LOG_DEBUG(SYSTEM, "OLED: Controller ID bits (masked) = 0x%02X", controllerBits); - oledControllerType_e detected; const char *controllerName; @@ -373,8 +365,6 @@ bool ug2864hsweg01InitI2C(void) return false; } - LOG_DEBUG(SYSTEM, "OLED: Bus device initialized, detecting controller type..."); - // Detect the OLED controller type before initialization detectedController = detectOledController(); @@ -384,8 +374,6 @@ bool ug2864hsweg01InitI2C(void) return false; } - LOG_DEBUG(SYSTEM, "OLED: Display OFF command sent, starting init sequence"); - i2c_OLED_send_cmd(0xD4); // Set Display Clock Divide Ratio / OSC Frequency i2c_OLED_send_cmd(0x80); // Display Clock Divide Ratio / OSC Frequency i2c_OLED_send_cmd(0xA8); // Set Multiplex Ratio @@ -410,99 +398,9 @@ bool ug2864hsweg01InitI2C(void) i2c_OLED_send_cmd(0xA6); // Set display not inverted i2c_OLED_send_cmd(0xAF); // Set display On - LOG_DEBUG(SYSTEM, "OLED: Init sequence complete, clearing display"); - i2c_OLED_clear_display(); - LOG_DEBUG(SYSTEM, "OLED: Initialization complete, controller=%d", detectedController); - return true; } -/** - * Column-offset diagnostic test pattern. - * - * Draws a double-outline rectangle using only column-exact pixel writes so - * that a wrong SH1106 +2-pixel column offset is immediately visible on the - * physical display. - * - * Left edge layout (logical columns, after any controller offset is applied): - * col 0 : 0xFF outer left border (solid, full page height) - * col 1 : 0x00 1-pixel gap - * col 2 : 0xFF inner left border (solid, full page height) - * col 3..124: interior (see below) - * col 125: 0xFF inner right border (solid, full page height) - * col 126: 0x00 1-pixel gap - * col 127: 0xFF outer right border (solid, full page height) - * - * Top/bottom horizontal lines (1 pixel wide): - * page 0, cols 3..124: 0x01 (bit 0 = topmost pixel of the page) - * page 7, cols 3..124: 0x80 (bit 7 = bottommost pixel of the page) - * pages 1..6, cols 3..124: 0x00 (interior empty) - * - * How to read the result on the display: - * Correct offset : outer border flush at physical screen edge, 1-pixel gap, - * then inner border; symmetric on both sides. - * Offset off by 1 : one border merges with its neighbour or a gap doubles. - * Offset off by 2 : outer border disappears off the left (or right) edge. - * - * Call this function once after ug2864hsweg01InitI2C() to verify the column - * offset. Do NOT call it from production code paths. - */ -void ug2864hsweg01TestPattern(void) -{ - i2c_OLED_clear_display(); - - for (uint8_t page = 0; page < 8; page++) { - // Position to the start of this page (col 0, with controller offset applied) - i2c_OLED_set_line(page); - - // Determine the fill byte for the interior columns 3..124 on this page - uint8_t interiorByte; - if (page == 0) { - interiorByte = 0x01; // top horizontal border: only the topmost pixel - } else if (page == 7) { - interiorByte = 0x80; // bottom horizontal border: only the bottommost pixel - } else { - interiorByte = 0x00; // empty interior - } - - // col 0: outer left border (solid vertical stripe) - i2c_OLED_send_byte(0xFF); - - // col 1: 1-pixel gap - i2c_OLED_send_byte(0x00); - - // col 2: inner left border (solid vertical stripe) - i2c_OLED_send_byte(0xFF); - - // cols 3..124: interior (122 columns) - for (uint8_t col = 3; col <= 124; col++) { - i2c_OLED_send_byte(interiorByte); - } - - // col 125: inner right border (solid vertical stripe) - i2c_OLED_send_byte(0xFF); - - // col 126: 1-pixel gap - i2c_OLED_send_byte(0x00); - - // col 127: outer right border (solid vertical stripe) - i2c_OLED_send_byte(0xFF); - } - - // Display detected controller name centered on page 3 (vertical middle) - const char *name; - uint8_t nameLen; - switch (detectedController) { - case OLED_CONTROLLER_SH1106: name = "SH1106"; nameLen = 6; break; - case OLED_CONTROLLER_SH1107: name = "SH1107"; nameLen = 6; break; - case OLED_CONTROLLER_SSD1309: name = "SSD1309"; nameLen = 7; break; - case OLED_CONTROLLER_SSD1306: - default: name = "SSD1306"; nameLen = 7; break; - } - i2c_OLED_set_xy((SCREEN_CHARACTER_COLUMN_COUNT - nameLen) / 2, 3); - i2c_OLED_send_string(name); -} - #endif // USE_OLED_UG2864 diff --git a/src/main/drivers/display_ug2864hsweg01.h b/src/main/drivers/display_ug2864hsweg01.h index 627a15ebdf2..45e012b2806 100644 --- a/src/main/drivers/display_ug2864hsweg01.h +++ b/src/main/drivers/display_ug2864hsweg01.h @@ -43,7 +43,3 @@ void i2c_OLED_send_string(const char *string); bool i2c_OLED_send_byte(uint8_t val); void i2c_OLED_clear_display(void); void i2c_OLED_clear_display_quick(void); - -// Column-offset diagnostic: call once after init to verify SH1106/SSD1306 -// offset correctness. Do NOT call from production code paths. -void ug2864hsweg01TestPattern(void);