/*
 * touch.c
 *
 *  Created on: 2021年8月7日
 *      Author: wuwenfeng
 */

#include "touch.h"
#include "LCD.h"
#include "eeprom.h"

//默认为touchtype=0的数据.
#define CMD_RDX 0X90
#define CMD_RDY 0XD0


//SPI写数据
//向触摸屏IC写入1byte数据
//num:要写入的数据
void TP_Write_Byte(char num)
{
	for(uint8_t count=0;count<8;count++)
	{
		if(num&0x80){TDIN(1);}
		else {TDIN(0);}
		num<<=1;
		TCLK(0);
		TCLK(1);		//上升沿有效
	}
}

//SPI读数据
//从触摸屏IC读取adc值
//CMD:指令
//返回值:读到的数据
uint16_t TP_Read_AD(char CMD)
{
	uint16_t Num=0;
	TCLK(0);		//先拉低时钟
	TDIN(0); 	//拉低数据线
	TCS(0); 		//选中触摸屏IC
	TP_Write_Byte(CMD);//发送命令字
	HAL_GetTick();     //稍微延时，ad转换需要时间
	HAL_GetTick();
	HAL_GetTick();
	HAL_GetTick();
	HAL_GetTick();
	HAL_GetTick();
	TCLK(1);		//给1个时钟，清除BUSY
	TCLK(0);
	for(uint8_t count=0;count<16;count++)//读出16位数据,只有高12位有效
	{
		Num<<=1;
		TCLK(0);	//下降沿有效
		TCLK(1);;
		if(TDOUT){Num++;}
	}
	Num>>=4;   	//只有高12位有效.
	TCS(1);		//释放片选
	return(Num);
}

//读取一个坐标值(x或者y)
//连续读取READ_TIMES次数据,对这些数据升序排列,
//然后去掉最低和最高LOST_VAL个数,取平均值
//xy:指令（CMD_RDX/CMD_RDY）
//返回值:读到的数据
#define READ_TIMES 5 	//读取次数
#define LOST_VAL 1	  	//丢弃值
uint16_t TP_Read_XOY(uint8_t xy)
{
	uint16_t i, j;
	uint16_t buf[READ_TIMES];
	uint16_t sum=0;
	uint16_t temp;
	for(i=0;i<READ_TIMES;i++)buf[i]=TP_Read_AD(xy);
	for(i=0;i<READ_TIMES-1; i++)//排序
	{
		for(j=i+1;j<READ_TIMES;j++)
		{
			if(buf[i]>buf[j])//升序排列
			{
				temp=buf[i];
				buf[i]=buf[j];
				buf[j]=temp;
			}
		}
	}
	sum=0;
	for(i=LOST_VAL;i<READ_TIMES-LOST_VAL;i++)sum+=buf[i];
	temp=sum/(READ_TIMES-2*LOST_VAL);
	return temp;
}

//读取x,y坐标
//x,y:读取到的坐标ADC值
void TP_Read_XY_ADC(int16_t *x,int16_t *y)
{
	int16_t xtemp,ytemp;
	xtemp=TP_Read_XOY(CMD_RDX);
	ytemp=TP_Read_XOY(CMD_RDY);
	*x=xtemp;
	*y=ytemp;
}
//连续2次读取触摸屏IC,且这两次的偏差不能超过
//ERR_RANGE,满足条件,则认为读数正确,否则读数错误.
//该函数能大大提高准确度
//x,y:读取到的坐标值
//返回值:0,失败;1,成功。
#define ERR_RANGE 10 //误差范围
uint8_t TP_Read_XY2(int16_t *x,int16_t *y)
{
	int16_t x1,y1;
	int16_t x2,y2;

    TP_Read_XY_ADC(&x1,&y1);

    TP_Read_XY_ADC(&x2,&y2);

    if(((x2<=x1&&x1<x2+ERR_RANGE)||(x1<=x2&&x2<x1+ERR_RANGE))//前后两次采样在+-50内
    &&((y2<=y1&&y1<y2+ERR_RANGE)||(y1<=y2&&y2<y1+ERR_RANGE)))
    {
        *x=(x1+x2)/2;
        *y=(y1+y2)/2;
        return 1;
    }else return 0;
}
touch_device t0;// t0 yyds~
touch_config tconfig;
//触摸更新服务，状态机写法，循环获取坐标
void TP_Server()
{
	if(TPEN==0)                                             //如果有触摸
	{
		if(TP_Read_XY2(&t0.adc_x,&t0.adc_y))
		{//先读取ad值
			t0.pix_x=(t0.adc_x/tconfig.x_acc)-tconfig.x_offset;//转换为像素坐标
			t0.pix_y=(t0.adc_y/tconfig.y_acc)-tconfig.y_offset;
			t0.d=1;
		}else
		{
			t0.d=0;
		}
		t0.c=1;

	}else
	{
		t0.c=0;
		t0.pix_x=-1;
		t0.pix_y=-1;
	}
}
//直接读取
char TP_XY(int *x,int *y)
{
	if(TPEN==0)                                             //如果有触摸
	{
		TP_Read_XY2(&t0.adc_x,&t0.adc_y);                  //先读取ad值
		*x=(t0.adc_x/tconfig.x_acc)-tconfig.x_offset;//转换为像素坐标
		*y=(t0.adc_y/tconfig.y_acc)-tconfig.y_offset;
		return 1;
	}
	return 0;
}
//校准用，画一个目标坐标
//r=坐标半径，显示特效用
void TP_DrwaTrage(int x,int y,int r)
{
	Draw_Circle(x,y,r+1,GRAY);
	Draw_Circle(x,y,r,RED);
	LCD_DrawLine(x,y,x+10,y,RED);
	LCD_DrawLine(x,y,x,y+10,RED);
	LCD_DrawLine(x,y,x-10,y,RED);
	LCD_DrawLine(x,y,x,y-10,RED);
}
//触摸屏校准
//mode 校准模式，0 被动校准 ，1 主动校准
void TP_adjustment(char mode)
{
	//判断是否需要校准，从eeprom获取数据
	EEPROM_READ_BATY(16,(char *)&tconfig,sizeof(touch_config));
	if(tconfig.begin==0xab&&tconfig.end==0xcd&&mode==0)     //判断校准标记
	{
		return;												//已经校准过了
	}
	//校准方法比较简单，读取4个坐标计算ad值与像素的关系
	char str[64];                               //用于字符串提示
	uint16_t y_adc,x_adc,step=0,r=10;			//adc缓存，校准步骤，坐标的半径
	uint16_t y1,y2,y3,y4,x1,x2,x3,x4;			//4个点缓存
	int y5,x5,xd,xl,yd,yl;						//通过4个点算出xy的长边和短边
	float acc_x,acc_y;							//算出的关系倍率
	int   offset_x,offset_y;					//算出的偏差
	uint32_t wait=HAL_GetTick()+50000,ms100=0;  //校准时间，50秒没操作就自动退出
												//显示字符串提示
	LCD_Clear(GRAY);
	LCD_ShowString(0,50,"Calibrate the touch screen",16,RED,RED);
	//TP_DrwaTrage(30,30,10);
												//开始校准
	while(HAL_GetTick()<wait)
	{
		if(TPEN==0)								//如果屏幕被按下
		{
			wait=HAL_GetTick()+50000;			//重置50秒

			TP_Read_XY2(&x_adc,&y_adc);			//读取xy ad值
												//将读到的值显示出来
			sprintf(str,"ADC_X:%04d",x_adc);
			LCD_ShowString(100, 0, str, 16, RED, GRAY);
			sprintf(str,"ADC_Y:%04d",y_adc);
			LCD_ShowString(100, 16, str, 16, RED, GRAY);
												//特效，半径开始收缩
			if(HAL_GetTick()>ms100)
			{
				ms100=HAL_GetTick()+100;
				if(r>0){r--;}
			}
		}
		//步骤0，将点画在（30，30）此时半径为10
		if(step==0)
		{
			TP_DrwaTrage(30,30,r);
			if(r==0)//当半径收缩为0的时候
			{
					//进入下一个步骤，缓存这个点的值，显示出来
				step+=1;
				y1=y_adc;
				x1=x_adc;
				sprintf(str,"point_1 x:%d y:%d",x1,y1);
				LCD_ShowString(0,66,str,16,RED,RED);
			}
		}
		//步骤1，等待屏幕被松开，进入下一个步骤，重置半径
		if(step==1)
		{
			if(TPEN==1)
			{
				step+=1;
				r=10;
			}

		}
		//下面几个步骤和上面一样
		if(step==2)
		{
			TP_DrwaTrage(290,30,r);
			if(r==0)
			{
				step+=1;
				y2=y_adc;
				x2=x_adc;
				sprintf(str,"point_2 x:%d y:%d",x2,y2);
				LCD_ShowString(0,66+16,str,16,RED,RED);
			}
		}
		if(step==3)
		{
			if(TPEN==1)
			{
				step+=1;
				r=10;
			}

		}
		if(step==4)
		{
			TP_DrwaTrage(30,210,r);
			if(r==0)
			{
				step+=1;
				y3=y_adc;
				x3=x_adc;
				sprintf(str,"point_3 x:%d y:%d",x3,y3);
				LCD_ShowString(0,66+16+16,str,16,RED,RED);
			}
		}
		if(step==5)
		{
			if(TPEN==1)
			{
				step+=1;
				r=10;
			}

		}
		if(step==6)
		{
			TP_DrwaTrage(290,210,r);
			if(r==0)
			{
				step+=1;
				y4=y_adc;
				x4=x_adc;
				sprintf(str,"point_4 x:%d y:%d",x4,y4);
				LCD_ShowString(0,66+16+16+16,str,16,RED,RED);
			}
		}
		if(step==7)
		{
			if(TPEN==1)
			{
				step+=1;
				r=10;
			}
		}
		//当4个点读取完，开始计算关系
		if(step==8)
		{
			//其实只需要两个点就能校准，通过取平均值获得xy的长边和短边
			xd=((x1+x3)/2);
			xl=((x2+x4)/2);
			yd=((y1+y2)/2);
			yl=((y3+y4)/2);
			//长边减去短边可以再获得一个点
			x5=xl-xd;
			y5=yl-yd;
			//这个点如果是负数，肯定有错，可能是xy搞反了
			if(x5<0||y5<0)
			{
				//显示error
				sprintf(str,"ERROR");
				LCD_ShowString(0,66+16+16+16+16,str,16,RED, GRAY);
			}else
			{
				//计算关系倍率
				//ad的长边减去短边再除去实际屏幕像素的长边减短边（260=320-30-30，180=240-30-30）
				acc_x=x5/260.0;
				acc_y=y5/180.0;
				//验证倍率，将实际值减去验证值就等于误差值，因为有两个点，所以计算了两个误差后求了平均值
				offset_x=(((xd/acc_x)-30)+((xl/acc_x)-290))/2;
				offset_y=(((yd/acc_y)-30)+((yl/acc_y)-210))/2;
				//保存计算结果
				tconfig.x_acc=acc_x;
				tconfig.x_offset=offset_x;
				tconfig.y_acc=acc_y;
				tconfig.y_offset=offset_y;
				//eeprom块标记
				tconfig.begin=0xab;
				tconfig.end=0xcd;

				//显示计算结果
				sprintf(str,"x_acc=%f y_acc=%f",acc_x,acc_y);
				LCD_ShowString(0,66+16+16+16+16,str,16,RED,RED);
				sprintf(str,"x_offset=%d y_offset=%d",offset_x,offset_y);
				LCD_ShowString(0,66+16+16+16+16+16,str,16,RED,RED);

			}
			//将结果保存起来
			EEPROM_WRITE_BATY(16,(char *)&tconfig,sizeof(touch_config));
			HAL_Delay(1000);
			return;

		}
	}

}

