Files

316 lines
7.8 KiB
C

#include "touch.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_rom_sys.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ili9341.h"
#define SCREEN_W 320
#define SCREEN_H 240
#define PREV_W 240
#define PREV_H 320
#define TOUCH_PIN_CLK GPIO_NUM_25
#define TOUCH_PIN_MOSI GPIO_NUM_32
#define TOUCH_PIN_CS GPIO_NUM_33
#define TOUCH_PIN_MISO GPIO_NUM_39
#define TOUCH_PIN_IRQ GPIO_NUM_36
#define TOUCH_SAMPLES 7
static const char *TAG = "touch";
static touch_calib_t s_cal = {
.x_min = 281,
.x_max = 1781,
.y_min = 162,
.y_max = 1725,
.invert_x = false,
.invert_y = true,
};
static inline void touch_delay_us(int us)
{
esp_rom_delay_us(us);
}
static inline void touch_clk_pulse(void)
{
gpio_set_level(TOUCH_PIN_CLK, 1);
touch_delay_us(1);
gpio_set_level(TOUCH_PIN_CLK, 0);
touch_delay_us(1);
}
static uint16_t xpt2046_transfer_12b(uint8_t cmd)
{
uint16_t value = 0;
gpio_set_level(TOUCH_PIN_CS, 0);
touch_delay_us(2);
for (int i = 7; i >= 0; --i) {
gpio_set_level(TOUCH_PIN_MOSI, (cmd >> i) & 0x1);
touch_clk_pulse();
}
for (int i = 0; i < 16; ++i) {
gpio_set_level(TOUCH_PIN_CLK, 1);
touch_delay_us(1);
value = (uint16_t)((value << 1) | gpio_get_level(TOUCH_PIN_MISO));
gpio_set_level(TOUCH_PIN_CLK, 0);
touch_delay_us(1);
}
gpio_set_level(TOUCH_PIN_CS, 1);
touch_delay_us(2);
return (uint16_t)(value >> 4);
}
static int map_clamped(int v, int in_min, int in_max, int out_min, int out_max)
{
if (v < in_min) {
v = in_min;
}
if (v > in_max) {
v = in_max;
}
int den = (in_max - in_min);
if (den == 0) {
return out_min;
}
int num = (v - in_min) * (out_max - out_min);
return out_min + (num / den);
}
static void draw_crosshair(int x, int y, uint16_t color)
{
const int arm = 10;
for (int dx = -arm; dx <= arm; ++dx) {
int px = x + dx;
if (px >= 0 && px < SCREEN_W && y >= 0 && y < SCREEN_H) {
(void)ili9341_draw_pixel((uint16_t)px, (uint16_t)y, color);
}
}
for (int dy = -arm; dy <= arm; ++dy) {
int py = y + dy;
if (x >= 0 && x < SCREEN_W && py >= 0 && py < SCREEN_H) {
(void)ili9341_draw_pixel((uint16_t)x, (uint16_t)py, color);
}
}
}
static bool wait_for_stable_touch(uint16_t *out_x, uint16_t *out_y)
{
uint32_t sx = 0;
uint32_t sy = 0;
int count = 0;
int idle_loops = 0;
while (count < 20) {
uint16_t rx = 0;
uint16_t ry = 0;
uint16_t rz = 0;
if (touch_read_raw(&rx, &ry, &rz)) {
sx += rx;
sy += ry;
++count;
idle_loops = 0;
} else {
++idle_loops;
if (count > 0 && idle_loops > 25) {
sx = 0;
sy = 0;
count = 0;
}
}
vTaskDelay(pdMS_TO_TICKS(8));
}
*out_x = (uint16_t)(sx / count);
*out_y = (uint16_t)(sy / count);
return true;
}
esp_err_t touch_init(void)
{
gpio_config_t out_cfg = {
.pin_bit_mask = (1ULL << TOUCH_PIN_CLK) | (1ULL << TOUCH_PIN_MOSI) | (1ULL << TOUCH_PIN_CS),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&out_cfg));
gpio_config_t in_cfg = {
.pin_bit_mask = (1ULL << TOUCH_PIN_MISO) | (1ULL << TOUCH_PIN_IRQ),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&in_cfg));
gpio_set_level(TOUCH_PIN_CS, 1);
gpio_set_level(TOUCH_PIN_CLK, 0);
gpio_set_level(TOUCH_PIN_MOSI, 0);
ESP_LOGI(TAG,
"touch ready x=[%d,%d] y=[%d,%d] invert=(%d,%d)",
s_cal.x_min,
s_cal.x_max,
s_cal.y_min,
s_cal.y_max,
s_cal.invert_x,
s_cal.invert_y);
return ESP_OK;
}
bool touch_read_raw(uint16_t *x, uint16_t *y, uint16_t *z)
{
if (x == NULL || y == NULL || z == NULL) {
return false;
}
if (gpio_get_level(TOUCH_PIN_IRQ) != 0) {
return false;
}
uint32_t sx = 0;
uint32_t sy = 0;
uint32_t sz = 0;
int valid = 0;
for (int i = 0; i < TOUCH_SAMPLES; ++i) {
uint16_t z1 = xpt2046_transfer_12b(0xB0);
uint16_t z2 = xpt2046_transfer_12b(0xC0);
uint16_t pressure = (z1 > 0 && z2 > z1) ? (uint16_t)(z1 + (4095 - z2)) : 0;
if (pressure < 80) {
continue;
}
sx += xpt2046_transfer_12b(0xD0);
sy += xpt2046_transfer_12b(0x90);
sz += pressure;
++valid;
}
if (valid == 0) {
return false;
}
*x = (uint16_t)(sx / valid);
*y = (uint16_t)(sy / valid);
*z = (uint16_t)(sz / valid);
return true;
}
bool touch_read_screen(int *x, int *y, uint16_t *z)
{
if (x == NULL || y == NULL || z == NULL) {
return false;
}
uint16_t raw_x = 0;
uint16_t raw_y = 0;
uint16_t raw_z = 0;
if (!touch_read_raw(&raw_x, &raw_y, &raw_z)) {
return false;
}
int old_x = map_clamped((int)raw_x,
s_cal.x_min,
s_cal.x_max,
s_cal.invert_x ? (PREV_W - 1) : 0,
s_cal.invert_x ? 0 : (PREV_W - 1));
int old_y = map_clamped((int)raw_y,
s_cal.y_min,
s_cal.y_max,
s_cal.invert_y ? (PREV_H - 1) : 0,
s_cal.invert_y ? 0 : (PREV_H - 1));
/* Keep 320x240 landscape, rotated 180° from prior orientation. */
*x = (PREV_H - 1) - old_y;
*y = old_x;
*z = raw_z;
return true;
}
esp_err_t touch_calibrate(void)
{
const int margin = 18;
const int tx[4] = {margin, SCREEN_W - margin, SCREEN_W - margin, margin};
const int ty[4] = {margin, margin, SCREEN_H - margin, SCREEN_H - margin};
const char *name[4] = {"TOP-LEFT", "TOP-RIGHT", "BOTTOM-RIGHT", "BOTTOM-LEFT"};
uint16_t raw_x[4] = {0};
uint16_t raw_y[4] = {0};
ili9341_fill_screen(ILI9341_COLOR_BLACK);
ili9341_draw_text(6, 6, "TOUCH CAL", ILI9341_COLOR_WHITE, ILI9341_COLOR_BLACK);
ESP_LOGI(TAG, "Calibration start");
for (int i = 0; i < 4; ++i) {
ili9341_fill_screen(ILI9341_COLOR_BLACK);
draw_crosshair(tx[i], ty[i], ILI9341_COLOR_GREEN);
ESP_LOGI(TAG, "Tap and hold %s", name[i]);
wait_for_stable_touch(&raw_x[i], &raw_y[i]);
draw_crosshair(tx[i], ty[i], ILI9341_COLOR_BLUE);
vTaskDelay(pdMS_TO_TICKS(350));
}
int x_left = ((int)raw_x[0] + (int)raw_x[3]) / 2;
int x_right = ((int)raw_x[1] + (int)raw_x[2]) / 2;
int y_top = ((int)raw_y[0] + (int)raw_y[1]) / 2;
int y_bottom = ((int)raw_y[2] + (int)raw_y[3]) / 2;
s_cal.x_min = (x_left < x_right) ? x_left : x_right;
s_cal.x_max = (x_left > x_right) ? x_left : x_right;
s_cal.y_min = (y_top < y_bottom) ? y_top : y_bottom;
s_cal.y_max = (y_top > y_bottom) ? y_top : y_bottom;
s_cal.invert_x = !(x_right > x_left);
s_cal.invert_y = !(y_bottom > y_top);
ESP_LOGI(TAG,
"Calibration done: x=[%d,%d] y=[%d,%d] invert=(%d,%d)",
s_cal.x_min,
s_cal.x_max,
s_cal.y_min,
s_cal.y_max,
s_cal.invert_x,
s_cal.invert_y);
return ESP_OK;
}
const touch_calib_t *touch_get_calibration(void)
{
return &s_cal;
}
esp_err_t touch_set_calibration(const touch_calib_t *calib)
{
if (calib == NULL) {
return ESP_ERR_INVALID_ARG;
}
s_cal = *calib;
return ESP_OK;
}