Files
Radick/main/board.c
dami b473ed4369
Some checks failed
build / host-tests (push) Has been cancelled
build / esp-idf (push) Has been cancelled
Implement native ESP-IDF radar firmware
2026-07-21 07:47:02 +00:00

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;
}