stm32驅動3.2寸觸控式螢幕(包括IO模擬,SPI硬體介面)
阿新 • • 發佈:2019-01-23
#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