first commit
This commit is contained in:
commit
0ef2017d18
28 files changed
+1882
No files matched your search
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* STC8H1K28 board controller, CrowPanel 7" Advance v1.3.
|
||||
*
|
||||
* A small 8051-class MCU sitting on the I2C bus at 0x30. It drives the LCD
|
||||
* backlight and, critically, the GT911 reset line -- there is no ESP32 GPIO
|
||||
* for touch reset on this board, so the GT911 stays mute until the sequence
|
||||
* below has run.
|
||||
*
|
||||
* Protocol is single raw bytes, no register addressing:
|
||||
* 0x19 wake
|
||||
* 0x10, 0x18 configure / release GT911 from reset
|
||||
* 0x00 - 0xF5 brightness, INVERTED (0x00 = full, 0xF5 = off)
|
||||
*
|
||||
* v1.2 boards use a different, narrower range (0x05 = off .. 0x10 = max).
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "backlight.h"
|
||||
#include "i2c_bus.h"
|
||||
|
||||
static const char *TAG = "backlight";
|
||||
|
||||
#define STC8_ADDR 0x30
|
||||
#define STC8_I2C_HZ 100000
|
||||
#define STC8_TIMEOUT_MS 100
|
||||
|
||||
#define STC8_CMD_WAKE 0x19
|
||||
#define STC8_CMD_CFG1 0x10
|
||||
#define STC8_CMD_CFG2 0x18
|
||||
|
||||
/* Inverted brightness range. */
|
||||
#define STC8_DUTY_MAX_ON 0x00
|
||||
#define STC8_DUTY_OFF 0xF5
|
||||
|
||||
static i2c_master_dev_handle_t dev = NULL;
|
||||
|
||||
static esp_err_t stc8_cmd(uint8_t cmd)
|
||||
{
|
||||
return i2c_master_transmit(dev, &cmd, 1, STC8_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
esp_err_t backlight_init(void)
|
||||
{
|
||||
const i2c_device_config_t cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = STC8_ADDR,
|
||||
.scl_speed_hz = STC8_I2C_HZ,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(i2c_master_bus_add_device(i2c_bus_get(), &cfg, &dev), TAG,
|
||||
"could not add STC8H1K28 at 0x%02X", STC8_ADDR);
|
||||
|
||||
ESP_RETURN_ON_ERROR(stc8_cmd(STC8_CMD_WAKE), TAG, "wake failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
/* These two also release the GT911 from reset. */
|
||||
ESP_RETURN_ON_ERROR(stc8_cmd(STC8_CMD_CFG1), TAG, "config 1 failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
ESP_RETURN_ON_ERROR(stc8_cmd(STC8_CMD_CFG2), TAG, "config 2 failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
ESP_LOGI(TAG, "STC8H1K28 initialized (GT911 released from reset)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t backlight_set(uint8_t brightness)
|
||||
{
|
||||
if (dev == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Map 0..255 (intuitive) onto 0xF5..0x00 (wire, inverted). */
|
||||
const uint8_t duty = (uint8_t)(STC8_DUTY_OFF -
|
||||
((uint32_t)brightness * STC8_DUTY_OFF) / 255);
|
||||
return stc8_cmd(duty);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef BACKLIGHT_H
|
||||
#define BACKLIGHT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/*
|
||||
* STC8H1K28 board controller (I2C 0x30) on the CrowPanel 7" Advance.
|
||||
*
|
||||
* It owns more than the backlight: its init sequence also drives the GT911
|
||||
* reset line, so this must run before the touch controller will answer on
|
||||
* the bus at all. Requires i2c_bus_init() first.
|
||||
*/
|
||||
esp_err_t backlight_init(void);
|
||||
|
||||
/* 0 = off, 255 = full. The v1.3 wire protocol is inverted; that is handled
|
||||
* internally so callers can use the intuitive direction. */
|
||||
esp_err_t backlight_set(uint8_t brightness);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* RGB parallel panel driver for the Elecrow CrowPanel 7" ADVANCE (800x480).
|
||||
*
|
||||
* NOTE: the Advance and the Basic CrowPanel 7" share almost no pins. Every
|
||||
* data line, every sync line, the pixel clock, the porches and the sync
|
||||
* polarities differ, and the Advance drives its backlight over I2C rather
|
||||
* than from a GPIO. Configuration below is the Advance; do not mix the two.
|
||||
*
|
||||
* Reference: Elecrow Advance-series wiki and the LovyanGFX board config in
|
||||
* the FluidTouch project (elecrow-crowpanel-7-advance-v13).
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
#include "esp_log.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
#include "display_rgb.h"
|
||||
|
||||
static const char *TAG = "display_rgb";
|
||||
|
||||
/* --- Sync / control pins (Advance) --- */
|
||||
#define RGB_DE_GPIO 42
|
||||
#define RGB_VSYNC_GPIO 41
|
||||
#define RGB_HSYNC_GPIO 40
|
||||
#define RGB_PCLK_GPIO 39
|
||||
|
||||
/*
|
||||
* --- Data pins (Advance), 16-bit RGB565 ---
|
||||
* data_gpio_nums[0..4] = B0-B4, [5..10] = G0-G5, [11..15] = R0-R4.
|
||||
*/
|
||||
#define RGB_B0_GPIO 21
|
||||
#define RGB_B1_GPIO 47
|
||||
#define RGB_B2_GPIO 48
|
||||
#define RGB_B3_GPIO 45
|
||||
#define RGB_B4_GPIO 38
|
||||
|
||||
#define RGB_G0_GPIO 9
|
||||
#define RGB_G1_GPIO 10
|
||||
#define RGB_G2_GPIO 11
|
||||
#define RGB_G3_GPIO 12
|
||||
#define RGB_G4_GPIO 13
|
||||
#define RGB_G5_GPIO 14
|
||||
|
||||
#define RGB_R0_GPIO 7
|
||||
#define RGB_R1_GPIO 17
|
||||
#define RGB_R2_GPIO 18
|
||||
#define RGB_R3_GPIO 3
|
||||
#define RGB_R4_GPIO 46
|
||||
|
||||
/*
|
||||
* --- Panel timings (Advance) ---
|
||||
*
|
||||
* 18MHz is not tunable in practice. With htotal 820 and vtotal 500 it gives
|
||||
* ~44 Hz, which this panel locks to; dropping to 14MHz yields ~34 Hz and the
|
||||
* image rolls vertically because the panel cannot lock that slow. Bandwidth
|
||||
* is dealt with by the bounce buffer instead.
|
||||
*/
|
||||
#define RGB_PCLK_HZ (18 * 1000 * 1000)
|
||||
#define RGB_HSYNC_PULSE_WIDTH 4
|
||||
#define RGB_HSYNC_BACK_PORCH 8
|
||||
#define RGB_HSYNC_FRONT_PORCH 8
|
||||
#define RGB_VSYNC_PULSE_WIDTH 4
|
||||
#define RGB_VSYNC_BACK_PORCH 8
|
||||
#define RGB_VSYNC_FRONT_PORCH 8
|
||||
|
||||
/*
|
||||
* Scanlines staged in internal RAM per bounce-buffer chunk (two are
|
||||
* allocated, so this costs 2 * 800 * LINES * 2 bytes of internal RAM).
|
||||
*
|
||||
* Bigger is better here: each chunk buys the refill ISR that much more time
|
||||
* to copy the next one out of PSRAM before the LCD needs it. When it runs
|
||||
* late -- which WiFi interrupt activity is enough to cause -- the panel
|
||||
* displays whatever was left in the buffer, showing up as short stale lines
|
||||
* at the left edge where each chunk begins.
|
||||
*/
|
||||
/* 16 is the practical ceiling: two buffers at 20 lines need 64KB of internal
|
||||
* RAM and the allocation fails once WiFi is up. */
|
||||
#define BOUNCE_BUFFER_LINES 16
|
||||
|
||||
static esp_lcd_panel_handle_t panel = NULL;
|
||||
static lv_display_t *disp = NULL;
|
||||
|
||||
/*
|
||||
* Partial-mode flush: blit the dirty rectangle from a small internal-RAM
|
||||
* draw buffer into the single PSRAM framebuffer.
|
||||
*
|
||||
* Page-flipping two framebuffers instead would be cheaper, but the swap
|
||||
* lands mid-scanout on this panel and tears visibly. Copying into one
|
||||
* framebuffer that the LCD scans continuously is what the vendor driver does.
|
||||
*/
|
||||
/*
|
||||
* Double-buffered flush.
|
||||
*
|
||||
* LVGL renders into whichever framebuffer the LCD is NOT currently scanning,
|
||||
* and the final flush of a refresh hands that buffer to the panel, which
|
||||
* switches to it. Nothing ever writes into the buffer being displayed.
|
||||
*
|
||||
* This matters more than it sounds: copying a partial rectangle into the live
|
||||
* framebuffer races the LCD's own reads, and the corruption scales with how
|
||||
* much is written -- forcing full-width flushes made it visibly worse. Those
|
||||
* corrupt pixels then persist indefinitely, because LVGL has no idea they are
|
||||
* wrong and never reinvalidates them.
|
||||
*/
|
||||
static void rgb_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *px_map)
|
||||
{
|
||||
if (lv_display_flush_is_last(display)) {
|
||||
esp_lcd_panel_draw_bitmap(panel, 0, 0, DISPLAY_H_RES, DISPLAY_V_RES, px_map);
|
||||
}
|
||||
lv_display_flush_ready(display);
|
||||
}
|
||||
|
||||
esp_err_t display_init(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initializing 800x480 RGB panel (CrowPanel Advance)");
|
||||
|
||||
/* lv_display_create() below allocates from LVGL's heap. Without lv_init()
|
||||
* that heap is a null pool and the call faults with LoadProhibited. */
|
||||
ESP_RETURN_ON_FALSE(lv_is_initialized(), ESP_ERR_INVALID_STATE, TAG,
|
||||
"lv_init() must run before display_init()");
|
||||
|
||||
const esp_lcd_rgb_panel_config_t panel_config = {
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.timings = {
|
||||
.pclk_hz = RGB_PCLK_HZ,
|
||||
.h_res = DISPLAY_H_RES,
|
||||
.v_res = DISPLAY_V_RES,
|
||||
.hsync_pulse_width = RGB_HSYNC_PULSE_WIDTH,
|
||||
.hsync_back_porch = RGB_HSYNC_BACK_PORCH,
|
||||
.hsync_front_porch = RGB_HSYNC_FRONT_PORCH,
|
||||
.vsync_pulse_width = RGB_VSYNC_PULSE_WIDTH,
|
||||
.vsync_back_porch = RGB_VSYNC_BACK_PORCH,
|
||||
.vsync_front_porch = RGB_VSYNC_FRONT_PORCH,
|
||||
.flags = {
|
||||
/*
|
||||
* The Advance runs both syncs idle-HIGH (LovyanGFX
|
||||
* hsync_polarity/vsync_polarity = 1), which is esp_lcd's
|
||||
* default, so the *_idle_low flags stay clear. Its
|
||||
* pclk_idle_high = 1 maps onto LCD_CK_OUT_EDGE, which esp_lcd
|
||||
* exposes as pclk_active_neg.
|
||||
*/
|
||||
.pclk_active_neg = true,
|
||||
},
|
||||
},
|
||||
.data_width = 16,
|
||||
.bits_per_pixel = 16,
|
||||
/* Two framebuffers so LVGL never draws into the one being scanned. */
|
||||
.num_fbs = 2,
|
||||
/*
|
||||
* Bounce buffer in internal RAM. Fetching the framebuffer straight
|
||||
* from PSRAM at 18MHz underruns the LCD FIFO and the image walks
|
||||
* sideways; this stages each chunk in internal RAM first.
|
||||
*
|
||||
* Its refill ISR reads PSRAM, and PSRAM shares the cache with flash
|
||||
* on the S3, so a flash write while the panel is running faults it
|
||||
* with "Cache disabled but cached memory region accessed". app_main
|
||||
* therefore finishes all NVS/WiFi flash activity *before* calling
|
||||
* display_init(), and CONFIG_ESP_WIFI_NVS_ENABLED is off so the WiFi
|
||||
* stack does not write to flash later.
|
||||
*/
|
||||
.bounce_buffer_size_px = DISPLAY_H_RES * BOUNCE_BUFFER_LINES,
|
||||
.dma_burst_size = 64,
|
||||
.hsync_gpio_num = RGB_HSYNC_GPIO,
|
||||
.vsync_gpio_num = RGB_VSYNC_GPIO,
|
||||
.de_gpio_num = RGB_DE_GPIO,
|
||||
.pclk_gpio_num = RGB_PCLK_GPIO,
|
||||
.disp_gpio_num = GPIO_NUM_NC,
|
||||
.data_gpio_nums = {
|
||||
RGB_B0_GPIO, RGB_B1_GPIO, RGB_B2_GPIO, RGB_B3_GPIO, RGB_B4_GPIO,
|
||||
RGB_G0_GPIO, RGB_G1_GPIO, RGB_G2_GPIO, RGB_G3_GPIO, RGB_G4_GPIO, RGB_G5_GPIO,
|
||||
RGB_R0_GPIO, RGB_R1_GPIO, RGB_R2_GPIO, RGB_R3_GPIO, RGB_R4_GPIO,
|
||||
},
|
||||
.flags = {
|
||||
.fb_in_psram = true,
|
||||
},
|
||||
};
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_new_rgb_panel(&panel_config, &panel), TAG,
|
||||
"esp_lcd_new_rgb_panel failed");
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(panel), TAG, "panel reset failed");
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_init(panel), TAG, "panel init failed");
|
||||
|
||||
void *fb0 = NULL;
|
||||
void *fb1 = NULL;
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(panel, 2, &fb0, &fb1), TAG,
|
||||
"failed to get framebuffers");
|
||||
|
||||
const size_t fb_size = DISPLAY_H_RES * DISPLAY_V_RES * sizeof(uint16_t);
|
||||
memset(fb0, 0, fb_size);
|
||||
memset(fb1, 0, fb_size);
|
||||
|
||||
disp = lv_display_create(DISPLAY_H_RES, DISPLAY_V_RES);
|
||||
ESP_RETURN_ON_FALSE(disp != NULL, ESP_FAIL, TAG, "lv_display_create failed");
|
||||
|
||||
lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
|
||||
lv_display_set_flush_cb(disp, rgb_flush_cb);
|
||||
|
||||
/*
|
||||
* DIRECT mode with the two framebuffers as LVGL's own buffers. LVGL draws
|
||||
* the dirty regions into the back buffer and keeps both in sync itself;
|
||||
* no separate internal draw buffer is needed, which also hands ~25KB of
|
||||
* internal RAM back for the bounce buffers.
|
||||
*/
|
||||
lv_display_set_buffers(disp, fb0, fb1, fb_size, LV_DISPLAY_RENDER_MODE_DIRECT);
|
||||
|
||||
ESP_LOGI(TAG, "Panel up: %dx%d @ %d MHz, double-buffered fb0=%p fb1=%p (%u KB each)",
|
||||
DISPLAY_H_RES, DISPLAY_V_RES, RGB_PCLK_HZ / 1000000,
|
||||
fb0, fb1, (unsigned)(fb_size / 1024));
|
||||
ESP_LOGI(TAG, "Free internal heap: %u KB",
|
||||
(unsigned)(heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
lv_display_t *display_get_default(void)
|
||||
{
|
||||
return disp;
|
||||
}
|
||||
|
||||
esp_lcd_panel_handle_t display_get_panel(void)
|
||||
{
|
||||
return panel;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef DISPLAY_RGB_H
|
||||
#define DISPLAY_RGB_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
#define DISPLAY_H_RES 800
|
||||
#define DISPLAY_V_RES 480
|
||||
|
||||
/* Requires lv_init() to have already run -- call lvgl_port_init() first.
|
||||
* Backlight is NOT owned here on the Advance; see drivers/backlight. */
|
||||
esp_err_t display_init(void);
|
||||
|
||||
lv_display_t *display_get_default(void);
|
||||
|
||||
/* Underlying esp_lcd panel, for drawing that bypasses LVGL. */
|
||||
esp_lcd_panel_handle_t display_get_panel(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "i2c_bus.h"
|
||||
|
||||
static const char *TAG = "i2c_bus";
|
||||
|
||||
/*
|
||||
* CrowPanel 7" Advance pins. The Basic model uses 19/20 instead -- and on the
|
||||
* ESP32-S3 those are the native USB D-/D+ pins, which is a separate trap.
|
||||
*/
|
||||
#define I2C_BUS_SDA_GPIO 15
|
||||
#define I2C_BUS_SCL_GPIO 16
|
||||
#define I2C_BUS_PORT I2C_NUM_0
|
||||
|
||||
static i2c_master_bus_handle_t bus = NULL;
|
||||
|
||||
esp_err_t i2c_bus_init(void)
|
||||
{
|
||||
if (bus != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const i2c_master_bus_config_t cfg = {
|
||||
.i2c_port = I2C_BUS_PORT,
|
||||
.sda_io_num = I2C_BUS_SDA_GPIO,
|
||||
.scl_io_num = I2C_BUS_SCL_GPIO,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags.enable_internal_pullup = true,
|
||||
};
|
||||
|
||||
ESP_RETURN_ON_ERROR(i2c_new_master_bus(&cfg, &bus), TAG, "i2c bus init failed");
|
||||
ESP_LOGI(TAG, "I2C bus up on SDA=%d SCL=%d", I2C_BUS_SDA_GPIO, I2C_BUS_SCL_GPIO);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
i2c_master_bus_handle_t i2c_bus_get(void)
|
||||
{
|
||||
return bus;
|
||||
}
|
||||
|
||||
void i2c_bus_scan(const char *tag)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
ESP_LOGW(tag, "I2C bus not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
for (uint8_t addr = 0x03; addr < 0x78; addr++) {
|
||||
if (i2c_master_probe(bus, addr, 20) == ESP_OK) {
|
||||
const char *what = "?";
|
||||
if (addr == 0x30) what = "STC8H1K28 board controller";
|
||||
else if (addr == 0x5D || addr == 0x14) what = "GT911 touch";
|
||||
ESP_LOGI(tag, " I2C 0x%02X %s", addr, what);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(tag, " %d device(s) on the bus", n);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef I2C_BUS_H
|
||||
#define I2C_BUS_H
|
||||
|
||||
#include "driver/i2c_master.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/*
|
||||
* Shared I2C bus. On the CrowPanel Advance both the STC8H1K28 board
|
||||
* controller (0x30) and the GT911 touch controller (0x5D) live on it, so a
|
||||
* single owner avoids two drivers trying to install the same port.
|
||||
*/
|
||||
esp_err_t i2c_bus_init(void);
|
||||
|
||||
i2c_master_bus_handle_t i2c_bus_get(void);
|
||||
|
||||
/* Logs every responding address. Diagnostics for bring-up. */
|
||||
void i2c_bus_scan(const char *tag);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* GT911 capacitive touch controller, CrowPanel 7" Advance.
|
||||
*
|
||||
* The GT911 uses 16-bit register addressing, so every read writes the two
|
||||
* address bytes (MSB first) then does a repeated-start read.
|
||||
*
|
||||
* On the Advance there is no ESP32 GPIO for touch reset or interrupt -- the
|
||||
* STC8H1K28 board controller owns both. backlight_init() must therefore run
|
||||
* first, or the GT911 is still in reset and NACKs every address.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
#include "display_rgb.h"
|
||||
#include "i2c_bus.h"
|
||||
#include "touch_gt911.h"
|
||||
|
||||
static const char *TAG = "touch_gt911";
|
||||
|
||||
#define TOUCH_I2C_HZ 400000
|
||||
#define TOUCH_I2C_TIMEOUT_MS 100
|
||||
|
||||
#define GT911_ADDR_LOW 0x5D
|
||||
#define GT911_ADDR_HIGH 0x14
|
||||
|
||||
#define GT911_REG_PRODUCT_ID 0x8140
|
||||
#define GT911_REG_STATUS 0x814E
|
||||
#define GT911_REG_TRACK_ID 0x814F
|
||||
/* Coordinate block: x_lo, x_hi, y_lo, y_hi, size_lo, size_hi */
|
||||
#define GT911_REG_POINT1_XY 0x8150
|
||||
|
||||
#define GT911_STATUS_READY 0x80
|
||||
#define GT911_POINT_COUNT_MASK 0x0F
|
||||
#define GT911_MAX_POINTS 5
|
||||
|
||||
static i2c_master_dev_handle_t dev = NULL;
|
||||
static lv_indev_t *indev = NULL;
|
||||
|
||||
static esp_err_t gt911_read(uint16_t reg, uint8_t *data, size_t len)
|
||||
{
|
||||
const uint8_t addr[2] = { (uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF) };
|
||||
return i2c_master_transmit_receive(dev, addr, sizeof(addr), data, len,
|
||||
TOUCH_I2C_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
static esp_err_t gt911_write_u8(uint16_t reg, uint8_t value)
|
||||
{
|
||||
const uint8_t buf[3] = { (uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF), value };
|
||||
return i2c_master_transmit(dev, buf, sizeof(buf), TOUCH_I2C_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
/* The status register latches until cleared; leaving it set stops the
|
||||
* controller reporting new coordinates. */
|
||||
static void gt911_clear_status(void)
|
||||
{
|
||||
const esp_err_t err = gt911_write_u8(GT911_REG_STATUS, 0x00);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to clear status register: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
static void touch_read_cb(lv_indev_t *drv, lv_indev_data_t *data)
|
||||
{
|
||||
/* LVGL expects the last coordinate to persist through release, otherwise
|
||||
* the release event lands at (0,0) and cancels the gesture. */
|
||||
static int32_t last_x = 0;
|
||||
static int32_t last_y = 0;
|
||||
static bool was_pressed = false;
|
||||
|
||||
data->point.x = last_x;
|
||||
data->point.y = last_y;
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
|
||||
uint8_t status = 0;
|
||||
if (gt911_read(GT911_REG_STATUS, &status, 1) != ESP_OK ||
|
||||
!(status & GT911_STATUS_READY)) {
|
||||
was_pressed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t points = status & GT911_POINT_COUNT_MASK;
|
||||
if (points == 0 || points > GT911_MAX_POINTS) {
|
||||
was_pressed = false;
|
||||
gt911_clear_status();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Only the first contact is used -- the UI has no multi-touch gestures. */
|
||||
uint8_t buf[4];
|
||||
if (gt911_read(GT911_REG_POINT1_XY, buf, sizeof(buf)) == ESP_OK) {
|
||||
const uint16_t x = (uint16_t)((buf[1] << 8) | buf[0]);
|
||||
const uint16_t y = (uint16_t)((buf[3] << 8) | buf[2]);
|
||||
|
||||
if (x < DISPLAY_H_RES && y < DISPLAY_V_RES) {
|
||||
/* Log only the initial contact: the panel reports ~30 samples a
|
||||
* second for as long as a finger stays down. */
|
||||
if (!was_pressed) {
|
||||
ESP_LOGD(TAG, "touch at (%u, %u)", x, y);
|
||||
}
|
||||
was_pressed = true;
|
||||
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
data->point.x = x;
|
||||
data->point.y = y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "touch out of range: raw (%u, %u)", x, y);
|
||||
}
|
||||
}
|
||||
|
||||
gt911_clear_status();
|
||||
}
|
||||
|
||||
static esp_err_t gt911_try_address(uint8_t addr)
|
||||
{
|
||||
const i2c_device_config_t dev_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = addr,
|
||||
.scl_speed_hz = TOUCH_I2C_HZ,
|
||||
};
|
||||
|
||||
ESP_RETURN_ON_ERROR(i2c_master_bus_add_device(i2c_bus_get(), &dev_cfg, &dev), TAG,
|
||||
"failed to add device 0x%02X", addr);
|
||||
|
||||
uint8_t product_id[4] = {0};
|
||||
const esp_err_t err = gt911_read(GT911_REG_PRODUCT_ID, product_id, sizeof(product_id));
|
||||
if (err != ESP_OK) {
|
||||
i2c_master_bus_rm_device(dev);
|
||||
dev = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "GT911 found at 0x%02X (product ID \"%.4s\")", addr, (char *)product_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t touch_init(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(i2c_bus_get() != NULL, ESP_ERR_INVALID_STATE, TAG,
|
||||
"i2c_bus_init() must run first");
|
||||
|
||||
if (gt911_try_address(GT911_ADDR_LOW) != ESP_OK &&
|
||||
gt911_try_address(GT911_ADDR_HIGH) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "No GT911 at 0x%02X or 0x%02X", GT911_ADDR_LOW, GT911_ADDR_HIGH);
|
||||
i2c_bus_scan(TAG);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
gt911_clear_status();
|
||||
|
||||
indev = lv_indev_create();
|
||||
ESP_RETURN_ON_FALSE(indev != NULL, ESP_FAIL, TAG, "lv_indev_create failed");
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(indev, touch_read_cb);
|
||||
lv_indev_set_display(indev, display_get_default());
|
||||
|
||||
ESP_LOGI(TAG, "Touch registered as LVGL pointer input");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
lv_indev_t *touch_get_indev(void)
|
||||
{
|
||||
return indev;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef TOUCH_GT911_H
|
||||
#define TOUCH_GT911_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
/*
|
||||
* Probes the GT911 and registers it as an LVGL pointer input device.
|
||||
*
|
||||
* Requires i2c_bus_init(), backlight_init() (the STC8H1K28 holds the GT911
|
||||
* in reset until it runs), lv_init() and display_init() to have happened.
|
||||
*/
|
||||
esp_err_t touch_init(void);
|
||||
|
||||
lv_indev_t *touch_get_indev(void);
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user