feat(amoled-18): auto-detect CO5300/CST816 panel revision
Waveshare ships two hardware revisions under "ESP32-S3-Touch-AMOLED-1.8":
the original (SH8601 display + FT3168 touch @0x38) and a later one
(CO5300 display + CST816 touch @0x15). On the later board the SH8601
driver left the panel blank and the FT3168 probe spammed
ESP_ERR_INVALID_STATE because nothing answers at 0x38.
Detect the revision at boot by probing the touch address and pick the
matching drivers, so one binary drives both boards:
- board_rev.{h}/board_init.cpp: probe 0x15 (CST816) vs 0x38 (FT3168)
- display.cpp: instantiate Arduino_CO5300 or Arduino_SH8601 behind a
shared Arduino_OLED*; CO5300 gets a 16px column offset to center the
368-wide image (verified on hardware)
- touch.cpp: one FocalTech-style reader (regs 0x02..0x06) for both
chips, address chosen by revision
- board.h: add CST816_ADDR 0x15
- CLAUDE.md: document the dual revision
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
aa9533a878
commit
4e54363168
@@ -26,9 +26,12 @@
|
||||
#define IIC_SDA 15
|
||||
#define IIC_SCL 14
|
||||
|
||||
// ---- Touch (FT3168 via vendored minimal I2C reader) ----
|
||||
// ---- Touch (minimal inline I2C reader; chip varies by board revision) ----
|
||||
// Both controllers share the FocalTech-style data layout at regs 0x02..0x06,
|
||||
// so only the I2C address differs. Revision is detected in board_init().
|
||||
#define TP_INT 21
|
||||
#define FT3168_ADDR 0x38
|
||||
#define FT3168_ADDR 0x38 // original revision
|
||||
#define CST816_ADDR 0x15 // later (CO5300) revision
|
||||
|
||||
// ---- PMU ----
|
||||
#define AXP2101_ADDR 0x34
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
#include "board.h"
|
||||
#include "board_rev.h"
|
||||
#include "io_expander.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// AMOLED-1.8 also needs the XCA9554 IO expander up first — the display
|
||||
// and touch controllers stay in reset until EXIO0..1 go HIGH.
|
||||
|
||||
static BoardRev g_rev = REV_SH8601_FT3168;
|
||||
|
||||
BoardRev board_rev(void) { return g_rev; }
|
||||
|
||||
static bool i2c_present(uint8_t addr) {
|
||||
Wire.beginTransmission(addr);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
extern "C" void board_init(void) {
|
||||
Wire.begin(IIC_SDA, IIC_SCL);
|
||||
io_expander_init();
|
||||
delay(10); // let the touch controller exit reset before probing
|
||||
|
||||
// Detect the panel revision by which touch controller answers. CST816
|
||||
// (0x15) ships on the CO5300 panel; FT3168 (0x38) on the original SH8601.
|
||||
if (i2c_present(CST816_ADDR)) {
|
||||
g_rev = REV_CO5300_CST816;
|
||||
Serial.println("Board revision: CO5300 + CST816");
|
||||
} else {
|
||||
g_rev = REV_SH8601_FT3168;
|
||||
Serial.println("Board revision: SH8601 + FT3168");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
// Two shipping hardware revisions of the Waveshare 1.8" AMOLED differ only in
|
||||
// their bonded panel module:
|
||||
// - original: SH8601 display (QSPI) + FT3168 touch (I2C 0x38)
|
||||
// - later: CO5300 display (QSPI) + CST816 touch (I2C 0x15)
|
||||
// Everything else (PMU, IMU, IO expander, pinout) is identical. We detect the
|
||||
// revision at boot by probing the touch controller's I2C address — board_init()
|
||||
// sets it BEFORE display_hal_init()/touch_hal_init() so both pick the right
|
||||
// driver. The touch chip's presence is a reliable proxy for the panel module.
|
||||
|
||||
enum BoardRev {
|
||||
REV_SH8601_FT3168 = 0, // original
|
||||
REV_CO5300_CST816 = 1, // later revision
|
||||
};
|
||||
|
||||
// Valid only after board_init() has run.
|
||||
BoardRev board_rev(void);
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "../../hal/display_hal.h"
|
||||
#include "board.h"
|
||||
#include "board_rev.h"
|
||||
#include "io_expander.h"
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_GFX_Library.h>
|
||||
@@ -8,17 +9,31 @@
|
||||
// Display reset is routed through the XCA9554 IO expander (EXIO1) which
|
||||
// must be initialized + released before gfx->begin() runs — main.cpp
|
||||
// arranges this by calling display_hal_init() after io_expander_init().
|
||||
//
|
||||
// Two panel revisions: original SH8601 and a later CO5300 module. Both are
|
||||
// Arduino_OLED subclasses, so we hold the chosen one behind a base pointer.
|
||||
// The CO5300's 368-wide active area starts partway into the controller GRAM,
|
||||
// so it needs a column offset to center the image (the SH8601 needs none).
|
||||
|
||||
#define CO5300_COL_OFFSET 16 // centers the 368-wide image (verified on panel)
|
||||
|
||||
static Arduino_DataBus* bus = nullptr;
|
||||
static Arduino_SH8601* gfx = nullptr;
|
||||
static Arduino_OLED* gfx = nullptr;
|
||||
|
||||
void display_hal_init(void) {
|
||||
bus = new Arduino_ESP32QSPI(
|
||||
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
|
||||
// SH8601 constructor: (bus, rst, rotation, w, h)
|
||||
gfx = new Arduino_SH8601(
|
||||
bus, GFX_NOT_DEFINED /* reset via XCA9554 */, 0,
|
||||
LCD_WIDTH, LCD_HEIGHT);
|
||||
if (board_rev() == REV_CO5300_CST816) {
|
||||
// CO5300: (bus, rst, rotation, w, h, col_off1, row_off1, col_off2, row_off2)
|
||||
gfx = new Arduino_CO5300(
|
||||
bus, GFX_NOT_DEFINED /* reset via XCA9554 */, 0,
|
||||
LCD_WIDTH, LCD_HEIGHT, CO5300_COL_OFFSET, 0, 0, 0);
|
||||
} else {
|
||||
// SH8601: (bus, rst, rotation, w, h)
|
||||
gfx = new Arduino_SH8601(
|
||||
bus, GFX_NOT_DEFINED /* reset via XCA9554 */, 0,
|
||||
LCD_WIDTH, LCD_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
void display_hal_begin(void) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "../../hal/touch_hal.h"
|
||||
#include "board.h"
|
||||
#include "board_rev.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// Minimal FT3168 reader (FocalTech standard register layout). Avoids
|
||||
// vendoring Waveshare's GPLv3 Arduino_DriveBus library.
|
||||
// Minimal capacitive-touch reader. Both shipping panel revisions expose the
|
||||
// FocalTech-style touch-data layout, so one read path serves both — only the
|
||||
// I2C address differs (FT3168 @ 0x38 vs CST816 @ 0x15), chosen from board_rev().
|
||||
// reg 0x02: low nibble = active finger count
|
||||
// reg 0x03 / 0x04: X1 high (low nibble) + X1 low
|
||||
// reg 0x05 / 0x06: Y1 high (low nibble) + Y1 low
|
||||
@@ -13,16 +15,17 @@ static volatile bool touch_data_ready = false;
|
||||
static volatile bool touch_pressed = false;
|
||||
static volatile uint16_t touch_x = 0;
|
||||
static volatile uint16_t touch_y = 0;
|
||||
static uint8_t touch_addr = FT3168_ADDR;
|
||||
|
||||
static void IRAM_ATTR touch_isr(void) {
|
||||
touch_data_ready = true;
|
||||
}
|
||||
|
||||
static void ft3168_read_into_shared_state(void) {
|
||||
Wire.beginTransmission(FT3168_ADDR);
|
||||
static void touch_read_into_shared_state(void) {
|
||||
Wire.beginTransmission(touch_addr);
|
||||
Wire.write(0x02);
|
||||
if (Wire.endTransmission(false) != 0) { touch_pressed = false; return; }
|
||||
if (Wire.requestFrom(FT3168_ADDR, (uint8_t)5) != 5) { touch_pressed = false; return; }
|
||||
if (Wire.requestFrom(touch_addr, (uint8_t)5) != 5) { touch_pressed = false; return; }
|
||||
uint8_t fingers = Wire.read() & 0x0F;
|
||||
uint8_t xH = Wire.read();
|
||||
uint8_t xL = Wire.read();
|
||||
@@ -38,31 +41,38 @@ static void ft3168_read_into_shared_state(void) {
|
||||
}
|
||||
|
||||
void touch_hal_init(void) {
|
||||
// Power-mode register 0xA5 = 0x00: active scanning.
|
||||
Wire.beginTransmission(FT3168_ADDR);
|
||||
Wire.write(0xA5);
|
||||
Wire.write(0x00);
|
||||
Wire.endTransmission();
|
||||
bool is_cst816 = (board_rev() == REV_CO5300_CST816);
|
||||
touch_addr = is_cst816 ? CST816_ADDR : FT3168_ADDR;
|
||||
|
||||
// Verify device ID register 0xA0 (FT3168 reports 0x03 but Waveshare's
|
||||
// panel sometimes returns 0x86 — log but don't fail).
|
||||
Wire.beginTransmission(FT3168_ADDR);
|
||||
Wire.write(0xA0);
|
||||
if (Wire.endTransmission(false) == 0 && Wire.requestFrom(FT3168_ADDR, (uint8_t)1) == 1) {
|
||||
Serial.printf("FT3168 ID=0x%02X\n", Wire.read());
|
||||
if (!is_cst816) {
|
||||
// FT3168 power-mode register 0xA5 = 0x00: active scanning.
|
||||
// (CST816 reports by default; no equivalent setup needed.)
|
||||
Wire.beginTransmission(touch_addr);
|
||||
Wire.write(0xA5);
|
||||
Wire.write(0x00);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
// Verify the controller answers. FT3168 chip-id is reg 0xA0; CST816 reg 0xA7.
|
||||
uint8_t id_reg = is_cst816 ? 0xA7 : 0xA0;
|
||||
Wire.beginTransmission(touch_addr);
|
||||
Wire.write(id_reg);
|
||||
if (Wire.endTransmission(false) == 0 && Wire.requestFrom(touch_addr, (uint8_t)1) == 1) {
|
||||
Serial.printf("Touch %s ID=0x%02X (addr 0x%02X)\n",
|
||||
is_cst816 ? "CST816" : "FT3168", Wire.read(), touch_addr);
|
||||
} else {
|
||||
Serial.println("FT3168 ID read failed");
|
||||
Serial.printf("Touch ID read failed (addr 0x%02X)\n", touch_addr);
|
||||
}
|
||||
|
||||
pinMode(TP_INT, INPUT_PULLUP);
|
||||
attachInterrupt(TP_INT, touch_isr, FALLING);
|
||||
Serial.println("FT3168 attached on INT pin");
|
||||
Serial.println("Touch attached on INT pin");
|
||||
}
|
||||
|
||||
void touch_hal_read(uint16_t* x, uint16_t* y, bool* pressed) {
|
||||
if (touch_data_ready) {
|
||||
touch_data_ready = false;
|
||||
ft3168_read_into_shared_state();
|
||||
touch_read_into_shared_state();
|
||||
}
|
||||
*x = touch_x;
|
||||
*y = touch_y;
|
||||
|
||||
Reference in New Issue
Block a user