#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 /* * 实测(IRQ 有效按下):raw.x 常贴高,上下主要改 raw.y → 不要 SWAP。 * Y 镜像:上方「万能」对应更大 raw.y。 * 空闲 ADC 噪声是 raw.y=4095,禁止走 ADC 回退,否则幽灵点击。 */ #define TP_SWAP_XY 0 #define TP_MIRROR_X 0 #define TP_MIRROR_Y 1 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) { 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----------------------- */