mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-03-30 13:45:38 +00:00
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>
324 lines
8.1 KiB
C++
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
|