1. 程式人生 > >stm32驅動3.2寸觸控式螢幕(包括IO模擬,SPI硬體介面)

stm32驅動3.2寸觸控式螢幕(包括IO模擬,SPI硬體介面)

#ifndef TOUCH_H
#define TOUCH_H
#define SPI   0           //通過巨集定義來選擇SPI驅動,還是IO口模擬
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_spi.h"
#include "math.h"
#include "TFT.h"
#define TCS_HIGH     GPIO_SetBits(GPIOB,GPIO_Pin_12)   // NSS Soft Mode 
#define TCS_LOW      GPIO_ResetBits(GPIOB,GPIO_Pin_12) // NSS Soft Mode
#define PEN          GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) //INT state
#define TOUT         GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)
#define TDIN_HIGH    GPIO_SetBits(GPIOB,GPIO_Pin_15)
#define TDIN_LOW     GPIO_ResetBits(GPIOB,GPIO_Pin_15)
#define TCLK_HIGH    GPIO_SetBits(GPIOB,GPIO_Pin_13)
#define TCLK_LOW     GPIO_ResetBits(GPIOB,GPIO_Pin_13)
#define TXMAX        4000         //根據設定的校準值,超出範圍 資料無效
#define TYMAX        4000
#define TXMIN        100
#define TYMIN        100
#define SCREEN_W      240
#define SCREEN_H      320
#define ERROR_RANGE   100
#define KEY_UP        0x01 
#define KEY_DOWN      0x00
#define CHX      0xd0
#define CHY      0x90
#define EXTI_ENABLE		     EXTI->IMR|=0X0001          //開啟線0中斷
#define EXTI_DISABLE       EXTI->IMR&=~0X0001        //關閉線0中斷         //ads7843晶片在第一個時鐘的上升沿輸入,第一個時鐘的
typedef struct sldkf                         //下降沿輸出,所以SPI要設定為1時鐘,上升沿。在讀取的時候,
{                                            //也是在1時鐘,上升沿讀取。但ads是在下降沿輸出,所以,第一個值,是廢值
	u16 x0,y0;     //原始座標,即AD值         //所以左移3位
	u16 x,y;       //最終座標,畫素點值
	u8  flag;      //當前狀態,其實就是判斷中斷否發生的標誌
	float xfac,yfac,xoff,yoff; //偏移引數
}Hand;
Hand pence;
u16 TX,TY;
int ABS(int x)
{
	return x>0?x:-x;
}
void Touch_SPI_inti()  //SPI驅動ADS所用到的初始化
{
	SPI_InitTypeDef spi;
	GPIO_InitTypeDef gpio;
	NVIC_InitTypeDef nvic;
	EXTI_InitTypeDef exit;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	gpio.GPIO_Pin=GPIO_Pin_12;  //nss 推輓輸出
	gpio.GPIO_Mode=GPIO_Mode_Out_PP;
	gpio.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&gpio);
	
	gpio.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_15; // sck,mosi 複用推輓輸出
	gpio.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_Init(GPIOB,&gpio);
	
	gpio.GPIO_Pin=GPIO_Pin_14;             //miso 浮空輸入
	gpio.GPIO_Mode=GPIO_Mode_IPU;
	//	gpio.GPIO_Mode=GPIO_Mode_IN_FLOATING;//miso 配置為複用輸出,或者浮空輸入效果沒差別,不解
	GPIO_Init(GPIOB,&gpio);	
	
	SPI_Cmd(SPI2,DISABLE);
	spi.SPI_Direction=SPI_Direction_2Lines_FullDuplex;  //全雙工
	spi.SPI_Mode=SPI_Mode_Master;                       //主模式 
	spi.SPI_DataSize=SPI_DataSize_8b;                  //資料16位 
	spi.SPI_CPOL=SPI_CPOL_Low;                         //時鐘空閒高電源
	spi.SPI_CPHA=SPI_CPHA_1Edge;                        //1個邊沿捕捉
	spi.SPI_NSS=SPI_NSS_Soft;                           //NSS軟體模式?
	spi.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_64;    //64分頻,
	spi.SPI_FirstBit=SPI_FirstBit_MSB;                  //高位在前
	spi.SPI_CRCPolynomial=7;
	SPI_Init(SPI2,&spi);
	SPI_Cmd(SPI2,ENABLE);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); //選擇PB.0作為中斷0的輸入
	
	exit.EXTI_Line=EXTI_Line0;               //外部中斷0
	exit.EXTI_Mode=EXTI_Mode_Interrupt;       //中斷
	exit.EXTI_Trigger=EXTI_Trigger_Falling;   //下降沿觸發
	exit.EXTI_LineCmd=ENABLE;
	EXTI_Init(&exit);
	
	nvic.NVIC_IRQChannel=EXTI0_IRQChannel;  //nvic 中斷配置
 	nvic.NVIC_IRQChannelPreemptionPriority=0;
	nvic.NVIC_IRQChannelSubPriority=2;
	nvic.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&nvic);
}
u8 SPI2_Byte(u8 cmd)
{
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
	
	SPI_I2S_SendData(SPI2,cmd);
	
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
	
	return SPI_I2S_ReceiveData(SPI2);
}
void Touch_IO_inti()  //IO 口模擬
{
	GPIO_InitTypeDef gpio;
	NVIC_InitTypeDef nvic;
	EXTI_InitTypeDef exit;

//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB||RCC_APB2Periph_AFIO,ENABLE); //不能一起用,具體原因不知道
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	
	gpio.GPIO_Pin=GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_15; //cs,clk,mosi
	gpio.GPIO_Mode=GPIO_Mode_Out_PP;
	gpio.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&gpio);
	
	gpio.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_14;     //上拉輸入 在spi裡面已經使能了PB的時鐘,int,miso 
	gpio.GPIO_Mode=GPIO_Mode_IPU;
	gpio.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&gpio);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); //選擇PB.0作為中斷0的輸入
	
	exit.EXTI_Line=EXTI_Line0;               //外部中斷0
	exit.EXTI_Mode=EXTI_Mode_Interrupt;       //中斷
	exit.EXTI_Trigger=EXTI_Trigger_Falling;   //下降沿觸發
	exit.EXTI_LineCmd=ENABLE;
	EXTI_Init(&exit);
	
	nvic.NVIC_IRQChannel=EXTI0_IRQChannel;  //nvic 中斷配置
 	nvic.NVIC_IRQChannelPreemptionPriority=0;
	nvic.NVIC_IRQChannelSubPriority=2;
	nvic.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&nvic);
}
#if SPI
void CMD_Write(u8 cmd)   //SPI 寫
{
	SPI2_Byte(cmd);
}
u16 CMD_Read()          //SPi 讀
{
	u16 ans=0,temp;
	temp=SPI2_Byte(0x00);
	ans=temp<<8;
	temp=SPI2_Byte(0x00);
	ans|=temp;
	ans>>=3;
	return ans&0x0fff;
}
#else 
void CMD_Write(u8 num)     //IO 模擬
{  
	u8 count=0;   
	for(count=0;count<8;count++)  
	{ 	  
		if(num&0x80)TDIN_HIGH;  
		else TDIN_LOW;   
		num<<=1;    
		TCLK_LOW;//上升沿有效	   	 
		TCLK_HIGH;      
	} 			    
}

u16 CMD_Read()      //IO 模擬
{
	u16 i,ans;
	ans=0;
	for(i=0;i<12;i++)
	{
		ans<<=1;
		TCLK_HIGH;
		TCLK_LOW;
		if(TOUT)ans++;
	}
	return ans;
}
#endif
u8 Read_IO_ADS()  //讀取一次
{
	TCS_LOW;
	
	CMD_Write(CHX);   
	#if SPI          //對SPI來說,取樣的速率太快是邊沿出現散點的原因
	SysTick_us(30); //對SPI來說,頻率為64分頻時,,適當的延時可以達到很好的效果,用的SPI驅動,所以不要用下降沿清除BUSY
	                //硬體來驅動,時序基本上不用考慮,唯一要考慮的是SPI的頻率過高,可能導致取樣不準
	               //所以,SPI初始化完成後的時序是不用考慮的,看ads的時序圖,也可以看出這一點
	#else    //IO模擬必須要下降沿,原因不明,下降沿後,效果好很多
	         //如果沒有,螢幕區域基本上集中在左上角1/4區域?
	SysTick_us(10);//經過研究時序圖發現,此處給一個下降沿,是為了清除BUSY標誌,同時啟動傳輸
 	TCLK_HIGH;     //如果沒有,那麼在CMD_Read函式裡面的讀到的數的第12位會恆為0
	SysTick_us(10);//這樣解釋了,為什麼X,Y的範圍都會減半。在CMD_Read函式裡面,在第一個下降沿過後,資料才開始傳輸
 	TCLK_LOW;     //所以,第一個下降沿的作用就是啟動傳輸與清楚BUSY標誌,所以在第一個下降沿得到的數肯定為0(因為剛剛開始傳)
	SysTick_us(10);
	#endif
	TX=CMD_Read();
	
	CMD_Write(CHY);
	#if  SPI
	SysTick_us(30);
	#else         //IO模擬必須要下降沿,原因不明
SysTick_us(10); TCLK_HIGH; SysTick_us(10); TCLK_LOW; SysTick_us(10); #endif TY=CMD_Read(); TCS_HIGH; if(TX>TXMAX||TY>TXMAX||TX<TXMIN||TY<TYMIN) { return 0; } return 1; } void Read_IO_XY(u16 *x,u16 *y) { u16 xy[2][10],cnt,i,j,temp,t,XY[2]; cnt=0; do //採集10個數,取中間的4個 { if(Read_IO_ADS()) //採集10次合法資料 { xy[0][cnt]=TX; xy[1][cnt]=TY; cnt++; } }while(PEN==0&&cnt<9); if(cnt<9) { return ; } for(t=0;t<2;t++) { for(i=0;i<cnt;i++) //選擇排序 { temp=i; for(j=i+1;j<cnt;j++) { if(xy[t][j]<xy[t][temp]) { temp=j; } } if(temp!=i) { xy[t][i]^=xy[t][temp]; xy[t][temp]^=xy[t][i]; xy[t][i]^=xy[t][temp]; } } XY[t]=(xy[t][3]+xy[t][4]+xy[t][5]+xy[t][6])/4; } *x=XY[0]; *y=XY[1]; } u8 GetXY(u16 *x,u16 *y) { u16 x1,y1,x2,y2; Read_IO_XY(&x1,&y1); Read_IO_XY(&x2,&y2); if(ABS(x1-x2)>ERROR_RANGE||ABS(y1-y2)>ERROR_RANGE)//如果兩次相差過大,去掉 { return 0; } *x=(x1+x2)/2; *y=(y1+y2)/2; return 1; } u8 Read_TP() { u8 t=0; EXTI_DISABLE; //中斷關閉 pence.flag=KEY_UP; //狀態改變 GetXY(&pence.x0,&pence.y0); while(t<250&&PEN==0) { t++; SysTick_ms(10); } EXTI_ENABLE; if(t>=250)return 0; return 1; } void Touch_correct() //校準過後能得到一個大體上比較準確的值。但是還是會有些誤差 { //我在這裡得到的值,是根據實際情況,由此函式得到的值修改而來。修改的範圍是很小的
                    //經過校準得到的值是xfac=0.0771,xoff=-18.1920,yfac=0.1014,yoff=-2.8338
                   //修改為 xfac=0.0671,xoff=-18.1920,yfac=0.0914,yoff=-18.8
	int x[4],y[4],cnt,temp1,temp2,ans;
	float d1,d2;
	TFT_Pant(BLUE);          //第一個校準點
	Draw_Touch_Point(20,20);  
	pence.flag=KEY_UP;          //清除標誌
	cnt=0;
	while(1)
	{
		if(pence.flag==KEY_DOWN) //螢幕觸碰 
		{
			if(Read_TP())      //讀取AD值
			{
				x[cnt]=pence.x0;
				y[cnt]=pence.y0;
				cnt++;
			}
			switch(cnt)
			{
				case 1:
					TFT_Pant(BLUE);             //第2個校準點
					Draw_Touch_Point(220,20);
					break;
				case 2:
					TFT_Pant(BLUE);
					Draw_Touch_Point(20,300);//第3個?
				  break;
				case 3:
					TFT_Pant(BLUE);
					Draw_Touch_Point(220,300); //第4個
					break;
				case 4:                  //校準點完畢,進行合法性檢查
					temp1=ABS(x[0]-x[1]);
					temp2=ABS(y[0]-y[1]);
					d1=sqrt(temp1*temp1+temp2*temp2); //1,2點的距離
				
				  temp1=ABS(x[2]-x[3]);             //3.4點的距離
					temp2=ABS(y[2]-y[3]);
					d2=sqrt(temp1*temp1+temp2*temp2);
					if(d1==0||d2==0||d1/d2>1.05||d1/d2<0.95) //距離差太多
					{
						cnt=0;
						TFT_Pant(BLACK);
						TFT_ShowString(35,100,"Adjust Failed(1)!!!");
						SysTick_ms(1000);
						TFT_Pant(BLUE);
						Draw_Touch_Point(20,20);
					//	continue;
						break;
					}
					
					temp1=ABS(x[0]-x[2]);
					temp2=ABS(y[0]-y[2]);
					d1=sqrt(temp1*temp1+temp2*temp2); //1,3點的距離	
					temp1=ABS(x[1]-x[3]);             //2.4點的距離
					temp2=ABS(y[1]-y[3]);
					d2=sqrt(temp1*temp1+temp2*temp2);					
					if(d1==0||d2==0||d1/d2>1.05||d1/d2<0.95) //距離差太多
					{
						cnt=0;
						TFT_Pant(BLACK);
						TFT_ShowString(35,100,"Adjust Failed(2)!!!");
						SysTick_ms(1000);
						TFT_Pant(BLUE);
						Draw_Touch_Point(20,20);
					//	continue;
						break;
					}
					
					temp1=ABS(x[0]-x[3]);
					temp2=ABS(y[0]-y[3]);
					d1=sqrt(temp1*temp1+temp2*temp2); //1,4點的距離	
					temp1=ABS(x[1]-x[2]);             //2.3點的距離
					temp2=ABS(y[1]-y[2]);
					d2=sqrt(temp1*temp1+temp2*temp2);					
					if(d1==0||d2==0||d1/d2>1.05||d1/d2<0.95) //距離差太多
					{
						cnt=0;
						TFT_Pant(BLACK);
						TFT_ShowString(35,100,"Adjust Failed(3)!!!");
						SysTick_ms(1000);
						Draw_Touch_Point(20,20);
					//	continue;
						break;
					}
					//引數檢查完畢,手指觸碰沒有太大的誤差
					pence.xfac=200.0/(x[1]-x[0]);               //解2元1次方程組
					pence.xoff=(240.0-pence.xfac*(x[1]+x[0]))/2;
					pence.yfac=280.0/(y[2]-y[0]);
					pence.yoff=(320.0-pence.yfac*(y[0]+y[2]))/2;
					TFT_Pant(BLUE);
					TFT_ShowString(35,100,"Touch Screen Adjust OK!");
					SysTick_ms(1000);
					TFT_Pant(WHITE);
					TFT_ShowNum(0,180,x[0]);
					TFT_ShowNum(0+CHARSIZE_W*5,180,y[0]);
					TFT_ShowNum(0,200,x[1]);
					TFT_ShowNum(0+CHARSIZE_W*5,200,y[1]);
					TFT_ShowNum(0,220,x[2]);
					TFT_ShowNum(0+CHARSIZE_W*5,220,y[2]);
					TFT_ShowNum(0,240,x[3]);
					TFT_ShowNum(0+CHARSIZE_W*5,240,y[3]);
					TFT_ShowFloat(100,100,pence.xfac);
					TFT_ShowFloat(100,120,pence.xoff);
					TFT_ShowFloat(100,140,pence.yfac);
					TFT_ShowFloat(100,160,pence.yoff);
					return ;
					default :break;
			}
		}
	}
}
void ConvertXY()
{
	float x,y;
	GetXY(&pence.x0,&pence.y0); //不能小於0
	x=pence.x0*pence.xfac+pence.xoff;
	y=pence.y0*pence.yfac+pence.yoff;
	pence.x=(u16)x;
	pence.y=(u16)y;
}
void Pence_inti()
{
	pence.flag=KEY_UP;
	pence.x=pence.y=0;
	pence.x0=pence.y0=0;
	pence.xfac=pence.xoff=0;
	pence.yfac=pence.yoff=0;
}
void Pence_adjust() //下面的是校準了的比較精確的引數
{
	pence.xfac=0.0671;
	pence.xoff=-18.1920;
	pence.yfac=0.0914;
	pence.yoff=-18.8;
}
void Touch_inti() //通過巨集SPI,來選擇硬體SPI驅動as,還是IO口模擬
{                //實際上來說,用SPI是沒必要的,觸控式螢幕對速度要求不高
	Pence_inti();  //如果用SPI,那麼分頻係數小了,或者在Read_IO_ADS函式中的延時
	#if SPI        //少了,會導致採集到的點過多,會出現散點的現象
	               //經過幾次試驗,發現64分頻,延時30us,效果不錯
      	Touch_SPI_inti();  //SPI
	#else
	      Touch_IO_inti();  //IO模擬
	#endif
	Pence_adjust();
//	Touch_correct();
}
void EXTI0_IRQHandler()
{
	static u32 count=0;
	if(EXTI_GetITStatus(EXTI_Line0)==SET)
	{
		count++;
		EXTI_ClearITPendingBit(EXTI_Line0);
		pence.flag=KEY_DOWN;    //螢幕觸控
		SysTick_ms(50);        //延時消抖
	}
}
#endif