Files
pyxis/lib/tdeck_ui/Hardware/TDeck/Touch.cpp
torlando-tech ac6ceca9f8 Initial commit: standalone Pyxis T-Deck firmware
Split T-Deck firmware from microReticulum examples/lxmf_tdeck/ into its
own repo. microReticulum is consumed as a git submodule dependency pinned
to feat/t-deck. All include paths updated from relative symlinks to bare
includes resolved via library build flags.

Both tdeck (NimBLE) and tdeck-bluedroid environments compile successfully.
Licensed under AGPLv3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 19:48:33 -05:00

324 lines
8.1 KiB
C++

// Copyright (c) 2024 microReticulum contributors
// SPDX-License-Identifier: MIT
#include "Touch.h"
#ifdef ARDUINO
#include "Log.h"
using namespace RNS;
namespace Hardware {
namespace TDeck {
TwoWire* Touch::_wire = nullptr;
bool Touch::_initialized = false;
uint8_t Touch::_i2c_addr = I2C::TOUCH_ADDR_1;
Touch::TouchPoint Touch::_points[Tch::MAX_TOUCH_POINTS];
uint8_t Touch::_touch_count = 0;
uint32_t Touch::_last_poll_time = 0;
bool Touch::init(TwoWire& wire) {
if (_initialized) {
return true;
}
INFO("Initializing T-Deck touch controller");
// Initialize hardware first
if (!init_hardware_only(wire)) {
return false;
}
// Register LVGL input device
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = lvgl_read_cb;
lv_indev_t* indev = lv_indev_drv_register(&indev_drv);
if (!indev) {
ERROR("Failed to register touch controller with LVGL");
return false;
}
INFO("Touch controller initialized successfully");
return true;
}
bool Touch::init_hardware_only(TwoWire& wire) {
if (_initialized) {
return true;
}
INFO("Initializing touch hardware");
_wire = &wire;
// Detect I2C address (0x5D or 0x14)
if (!detect_i2c_address()) {
ERROR(" Failed to detect GT911 I2C address");
return false;
}
INFO((" GT911 detected at address 0x" + String(_i2c_addr, HEX)).c_str());
// Verify product ID
if (!verify_product_id()) {
WARNING(" Could not verify GT911 product ID (may not be critical)");
}
// Clear initial touch state
for (uint8_t i = 0; i < Tch::MAX_TOUCH_POINTS; i++) {
_points[i].valid = false;
}
_touch_count = 0;
_initialized = true;
INFO(" Touch hardware ready");
return true;
}
uint8_t Touch::poll() {
if (!_initialized) {
return 0;
}
uint32_t now = millis();
if (now - _last_poll_time < Tch::POLL_INTERVAL_MS) {
return _touch_count; // Don't poll too frequently
}
_last_poll_time = now;
// Read status register
uint8_t status = 0;
if (!read_register(Tch::REG_STATUS, &status)) {
return 0;
}
// Check if touch data is ready
bool buffer_status = (status & 0x80) != 0;
uint8_t touch_count = status & 0x0F;
if (!buffer_status || touch_count == 0) {
// No touch or data not ready
_touch_count = 0;
for (uint8_t i = 0; i < Tch::MAX_TOUCH_POINTS; i++) {
_points[i].valid = false;
}
// Clear status register
write_register(Tch::REG_STATUS, 0x00);
return 0;
}
// Limit to max supported points
if (touch_count > Tch::MAX_TOUCH_POINTS) {
touch_count = Tch::MAX_TOUCH_POINTS;
}
// Read touch point data
_touch_count = 0;
for (uint8_t i = 0; i < touch_count; i++) {
uint8_t point_data[8];
uint16_t point_reg = Tch::REG_POINT_1 + (i * 8);
if (read_registers(point_reg, point_data, 8)) {
uint8_t track_id = point_data[0];
uint16_t x = point_data[1] | (point_data[2] << 8);
uint16_t y = point_data[3] | (point_data[4] << 8);
uint16_t size = point_data[5] | (point_data[6] << 8);
// Validate coordinates
if (x < Tch::RAW_WIDTH && y < Tch::RAW_HEIGHT) {
_points[i].x = x;
_points[i].y = y;
_points[i].size = size;
_points[i].track_id = track_id;
_points[i].valid = true;
_touch_count++;
} else {
_points[i].valid = false;
}
} else {
_points[i].valid = false;
}
}
// Clear remaining points
for (uint8_t i = touch_count; i < Tch::MAX_TOUCH_POINTS; i++) {
_points[i].valid = false;
}
// Clear status register
write_register(Tch::REG_STATUS, 0x00);
return _touch_count;
}
bool Touch::get_point(uint8_t index, TouchPoint& point) {
if (index >= Tch::MAX_TOUCH_POINTS) {
return false;
}
if (!_points[index].valid) {
return false;
}
point = _points[index];
return true;
}
bool Touch::is_touched() {
return _touch_count > 0;
}
uint8_t Touch::get_touch_count() {
return _touch_count;
}
String Touch::get_product_id() {
uint8_t product_id[4];
if (!read_registers(Tch::REG_PRODUCT_ID, product_id, 4)) {
return "";
}
char id_str[5];
id_str[0] = (char)product_id[0];
id_str[1] = (char)product_id[1];
id_str[2] = (char)product_id[2];
id_str[3] = (char)product_id[3];
id_str[4] = '\0';
return String(id_str);
}
void Touch::lvgl_read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) {
// Poll for new touch data
poll();
// Get first touch point
TouchPoint point;
if (get_point(0, point)) {
data->state = LV_INDEV_STATE_PRESSED;
// Transform touch coordinates for landscape display rotation
// Display uses MADCTL with MX|MV for landscape (rotation=1)
// GT911 reports in native portrait orientation:
// - raw X: 0-239 (portrait width, maps to screen Y after rotation)
// - raw Y: 0-319 (portrait height, maps to screen X after rotation)
// Transform: swap X/Y and invert the new Y axis
data->point.x = point.y;
data->point.y = Disp::HEIGHT - 1 - point.x; // Use display height (240), not raw height
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
// Continue reading is set automatically by LVGL based on state
}
bool Touch::write_register(uint16_t reg, uint8_t value) {
if (!_wire) {
return false;
}
_wire->beginTransmission(_i2c_addr);
_wire->write((uint8_t)(reg >> 8)); // Register high byte
_wire->write((uint8_t)(reg & 0xFF)); // Register low byte
_wire->write(value);
uint8_t result = _wire->endTransmission();
return (result == 0);
}
bool Touch::read_register(uint16_t reg, uint8_t* value) {
if (!_wire) {
return false;
}
// Write register address
_wire->beginTransmission(_i2c_addr);
_wire->write((uint8_t)(reg >> 8)); // Register high byte
_wire->write((uint8_t)(reg & 0xFF)); // Register low byte
uint8_t result = _wire->endTransmission(false); // Send repeated start
if (result != 0) {
return false;
}
// Read register value
if (_wire->requestFrom(_i2c_addr, (uint8_t)1) != 1) {
return false;
}
*value = _wire->read();
return true;
}
bool Touch::read_registers(uint16_t reg, uint8_t* buffer, size_t len) {
if (!_wire) {
return false;
}
// Write register address
_wire->beginTransmission(_i2c_addr);
_wire->write((uint8_t)(reg >> 8)); // Register high byte
_wire->write((uint8_t)(reg & 0xFF)); // Register low byte
uint8_t result = _wire->endTransmission(false); // Send repeated start
if (result != 0) {
return false;
}
// Read register values
if (_wire->requestFrom(_i2c_addr, (uint8_t)len) != len) {
return false;
}
for (size_t i = 0; i < len; i++) {
buffer[i] = _wire->read();
}
return true;
}
bool Touch::detect_i2c_address() {
// Try primary address first (0x5D)
_i2c_addr = I2C::TOUCH_ADDR_1;
_wire->beginTransmission(_i2c_addr);
uint8_t result = _wire->endTransmission();
if (result == 0) {
return true;
}
// Try alternative address (0x14)
_i2c_addr = I2C::TOUCH_ADDR_2;
_wire->beginTransmission(_i2c_addr);
result = _wire->endTransmission();
return (result == 0);
}
bool Touch::verify_product_id() {
String product_id = get_product_id();
if (product_id.length() == 0) {
return false;
}
// GT911 should return "911" as product ID
if (product_id.indexOf("911") >= 0) {
INFO((" Touch product ID: " + product_id).c_str());
return true;
}
WARNING((" Unexpected touch product ID: " + product_id).c_str());
return false;
}
} // namespace TDeck
} // namespace Hardware
#endif // ARDUINO