基于HAL库的STM32F704的电阻式触摸屏的学习
内容基于正点原子的代码分析
一、XPT2046介绍
XPT2046是一款4导线制触摸屏控制器,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046支持从1.5V到5.25V的低电压I/O接口。XPT2046能通过执行两次A/D转换查出被按的屏幕位置, 除此之外,还可以测量加在触摸屏上的压力。内部自带2.5V参考电压可以作为辅助输入、温度测量和电池监测模式之用,电池监测的电压范围可以从0V到6V。XPT2046片内集成有一个温度传感器
红框内为XPT2046与MCU通讯用引脚(错了,NO BUSY;YES PENIRQ )
二、XPT2046底层代码的编写
1.根据时序图编写读写程序
时序图,课根据时序图,写出伪代码,再讲伪代码编辑为实际代码。关于延时部分参考XPT2046数据手册(请点进去后复制网址)。这里不再重复造轮子
/*
*************************************************************
*伪代码,函数功能:MCU发送数据给XPT2046,XPT2046返回数据给MCU
**************************************************************
*/
void xpt2046_write_byte(u8 write_data)
{
for(i=0;i<8;i++)
{
DCLK = 0;
if(write_data & 0x80)//S为1时有效
TDIN = 1;//MOSI
else
TDIN = 0;
//1us //MCU准备数据完成
DCLK = 1;
//1us
rite_data <<= 1;
}
}
u16 xpt2046_read(u8 CMD)
{
u16 xpt2046_read = 0;
CS = 0;//片选
DCLK = 0;//拉低
DIN = 0;
xpt2046_write_byte(CMD);
/* 过滤忙信号 */
DCLK = 0;
//延时
DCLK = 1;
//延时
for(i = 0 ; i<16;i++)
{
DCLK = 0; //XPT2046开始准备数据
//1us
DCLK = 1; //XPT2046开始准备数据
xpt2046_read<<=1;//空出最低位来保存读取到的数据
xpt2046_read |= DOUT;//MISO
//1us
}
DCLK = 0; //完整周期
CS = 1; //取消选择
return (xpt2046_read<<=4);
}
/*
*************************************************************
*正点原子的代码
**************************************************************
*/
//SPI写数据
//向触摸屏IC写入1byte数据
//num:要写入的数据
void TP_Write_Byte(u8 num)
{
u8 count=0;
for(count=0;count<8;count++)
{
if(num&0x80)TDIN=1;
else TDIN=0;
num<<=1;
TCLK=0;
delay_us(1);
TCLK=1; //上升沿有效
}
}
//SPI读数据
//从触摸屏IC读取adc值
//CMD:指令
//返回值:读到的数据
u16 TP_Read_AD(u8 CMD)
{
u8 count=0;
u16 Num=0;
TCLK=0; //先拉低时钟
TDIN=0; //拉低数据线
TCS=0; //选中触摸屏IC
TP_Write_Byte(CMD);//发送命令字
delay_us(6);//ADS7846的转换时间最长为6us
TCLK=0;
delay_us(1);
TCLK=1; //给1个时钟,清除BUSY
delay_us(1);
TCLK=0;
for(count=0;count<16;count++)//读出16位数据,只有高12位有效
{
Num<<=1;
TCLK=0; //下降沿有效
delay_us(1);
TCLK=1;
if(DOUT)Num++;
}
Num>>=4; //只有高12位有效.
TCS=1; //释放片选
return(Num);
}
2.XPT2046命令
对应的两个命令:
u8 CMD_RDX=0XD0;//0XD0 为 11010000,读取X轴
u8 CMD_RDY=0X90;//0X90 为 10010000,读取Y轴
三、主要代码分析
1.结构体以及初始化
typedef struct
{
u8 (*init)(void); //初始化触摸屏控制器
u8 (*scan)(u8); //扫描触摸屏.0,屏幕扫描;1,物理坐标;
void (*adjust)(void); //触摸屏校准
u16 x[CT_MAX_TOUCH]; //当前坐标
u16 y[CT_MAX_TOUCH]; //电容屏有最多5组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,用
//x[4],y[4]存储第一次按下时的坐标.
u8 sta; //笔的状态
//b7:按下1/松开0;
//b6:0,没有按键按下;1,有按键按下.
//b5:保留
//b4~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)
/触摸屏校准参数(电容屏不需要校准)//
float xfac;
float yfac;
short xoff;
short yoff;
//新增的参数,当触摸屏的左右上下完全颠倒时需要用到.
//b0:0,竖屏(适合左右为X坐标,上下为Y坐标的TP)
// 1,横屏(适合左右为Y坐标,上下为X坐标的TP)
//b1~6:保留.
//b7:0,电阻屏
// 1,电容屏
u8 touchtype;
}_m_tp_dev;
/*
*****************************************************
结构体初始化
*****************************************************
*/
_m_tp_dev tp_dev=
{
TP_Init,
TP_Scan,
TP_Adjust,
0,
0,
0,
0,
0,
0,
0,
0,
};
通过结构体方便控制底层,HAL库也是如此,值得学习,包含了函数指针、变量等。其中
TP_Init(),TP_Scan(),TP_Adjust()是重点掌握的函数。
1.判断是否触摸
//
//触摸按键扫描
//tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用)
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
u8 TP_Scan(u8 tp)
{
if(PEN==0)//有按键按下,整个面板相当于一个大的上拉电阻,应该是类似于一个按键的功能
{
if(tp)TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]);//读取物理坐标
else if(TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]))//读取屏幕坐标
{
tp_dev.x[0]=tp_dev.xfac*tp_dev.x[0]+tp_dev.xoff;//LCDx=xfac*Px+xoff; 将结果转换为屏幕坐标
tp_dev.y[0]=tp_dev.yfac*tp_dev.y[0]+tp_dev.yoff;//LCDy=yfac*Py+yoff; 需要校准
}
if((tp_dev.sta&TP_PRES_DOWN)==0)//之前没有被按下
{
tp_dev.sta=TP_PRES_DOWN|TP_CATH_PRES;//按键按下 #define TP_PRES_DOWN 0x80
触屏被按 下 #define TP_CATH_PRES 0x40
tp_dev.x[4]=tp_dev.x[0];//记录第一次按下时的坐标
tp_dev.y[4]=tp_dev.y[0];
}
}else
{
if(tp_dev.sta&TP_PRES_DOWN)//之前是被按下的
{
tp_dev.sta&=~(1<<7);//b1置0,标志标记按键松开
}else//之前就没有被按下
{
tp_dev.x[4]=0;
tp_dev.y[4]=0;
tp_dev.x[0]=0xffff;
tp_dev.y[0]=0xffff;
}
}
return tp_dev.sta&TP_PRES_DOWN;//返回当前的触屏状态
}
2.校准
//触摸屏校准代码
//得到四个校准参数
void TP_Adjust(void)
{
u16 pos_temp[4][2];//坐标缓存值
u8 cnt=0;
u16 d1,d2;
u32 tem1,tem2;
double fac;
u16 outtime=0;
cnt=0;
POINT_COLOR=BLUE;
BACK_COLOR =WHITE;
LCD_Clear(WHITE);//清屏
POINT_COLOR=RED;//红色
LCD_Clear(WHITE);//清屏
POINT_COLOR=BLACK;
LCD_ShowString(40,40,160,100,16,(u8*)TP_REMIND_MSG_TBL);//显示提示信息
TP_Drow_Touch_Point(20,20,RED);//画点1
tp_dev.sta=0;//消除触发信号
tp_dev.xfac=0;//xfac用来标记是否校准过,所以校准之前必须清掉!以免错误
while(1)//如果连续10秒钟没有按下,则自动退出
{
tp_dev.scan(1);//扫描物理坐标
if((tp_dev.sta&0xc0)==TP_CATH_PRES)//按键按下了一次(此时按键松开了.)
{
outtime=0;
tp_dev.sta&=~(1<<6);//标记按键已经被处理过了.
pos_temp[cnt][0]=tp_dev.x[0];
pos_temp[cnt][1]=tp_dev.y[0];
cnt++;
switch(cnt)
{
case 1:
TP_Drow_Touch_Point(20,20,WHITE); //清除点1
TP_Drow_Touch_Point(lcddev.width-20,20,RED); //画点2
break;
case 2:
TP_Drow_Touch_Point(lcddev.width-20,20,WHITE); //清除点2
TP_Drow_Touch_Point(20,lcddev.height-20,RED); //画点3
break;
case 3:
TP_Drow_Touch_Point(20,lcddev.height-20,WHITE); //清除点3
TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,RED); //画点4
break;
case 4: //全部四个点已经得到
//对边相等
tem1=abs(pos_temp[0][0]-pos_temp[1][0]);//x1-x2
tem2=abs(pos_temp[0][1]-pos_temp[1][1]);//y1-y2
tem1*=tem1;
tem2*=tem2;
d1=sqrt(tem1+tem2);//得到1,2的距离
tem1=abs(pos_temp[2][0]-pos_temp[3][0]);//x3-x4
tem2=abs(pos_temp[2][1]-pos_temp[3][1]);//y3-y4
tem1*=tem1;
tem2*=tem2;
d2=sqrt(tem1+tem2);//得到3,4的距离
fac=(float)d1/d2;
if(fac<0.95||fac>1.05||d1==0||d2==0)//不合格
{
cnt=0;
TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE); //清除点4
TP_Drow_Touch_Point(20,20,RED); //画点1
TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据
continue;
}
tem1=abs(pos_temp[0][0]-pos_temp[2][0]);//x1-x3
tem2=abs(pos_temp[0][1]-pos_temp[2][1]);//y1-y3
tem1*=tem1;
tem2*=tem2;
d1=sqrt(tem1+tem2);//得到1,3的距离
tem1=abs(pos_temp[1][0]-pos_temp[3][0]);//x2-x4
tem2=abs(pos_temp[1][1]-pos_temp[3][1]);//y2-y4
tem1*=tem1;
tem2*=tem2;
d2=sqrt(tem1+tem2);//得到2,4的距离
fac=(float)d1/d2;
if(fac<0.95||fac>1.05)//不合格
{
cnt=0;
TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE); //清除点4
TP_Drow_Touch_Point(20,20,RED); //画点1
TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据
continue;
}//正确了
//对角线相等
tem1=abs(pos_temp[1][0]-pos_temp[2][0]);//x1-x3
tem2=abs(pos_temp[1][1]-pos_temp[2][1]);//y1-y3
tem1*=tem1;
tem2*=tem2;
d1=sqrt(tem1+tem2);//得到1,4的距离
tem1=abs(pos_temp[0][0]-pos_temp[3][0]);//x2-x4
tem2=abs(pos_temp[0][1]-pos_temp[3][1]);//y2-y4
tem1*=tem1;
tem2*=tem2;
d2=sqrt(tem1+tem2);//得到2,3的距离
fac=(float)d1/d2;
if(fac<0.95||fac>1.05)//不合格
{
cnt=0;
TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE); //清除点4
TP_Drow_Touch_Point(20,20,RED); //画点1
TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据
continue;
}//正确了
//计算结果
tp_dev.xfac=(float)(lcddev.width-40)/(pos_temp[1][0]-pos_temp[0][0]);//得到xfac
tp_dev.xoff=(lcddev.width-tp_dev.xfac*(pos_temp[1][0]+pos_temp[0][0]))/2;//得到xoff
tp_dev.yfac=(float)(lcddev.height-40)/(pos_temp[2][1]-pos_temp[0][1]);//得到yfac
tp_dev.yoff=(lcddev.height-tp_dev.yfac*(pos_temp[2][1]+pos_temp[0][1]))/2;//得到yoff
if(abs(tp_dev.xfac)>2||abs(tp_dev.yfac)>2)//触屏和预设的相反了.
{
cnt=0;
TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE); //清除点4
TP_Drow_Touch_Point(20,20,RED); //画点1
LCD_ShowString(40,26,lcddev.width,lcddev.height,16,"TP Need readjust!");
tp_dev.touchtype=!tp_dev.touchtype;//修改触屏类型.
if(tp_dev.touchtype)//X,Y方向与屏幕相反
{
CMD_RDX=0X90;
CMD_RDY=0XD0;
}else //X,Y方向与屏幕相同
{
CMD_RDX=0XD0;
CMD_RDY=0X90;
}
continue;
}
POINT_COLOR=BLUE;
LCD_Clear(WHITE);//清屏
LCD_ShowString(35,110,lcddev.width,lcddev.height,16,"Touch Screen Adjust OK!");//校正完成
delay_ms(1000);
TP_Save_Adjdata();
LCD_Clear(WHITE);//清屏
return;//校正完成
}
}
delay_ms(10);
outtime++;
if(outtime>1000)
{
TP_Get_Adjdata();
break;
}
}
}
算了,就这样吧,不想写了
四、实战
CUBUMX相关配置
/*
*************************************************************************************
简易画板
*************************************************************************************
*/
static u8 Dft_Scan_Dir = 0;
u16 Ssd_Hor_Resolution =320; //LCD水平分辨率
u16 Ssd_Ver_Resolution =240; //LCD垂直分辨率
void drow_block_colar(void)
{
LCD_Fill(0,Ssd_Ver_Resolution-40,Ssd_Hor_Resolution*1/6,Ssd_Ver_Resolution,RED);
LCD_Fill(Ssd_Hor_Resolution*1/6,Ssd_Ver_Resolution-40,Ssd_Hor_Resolution*2/6,Ssd_Ver_Resolution,YELLOW);
LCD_Fill(Ssd_Hor_Resolution*2/6,Ssd_Ver_Resolution-40,Ssd_Hor_Resolution*3/6,Ssd_Ver_Resolution,BLUE);
LCD_Fill(Ssd_Hor_Resolution*3/6,Ssd_Ver_Resolution-40,Ssd_Hor_Resolution*4/6,Ssd_Ver_Resolution,GREEN);
LCD_Fill(Ssd_Hor_Resolution*4/6,Ssd_Ver_Resolution-40,Ssd_Hor_Resolution*5/6,Ssd_Ver_Resolution,BLACK);
LCD_ShowString(Ssd_Hor_Resolution*5/6+1,Ssd_Ver_Resolution-40,Ssd_Hor_Resolution,16,16,"ERASER");//显示清屏区域
}
//清空屏幕并在右上角显示"RST"
void Load_Drow_Dialog(void)
{
LCD_Clear(WHITE);//清屏
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(lcddev.width-24,0,200,16,16,"RST");//显示清屏区域
POINT_COLOR=RED;//设置画笔蓝色
drow_block_colar();
}
void rtp_test(void)
{
u8 key;
u8 i=0;
while(1)
{
key=Key_Scan(0);
tp_dev.scan(0);
if(tp_dev.sta&TP_PRES_DOWN) //触摸屏被按下
{
if(tp_dev.x[0]<lcddev.width&&tp_dev.y[0]<lcddev.height)
{
if(tp_dev.x[0]>(lcddev.width-24)&&tp_dev.y[0]<16)
Load_Drow_Dialog();//清除
else if(tp_dev.y[0]>Ssd_Ver_Resolution-40&&tp_dev.y[0]<Ssd_Ver_Resolution)
{
if(tp_dev.x[0]>0&&tp_dev.x[0]<=Ssd_Hor_Resolution*1/6)
POINT_COLOR = RED;
else if(tp_dev.x[0]>Ssd_Hor_Resolution*1/6&&tp_dev.x[0]<=Ssd_Hor_Resolution*2/6)
POINT_COLOR = YELLOW;
else if(tp_dev.x[0]>Ssd_Hor_Resolution*2/6&&tp_dev.x[0]<=Ssd_Hor_Resolution*3/6)
POINT_COLOR = BLUE;
else if(tp_dev.x[0]>Ssd_Hor_Resolution*3/6&&tp_dev.x[0]<=Ssd_Hor_Resolution*4/6)
POINT_COLOR = GREEN;
else if(tp_dev.x[0]>Ssd_Hor_Resolution*4/6&&tp_dev.x[0]<=Ssd_Hor_Resolution*5/6)
POINT_COLOR = BLACK;
else if(tp_dev.x[0]>Ssd_Hor_Resolution*5/6&&tp_dev.x[0]<=Ssd_Hor_Resolution)
POINT_COLOR = BACK_COLOR;
}
else
{
if(POINT_COLOR == BACK_COLOR)
LCD_Fill(tp_dev.x[0],tp_dev.y[0],tp_dev.x[0]+5,tp_dev.y[0]+5,POINT_COLOR);
else
TP_Draw_Big_Point(tp_dev.x[0],tp_dev.y[0],POINT_COLOR); //画图
}
}
}else delay_ms(10); //没有按键按下的时候
if(key==KEY0_PRES) //KEY0按下,则执行校准程序
{
LCD_Clear(WHITE); //清屏
TP_Adjust(); //屏幕校准
TP_Save_Adjdata();
Load_Drow_Dialog();
}
i++;
if(i%20==0)PFout(9)=!PFout(9);
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
u8 lcd_id[12]; //存放LCD ID字符串
u8 datatemp[SIZE];
u8 KEY;
u8 test[256];
u8 i;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_FSMC_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
delay_init(168);
HAL_TIM_Base_Start_IT(&htim6);
LCD_Init(); //初始化LCD FSMC接口
AT24CXX_Init(); //初始化IIC
if(Dft_Scan_Dir)
{
Ssd_Hor_Resolution = 320;
Ssd_Ver_Resolution = 240;
}
else
{
Ssd_Hor_Resolution = 240;
Ssd_Ver_Resolution = 320;
}
LCD_Display_Dir(Dft_Scan_Dir);
tp_dev.init(); //触摸屏初始化
drow_block_colar();
if(tp_dev.touchtype!=0XFF)
{
LCD_ShowString(0,0,200,16,16,"Press KEY0 to Adjust");//电阻屏才显示
}
delay_ms(1500);
Load_Drow_Dialog();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
rtp_test();
}
/* USER CODE END 3 */
}
效果图: