K1Guitar/device/LCD_ILI9341/Drv_XPT2046_Touch.c

476 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "includes.h"
// 触摸初始化(含引脚配置)
void XPT2046_Init(void)
{
gpio_init_type gpio_init_struct;
/* 必须先用默认值填充结构体,否则 out_type/drive_strength 等字段是随机值 */
gpio_default_para_init(&gpio_init_struct);
/* 使能用到的 GPIO 时钟 */
crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
// 输出引脚:CLK、DIN、CS(推挽输出)
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init_struct.gpio_pins = TP_CLK_PIN | TP_DIN_PIN | TP_CS_PIN;
gpio_init(GPIOC, &gpio_init_struct);
// DOUT:浮空输入(内部上拉易把空闲读成满量程)
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init_struct.gpio_pins = TP_OUT_PIN;
gpio_init(TP_OUT_PORT, &gpio_init_struct);
gpio_init_struct.gpio_pins = TP_IRQ_PIN;
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init(TP_IRQ_PORT, &gpio_init_struct);
// 初始状态
TP_CS_Set();
TP_CLK_Set();
}
// 配置IRQ中断(可选,用于触摸触发)
void XPT2046_IRQ_Init(void)
{
// GPIO_InitTypeDef GPIO_InitStruct = {0};
// EXTI_HandleTypeDef EXTI_HandleStruct = {0};
// // 复用IRQ引脚为外部中断
// GPIO_InitStruct.Pin = TP_IRQ_PIN;
// GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发(触摸时IRQ变低)
// GPIO_InitStruct.Pull = GPIO_PULLUP;
// HAL_GPIO_Init(TP_IRQ_PORT, &GPIO_InitStruct);
// // 配置EXTI中断线(PC12对应EXTI12)
// EXTI_HandleStruct.Line = EXTI_LINE_12;
// EXTI_HandleStruct.Mode = EXTI_MODE_INTERRUPT;
// EXTI_HandleStruct.Pull = EXTI_PULLUP;
// HAL_EXTI_Init(&EXTI_HandleStruct);
// // 使能中断并设置优先级
// HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0); // EXTI12属于EXTI15_10中断线
// HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
//SPI写数据
//向XPT2046写入1字节命令(MCU→DIN输出,XPT2046→DIN输入)
//num:要写入的数据
void TP_Write_Byte(uint8_t data)
{
uint8_t i;
TP_CLK_Clr(); // 时钟拉低,准备发送数据
Delay_us(1);
for (i = 0; i < 8; i++)
{
// MCU通过DIN输出数据位(高位先传),XPT2046通过自身DIN输入接收
if (data & 0x80)
{
TP_DIN_Set(); // DIN输出高
}
else
{
TP_DIN_Clr(); // DIN输出低
}
data <<= 1; // 左移,准备下一位
// 时钟上升沿:XPT2046采样DIN引脚的输入数据(匹配XPT2046时序)
TP_CLK_Set();
Delay_us(1);
TP_CLK_Clr();
Delay_us(1);
}
}
//SPI读数据
//从触摸屏IC读取adc值
//CMD:指令
//返回值:读到的数据
/* 在 CS 已拉低时写命令并读:与量产一致,16 时钟取高 12bit */
static uint16_t TP_Read_AD_CSHeld(uint8_t cmd)
{
uint8_t i;
uint16_t adc_val = 0;
TP_Write_Byte(cmd);
Delay_us(6);
for (i = 0; i < 16; i++)
{
adc_val <<= 1;
TP_CLK_Set();
Delay_us(1);
TP_CLK_Clr();
Delay_us(1);
if (TP_OUT_Read())
adc_val |= 0x01;
}
return (uint16_t)(adc_val >> 4);
}
uint16_t TP_Read_AD(uint8_t cmd)
{
uint16_t v;
TP_CS_Clr();
Delay_us(2);
v = TP_Read_AD_CSHeld(cmd);
TP_CS_Set();
return v;
}
#define TP_ADC_PRESS_MIN 120u
#define TP_ADC_PRESS_MAX 4095u
#define TP_PRESSURE_MIN 80u
/* 本板 IRQ 无效;压力仅作弱参考,不过度拦截(真触摸 z 也常偏大) */
#define TP_PRESSURE_MAX 5000u
static uint16_t TP_Median5(uint16_t *a)
{
uint8_t i, j;
uint16_t t;
for (i = 0; i < 4; i++) {
for (j = (uint8_t)(i + 1U); j < 5U; j++) {
if (a[j] < a[i]) {
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
return a[2];
}
static void TP_SampleXY(uint16_t *out_x, uint16_t *out_y)
{
uint16_t xs[5], ys[5];
uint8_t i;
for (i = 0; i < 5; i++) {
xs[i] = TP_Read_AD(XPT2046_READ_X);
Delay_us(15);
ys[i] = TP_Read_AD(XPT2046_READ_Y);
Delay_us(15);
}
*out_x = TP_Median5(xs);
*out_y = TP_Median5(ys);
}
static uint16_t TP_ReadPressure(void)
{
uint16_t z1, z2;
z1 = TP_Read_AD(XPT2046_READ_Z1);
Delay_us(15);
z2 = TP_Read_AD(XPT2046_READ_Z2);
return (z1 > z2) ? (uint16_t)(z1 - z2) : 0U;
}
static uint8_t TP_AdcCoordValid(uint16_t x, uint16_t y)
{
/* 仅排除明显无效;双高在本板上也可能是真触摸(Y 常贴高,垂直靠 raw.x) */
if (x < TP_ADC_PRESS_MIN && y < TP_ADC_PRESS_MIN)
return 0U;
if (x > 4000u && y > 4000u)
return 0U;
return (x <= TP_ADC_PRESS_MAX && y <= TP_ADC_PRESS_MAX) ? 1U : 0U;
}
#define CALIB_X_MIN 200
#define CALIB_X_MAX 3800
#define CALIB_Y_MIN 200
#define CALIB_Y_MAX 3950
/*
* 竖屏线性映射:raw.x→屏幕X,raw.y→屏幕Y。
* 本机 X 轴左右对调(点右触发左),开 TP_MIRROR_X。
* 若 TP_MIRROR_Y=1 会导致 Y 轴反转(点「专业」误中「万能」)。
* DOUT(PA15) 浮空输入;空闲 raw.y 噪声禁止走 ADC 回退。
*/
#define TP_SWAP_XY 0
#define TP_MIRROR_X 1
#define TP_MIRROR_Y 0
static uint16_t TP_MapAxis(uint16_t adc, uint16_t adc_min, uint16_t adc_max, uint16_t px_max)
{
int32_t v = (int32_t)adc;
int32_t a0 = (int32_t)adc_min;
int32_t a1 = (int32_t)adc_max;
int32_t span = a1 - a0;
int32_t px;
if (span <= 0)
return 0;
if (v < a0)
v = a0;
if (v > a1)
v = a1;
px = (v - a0) * (int32_t)px_max / span;
if (px < 0)
px = 0;
if (px > (int32_t)px_max)
px = (int32_t)px_max;
return (uint16_t)px;
}
static void TP_RawToScreen(uint16_t raw_x, uint16_t raw_y, uint16_t *sx, uint16_t *sy)
{
uint16_t ax, ay;
#if TP_SWAP_XY
ax = TP_MapAxis(raw_y, CALIB_Y_MIN, CALIB_Y_MAX, (uint16_t)(LCD_W - 1));
ay = TP_MapAxis(raw_x, CALIB_X_MIN, CALIB_X_MAX, (uint16_t)(LCD_H - 1));
#else
ax = TP_MapAxis(raw_x, CALIB_X_MIN, CALIB_X_MAX, (uint16_t)(LCD_W - 1));
ay = TP_MapAxis(raw_y, CALIB_Y_MIN, CALIB_Y_MAX, (uint16_t)(LCD_H - 1));
#endif
#if TP_MIRROR_X
ax = (uint16_t)(LCD_W - 1 - ax);
#endif
#if TP_MIRROR_Y
ay = (uint16_t)(LCD_H - 1 - ay);
#endif
*sx = ax;
*sy = ay;
}
void TP_LogStatus(void)
{
uint16_t x, y, z;
uint16_t sx, sy;
z = TP_ReadPressure();
TP_SampleXY(&x, &y);
TP_RawToScreen(x, y, &sx, &sy);
LOG_TPI("status irq=%u z=%u raw=%u,%u cal=%u,%u",
(unsigned)(TP_IRQ_Read() == 0U),
(unsigned)z,
(unsigned)x, (unsigned)y,
(unsigned)sx, (unsigned)sy);
}
static uint8_t s_tp_live;
void TP_SetLiveMode(uint8_t on)
{
s_tp_live = on ? 1U : 0U;
LOG_TPI("live %s (events only)", s_tp_live ? "on" : "off");
}
/* 不再空闲刷屏;按下事件仍由 touch task 的 scan/press/release 打印 */
void TP_LivePoll(void)
{
(void)s_tp_live;
}
uint8_t TP_CheckPressed(uint8_t n)
{
uint8_t count = 0;
uint8_t i;
for (i = 0; i < n; i++)
{
if (TP_IRQ_Read() == 0)
count++;
Delay_us(100);
}
return (count >= ((n + 1U) / 2U)) ? 1U : 0U;
}
TP_Point TP_ReadRaw(void)
{
TP_Point p = {0, 0, 0};
uint8_t irq_pressed;
static uint8_t s_raw_logged;
uint16_t rx, ry;
irq_pressed = TP_CheckPressed(3);
if (!irq_pressed) {
/*
* 空闲时 ADC 常读成 y=4095,会误触发幽灵点击。
* 本板真触摸 IRQ 能拉低,只认 IRQ 路径。
*/
s_raw_logged = 0U;
return p;
}
TP_SampleXY(&rx, &ry);
if (!TP_AdcCoordValid(rx, ry)) {
s_raw_logged = 0U;
return p;
}
p.x = rx;
p.y = ry;
p.pressed = 1;
if (!s_raw_logged) {
LOG_TPI("raw x=%u y=%u irq=1", (unsigned)p.x, (unsigned)p.y);
s_raw_logged = 1U;
}
return p;
}
TP_Point TP_Read(void)
{
TP_Point raw = TP_ReadRaw();
TP_Point calib = {0, 0, raw.pressed};
static uint8_t s_cal_logged;
if (!raw.pressed) {
s_cal_logged = 0U;
return calib;
}
TP_RawToScreen(raw.x, raw.y, &calib.x, &calib.y);
if (!s_cal_logged) {
LOG_TPI("cal x=%u y=%u (raw %u,%u)", (unsigned)calib.x, (unsigned)calib.y,
(unsigned)raw.x, (unsigned)raw.y);
s_cal_logged = 1U;
}
return calib;
}
// EXTI15_10中断服务函数(处理TP_IRQ触发)
//void EXTI15_10_IRQHandler(void)
//{
// HAL_EXTI_IRQHandler(&EXTI_HandleStruct); // 调用HAL库中断处理
//}
// 中断回调函数(触摸触发时执行)
//void HAL_EXTI_Callback(EXTI_HandleTypeDef *hexti)
//{
// if (hexti->Line == EXTI_LINE_12)
// {
// TP_Point p = TP_Read(); // 读取触摸坐标
// if (p.pressed)
// {
// // 处理触摸事件(如绘图、按键响应等)
// // LCD_DrawPixel(p.x, p.y, 0xF800); // 示例:画红点
// }
// }
//}
//////////////////////////////////////////////////////////////////////////////////
//与LCD部分有关的函数
//画一个触摸点
//用来校准用的
//x,y:坐标
//color:颜色
void TP_Drow_Touch_Point(uint16_t x, uint16_t y, uint16_t color)
{
LCD_DrawLine(x - 12, y, x + 13, y, color);//横线
LCD_DrawLine(x, y - 12, x, y + 13, color);//竖线
LCD_DrawPoint(x + 1, y + 1, color);
LCD_DrawPoint(x - 1, y + 1, color);
LCD_DrawPoint(x + 1, y - 1, color);
LCD_DrawPoint(x - 1, y - 1, color);
LCD_DrawCircle(x, y, 6, color);//画中心圈
}
//画一个大点(2*2的点)
//x,y:坐标
//color:颜色
void TP_Draw_Big_Point(uint16_t x, uint16_t y, uint16_t color)
{
LCD_DrawPoint(x, y, color);//中心点
LCD_DrawPoint(x + 1, y, color);
LCD_DrawPoint(x, y + 1, color);
LCD_DrawPoint(x + 1, y + 1, color);
}
/******************************************************************************
函数说明:画线
入口数据:x1,y1 起始坐标
x2,y2 终止坐标
color 线的颜色
返回值: 无
******************************************************************************/
void LCD_DrawRoughLine(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2,uint16_t color)
{
uint16_t t;
int xerr = 0, yerr = 0, delta_x, delta_y, distance;
int incx, incy, uRow, uCol;
delta_x = x2 - x1; //计算坐标增量
delta_y = y2 - y1;
uRow = x1;//画线起点坐标
uCol = y1;
if(delta_x > 0)
{
incx = 1; //设置单步方向
}
else if (delta_x == 0)
{
incx = 0;//垂直线
}
else
{
incx = -1;
delta_x = -delta_x;
}
if(delta_y > 0)
{
incy = 1;
}
else if (delta_y == 0)
{
incy = 0;//水平线
}
else
{
incy = -1;
delta_y = -delta_y;
}
if(delta_x > delta_y)
{
distance = delta_x; //选取基本增量坐标轴
}
else
{
distance = delta_y;
}
for(t=0;t<distance+1;t++)
{
TP_Draw_Big_Point(uRow, uCol, color);//画点
xerr += delta_x;
yerr += delta_y;
if(xerr > distance)
{
xerr -= distance;
uRow += incx;
}
if(yerr > distance)
{
yerr -= distance;
uCol += incy;
}
}
}
// 辅助函数:格式化坐标字符串(x,y转字符串)
void TP_FormatCoord(char *buf, TP_Point p)
{
sprintf(buf, "X:%4d Y:%4d", p.x, p.y); // 格式化为"X:xxxx Y:yyyy"
}
/* ------------------------END----------------------- */