feat(sensecap-indicator): FT6336 touch on the shared expander bus
The FT6336 sits at 0x48 on the same SDA39/SCL40 bus as the TCA9535, and its reset is expander pin P0.7, so the driver takes IndicatorIo::BusLock for every register write + read and pulses reset through the expander. It implements the heltecV4CapTouch* interface the touch UI already polls, with the same tap and swipe thresholds as the Wio Tracker L2, so UITask needs no new touch path. The controller reports both axes mirrored against the panel, so points are mapped as 479 - raw and clamped; the raw point is kept for the diagnostic overlay. UITask calls begin on every loop pass until it succeeds. Pulsing reset and waiting 300 ms on each call would starve loopTask when the controller does not answer, so reset runs on the first probe only, later probes run at most every 2 s, and the failure is logged once. The chip and vendor ids are logged, not checked, so a compatible controller with another id still works. Signed-off-by: Confituurke <9595028+Confituurke@users.noreply.github.com>
This commit is contained in:
1 parent
c2d50b5430
commit
3ccd27c80a
1 file changed
+161
-13
@@ -1,24 +1,172 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#if defined(HAS_SENSECAP_INDICATOR) && defined(ESP32)
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <helpers/input/HeltecV4CapTouch.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
|
||||
// Placeholder until the FT6336 reader lands: the touch API the UI calls, with no
|
||||
// touch behind it.
|
||||
#include "IndicatorExpander.h"
|
||||
#include "IndicatorIo.h"
|
||||
|
||||
bool heltecV4CapTouchBegin() { return false; }
|
||||
int heltecV4CapTouchCheck() { return BUTTON_EVENT_NONE; }
|
||||
bool heltecV4CapTouchPopTap(uint16_t*, uint16_t*) { return false; }
|
||||
bool heltecV4CapTouchGetLive(uint16_t*, uint16_t*) { return false; }
|
||||
bool heltecV4CapTouchPopSwipe(int8_t*, int8_t*) { return false; }
|
||||
bool heltecV4CapTouchStartBackgroundPoll(uint32_t) { return false; }
|
||||
bool heltecV4CapTouchIsAsyncPolling() { return false; }
|
||||
bool heltecV4CapTouchIsSwiping() { return false; }
|
||||
// FT6336 on the shared Wire bus (SDA39/SCL40), address 0x48. Registers used:
|
||||
// 0x02 TD_STATUS (low nibble = touch count), followed by P1_XH/XL/YH/YL (0x03..0x06)
|
||||
// 0xA3 ID_G_CIPHER (chip id, 0x64 for FT6336U), 0xA8 G_FOCALTECH_ID (vendor id)
|
||||
// Every transaction holds IndicatorIo::BusLock across register write + read.
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t kAddr = 0x48;
|
||||
constexpr uint8_t kRegStatus = 0x02;
|
||||
constexpr uint8_t kRegChipId = 0xA3;
|
||||
constexpr uint8_t kRegVendorId = 0xA8;
|
||||
constexpr int kMax = 479;
|
||||
// UITask calls begin on every loop pass until it succeeds. Only the first call
|
||||
// pulses reset and waits for the controller; later calls re-probe at most this often.
|
||||
constexpr uint32_t kReprobeMs = 2000;
|
||||
|
||||
bool s_ready = false;
|
||||
bool s_down = false;
|
||||
bool s_live = false;
|
||||
bool s_tapPending = false;
|
||||
bool s_swipePending = false;
|
||||
bool s_swiping = false;
|
||||
uint16_t s_x = 0, s_y = 0, s_startX = 0, s_startY = 0, s_tapX = 0, s_tapY = 0;
|
||||
uint16_t s_rawX = 0, s_rawY = 0;
|
||||
int8_t s_swipeX = 0, s_swipeY = 0;
|
||||
uint32_t s_downAt = 0;
|
||||
TaskHandle_t s_task = nullptr;
|
||||
bool s_async = false;
|
||||
uint32_t s_periodMs = 8;
|
||||
char s_debug[48] = "SenseCAP Indicator FT6336";
|
||||
|
||||
// Reads `len` bytes starting at `reg`. One BusLock scope covers write + read.
|
||||
bool readRegs(uint8_t reg, uint8_t* buf, uint8_t len) {
|
||||
IndicatorIo::BusLock lock;
|
||||
Wire.beginTransmission(kAddr);
|
||||
Wire.write(reg);
|
||||
if (Wire.endTransmission(true) != 0) return false;
|
||||
if (Wire.requestFrom((int)kAddr, (int)len) != len) return false;
|
||||
for (uint8_t i = 0; i < len; i++) buf[i] = Wire.read();
|
||||
return true;
|
||||
}
|
||||
|
||||
int clampCoord(int v) { return v < 0 ? 0 : (v > kMax ? kMax : v); }
|
||||
|
||||
bool readPoint(uint16_t& x, uint16_t& y) {
|
||||
uint8_t b[5];
|
||||
if (!readRegs(kRegStatus, b, sizeof(b))) return false;
|
||||
const uint8_t n = b[0] & 0x0F;
|
||||
if (n == 0 || n > 2) return false;
|
||||
const uint16_t rx = ((b[1] & 0x0F) << 8) | b[2];
|
||||
const uint16_t ry = ((b[3] & 0x0F) << 8) | b[4];
|
||||
s_rawX = rx; s_rawY = ry;
|
||||
x = (uint16_t)clampCoord(kMax - (int)rx);
|
||||
y = (uint16_t)clampCoord(kMax - (int)ry);
|
||||
return true;
|
||||
}
|
||||
|
||||
void poll() {
|
||||
uint16_t x = 0, y = 0;
|
||||
const bool touched = readPoint(x, y);
|
||||
if (touched) {
|
||||
s_x = x; s_y = y; s_live = true;
|
||||
if (!s_down) { s_down = true; s_startX = x; s_startY = y; s_downAt = millis(); s_swiping = false; }
|
||||
const int dx = (int)x - s_startX, dy = (int)y - s_startY;
|
||||
const int adx = abs(dx), ady = abs(dy);
|
||||
if (!s_swiping && adx >= 40 && adx > ady) s_swiping = true;
|
||||
return;
|
||||
}
|
||||
s_live = false;
|
||||
if (!s_down) return;
|
||||
s_down = false;
|
||||
const int dx = (int)s_x - s_startX, dy = (int)s_y - s_startY;
|
||||
const int adx = abs(dx), ady = abs(dy);
|
||||
s_swiping = false;
|
||||
if (adx >= 40 && adx > ady + 8) {
|
||||
s_swipeX = dx < 0 ? -1 : 1; s_swipeY = 0; s_swipePending = true;
|
||||
} else if (ady >= 40 && ady > adx + 8) {
|
||||
s_swipeX = 0; s_swipeY = dy < 0 ? -1 : 1; s_swipePending = true;
|
||||
} else if ((uint32_t)(millis() - s_downAt) >= 12 && adx <= 16 && ady <= 16) {
|
||||
s_tapX = s_x; s_tapY = s_y; s_tapPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
void pollTask(void*) {
|
||||
for (;;) { heltecV4CapTouchCheck(); vTaskDelay(pdMS_TO_TICKS(s_periodMs)); }
|
||||
}
|
||||
|
||||
void setReset(bool high) {
|
||||
IndicatorIo::BusLock lock;
|
||||
IndicatorIo::chip().setOutput(indicator::pins::kTouchReset, high);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool heltecV4CapTouchBegin() {
|
||||
static bool s_resetDone = false;
|
||||
static bool s_failLogged = false;
|
||||
static uint32_t s_lastProbe = 0;
|
||||
if (s_ready) return true;
|
||||
if (!s_resetDone) {
|
||||
// Reset pulse; the bus lock is not held across the sleeps.
|
||||
s_resetDone = true;
|
||||
setReset(false);
|
||||
delay(10);
|
||||
setReset(true);
|
||||
delay(300);
|
||||
} else if ((uint32_t)(millis() - s_lastProbe) < kReprobeMs) {
|
||||
return false;
|
||||
}
|
||||
s_lastProbe = millis();
|
||||
|
||||
uint8_t id = 0, vendor = 0;
|
||||
if (!readRegs(kRegChipId, &id, 1)) {
|
||||
snprintf(s_debug, sizeof(s_debug), "SenseCAP Indicator FT6336 (no ack)");
|
||||
if (!s_failLogged) {
|
||||
s_failLogged = true;
|
||||
Serial.println("[touch] FT6336 not responding at 0x48, re-probing every 2 s");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const bool haveVendor = readRegs(kRegVendorId, &vendor, 1);
|
||||
snprintf(s_debug, sizeof(s_debug), "SenseCAP Indicator FT6336 id=0x%02X", id);
|
||||
Serial.printf("[touch] FT6336 chip id 0x%02X (expect 0x64), vendor 0x%02X\n", id, haveVendor ? vendor : 0);
|
||||
s_ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int heltecV4CapTouchCheck() { if (!s_ready) return BUTTON_EVENT_NONE; poll(); return BUTTON_EVENT_NONE; }
|
||||
bool heltecV4CapTouchPopTap(uint16_t* x, uint16_t* y) {
|
||||
if (!s_tapPending) return false; s_tapPending = false;
|
||||
if (x) *x = s_tapX;
|
||||
if (y) *y = s_tapY;
|
||||
return true;
|
||||
}
|
||||
bool heltecV4CapTouchGetLive(uint16_t* x, uint16_t* y) {
|
||||
if (!s_live) return false;
|
||||
if (x) *x = s_x;
|
||||
if (y) *y = s_y;
|
||||
return true;
|
||||
}
|
||||
bool heltecV4CapTouchPopSwipe(int8_t* x, int8_t* y) {
|
||||
if (!s_swipePending) return false; s_swipePending = false;
|
||||
if (x) *x = s_swipeX;
|
||||
if (y) *y = s_swipeY;
|
||||
return true;
|
||||
}
|
||||
bool heltecV4CapTouchStartBackgroundPoll(uint32_t periodMs) {
|
||||
if (s_async || !s_ready) return false;
|
||||
s_periodMs = periodMs < 4 ? 4 : (periodMs > 100 ? 100 : periodMs);
|
||||
if (xTaskCreatePinnedToCore(pollTask, "indicator_touch", 3072, nullptr, 2, &s_task, 0) != pdPASS) return false;
|
||||
s_async = true; return true;
|
||||
}
|
||||
bool heltecV4CapTouchIsAsyncPolling() { return s_async; }
|
||||
bool heltecV4CapTouchIsSwiping() { return s_swiping; }
|
||||
void heltecV4CapTouchSetRotation(uint8_t) {}
|
||||
void heltecV4CapTouchSetPointRotation(uint8_t) {}
|
||||
void heltecV4CapTouchSetSlowPoll(bool) {}
|
||||
const char* heltecV4CapTouchDebug() { return "SenseCAP Indicator (no touch yet)"; }
|
||||
void heltecV4CapTouchGetRaw(uint16_t* x, uint16_t* y) { if (x) *x = 0; if (y) *y = 0; }
|
||||
void heltecV4CapTouchSetSlowPoll(bool slow) { s_periodMs = slow ? 50 : 8; }
|
||||
const char* heltecV4CapTouchDebug() { return s_debug; }
|
||||
void heltecV4CapTouchGetRaw(uint16_t* x, uint16_t* y) { if (x) *x = s_rawX; if (y) *y = s_rawY; }
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user