361 lines
11 KiB
C
361 lines
11 KiB
C
#include "board.h"
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "driver/i2c.h"
|
|
#include "driver/ledc.h"
|
|
#include "esp_check.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_lcd_panel_ops.h"
|
|
#include "esp_lcd_panel_rgb.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "lvgl.h"
|
|
|
|
#include "radick_config.h"
|
|
|
|
#define BOARD_I2C_PORT I2C_NUM_0
|
|
#define BOARD_I2C_FREQ_HZ 400000
|
|
#define BOARD_I2C_TIMEOUT_MS 30
|
|
#define GT911_READ_TIMEOUT_MS 5
|
|
#define GT911_REG_PRODUCT_ID 0x8140
|
|
#define GT911_REG_POINT_STATUS 0x814E
|
|
#define GT911_REG_FIRST_POINT 0x814F
|
|
#define LVGL_DRAW_LINES 48
|
|
#define LVGL_TICK_MS 2
|
|
|
|
static const char *TAG = "board";
|
|
|
|
static esp_lcd_panel_handle_t s_panel;
|
|
static lv_disp_draw_buf_t s_draw_buf;
|
|
static lv_disp_drv_t s_display_driver;
|
|
static lv_indev_drv_t s_input_driver;
|
|
static lv_color_t *s_buffer_a;
|
|
static lv_color_t *s_buffer_b;
|
|
static esp_timer_handle_t s_lvgl_tick_timer;
|
|
|
|
static bool s_touch_available;
|
|
static uint8_t s_touch_address;
|
|
static bool s_touch_pressed;
|
|
static uint16_t s_touch_x;
|
|
static uint16_t s_touch_y;
|
|
static int64_t s_touch_sample_us;
|
|
static volatile uint32_t s_last_touch_ms;
|
|
static uint8_t s_brightness;
|
|
|
|
static esp_err_t i2c_write(uint8_t address, const uint8_t *data, size_t length)
|
|
{
|
|
return i2c_master_write_to_device(BOARD_I2C_PORT, address, data, length,
|
|
pdMS_TO_TICKS(BOARD_I2C_TIMEOUT_MS));
|
|
}
|
|
|
|
static esp_err_t pca9557_write(uint8_t reg, uint8_t value)
|
|
{
|
|
const uint8_t payload[2] = {reg, value};
|
|
return i2c_write(RADICK_PCA9557_ADDRESS, payload, sizeof(payload));
|
|
}
|
|
|
|
static esp_err_t gt911_read(uint8_t address, uint16_t reg, uint8_t *data, size_t length)
|
|
{
|
|
const uint8_t command[2] = {(uint8_t)(reg >> 8), (uint8_t)reg};
|
|
return i2c_master_write_read_device(BOARD_I2C_PORT, address,
|
|
command, sizeof(command), data, length,
|
|
pdMS_TO_TICKS(GT911_READ_TIMEOUT_MS));
|
|
}
|
|
|
|
static esp_err_t gt911_write_byte(uint8_t address, uint16_t reg, uint8_t value)
|
|
{
|
|
const uint8_t payload[3] = {(uint8_t)(reg >> 8), (uint8_t)reg, value};
|
|
return i2c_write(address, payload, sizeof(payload));
|
|
}
|
|
|
|
static esp_err_t init_i2c(void)
|
|
{
|
|
const i2c_config_t config = {
|
|
.mode = I2C_MODE_MASTER,
|
|
.sda_io_num = RADICK_TOUCH_SDA_GPIO,
|
|
.scl_io_num = RADICK_TOUCH_SCL_GPIO,
|
|
.sda_pullup_en = GPIO_PULLUP_DISABLE,
|
|
.scl_pullup_en = GPIO_PULLUP_DISABLE,
|
|
.master.clk_speed = BOARD_I2C_FREQ_HZ,
|
|
.clk_flags = 0,
|
|
};
|
|
|
|
ESP_RETURN_ON_ERROR(i2c_param_config(BOARD_I2C_PORT, &config), TAG,
|
|
"I2C configuration failed");
|
|
return i2c_driver_install(BOARD_I2C_PORT, config.mode, 0, 0, 0);
|
|
}
|
|
|
|
static esp_err_t reset_v3_touch(void)
|
|
{
|
|
// Vendor-required V3 sequence. IO0 is GT911 RESET and IO1 is INT.
|
|
ESP_RETURN_ON_ERROR(pca9557_write(0x02, 0x00), TAG,
|
|
"PCA9557 polarity reset failed");
|
|
ESP_RETURN_ON_ERROR(pca9557_write(0x01, 0xFC), TAG,
|
|
"PCA9557 output setup failed");
|
|
ESP_RETURN_ON_ERROR(pca9557_write(0x03, 0xFC), TAG,
|
|
"PCA9557 pin setup failed");
|
|
vTaskDelay(pdMS_TO_TICKS(20));
|
|
|
|
ESP_RETURN_ON_ERROR(pca9557_write(0x01, 0xFD), TAG,
|
|
"GT911 reset release failed");
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
|
|
// Leave RESET driven high and release INT to an input.
|
|
return pca9557_write(0x03, 0xFE);
|
|
}
|
|
|
|
static bool find_gt911(void)
|
|
{
|
|
const uint8_t addresses[] = {RADICK_GT911_ADDRESS, RADICK_GT911_ALT_ADDRESS};
|
|
uint8_t product_id[4];
|
|
|
|
for (size_t i = 0; i < sizeof(addresses); ++i) {
|
|
if (gt911_read(addresses[i], GT911_REG_PRODUCT_ID,
|
|
product_id, sizeof(product_id)) == ESP_OK) {
|
|
s_touch_address = addresses[i];
|
|
ESP_LOGI(TAG, "GT911 at 0x%02x, product %.4s", s_touch_address,
|
|
(const char *)product_id);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool poll_touch(uint16_t *x, uint16_t *y)
|
|
{
|
|
if (!s_touch_available) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t status = 0;
|
|
const int64_t now_us = esp_timer_get_time();
|
|
if (gt911_read(s_touch_address, GT911_REG_POINT_STATUS, &status, 1) != ESP_OK) {
|
|
if ((now_us - s_touch_sample_us) > 100000) {
|
|
s_touch_pressed = false;
|
|
}
|
|
return s_touch_pressed;
|
|
}
|
|
|
|
if ((status & 0x80U) != 0U) {
|
|
const uint8_t count = status & 0x0FU;
|
|
if (count > 0U && count <= 5U) {
|
|
uint8_t point[8];
|
|
if (gt911_read(s_touch_address, GT911_REG_FIRST_POINT,
|
|
point, sizeof(point)) == ESP_OK) {
|
|
// The vendor's ROTATION_NORMAL + map() path ultimately yields
|
|
// these native landscape coordinates unchanged.
|
|
s_touch_x = (uint16_t)point[1] | ((uint16_t)point[2] << 8);
|
|
s_touch_y = (uint16_t)point[3] | ((uint16_t)point[4] << 8);
|
|
if (s_touch_x >= RADICK_LCD_H_RES) {
|
|
s_touch_x = RADICK_LCD_H_RES - 1;
|
|
}
|
|
if (s_touch_y >= RADICK_LCD_V_RES) {
|
|
s_touch_y = RADICK_LCD_V_RES - 1;
|
|
}
|
|
s_touch_pressed = true;
|
|
s_touch_sample_us = now_us;
|
|
s_last_touch_ms = (uint32_t)(now_us / 1000);
|
|
}
|
|
} else {
|
|
s_touch_pressed = false;
|
|
s_touch_sample_us = now_us;
|
|
}
|
|
(void)gt911_write_byte(s_touch_address, GT911_REG_POINT_STATUS, 0);
|
|
} else if ((now_us - s_touch_sample_us) > 100000) {
|
|
s_touch_pressed = false;
|
|
}
|
|
|
|
*x = s_touch_x;
|
|
*y = s_touch_y;
|
|
return s_touch_pressed;
|
|
}
|
|
|
|
static void lvgl_touch_read(lv_indev_drv_t *driver, lv_indev_data_t *data)
|
|
{
|
|
(void)driver;
|
|
uint16_t x = 0;
|
|
uint16_t y = 0;
|
|
const bool pressed = poll_touch(&x, &y);
|
|
data->state = pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
|
|
if (pressed) {
|
|
data->point.x = x;
|
|
data->point.y = y;
|
|
}
|
|
}
|
|
|
|
static void lvgl_flush(lv_disp_drv_t *driver, const lv_area_t *area,
|
|
lv_color_t *pixels)
|
|
{
|
|
(void)driver;
|
|
const esp_err_t err = esp_lcd_panel_draw_bitmap(s_panel,
|
|
area->x1, area->y1,
|
|
area->x2 + 1, area->y2 + 1,
|
|
pixels);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "LCD flush failed: %s", esp_err_to_name(err));
|
|
}
|
|
lv_disp_flush_ready(driver);
|
|
}
|
|
|
|
static void lvgl_tick(void *context)
|
|
{
|
|
(void)context;
|
|
lv_tick_inc(LVGL_TICK_MS);
|
|
}
|
|
|
|
static esp_err_t init_backlight(void)
|
|
{
|
|
const ledc_timer_config_t timer = {
|
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
|
.duty_resolution = LEDC_TIMER_10_BIT,
|
|
.timer_num = LEDC_TIMER_0,
|
|
.freq_hz = 5000,
|
|
.clk_cfg = LEDC_AUTO_CLK,
|
|
};
|
|
const ledc_channel_config_t channel = {
|
|
.gpio_num = RADICK_LCD_BACKLIGHT_GPIO,
|
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
|
.channel = LEDC_CHANNEL_0,
|
|
.intr_type = LEDC_INTR_DISABLE,
|
|
.timer_sel = LEDC_TIMER_0,
|
|
.duty = 0,
|
|
.hpoint = 0,
|
|
};
|
|
|
|
ESP_RETURN_ON_ERROR(ledc_timer_config(&timer), TAG,
|
|
"backlight timer init failed");
|
|
return ledc_channel_config(&channel);
|
|
}
|
|
|
|
static esp_err_t init_rgb_panel(void)
|
|
{
|
|
const esp_lcd_rgb_panel_config_t panel_config = {
|
|
.clk_src = LCD_CLK_SRC_DEFAULT,
|
|
.timings = {
|
|
.pclk_hz = 15000000,
|
|
.h_res = RADICK_LCD_H_RES,
|
|
.v_res = RADICK_LCD_V_RES,
|
|
.hsync_pulse_width = 48,
|
|
.hsync_back_porch = 40,
|
|
.hsync_front_porch = 40,
|
|
.vsync_pulse_width = 31,
|
|
.vsync_back_porch = 13,
|
|
.vsync_front_porch = 1,
|
|
.flags = {
|
|
.pclk_active_neg = true,
|
|
},
|
|
},
|
|
.data_width = 16,
|
|
.bits_per_pixel = 16,
|
|
.num_fbs = 1,
|
|
.bounce_buffer_size_px = RADICK_LCD_H_RES * 10,
|
|
.dma_burst_size = 64,
|
|
.hsync_gpio_num = RADICK_LCD_HSYNC_GPIO,
|
|
.vsync_gpio_num = RADICK_LCD_VSYNC_GPIO,
|
|
.de_gpio_num = RADICK_LCD_DE_GPIO,
|
|
.pclk_gpio_num = RADICK_LCD_PCLK_GPIO,
|
|
.disp_gpio_num = GPIO_NUM_NC,
|
|
.data_gpio_nums = {
|
|
GPIO_NUM_15, GPIO_NUM_7, GPIO_NUM_6, GPIO_NUM_5, GPIO_NUM_4,
|
|
GPIO_NUM_9, GPIO_NUM_46, GPIO_NUM_3, GPIO_NUM_8, GPIO_NUM_16,
|
|
GPIO_NUM_1, GPIO_NUM_14, GPIO_NUM_21, GPIO_NUM_47, GPIO_NUM_48,
|
|
GPIO_NUM_45,
|
|
},
|
|
.flags = {
|
|
.fb_in_psram = true,
|
|
},
|
|
};
|
|
|
|
ESP_RETURN_ON_ERROR(esp_lcd_new_rgb_panel(&panel_config, &s_panel), TAG,
|
|
"RGB panel allocation failed");
|
|
ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel), TAG,
|
|
"RGB panel reset failed");
|
|
return esp_lcd_panel_init(s_panel);
|
|
}
|
|
|
|
static esp_err_t init_lvgl(void)
|
|
{
|
|
lv_init();
|
|
|
|
const size_t pixels = RADICK_LCD_H_RES * LVGL_DRAW_LINES;
|
|
const size_t bytes = pixels * sizeof(lv_color_t);
|
|
s_buffer_a = heap_caps_malloc(bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
s_buffer_b = heap_caps_malloc(bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (s_buffer_a == NULL || s_buffer_b == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
lv_disp_draw_buf_init(&s_draw_buf, s_buffer_a, s_buffer_b, pixels);
|
|
lv_disp_drv_init(&s_display_driver);
|
|
s_display_driver.hor_res = RADICK_LCD_H_RES;
|
|
s_display_driver.ver_res = RADICK_LCD_V_RES;
|
|
s_display_driver.flush_cb = lvgl_flush;
|
|
s_display_driver.draw_buf = &s_draw_buf;
|
|
lv_disp_drv_register(&s_display_driver);
|
|
|
|
lv_indev_drv_init(&s_input_driver);
|
|
s_input_driver.type = LV_INDEV_TYPE_POINTER;
|
|
s_input_driver.read_cb = lvgl_touch_read;
|
|
lv_indev_drv_register(&s_input_driver);
|
|
|
|
const esp_timer_create_args_t timer_args = {
|
|
.callback = lvgl_tick,
|
|
.name = "lvgl_tick",
|
|
};
|
|
ESP_RETURN_ON_ERROR(esp_timer_create(&timer_args, &s_lvgl_tick_timer), TAG,
|
|
"LVGL tick timer create failed");
|
|
return esp_timer_start_periodic(s_lvgl_tick_timer, LVGL_TICK_MS * 1000);
|
|
}
|
|
|
|
esp_err_t board_init(void)
|
|
{
|
|
ESP_RETURN_ON_ERROR(init_backlight(), TAG, "backlight init failed");
|
|
ESP_RETURN_ON_ERROR(init_i2c(), TAG, "touch I2C init failed");
|
|
|
|
const esp_err_t reset_result = reset_v3_touch();
|
|
if (reset_result == ESP_OK) {
|
|
s_touch_available = find_gt911();
|
|
} else {
|
|
ESP_LOGW(TAG, "V3 touch reset failed: %s", esp_err_to_name(reset_result));
|
|
}
|
|
if (!s_touch_available) {
|
|
ESP_LOGW(TAG, "GT911 not found; display will remain usable without touch");
|
|
}
|
|
|
|
ESP_RETURN_ON_ERROR(init_rgb_panel(), TAG, "LCD init failed");
|
|
ESP_RETURN_ON_ERROR(init_lvgl(), TAG, "LVGL init failed");
|
|
return ESP_OK;
|
|
}
|
|
|
|
void board_set_brightness(uint8_t percent)
|
|
{
|
|
if (percent > 100U) {
|
|
percent = 100U;
|
|
}
|
|
const uint32_t max_duty = (1U << LEDC_TIMER_10_BIT) - 1U;
|
|
const uint32_t duty = (max_duty * percent) / 100U;
|
|
if (ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty) == ESP_OK) {
|
|
(void)ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
|
|
s_brightness = percent;
|
|
}
|
|
}
|
|
|
|
uint8_t board_get_brightness(void)
|
|
{
|
|
return s_brightness;
|
|
}
|
|
|
|
bool board_touch_available(void)
|
|
{
|
|
return s_touch_available;
|
|
}
|
|
|
|
uint32_t board_last_touch_ms(void)
|
|
{
|
|
return s_last_touch_ms;
|
|
}
|