K1Guitar/device/LCD_ILI9341/Drv_XPT2046_Touch.c

476 lines
11 KiB
C
Raw Permalink 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→屏幕Xraw.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----------------------- */