第33章触摸屏实验
本章我们将介绍如何使用STM32F4驱动触摸屏,ALIENTEK Explorer STM32F4开发板
它本身没有触摸屏控制器,但支持触摸屏,可以连接带触摸屏的液晶模块(如
ALIENTEK TFTLCD模块)实现触摸屏控制。本章我们将为您介绍STM32控制
ALIENTKE TFTLCD模块(包括电阻式触摸和电容式触摸),实现触摸屏驱动,最终实现手写
董事会职能。 本章分为以下几个部分:
33.1 电阻式和电容式触摸屏简介
33.2 硬件设计
33.3 软件设计
33.4 下载验证
33.1 触摸屏简介
目前最常用的触摸屏有两种:电阻式触摸屏和电容式触摸屏。 下面,我们将分别进行介绍。
33.1.1 电阻式触摸屏
在iPhone问世之前,几乎都是电阻式触摸屏。 电阻式触摸屏使用压力感应。
线接触检测控制需要直接施力接触,通过检测电阻来定位触摸位置。
ALIENTEK 2.4/2.8/3.5英寸TFTLCD模组附带的触摸屏均为电阻式触摸屏,下面简单介绍一下。
了解电阻式触摸屏的原理。
电阻式触摸屏的主要部分是与显示表面匹配非常好的电阻膜屏幕。 它是多层复合材料
薄膜,以玻璃或硬塑料板为基层,涂有一层透明氧化物金属(透明导电电阻)
导电层上覆盖有硬化、光滑且耐刮擦的塑料层,其内表面还涂有涂层。
它们之间有许多小的(小于1/1000英寸)透明隔离点,将两个导电层分开和绝缘。当手指
当触摸屏幕时,两个导电层在触摸点接触,电阻发生变化,在X和Y方向产生电阻。
信号探索者钢结构模块,然后发送到触摸屏控制器。控制器检测到此接触并计算出(X,Y)的位置,然后根据获得的
位置模拟鼠标的工作方式。 这就是电阻式技术触摸屏最基本的原理。
电阻式触摸屏的优点:精度高、价格低、抗干扰能力强、稳定性好。
电阻式触摸屏的缺点:容易划伤、透光率差、不支持多点触控。
从上面的介绍我们可以知道,触摸屏需要AD转换器,一般都需要控制器。
ALIENTEK TFTLCD模块选择四线电阻式触摸屏。 这种触摸屏的控制芯片有很多,包括:
ADS7843、ADS7846、TSC2046、XPT2046和AK4182等,这些芯片的驱动程序基本相同。
也就是说,只要你写了ADS7843的驱动,这个驱动对其他几款芯片也有效。而且封装里还有一个
这样,完全 PIN TO PIN 兼容。 所以更换起来非常方便。
ALIENTEK TFTLCD模块自带的触摸屏控制芯片是XPT2046。 XPT2046是4线触点
触摸屏控制器包含一个12位分辨率125KHz转换率渐进逼近A/D转换器。 XPT2046支持1.5V起
低电压 I/O 接口至 5.25V。 XPT2046可以通过进行两次A/D转换来检测按下的屏幕位置,此外
另外,还可以测量施加在触摸屏上的压力。内置2.5V参考电压可作为辅助输入和温度测量
与电池监控模式配合使用时,电池监控的电压范围可以是0V至6V。 XPT2046 片上集成温度传感器
传感器。 在2.7V典型工作条件下,参考电压关闭时,功耗可低于0.75mW。 XPT2046采用微型
封装形式:TSSOP-16、QFN-16(0.75mm 厚度)和 VFBGA-48。 工作温度范围为-40℃~+85℃。
该芯片与ADS7843和ADS7846完全兼容。 该芯片的详细使用可以参考这两篇
芯片数据表。
这就是电阻式触摸屏的介绍。
33.1.2 电容式触摸屏
如今,几乎所有的智能手机,包括平板电脑,都采用电容屏作为触摸屏。 电容屏利用人体感觉
应进行接触检测控制,不需要直接接触或只需轻微接触,通过检测感应电流来定位触摸坐标。
ALIENTEK 4.3/7英寸TFTLCD模块附带的触摸屏采用电容式触摸屏。 这里简单介绍一下
电容式触摸屏的原理。
电容式触摸屏主要有两种类型:
1.表面电容触摸屏。
表面电容式触摸屏技术采用ITO(氧化铟锡,一种透明导电材料)导电薄膜来传递电流
场感应方式感应屏幕表面的触摸行为。但表面电容触摸屏有一定的局限性,只能识别
一根手指或一次触摸。
2.投射式电容触摸屏。
投射电容式触摸屏是利用触摸屏电极发射静电场线的传感器。通常用于投射电容传感技术
电容有两种类型:自电容和交互电容。
自电容又称绝对电容,是应用最广泛的方法。 自电容通常指的是扫描电极和地。
玻璃表面有由ITO制成的水平和垂直扫描电极。 这些电极和地面之间形成电流。
公差的两个极端。当你用手或手写笔触摸时,电路中会并联一个电容,从而使扫描线
整体电容发生了变化。扫描时,控制IC依次扫描纵向和横向电极,
利用电容的变化来确定触摸点的坐标位置。笔记本电脑的触摸输入板就采用这种方法。 笔记本电脑
输入板采用X*Y传感电极阵列形成传感网格。 当手指靠近触摸输入板时,手指与传感器之间就会产生信号。
感应电极之间会产生少量电荷。使用特定算法处理来自行和列传感器的信号来确定手指
的位置。
交互电容也称为交叉电容,它在玻璃表面水平和垂直ITO电极的交叉处形成电容。
交互电容的扫描方式是通过扫描每个交叉点的电容变化来确定触摸点的位置。当触摸时
它会影响相邻电极的耦合,从而改变交叉点处的电容。 交互电容的扫描方式可以检测到每个交叉点。
交叉点的电容值随着触摸后电容的变化而变化,因此其所需的扫描时间比自电容扫描方式要长。
需要对X*Y电极进行扫描检测。 目前,智能手机/平板电脑等的触摸屏均采用交互式电容技术。
ALIENTEK选择的电容式触摸屏也采用的是投射式电容屏(交互电容式),所以后者
本节仅以投射电容屏作为介绍。
透射式电容触摸屏采用两排垂直和水平电极组成感应矩阵来感应触摸。 使用两个交叉的电极矩阵,
即:X轴电极和Y轴电极检测各栅传感单元的电容变化,如图33.1.2.1所示:
图33.1.2.1 投射电容屏电极矩阵示意图
示意图中的电极实际上是透明的。 这是为了方便大家理解。图中X、Y轴上的透明电极
电容屏的精度和分辨率与X轴和Y轴的通道数有关。 通道越多,精度越高。以上为电容触摸
屏幕的基本原理,我们来看看电容式触摸屏的优缺点:
电容式触摸屏的优点:手感好、无需校准、支持多点触摸、透光率好。
电容式触摸屏的缺点:成本高、精度低、抗干扰能力差。
在这里提醒大家,电容式触摸屏对于工作环境的要求比较高。 在潮湿、多尘、高低温环境下,
以下情况不适合使用电容屏。
电容触摸屏一般需要驱动IC来检测电容触摸,一般通过IIC接口输出触摸数据。
的。 ALIENTEK 7' TFTLCD模块的电容式触摸屏采用15*10驱动结构(10个感应通道,
15个驱动通道),采用GT811/FT5206作为驱动IC。 ALIENTEK 4.3' TFTLCD 模块有两种类型
做成触摸屏: 1、采用OTT2001A作为驱动IC,采用13*8驱动结构(8个感应通道,13个驱动
渠道); 2、采用GT9147作为驱动IC,采用17*10驱动结构(10个传感通道,17个驱动通道)
路)。
两个模块仅支持最多 5 个触摸点。 本示例支持ALIENTEK的4.3英寸屏幕模块和新的7英寸屏幕模块。
屏幕模块(采用SSD1963+FT5206方案)、电容触摸驱动IC,这里仅介绍OTT2001A和GT9147。
GT811/FT5206的驱动方法与这两款IC类似,可以参考学习。
OTT2001A是台湾旭耀科技生产的电容式触摸屏驱动IC,最多支持208通道支持
SPI/IIC接口,在ALIENTEK 4.3' TFTLCD电容触摸屏上,OTT2001A仅使用104个通道,使用
使用IIC接口。 IIC接口模式下,驱动IC与STM32F4的连接只需要4根线:SDA、SCL、RST
INT、SDA、SCL用于IIC通信,RST为复位引脚(低电平有效),INT为中断输出信号,
我们不详细介绍IIC,请参考第29章。
OTT2001A的设备地址为0X59(不含最低位,转换为读写命令:读:0XB3,写:0XB2),
接下来介绍一下OTT2001A的几个重要寄存器。
1. 手势ID寄存器
手势ID寄存器(00H)用于告诉MCU哪些点是有效的,哪些点是无效的,从而读取相应的数据。
该寄存器各位的说明如表33.1.2.1所示:
表 33.1.2.1 手势 ID 寄存器
OTT2001A最多支持5个触摸点,因此表中仅用5位来表示对应点坐标是否有效,其余的
该位是保留位(读为 0)。 通过读取这个寄存器,我们可以知道哪些点有数据,哪些点没有数据。 如果
读全0表示没有触摸。
2.传感器控制寄存器(ODH)
传感器控制寄存器(ODH),这个寄存器也是8位,只有最高位有效,其他位保留,当最
当高位设置为1时,传感器开启(开始检测); 当高位设置为0时,传感器关闭(停止检测)。
3.坐标数据寄存器(共20个)
共有20个坐标数据寄存器,每个坐标占用4个寄存器。 坐标寄存器与坐标的对应关系如下:
表 33.1.2.2 显示:
表 33.1.2.2 坐标寄存器及坐标对应表
从表中可以看出,可以通过4个寄存器来读取每个坐标的值,比如读取坐标1(X1,Y1),
我们可以读取01H~04H来知道当前坐标1的具体值。这里我们也可以只发送注册
寄存器01,然后连续读取4个字节。 也可以正常读取坐标1。 寄存器地址会自动增加,从而提高读取速度。
加快速度。
这是OTT2001A相关寄存器的介绍。 更详细的信息请参考:OTT2001A IIC协议
本文档的说明.pdf。 OTT2001A只需要简单的初始化就可以正常使用。 初始化过程如下:
位→延迟100ms→释放复位→将传感器控制寄存器的最高位设置为1以启用传感器检查。
正常使用。
另外,OTT2001A还有两个地方需要特别注意:
1、
OTT2001A的寄存器是8位,但发送时必须发送16位(高八位有效)。
正常使用。
2、
OTT2001A的输出坐标默认为:X坐标最大值为2700,Y坐标最大值为1500
分辨率为输出,即输出范围为:X:0~2700,Y:0~1500; MCU 读取
打标后,必须根据LCD分辨率进行转换,才能得到真正的LCD坐标。
下面我们简单介绍一下GT9147。 该芯片是深圳汇顶科技研发的电容式触摸屏驱动IC。
支持100Hz接触扫描频率,支持5点触摸,支持18*10检测通道,适合4.5英寸以下电容触摸
屏幕使用。
与OTT2001A一样,GT9147通过4根线连接到MCU:SDA、SCL、RST和INT。
然而GT9147的IIC地址可以是0X14或0X5D。 复位完成后5ms内,如果INT为高电平
如果是平的,则使用0X14作为地址,否则使用0X5D作为地址。 具体设置过程请参见:GT9147数据
手册.pdf 本文档。 本章我们使用0X14作为设备地址(不包括最低位),转换成读写命令,则读取为:
0X29,写入:0X28),接下来介绍GT9147的一些重要寄存器。
1.控制命令寄存器(0X8040)
该寄存器可以写入不同的值,以实现不同的控制。 我们一般使用0和2这两个值,写2,即
GT9147可以软复位。 硬复位后,一般向该寄存器写入2,实现软复位。然后,写入0,就可以了
正常读取坐标数据(软复位结束)。
2、配置寄存器组(0X8047~0X8100)
这里一共有186个寄存器,用来配置GT9147的各种参数。 这些配置一般都是由厂家提供给我们的(a
数组),所以我们只需要将厂家给我们的配置写入到这些寄存器中就可以完成GT9147
配置。 由于GT9147可以保存配置信息(可以写入内部FLASH,所以不需要每次上电都更新配置),
有几点需要提醒大家: 1、0X8047寄存器用于表示配置文件的版本号。 程序写入的版本号
该版本号必须大于或等于本地保存的 GT9147 版本号,才能更新配置。 2. 0X80FF寄存器用于存储
存储校验和,使0X8047~0X80FF之间所有数据的和为0。 3、0X8100用于控制是否保存配置
本地,写0则不保存配置,写1则保存配置。
3.产品ID寄存器(0X8140~0X8143)
这里一共有4个寄存器,用来保存产品ID。 对于GT9147来说,这4个寄存器的读取为:
四个字符9、1、4、7(ASCII码格式)。因此,我们可以通过这四个寄存器的值来判断驱动程序。
判断IC型号是OTT2001A还是GT9147,以便进行不同的初始化。
4.状态寄存器(0X814E)
该寄存器各位的说明如表33.1.2.3所示:
表 33.1.2.3 各状态寄存器说明
这里,我们只关心最高位和最低4位。 最高位用于指示缓冲区状态。 如果有数据(坐标/
按钮),缓冲区将为1,最低4位用于表示有效触点的数量,范围为:0~5,0表示无触摸,
5表示有5次触摸。这与OTT2001A之前的表示方法略有不同。 OTT2001A代表每一位
联系人,这里是有效联系人值的数量。 最后,每次读取该寄存器后,如果 bit7 有效,
必须写0清除该位,否则下一个数据将不会输出! ! 这点要特别注意! ! !
5、坐标数据寄存器(共30个)
分为5组(5点),每组有6个寄存器来存储数据。 以触点1的坐标数据寄存器组为例。
如表33.1.2.4所示:
表 33.1.2.4 触点 1 坐标寄存器组说明
我们一般只用到触点的x、y坐标,所以我们只需要读取0X8150~0X8153中的数据并组合起来就可以得到
到接触坐标。 另外 4 组由 0X8158、0X8160、0X8168、0X8170 开头的 16 个寄存器组成,分别对应触点 2~4 的坐标。同样,GT9147 也支持寄存器地址自增,我们只需要发送寄存器即可
可以连续读取该组的首地址,GT9147会自动递增地址以提高读取速度。
这是GT9147相关寄存器的介绍。 更详细的信息请参考:GT9147编程指南.pdf
这个文件。
GT9147只需要简单的初始化就可以正常使用。 初始化过程为:硬复位→延时10ms→
结束硬复位→设置IIC地址→延时100ms→软复位→更新配置(如果需要)→结束软复位。此时
GT9147已准备好正常使用。
然后,我们不断查询0X814E寄存器,判断是否有有效触点,如果有,则读取坐标数据
注册以获取联系坐标。 特别注意如果0X814E读取的值的最高位为1,则必须向该位写入0,否则
那么就无法读取下一个坐标数据。
这里介绍电容式触摸屏部分。
33.2 硬件设计
本章实验功能介绍:打开电脑时,首先初始化LCD,读取LCD ID,然后根据LCD ID进行判断。
是电阻式触摸屏还是电容式触摸屏? 如果是电阻式触摸屏,首先读取24C02的数据,判断触摸屏是否已被
校准后,如果没有,请执行校准程序。 校准完毕后,进入电阻式触摸屏测试程序。 如果有
校准完毕后,直接进入电阻式触摸屏测试程序。
如果是4.3寸电容触摸屏,先读取芯片ID,判断是否是GT9147。 如果是,则执行GT9147
如果没有,则执行OTT2001A的初始化代码; 如果是7寸电容触摸屏(仅支持
新款7寸屏采用SSD1963+FT5206方案),执行FT5206的初始化代码,初始化电容触摸
触摸屏幕后,进入电容触摸屏测试程序(电容触摸屏不需要校准!!)。
电阻式触摸屏测试流程与电容式触摸屏测试流程基本相同,只不过电容式触摸屏最多支持同时5点。
触摸,电阻式触摸屏只支持一次触摸,其余完全一样。测试界面右上角会有一个空白的操作区域。
域(RST),点击该处将清除所有输入并恢复白板状态。使用电阻式触摸屏时,可以
通过按KEY0实现强制触摸屏校准,只要按KEY0,就会进入强制校准程序。
需要用到的硬件资源如下:
1) 指示灯DS0
2)KEY0按钮
3)TFTLCD模块(带电阻式/电容式触摸屏)
4)24C02
所有这些资源与STM32F4的连接图之前已经介绍过。 这里我们只关注TFTLCD模块。
模块与STM32F4的连接端口再次说明。 TFTLCD模块的触摸屏(电阻式触摸屏)共有5条线。
与STM32F4连接,连接电路图如图33.2.1所示:
图33.2.1 触摸屏与STM32F4连接图
从图中可以看出,T_MOSI、T_MISO、T_SCK、T_CS和T_PEN分别连接到:PF11、
在 PB2、PB0、PC13 和 PB1 上。
如果是电容式触摸屏,我们的界面和电阻式触摸屏是一样的(上图右侧的界面),只是不使用
有四行而不是五行,即:T_PEN(CT_INT)、T_CS(CT_RST)、T_CLK(CT_SCL) 和
T_MOSI(CT_SDA)。 其中:CT_INT、CT_RST、CT_SCL、CT_SDA分别为OTT2001A/GT9147/FT5206
of:IIC的中断输出信号、复位信号、SCL和SDA信号。这里我们用查询的方式来读取
OTT2001A/GT9147/FT5206的数据不使用OTT2001A/FT5206的中断信号(CT_INT),因此
与STM32F4的连接只需要3根线,但GT9147还需要使用CT_INT进行IIC地址设置,所以
需要 4 根电线进行连接。
33.3 软件设计
当你打开本章的实验工程目录时,可以看到我们在HARDWARE文件夹下新建了一个TOUCH文件。
文件夹,然后创建touch.c、touch.h、ctiic.c、ctiic.h、ott2001a.c、ott2001a.h、gt9147.c、gt9147.h、
ft5206.c、ft5206.h等十个文件用于存放触摸屏相关代码,同时将这些源文件引入到工程中
在HARDWARE组下,将TOUCH文件夹添加到头文件包含路径中。 其中touch.c和touch.h
是电阻式触摸屏部分的代码,顺便也管理了电容式触摸屏的管理和控制。 其他的是电容触摸屏部分的代码。
打开touch.c文件,里面主要是触摸屏相关的代码(主要是电阻式触摸屏的代码)。 我在这
我们没有把它们全部贴出来,只是介绍了几个重要的功能。
首先我们要介绍一下函数TP_Read_XY2,该函数是专门用来读取电阻式触摸屏控制IC的
坐标值(0~4095),TP_Read_XY2代码如下:
//连续 2 次读取触摸屏 IC,且这两次的偏差不能超过
//ERR_RANGE,满足条件,则认为读数正确,否则读数错误.
//该函数能大大提高准确度
//x,y:读取到的坐标值
//返回值:0,失败;1,成功。
#define ERR_RANGE 50 //误差范围
u8 TP_Read_XY2(u16 *x,u16 *y)
{
u16 x1,y1;
u16 x2,y2;
u8 flag;
flag=TP_Read_XY(&x1,&y1);
if(flag==0)return(0);
flag=TP_Read_XY(&x2,&y2);
if(flag==0)return(0);
//前后两次采样在+-50 内
if(((x2<=x1&&x1
该函数使用了一个非常好的方法来读取屏幕坐标值,就是连续读取两次,两次读取的值的差值
不能超过一个特定值(ERR_RANGE),这样可以大大提高触摸屏的准确度。其他
该函数调用的TP_Read_XY函数用于单次读取坐标值。 TP_Read_XY也使用了一些软件过滤
算法,详细见光盘源代码。 接下来我们介绍另一个函数TP_Adjust。 该函数的源码如下:
/触摸屏校准代码
//得到四个校准参数
void TP_Adjust(void)
{
u16 pos_temp[4][2];//坐标缓存值
u8 cnt=0; u32 tem1,tem2;
u16 d1,d2; u16 outtime=0;
double fac;
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;
pos_temp[cnt][1]=tp_dev.y;
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 {CMD_RDX=0XD0;CMD_RDY=0X90;}
//X,Y 方向与屏幕相同
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; }
}
}/
TP_Adjust是这部分的核心代码。 在这里,我向大家介绍一下我们这里使用的触摸屏校准。
正向原理:我们传统的鼠标是相对定位系统,只和上一次鼠标位置的坐标有关。触摸屏是
它是一个绝对坐标系。 您可以直接单击要选择的任何位置。 它与相对定位系统有着本质的区别。绝对坐标系
特点是每个定位坐标与前一个定位坐标没有关系。 每次触摸的数据通过校准转换为屏幕上的位置。
无论什么情况,触摸屏上同一点的这组坐标的输出数据都是稳定的。但是,由于技术原理
原因是不能保证每次触摸同一个点时采样的数据都是相同的。 无法保证绝对坐标定位,点位不准确。 这是
触摸屏最令人担心的问题是漂移。 对于性能和质量都不错的触摸屏来说,漂移并不是很严重。
因此,很多使用触摸屏的系统启动后,在进入应用程序之前必须执行一个校准程序。通常用于应用程序中
使用的 LCD 坐标以像素为单位。 例如:左上角的坐标是一组非0值,如(20, 20),
右下角的坐标为(220, 300)。这些点的坐标以像素为单位,从触摸屏读出的点为
坐标轴的物理坐标,比例因子
程序中首先将物理坐标转换为像素坐标,然后赋值给POS结构体,以达到坐标转换的目的。
校正思路:了解了校正原理后,我们可以推导出以下物理坐标到像素坐标的转换:
改变关系:
LCDx=xfac*Px+xoff;
LCDy=yfac*Py+yoff;
其中(LCDx,LCDy)是LCD上的像素坐标,(Px,Py)是从触摸屏读取的物理坐标。 xfac,
yfac分别是X轴方向和Y轴方向的缩放因子,而xoff和yoff是这两个方向的偏移量。
这样我们只需要提前在屏幕上显示4个点(这四个点的坐标是已知的),并分别按下这四个点即可。
从触摸屏上可以读取四个物理坐标,从而可以通过待定系数法得到四个物理坐标xfac、yfac、xoff、yoff。
参数。我们保存这四个参数。 在以后的使用中,我们会根据这个关系来调整得到的所有物理坐标。
使用公式计算,您将得到准确的屏幕坐标。 就达到了触摸屏校准的目的。
TP_Adjust就是基于上述原理设计的校准函数。 请注意,此函数会被多次使用。
lcddev.width和lcddev.height用于坐标设置,主要是为了兼容不同尺寸的LCD(如320*240、
480*320 和 800*480 屏幕均兼容)。
接下来我们看一下触摸屏初始化函数:TP_Init。 该函数根据LCD的ID(即lcddev.id)来判断触摸屏是否为触摸屏。
无论是电阻屏还是电容屏,都会进行不同的初始化。 函数代码如下:
//触摸屏初始化
//返回值:0,没有进行校准 1,进行过校准
u8 TP_Init(void)
{
if(lcddev.id==0X5510)
//电容触摸屏
{
if(GT9147_Init()==0) //是 GT9147?
{
tp_dev.scan=GT9147_Scan; //扫描函数指向 GT9147 触摸屏扫描
}else
{
OTT2001A_Init();
tp_dev.scan=OTT2001A_Scan;//扫描函数指向 OTT2001A 触摸屏扫描
}
tp_dev.touchtype|=0X80;
//电容屏
tp_dev.touchtype|=lcddev.dir&0X01;//横屏还是竖屏
return 0;
} else if(lcddev.id==0X1963)
{
FT5206_Init();
tp_dev.scan=FT5206_Scan;
//扫描函数指向 GT9147 触摸屏扫描
tp_dev.touchtype|=0X80;
//电容屏
tp_dev.touchtype|=lcddev.dir&0X01;//横屏还是竖屏
return 0;
} else
{
__HAL_RCC_GPIOB_CLK_ENABLE();
//开启 GPIOB 时钟
__HAL_RCC_GPIOC_CLK_ENABLE();
//开启 GPIOC 时钟
__HAL_RCC_GPIOF_CLK_ENABLE();
//开启 GPIOF 时钟
//GPIOB1,2 初始化设置
GPIO_Initure.Pin=GPIO_PIN_1|GPIO_PIN_2;
//PB1/PB2 设置为上拉输入
GPIO_Initure.Mode=GPIO_MODE_INPUT;
//输入模式
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
//初始化
//PB0
GPIO_Initure.Pin=GPIO_PIN_0;
//PB0 设置为推挽输出
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
//初始化
//PC13
GPIO_Initure.Pin=GPIO_PIN_13;
//PC13 设置为推挽输出
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
//初始化
//PF11
GPIO_Initure.Pin=GPIO_PIN_11;
//PF11 设置推挽输出
HAL_GPIO_Init(GPIOF,&GPIO_Initure);
//初始化
TP_Read_XY(&tp_dev.x[0],&tp_dev.y[0]);
//第一次读取初始化
AT24CXX_Init();
//初始化 24CXX
if(TP_Get_Adjdata())return 0;
//已经校准
else
//未校准?
{
LCD_Clear(WHITE);//清屏
TP_Adjust();
//屏幕校准
TP_Save_Adjdata();
}
TP_Get_Adjdata();
}
return 1;
}
这个功能比较简单。 关键点是:tp_dev.scan。 该结构体函数指针默认指向TP_Scan。
如果是电阻屏就用默认的就可以了。 如果是电容屏,则指向新的扫描函数GT9147_Scan,
OTT2001A_Scan或FT5206_Scan(根据芯片ID判断指向哪一个),执行电容触摸屏的扫描功能
数,这些功能稍后会介绍。
其他功能我们这里就不介绍了。 接下来,打开 touch.h 文件。 代码如下:
#define TP_PRES_DOWN 0x80 //触屏被按下
#define TP_CATH_PRES 0x40 //有按键按下了
#define CT_MAX_TOUCH 5
//电容屏支持的点数,固定为 5 点
//触摸屏控制器
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;
extern _m_tp_dev tp_dev;
//触屏控制器在 touch.c 里面定义
//电阻屏芯片连接引脚
#define PEN
PBin(1)
//T_PEN
#define DOUT
PBin(2) //T_MISO
#define TDIN
PFout(11) //T_MOSI
#define TCLK
PBout(0) //T_SCK
#define TCS
PCout(13) //T_CS
//电阻屏函数
void TP_Write_Byte(u8 num);
//向控制芯片写入一个数据
u16 TP_Read_AD(u8 CMD);
//读取 AD 转换值
u16 TP_Read_XOY(u8 xy);
//带滤波的坐标读取(X/Y)
……(//省略部分代码)
u8 TP_Scan(u8 tp);
//扫描
u8 TP_Init(void);
//初始化
#endif
在上面的代码中,我们重点关注_m_tp_dev结构体。 修改后的结构用于管理和记录触摸屏(包括电阻
触摸屏和电容触摸屏)相关信息。通过该结构体,使用时我们通常直接调用tp_dev的phase
您可以通过关闭成员函数/变量屏幕来达到所需的效果。 这样的设计简化了界面,方便管理和维护。 你可以
照着做吧。
ctiic.c和ctiic.h是电容触摸屏的IIC接口部分代码,与第29章的myiic.c和myiic.h基本相同
一样的,这里就不单独介绍了。 接下来,查看文件 ott2001a.c。 代码如下:
//向 OTT2001A 写入一次数据
//reg:起始寄存器地址
//buf:数据缓缓存区
//len:写数据长度
//返回值:0,成功;1,失败.
u8 OTT2001A_WR_Reg(u16 reg,u8 *buf,u8 len)
{
u8 i; u8 ret=0;
CT_IIC_Start();
CT_IIC_Send_Byte(OTT_CMD_WR);CT_IIC_Wait_Ack();//发送写命令
CT_IIC_Send_Byte(reg>>8); CT_IIC_Wait_Ack();
//发送高 8 位地址
CT_IIC_Send_Byte(reg&0XFF); CT_IIC_Wait_Ack(); //发送低 8 位地址
for(i=0;i>8); CT_IIC_Wait_Ack();
//发送高 8 位地址
CT_IIC_Send_Byte(reg&0XFF); CT_IIC_Wait_Ack(); //发送低 8 位地址
CT_IIC_Start();
CT_IIC_Send_Byte(OTT_CMD_RD); CT_IIC_Wait_Ack();//发送读命令
for(i=0;introl(u8 cmd)
{
u8 regval=0X00;
if(cmd)regval=0X80;
OTT2001A_WR_Reg(OTT_CTRL_REG,®val,1);
}
//初始化触摸屏
//返回值:0,初始化成功;1,初始化失败
u8 OTT2001A_Init(void)
{
u8 regval=0;
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE();
//开启 GPIOB 时钟
__HAL_RCC_GPIOC_CLK_ENABLE();
//开启 GPIOC 时钟
//PB1
GPIO_Initure.Pin=GPIO_PIN_1;
//PB1 设置为上拉输入
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
//初始化
//PC13
GPIO_Initure.Pin=GPIO_PIN_13;
//PC13 设置为推挽输出
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
//初始化
CT_IIC_Init(); //初始化电容屏的 I2C 总线
OTT_RST=0;
//复位
delay_ms(100);
OTT_RST=1;
//释放复位
delay_ms(100);
OTT2001A_SensorControl(1);//打开传感器
OTT2001A_RD_Reg(OTT_CTRL_REG,®val,1);//读取传感器运行寄存器的值来判断
//I2C 通信是否正常
printf("CTP ID:%x\r\n",regval);
if(regval==0x80)return 0;
return 1;
}
const u16 OTT_TPX_TBL[5]={OTT_TP1_REG,OTT_TP2_REG,OTT_TP3_REG,OTT_TP4
_REG,OTT_TP5_REG};
//扫描触摸屏(采用查询方式)
//mode:0,正常扫描.
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
u8 OTT2001A_Scan(u8 mode)
{
u8 buf[4], i=0, res=0;
static u8 t=0;//控制查询间隔,从而降低 CPU 占用率
t++;
if((t)==0||t<10)//空闲时,每进入 10 次,才检测 1 次,从而节省 CPU 使用率
{
OTT2001A_RD_Reg(OTT_GSTID_REG,&mode,1);//读取触摸点的状态
if(mode&0X1F)
{
tp_dev.sta=(mode&0X1F)|TP_PRES_DOWN|TP_CATH_PRES;
for(i=0;i<5;i++)
{
if(tp_dev.sta&(1<240)t=10;//重新从 10 开始计数
return res;
}
该部分共有5个函数,其中OTT2001A_WR_Reg和OTT2001A_RD_Reg分别用于读取和写入。
对于OTT2001A芯片,请注意寄存器地址为16位,与OTT2001A手册中介绍的不同。
需要 16 位才能正常运行。此外,我们将重点关注 OTT2001A_Scan 功能。 OTT2001A_Scan功能使用
扫描电容触摸屏上是否有按键按下,由于我们没有使用中断方式读取OTT2001A的数据,
而是使用查询,所以这里使用静态变量来提高效率。 没有触摸时,尽量减少
保证有触摸时能够快速检测到CPU占用率。至于读取OTT2001A数据,则
就是我们上面介绍的方法。 首先读取手势ID寄存器(OTT_GSTID_REG)判断是否有
如果有有效数据,则读取,否则忽略,继续后续处理。
其他功能我们这里就不多介绍了。 接下来我们看一下gt9147.c中的代码。 这里我们只介绍
GT9147_Init和GT9147_Scan两个函数,代码如下:
//初始化 GT9147 触摸屏
//返回值:0,初始化成功;1,初始化失败
u8 GT9147_Init(void)
{
u8 temp[5];
__HAL_RCC_GPIOB_CLK_ENABLE();
//开启 GPIOB 时钟
__HAL_RCC_GPIOC_CLK_ENABLE();
//开启 GPIOC 时钟
//PB1
GPIO_Initure.Pin=GPIO_PIN_1;
//PB1 设置为上拉输入
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
//初始化
//PC13
GPIO_Initure.Pin=GPIO_PIN_13;
//PC13 设置为推挽输出
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
//初始化
CT_IIC_Init();
//初始化电容屏的 I2C 总线
GT_RST=0;
//复位
delay_ms(10);
GT_RST=1;
//释放复位
delay_ms(10);
GPIO_Initure.Pin=GPIO_PIN_1;
//PB1 设置为上拉输入
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //输出模式
GPIO_Initure.Pull=GPIO_NOPULL;
//无上下拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
//初始化
delay_ms(100);
GT9147_RD_Reg(GT_PID_REG,temp,4);//读取产品 ID
temp[4]=0;
printf("CTP ID:%s\r\n",temp);
//打印 ID
if(strcmp((char*)temp,"9147")==0)
//ID==9147
{
temp[0]=0X02;
GT9147_WR_Reg(GT_CTRL_REG,temp,1);//软复位 GT9147
GT9147_RD_Reg(GT_CFGS_REG,temp,1);//读取 GT_CFGS_REG 寄存器
if(temp[0]<0X60)//默认版本比较低,需要更新 flash 配置
{
printf("Default Ver:%d\r\n",temp[0]);
GT9147_Send_Cfg(1);//更新并保存配置
}
delay_ms(10);
temp[0]=0X00;
GT9147_WR_Reg(GT_CTRL_REG,temp,1);//结束复位
return 0;
}
return 1;
}
const u16 GT9147_TPX_TBL[5]={GT_TP1_REG,GT_TP2_REG,GT_TP3_REG,
GT_TP4_REG,GT_TP5_REG};
//扫描触摸屏(采用查询方式)
//mode:0,正常扫描.
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
u8 GT9147_Scan(u8 mode)
{
u8 buf[4]; u8 i=0; u8 res=0; u8 temp;
static u8 t=0;//控制查询间隔,从而降低 CPU 占用率
t++;
if((t)==0||t<10)//空闲时,每进入 10 次,函数才检测 1 次,从而节省 CPU 使用率
{
GT9147_RD_Reg(GT_GSTID_REG,&mode,1);//读取触摸点的状态
if((mode&0XF)&&((mode&0XF)<6))
{
temp=0XFF<<(mode&0XF);//将点的个数转换为 1 的位数,匹配 tp_dev.sta 定义
tp_dev.sta=(~temp)|TP_PRES_DOWN|TP_CATH_PRES;
for(i=0;i<5;i++)
{
if(tp_dev.sta&(1<240)t=10;//重新从 10 开始计数
return res;
}
上述代码中,GT9147_Init用于初始化GT9147。 该函数读取四个寄存器0X8140~0X8143
device并判断是否为:“9147”,判断是否为GT9147芯片。 读取正确的ID后,软复位GT9147。
然后根据当前芯片版本号判断是否需要更新配置,并通过GT9147_Send_Cfg函数发送配置信息。
信息(一个数组),配置完成后,软复位结束,即GT9147初始化完成。 GT9147_Scan函数,用于读取
获取触摸屏坐标数据。 这与之前的OTT2001A_Scan类似。 您可以查看源代码。
最后,我们打开main.c并修改一些代码。 我不会在这里发布所有内容,而只会引入三个重要功能:
//5 个触控点的颜色(电容触摸屏用)
const u16 POINT_COLOR_TBL[5]={RED,GREEN,BLUE,BROWN,GRED};
//电阻触摸屏测试函数
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-24)&&tp_dev.y[0]<16)Load_Drow_Dialog();
else TP_Draw_Big_Point(tp_dev.x[0],tp_dev.y[0],RED);/画图
}
}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)LED0=!LED0;
}
}
//电容触摸屏测试函数
void ctp_test(void)
{
u8 t=0; u8 i=0;
u16 lastpos[5][2];
//最后一次的数据
while(1)
{
tp_dev.scan(0);
for(t=0;t<5;t++)
{
if((tp_dev.sta)&(1<(lcddev.width-24)&&tp_dev.y[t]<20)
{
Load_Drow_Dialog();//清除
}
}
}else lastpos[t][0]=0XFFFF;
}
delay_ms(5);i++;
if(i%20==0)LED0=!LED0;
}
}
int main(void)
{
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz
delay_init(168); //初始化延时函数
uart_init(115200); //初始化 USART
usmart_dev.init(84);
//初始化 USMART
LED_Init();
//初始化 LED
KEY_Init();
//初始化 KEY
LCD_Init();
//初始化 LCD
tp_dev.init();
//触摸屏初始化
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"TOUCH TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2017/4/14");
if(tp_dev.touchtype!=0XFF)LCD_ShowString(30,130,200,16,16,"Press KEY0 to Adjust");
//电阻屏才显示
delay_ms(1500);
Load_Drow_Dialog();
if(tp_dev.touchtype&0X80)ctp_test();//电容屏测试
else rtp_test();
//电阻屏测试
}
这三个功能在下面介绍。
rtp_test,此功能用于测试电阻触摸屏。 功能代码相对简单,即扫描按钮和触摸屏。
如果按下触摸屏,将在触摸屏上绘制一条线。 如果按下“ rst”区域,则将清除屏幕。 如果键键0
按以执行触摸屏校准。
ctp_test,此功能用于电容式触摸屏测试,因为我们使用tp_dev.sta标记当前按下的触摸
屏幕点,以确定是否存在电容触摸屏,也就是说,以确定tp_dev.sta的最低数字,如果有数据,
如果没有数据,它将被忽略,并且五个虚线的颜色不同以易于区分。
需要校准,因此没有校准程序。
主要功能相对简单。 它初始化了相关的外围设备,然后选择根据触摸屏类型执行CTP_Test。
它是rtp_test。
就是这样,对于软件部分,让我们来看看下载验证。
33.4下载验证
成功编译代码后,我们将代码下载到Alientek Explorer STM32F4开发板和电阻器
触摸屏测试接口如图33.4.1所示:
图33.4.1电阻触摸屏测试程序运行效果
在图片中,我们在电阻屏幕上绘制了一些内容。 右上角的第一个可用于清除屏幕。 单击此区域清除屏幕。
重画。 另外,按KEY0进入校准模式。 如果发现触摸屏不准确,则可以按KEY0进入校准。
重新校准它,可以正常使用。
如果是电容触摸屏,则测试接口如图33.4.2:
图33.4.2电容触摸屏测试接口
在图片中,还输入了一些内容。电容屏幕支持多点触摸,每个点都有不同的颜色。 图片中的波浪
线条通过三分触摸绘制探索者钢结构模块,最多可以触摸五个点。注意:电容触摸屏支撑:Alientek 4.3英寸触摸屏
电容式触摸屏模块或Alientek的新型7英寸电容触摸屏模块(SSD1963+FT5206解决方案),旧模型7
此例程不支持1英寸电容触摸屏模块(CPLD+GT811解决方案)! !
同样,按右上角的RST徽标清除屏幕。 电容屏幕不需要校准,因此按键0没有效果。 Key0学校
准确性仅对电阻屏幕有效。