OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 5160|回复: 0

[XILINX] 【正点原子FPGA连载】第二十八章 TFT-LCD画板实验--摘自【正点原子】领航者ZYNQ之嵌入式开发指南_V1.2

[复制链接]

1107

主题

1118

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4615
金钱
4615
注册时间
2019-5-8
在线时间
1218 小时
发表于 2020-9-23 11:42:21 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2020-9-23 11:42 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入群聊
5)关注正点原子公众号,获取最新资料更新
1.jpg
1120.png

第二十八章TFT-LCD画板实验

现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。在本章中,我们将向大家介绍ZYNQ控制ALIENTKE LCD电容触摸模块,实现触摸屏驱动,最终实现一个手写板的功能。
本章包括以下几个部分:
1         
1.1        简介
1.2        实验任务
1.3        硬件设计
1.4        软件设计
1.5        下载验证
1.1 简介
目前最常用的触摸屏有两种:电阻式触摸屏与电容式触摸屏。下面,我们来分别介绍。
电阻式触摸屏
在Iphone面世之前,几乎清一色的都是使用电阻式触摸屏,电阻式触摸屏利用压力感应进行触点检测控制,需要直接应力接触,通过检测电阻来定位触摸位置。
正点原子 2.4/2.8/3.5寸LCD模块自带的触摸屏都属于电阻式触摸屏,下面简单介绍下电阻式触摸屏的原理。
电阻触摸屏的主要部分是一块与显示器表面非常配合的电阻薄膜屏,这是一种多层的复合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也涂有一层涂层、在它们之间有许多细小的(小于1/1000英寸)的透明隔离点把两层导电层隔开绝缘。当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在X和Y两个方向上产生信号,然后送触摸屏控制器。控制器侦测到这一接触并计算出(X,Y)的位置,再根据获得的位置模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本的原理。
电阻触摸屏的优点:精度高、价格便宜、抗干扰能力强、稳定性好。
电阻触摸屏的缺点:容易被划伤、透光性不太好、不支持多点触摸。
从以上介绍可知,触摸屏都需要一个AD转换器,一般来说是需要一个控制器的。正点原子 LCD模块选择的是四线电阻式触摸屏,这种触摸屏的控制芯片有很多,包括:ADS7843、ADS7846、TSC2046、XPT2046和AK4182等。这几款芯片的驱动基本上是一样的,也就是你只要写出了ADS7843的驱动,这个驱动对其他几个芯片也是有效的。而且封装也有一样的,完全PIN TO PIN兼容。所以在替换起来,很方便。
正点原子 LCD模块自带的触摸屏控制芯片为XPT2046。XPT2046是一款4导线制触摸屏控制器,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046支持从1.5V到5.25V的低电压I/O接口。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的,关于这个芯片的详细使用,可以参考这两个芯片的datasheet。
电阻式触摸屏就介绍到这里。
电容式触摸屏
现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。
正点原子 4.3/7寸LCD模块自带的触摸屏采用的是电容式触摸屏,下面简单介绍下电容式触摸屏的原理。
电容式触摸屏主要分为两种:
1、表面电容式电容触摸屏。
表面电容式触摸屏技术是利用ITO(铟锡氧化物,一种透明的导电材料)导电膜,通过电场感应方式感测屏幕表面的触摸行为进行。但是表面电容式触摸屏有一些局限性,它只能识别一个手指或者一次触摸。
投射式电容触摸屏
投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。一般用于投射电容传感技术的电容类型有两种:自我电容和交互电容。
自我电容又称绝对电容,是最广为采用的一种方法,自我电容通常是指扫描电极与地构成的电容。在玻璃表面有用ITO制成的横向与纵向的扫描电极,这些电极和地之间就构成一个电容的两极。当用手或触摸笔触摸的时候就会并联一个电容到电路中去,从而使在该条扫描线上的总体的电容量有所改变。在扫描的时候,控制IC依次扫描纵向和横向电极,并根据扫描前后的电容变化来确定触摸点坐标位置。笔记本电脑触摸输入板就是采用的这种方式,笔记本电脑的输入板采用X*Y的传感电极阵列形成一个传感格子,当手指靠近触摸输入板时,在手指和传感电极之间产生一个小量电荷。采用特定的运算法则处理来自行、列传感器的信号来确定手指的位置。
交互电容又叫做跨越电容,它是在玻璃表面的横向和纵向的 ITO 电极的交叉处形成电容。交互电容的扫描方式就是扫描每个交叉处的电容变化,来判定触摸点的位置。当触摸的时候就会影响到相邻电极的耦合,从而改变交叉处的电容量,交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化,因而它需要的扫描时间与自我电容的扫描方式相比要长一些,需要扫描检测X*Y根电极。目前智能手机/平板电脑等的触摸屏,都是采用交互电容技术。
正点原子 所选择的电容触摸屏,也是采用的是投射式电容屏(交互电容类型),所以后面仅以投射式电容屏作为介绍。
透射式电容触摸屏采用纵横两列电极组成感应矩阵,来感应触摸。以两个交叉的电极矩阵,即:X轴电极和Y轴电极,来检测每一格感应单元的电容变化,如下图所示:
image014.jpg

25.1.1 投射式电容屏电极矩阵示意图
示意图中的电极,实际是透明的,这里是为了方便大家理解。图中,X、Y轴的透明电极电容屏的精度、分辨率与X、Y轴的通道数有关,通道数越多,精度越高。以上就是电容触摸屏的基本原理,接下来看看电容触摸屏的优缺点:
电容触摸屏的优点:手感好、无需校准、支持多点触摸、透光性好。
电容触摸屏的缺点:成本高、精度不高、抗干扰能力差。
这里特别提醒大家电容触摸屏对工作环境的要求是比较高的,在潮湿、多尘、高低温环境下面,都是不适合使用电容屏的。
电容触摸屏一般都需要一个驱动IC来检测电容触摸,且一般是通过IIC接口输出触摸数据的。正点原子 7’LCD模块的电容触摸屏,使用FT5206/FT5426做为驱动IC,采用的是15*28的驱动结构(15个感应通道,28个驱动通道)。正点原子 4.3’LCD模块则使用GT9147作为驱动IC,采用17*10的驱动结构(10个感应通道,17个驱动通道)。
这两个模块都只支持最多5点触摸,本例程除CPLD方案的V1版本7寸屏模块不支持外,其他所有正点原子的LCD模块都支持,电容触摸驱动IC,这里只介绍GT9147的驱动,FT5206和FT5426的驱动同GT9147类似,大家可以参考着学习即可。
下面我们简单介绍下GT9147,该芯片是深圳汇顶科技研发的一颗电容触摸屏驱动IC,支持100Hz触点扫描频率,支持5点触摸,支持18*10个检测通道,适合小于4.5寸的电容触摸屏使用。
GT9147与FPGA连接是通过4根线:SDA、SCL、RST和INT。其中:SDA和SCL是IIC通信用的,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的各个参数,这些配置一般由厂家提供给我们(一个数组),所以我们只需要将厂家给我们的配置,写入到这些寄存器里面,即可完成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码格式)。因此,我们可以通过这4个寄存器的值,来判断驱动IC的型号,从而判断是GT9147还是FT5206,以便执行不同的初始化。
4,状态寄存器(0X814E)
该寄存器各位描述如下表所示:
表 25.1.2 寄存器定义
  
寄存器
  
bit7
bit6
bit5
bit4
bit3
bit2
bit1
bit0
0X814E
buffer状态
大点
接近
  
有效
按键
有效触点个数
这里,我们仅关心最高位和最低4位,最高位用于表示buffer状态,如果有数据(坐标/按键),buffer就会是1,最低4位用于表示有效触点的个数,范围是:0~5,0表示没有触摸,5表示有5点触摸。最后,该寄存器在每次读取后,如果bit7有效,则必须写0,清除这个位,否则不会输出下一次数据!!这个要特别注意!!!
5,坐标数据寄存器(共 30 个)
这里共分成5组(5个点),每组6个寄存器存储数据,以触点1的坐标数据寄存器组为例,如下表所示:
表 25.1.3 触点 1 坐标寄存器组描述
  
寄存器
  
bit7~0
寄存器
bit7~0
0X8150
触点1 x坐标低8位
0X8150
触点1 x坐标低8位
0X8150
触点1 x坐标低8位
0X8150
触点1 x坐标低8位
0X8150
触点1触摸尺寸低8位
0X8150
触点1触摸尺寸高8位
我们一般只用到触点的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,否则无法读到下一次坐标数据。
特别说明:FT5206和FT5426的驱动代码完全一模一样,他们只是版本号读取的时候稍有差异,读坐标数据和配置等操作动完全是一模一样的。所以,这两个电容屏驱动IC,可以共用一个.c文件(ft5206.c)。电容式触摸屏部分,就介绍到这里。
1.2 实验任务
本章我们使用ZYNQ结合TFT-LCD驱动,实现在LCD屏幕上触摸画线功能。
1.3 硬件设计
本章实验的PL端的硬件框架是在“PS通过VDMA驱动LCD显示”实验的基础上搭建起来的,整个系统的架构图如下图所示:
image015.png
图25.3.1 LCD的4个触摸驱动芯片引脚
可以看出,与“PS通过VDMA驱动LCD显示”实验相比,我们加入了通过EMIO来检测LCD屏触摸状态的功能。这一部分功能是由PS端来实现的。PS不断地扫描LCD的触摸状态,包括是否有触摸动作发生、当前触摸点的坐标等信息,根据扫描到的信息来决定是否向VDMA的Frame Buffer中写入数据以及向Frame Buffer的哪个地址写入数据等等。然后位于PL端的VDMA读取逻辑不断地将Frame Buffer中存储的数据送给LCD进行显示。
PS与LCD的触摸芯片之间的通信(即图中最下面的粉红色线),是由PS端GPIO引出的4个EMIO来完成的,包括CT_RST、CT_INT、IIC2_SCL、IIC2_SDA。CT_RST是ZYNQ送给触摸芯片的复位信号。CT_INT是触摸芯片送给ZYNQ的中断信号,用于指示是否有触摸事件发生。IIC2_SCL、IIC2_SDA是触摸芯片内置的IIC总线,ZYNQ使用它来访问触摸芯片的内部寄存器,已完成对触摸芯片的初始化、读取触摸点的坐标等动作。这4个引脚如下图所示:
image016.png

25.3.2 LCD4个触摸驱动芯片引脚
在Vivado IP Integrator设计画布中对PS添加4个EMIO的设置如下图所示:
image017.png
图25.3.2 添加4个EMIO的设置
添加后的4个EMIO如下图所示:
image018.png

图25.3.2 添加后的4个EMIO
对这4个EMIO进行管脚约束,如下所示:
  1. #lcd_scl:
  2. set_property -dict {PACKAGE_PIN V17IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[0]}]
  3. #lcd_sda:
  4. set_property -dict {PACKAGE_PIN M19IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[1]}]
  5. #CT_RST
  6. set_property -dict {PACKAGE_PIN Y17IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[2]}]
  7. #CT_INT
  8. set_property -dict {PACKAGE_PIN V18IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[3]}]
复制代码
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > Launch SDK,启动SDK软件。
1.4 软件设计
PS端软件的设计与“PS通过VDMA驱动LCD显示”实验部分相同,有差异的地方在于,加入了EMIO底层配置和驱动函数、触摸芯片底层驱动函数以及主函数中的扫描控制代码。工程源文件的目录结构如下图所示:
image011.png

25.4.1 工程目录结构
我们先来看看main.c源文件,这里就不全部贴出来了,仅介绍main函数,代码如下:
  1.   intmain(void)
  2.   {
  3.      //获取LCD的ID
  4.      XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);
  5.      lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
  6.      xil_printf("LCD ID: %x\r\n",lcd_id);
  7.   
  8.      //根据获取的LCD的ID号来进行video参数的选择
  9.      switch(lcd_id){
  10.          case 0x4342 :vd_mode = VMODE_480x272; break;  //4.3寸屏,480*272分辨率
  11.          case 0x7084 :vd_mode = VMODE_800x480; break;  //7寸屏,800*480分辨率
  12.          case 0x7016 :vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率
  13.          case 0x1018 :vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率
  14.          default :vd_mode = VMODE_800x480; break;
  15.      }
  16.   
  17.      emio_init();
  18.   
  19.      //配置VDMA
  20.      run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width,vd_mode.height,
  21.                              frame_buffer_addr,0, 0,ONLY_READ);
  22.   
  23.      //清空DDR3帧缓存空间
  24.     memset(frame_buffer_addr,0xFF,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*BYTES_PIXEL);
  25.      //关闭cache,否则由于对FrameBuffer的写入延时比较大,画板的效果会很慢
  26.      Xil_DCacheDisable();
  27.   
  28.       //初始化Display controller
  29.      DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
  30.       //设置VideoMode
  31.      DisplaySetMode(&dispCtrl, &vd_mode);
  32.      DisplayStart(&dispCtrl);
  33.   
  34.      tp_dev.init(); //触摸屏初始化
  35.      delay_ms(1500);
  36.   
  37.      if (tp_dev.touchtype& 0X80)  //如果是电容屏
  38.      {
  39.          u8 t = 0;
  40.          u8 i = 0;
  41.          u16 lastpos[10][2]; //最后一次的数据
  42.          u8 maxp = 5;        //最大触摸点数
  43.          if (lcd_id == 0X1018)
  44.              maxp = 10;
  45.          while(1)
  46.          {
  47.              //扫描当前触摸屏的触摸状态
  48.              tp_dev.scan();
  49.   
  50.              //对每个触摸点进行处理
  51.              for (t = 0; t < maxp; t++)
  52.              {
  53.                  if( (tp_dev.sta) & (1 << t) )    //如果有触摸
  54.                  {
  55.                      //如果当前触摸点的坐标在LCD的宽度和高度范围内
  56.                      if( (tp_dev.x[t] < vd_mode.height) && (tp_dev.y[t] < vd_mode.width) )
  57.                      {
  58.                          if (lastpos[t][0] == 0XFFFF)
  59.                          {
  60.                              lastpos[t][0] =tp_dev.x[t];
  61.                              lastpos[t][1] =tp_dev.y[t];
  62.                          }
  63.                          lcd_draw_bline(lastpos[t][0] , lastpos[t][1],
  64.                                          tp_dev.x[t]   , tp_dev.y[t],
  65.                                          2             , POINT_COLOR_TBL[t]
  66.                                        );  //画线
  67.                          lastpos[t][0] =tp_dev.x[t];
  68.                          lastpos[t][1] =tp_dev.y[t];
  69.   
  70.                          if( (tp_dev.x[t] < 30) && (tp_dev.y[t] < 30) )
  71.                          {
  72.                              //清空DDR3帧缓存空间
  73.    memset(frame_buffer_addr,0xFF,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*BYTES_PIXEL);
  74.                          }
  75.                      }
  76.                  }
  77.                  //如果无触摸
  78.                  else
  79.                      lastpos[t][0] = 0XFFFF;
  80.              }
  81.              delay_ms(5);
  82.              i++;
  83.          }
  84.      }
  85.       return 0;
复制代码
main 函数比较简单,获取LCD的ID,初始化相关外设,输出提示信息。然后判断当前的触摸屏是不是电容屏,如果是,则在while(1)死循环中不断地扫描是否有触摸发生,如代码中第96行所示。我们采用tp_dev.sta来标记当前按下的触摸屏点数,所以判断是否有电容触摸屏按下,也就是判断tp_dev.sta的最低5位,如果有数据,则划线,如果没数据则忽略,且5个点划线的颜色各不一样,方便区分。另外,电容触摸屏不需要校准,所以没有校准程序。
此外还加入清屏功能,如果检测到XY方向的坐标值均小于30,即当前触摸点在屏幕的最边角处,则清除整个Frame Buffer,如代码第118行至122行所示。
APP文件夹下是延时函数的源文件。delay.c文件内容如下:
  1.   #include "delay.h"
  2.   #include "xil_types.h"
  3.   
  4.   //延时函数,单位毫秒
  5.   void delay_ms(u32n)
  6.   {
  7.       usleep(n*1000);
  8.   }
  9.   
  10.   //延时函数,单位微秒
  11.   void delay_us(u32n)
  12.   {
  13.       usleep(n);
  14.   }
复制代码
可以看到,其封装了两个函数,一个延时函数以毫秒为单位,另一个以微秒为单位。
emio_iic_cfg文件夹存放用于EMIO底层配置和驱动函数的源文件。
emio_iic_cfg.c源文件的代码如下所示:
  1.   #include"emio_iic_cfg.h"
  2.   
  3.   #define GPIOPS_ID XPAR_XGPIOPS_0_DEVICE_ID  //PS 端GPIO 器件 ID
  4.   
  5.   static  XGpioPs gpiops_inst; //PS端 GPIO 驱动实例
  6.   
  7.   //EMIO初始化
  8.   void emio_init(void)
  9.   {
  10.       XGpioPs_Config *gpiops_cfg_ptr; //PS 端GPIO 配置信息
  11.   
  12.       //根据器件 ID 查找配置信息
  13.       gpiops_cfg_ptr =XGpioPs_LookupConfig(GPIOPS_ID);
  14.   
  15.       //初始化器件驱动
  16.       XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);
  17.   
  18.       //设置 iic端口 为输出
  19.       XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SCL_NUM, 1);
  20.       XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SDA_NUM, 1);
  21.       XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_CT_RST_NUM, 1);
  22.   
  23.       //使能iic端口 输出
  24.       XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SCL_NUM, 1);
  25.       XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SDA_NUM, 1);
  26.       XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_CT_RST_NUM, 1);
  27.   
  28.       //将iic的SCLK和SDA都拉高
  29.       XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);
  30.       XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, 1);
  31.   }
  32.   
  33.   //设置 iic的SDA端口为输入
  34.   void  SDA_IN( void )
  35.   {
  36.       XGpioPs_SetOutputEnablePin (&gpiops_inst, EMIO_SDA_NUM, 0);
  37.       XGpioPs_SetDirectionPin    (&gpiops_inst,EMIO_SDA_NUM, 0);
  38.   }
  39.   
  40.   //设置 iic的SDA端口为输出
  41.   void  SDA_OUT( void )
  42.   {
  43.       XGpioPs_SetDirectionPin    (&gpiops_inst,EMIO_SDA_NUM, 1);
  44.       XGpioPs_SetOutputEnablePin (&gpiops_inst, EMIO_SDA_NUM, 1);
  45.   }
  46.   
  47.   //赋值 iic的SCL端口
  48.   void  IIC_SCL( u8 x )
  49.   {
  50.       XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, x);
  51.   }
  52.   
  53.   //赋值 iic的SDA端口
  54.   void  IIC_SDA( u8 x )
  55.   {
  56.       XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, x);
  57.   }
  58.   
  59.   //读取 iic的SDA端口
  60.   u8 READ_SDA ( void )
  61.   {
  62.       return  XGpioPs_ReadPin(&gpiops_inst,EMIO_SDA_NUM) ;
  63.   }
  64.   
  65.   //赋值 触摸芯片的RST端口
  66.   void  CT_RST( u8 x )
  67.   {
  68.       XGpioPs_WritePin(&gpiops_inst, EMIO_CT_RST_NUM, x);
  69.   }
  70.   
  71.   //设置触摸芯片的INT端口的方向
  72.   void  INT_DIR( u8 x )
  73.   {
  74.   
  75.       XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_CT_INT_NUM, x);
  76.       XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_CT_INT_NUM, x);
  77.   }
  78.   
  79.   //赋值触摸芯片的INT端口
  80.   void  INT( u8 x )
  81.   {
  82.       XGpioPs_WritePin(&gpiops_inst, EMIO_CT_INT_NUM, x);
  83.   }
  84.   
  85.   //读取触摸芯片的INT端口
  86.   u8 INT_RD( void )
  87.   {
  88.       return  XGpioPs_ReadPin(&gpiops_inst,EMIO_CT_INT_NUM) ;
  89.   }
复制代码
可以看到,其中包含了对EMIO(包括IIC的SCK和SDA引脚、触摸芯片的RST和INT引脚)进行初始化和操作的各个函数。我们可以在程序中的其他地方直接调用这些函数,以完成所需的功能。
TOUCH文件夹下的文件用于驱动正点原子不同型号的采用IIC协议的电容触摸屏,myiic文件是用C语言模拟的IIC协议。
我们先来看一下使用C语言来模拟IIC协议的myiic.c文件,如下所示:
  1.   #include "myiic.h"
  2.   #include"../emio_iic_cfg/emio_iic_cfg.h"

  3.   //初始化IIC
  4.   void IIC_Init(void)
  5.   {
  6.       IIC_SCL(1);
  7.       IIC_SDA(1);
  8.   }
  9.   
  10.   //产生IIC起始信号
  11.   void IIC_Start(void)
  12.   {
  13.       SDA_OUT();   //sda线输出
  14.       IIC_SDA(1);
  15.       IIC_SCL(1);
  16.       usleep(2);
  17.       IIC_SDA(0);  //START信号:SCL为高时,SDA由高变低
  18.       usleep(2);
  19.       IIC_SCL(0);  //钳住I2C总线,准备发送或接收数据
  20.   }
  21.   
  22.   //产生IIC停止信号
  23.   void IIC_Stop(void)
  24.   {
  25.       SDA_OUT();  //sda线输出
  26.       IIC_SCL(0);
  27.       IIC_SDA(0); //STOP信号:SCL为高时,SDA由低变高
  28.       usleep(2);
  29.       IIC_SCL(1);
  30.       usleep(2);
  31.       IIC_SDA(1); //发送I2C总线结束信号
  32.       usleep(2);
  33.   }
  34.   
  35.   //等待应答信号到来
  36.   //返回值:1,接收应答失败
  37.   //        0,接收应答成功
  38.   u8 IIC_Wait_Ack(void)
  39.   {
  40.       u8 ucErrTime=0;
  41.       SDA_IN();      //SDA设置为输入
  42.       IIC_SDA(1);
  43.       usleep(2);
  44.       IIC_SCL(1);
  45.       usleep(2);
  46.       while( READ_SDA() ) {
  47.           ucErrTime++;
  48.           if(ucErrTime>250) {
  49.               IIC_Stop();
  50.               return 1;
  51.           }
  52.       }
  53.       IIC_SCL(0);//时钟输出0
  54.       return 0;
  55.   }
  56.   
  57.   //产生ACK应答
  58.   void IIC_Ack(void)
  59.   {
  60.       IIC_SCL(0);
  61.       SDA_OUT();
  62.       IIC_SDA(0);
  63.       usleep(2);
  64.       IIC_SCL(1);
  65.       usleep(2);
  66.       IIC_SCL(0);
  67.   }
  68.   
  69.   //不产生ACK应答
  70.   void IIC_NAck(void)
  71.   {
  72.       IIC_SCL(0);
  73.       SDA_OUT();
  74.       IIC_SDA(1);
  75.       usleep(1);
  76.       IIC_SCL(1);
  77.       usleep(1);
  78.       IIC_SCL(0);
  79.   }
  80.   
  81.   //IIC发送一个字节
  82.   //返回从机有无应答
  83.   //1,有应答
  84.   //0,无应答
  85.   void IIC_Send_Byte(u8txd)
  86.   {
  87.       u8 t;
  88.       SDA_OUT();
  89.       IIC_SCL(0);//拉低时钟开始数据传输
  90.       for(t=0; t<8; t++) {
  91.           IIC_SDA((txd&0x80)>>7);
  92.           txd<<=1;
  93.           usleep(1);
  94.           IIC_SCL(1);
  95.           usleep(1);
  96.           IIC_SCL(0);
  97.           usleep(1);
  98.       }
  99.   }
  100.   
  101.   //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
  102.   u8 IIC_Read_Byte(unsigned char ack)
  103.   {
  104.       unsigned char i,receive=0;
  105.       SDA_IN();//SDA设置为输入
  106.       for(i=0; i<8; i++ ) {
  107.           IIC_SCL(0);
  108.           usleep(1);
  109.           IIC_SCL(1);
  110.           receive<<=1;
  111.           if( READ_SDA() )
  112.               receive++;
  113.           usleep(1);
  114.       }
  115.       if (!ack)
  116.           IIC_NAck();//发送nACK
  117.       else
  118.           IIC_Ack(); //发送ACK
  119.       returnreceive;
  120.   }
复制代码
该部分为IIC驱动代码,实现包括IIC的初始化(IO口)、IIC开始、IIC结束、ACK、IIC读写等功能,在其他函数里面,只需要调用相关的IIC函数就可以和外部IIC器件通信了,这里并不局限于TOUCH的驱动,该段代码可以用在任何IIC设备上。
接下来我们看一下电容触摸屏的驱动代码。touch.h头文件的代码如下所示:
  1.   #ifndef__TOUCH_H__
  2.   #define __TOUCH_H__
  3.   
  4.   #include "gt9147.h"
  5.   #include "gt9271.h"
  6.   #include "ft5206.h"
  7.   #include "xil_types.h"
  8.   
  9.   #define TP_PRES_DOWN 0x8000     //触屏被按下
  10.   #define TP_CATH_PRES 0x4000     //有按键按下了
  11.   #define CT_MAX_TOUCH 10         //电容屏支持的点数,固定为10点
  12.   
  13.   #define PEN  IORD_ALTERA_AVALON_PIO_DATA(TOUCH_INT_BASE)   //T_PEN
  14.   
  15.   //触摸屏控制器
  16.   typedef struct
  17.   {
  18.       u8 (*init)(void);           //初始化触摸屏控制器
  19.       u8 (*scan)(void);           //扫描触摸屏.0,屏幕扫描;1,物理坐标;
  20.       u16 x[CT_MAX_TOUCH];        //当前坐标
  21.       u16 y[CT_MAX_TOUCH];        
  22.       //电容屏最多有10组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,用
  23.                                   //x[9],y[9]存储第一次按下时的坐标.
  24.       u16 sta;                    //笔的状态
  25.                                   //b15:按下1/松开0;
  26.                                   //b14:0,没有按键按下;1,有按键按下.
  27.                                   //b13~b10:保留
  28.                                   //b9~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)
  29.   //新增的参数,当触摸屏的左右上下完全颠倒时需要用到.
  30.   //b0:0,竖屏(适合左右为X坐标,上下为Y坐标的TP)
  31.   //   1,横屏(适合左右为Y坐标,上下为X坐标的TP)
  32.   //b1~6:保留.
  33.   //b7:0,电阻屏
  34.   //   1,电容屏
  35.       u8 touchtype;
  36.   }_m_tp_dev;
  37.   
  38.   extern_m_tp_dev tp_dev;        //触屏控制器在touch.c里面定义
  39.   
  40.   //电容屏 共用函数
  41.   u8 TP_Scan();                   //扫描
  42.   void TP_Init(void);             //初始化
  43.   
  44.   #endif
复制代码
上述代码,我们重点看看_m_tp_dev结构体,该结构体用于管理和记录触摸屏相关信息。通过结构体,在使用的时候,我们一般直接调用tp_dev的相关成员函数/变量,即可达到需要的效果,这种设计简化了接口,且方便管理和维护,大家可以效仿一下。
现在,我们看一下touch.c里面的代码:
  1.   #include "touch.h"
  2.   #include "../APP/delay.h"
  3.   #include "stdlib.h"
  4.   #include "math.h"
  5.   #include "../main.h"
  6.   #include "xil_types.h"
  7.   #include"../emio_iic_cfg/emio_iic_cfg.h"
  8.   
  9.   _m_tp_dev tp_dev= {
  10.       TP_Init,
  11.       TP_Scan,
  12.       0,
  13.       0,
  14.       0,
  15.       0,
  16.   };
  17.   
  18.   //触摸按键扫描
  19.   //tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用)
  20.   //返回值:当前触屏状态.
  21.   //0,触屏无触摸;1,触屏有触摸
  22.   u8 TP_Scan(void)
  23.   {
  24.       if( INT_RD() == 0 )                                 //如果有按键按下
  25.       {
  26.           if( (tp_dev.sta & TP_PRES_DOWN) == 0 )          //之前没有被按下
  27.           {
  28.               tp_dev.sta =TP_PRES_DOWN | TP_CATH_PRES ;  //按键按下
  29.   
  30.               tp_dev.x[4] =tp_dev.x[0];                 //记录第一次按下时的坐标
  31.               tp_dev.y[4] =tp_dev.y[0];
  32.           }
  33.           printf("tp_dev.sta =%X\r\n",tp_dev.sta);
  34.       }
  35.       else                                               //如果无按键按下
  36.       {
  37.           if(tp_dev.sta & TP_PRES_DOWN)                  //之前是被按下的
  38.           {
  39.               tp_dev.sta &= ~(1<<7) ;                    //标记按键松开
  40.           }
  41.           else
  42.           {                                               //之前就没有被按下
  43.               tp_dev.x[4]=0;
  44.               tp_dev.y[4]=0;
  45.               tp_dev.x[0]=0xffff;
  46.               tp_dev.y[0]=0xffff;
  47.           }
  48.       }
  49.       printf("tp_dev.sta =%X\r\n",tp_dev.sta);
  50.       returntp_dev.sta & TP_PRES_DOWN ;                  //返回当前的触屏状态
  51.   }
  52.   
  53.   //触摸屏初始化
  54.   void TP_Init(void)
  55.   {
  56.       if( lcd_id==0X5510 || lcd_id==0X4342 )  //电容触摸屏,4.3寸屏
  57.       {
  58.           GT9147_Init();
  59.           tp_dev.scan=GT9147_Scan;            //扫描函数指向GT9147触摸屏扫描
  60.           tp_dev.touchtype|=0X80;             //电容屏
  61.       }
  62.       //SSD1963 7寸屏或者 7寸800*480/1024*600 RGB屏
  63.       else if( lcd_id==0X1963 || lcd_id==0X7084 || lcd_id==0X7016) {
  64.           FT5206_Init();
  65.           tp_dev.scan=FT5206_Scan;            //扫描函数指向FT5206触摸屏扫描
  66.           tp_dev.touchtype|=0X80;             //电容屏
  67.       } else if( lcd_id==0X1018 ) {
  68.           GT9271_Init();
  69.           tp_dev.scan=GT9271_Scan;            //扫描函数指向GT9271触摸屏扫描
  70.           tp_dev.touchtype|=0X80;             //电容屏
  71.       }
  72.   }
复制代码
第56行的TP_Init()函数根据LCD的ID(即main()函数中的lcd_id变量)判别不同的电容屏,然后执行不同的初始化。该函数比较简单,重点说一下:tp_dev.scan,这个结构体函数指针,默认是指向TP_Scan的,如果是电阻屏则用默认的即可;如果是电容屏,则指向新的扫描函数GT9147_Scan或FT5206_Scan(根据芯片ID判断到底指向那个),执行电容触摸屏的扫描函数。
因为GT9147和GT9271为同一系列的触摸屏,除配置参数有差别外,其它的差别不大,而FT5206的代码与GT9147的差别在于无需通过int引脚来选择不同的i2c器件地址,也无需配置繁杂的寄存器,其余的和GT9147差别不大。所以下面我们主要针对GT9147的驱动进行介绍。
首先我们看下gt9147.h代码,如下:
  1.   #ifndef__GT9147_H
  2.   #define __GT9147_H
  3.   
  4.   #include "xil_types.h"
  5.   
  6.   //I2C读写命令
  7.   #define GT_CMD_WR       0X28        //写命令
  8.   #define GT_CMD_RD       0X29        //读命令
  9.   
  10.   //GT9147 部分寄存器定义
  11.   #define GT_CTRL_REG     0X8040      //GT9147控制寄存器
  12.   #define GT_CFGS_REG     0X8047      //GT9147配置起始地址寄存器
  13.   #define GT_CHECK_REG    0X80FF      //GT9147校验和寄存器
  14.   #define GT_PID_REG      0X8140      //GT9147产品ID寄存器
  15.   
  16.   #define GT_GSTID_REG    0X814E      //GT9147当前检测到的触摸情况
  17.   #define GT_TP1_REG      0X8150      //第一个触摸点数据地址
  18.   #define GT_TP2_REG      0X8158      //第二个触摸点数据地址
  19.   #define GT_TP3_REG      0X8160      //第三个触摸点数据地址
  20.   #define GT_TP4_REG      0X8168      //第四个触摸点数据地址
  21.   #define GT_TP5_REG      0X8170      //第五个触摸点数据地址
  22.   
  23.   u8 GT9147_Send_Cfg(u8 mode);                //发送GT9147配置参数
  24.   u8 GT9147_WR_Reg(u16 reg,u8 *buf,u8len);   //向GT9147写入一次数据
  25.   void GT9147_RD_Reg(u16reg,u8 *buf,u8 len); //从GT9147读出一次数据
  26.   u8 GT9147_Init(void);                       //初始化GT9147触摸屏
  27.   u8 GT9147_Scan(u8 mode);                    //扫描触摸屏(采用查询方式)
  28.   
  29.   #endif
复制代码
在该头文件中我们宏定义了GT9147的寄存器地址,并声明了GT9147的操作函数,主要用于方便修改和管理。
接下来看下gt9147.c里面的代码,这里我们仅介绍GT9147_Init和GT9147_Scan两个函数,代码如下:
  1.   //初始化GT9147触摸屏
  2.   //返回值:0,初始化成功;1,初始化失败
  3.   u8 GT9147_Init(void)
  4.   {
  5.       u8 temp[5];
  6.       GT9147_INT_DIR(1);  //TOUCH_INT引脚设置为输出
  7.       GT9147_INT(1);      //TOUCH_INT输出为1
  8.       IIC_Init();         //初始化电容屏的I2C总线
  9.       GT9147_RST(0);              //复位
  10.       delay_ms(10);
  11.       GT9147_RST(1);              //释放复位
  12.       delay_ms(10);
  13.       GT9147_INT_DIR(0);
  14.       delay_ms(100);
  15.       GT9147_RD_Reg(GT_PID_REG,temp,4);//读取产品ID
  16.       temp[4]=0;
  17.       printf("CTP ID:%s\r\n",temp);   //打印ID
  18.       if(strcmp((char*)temp,"9147")==0) { //ID==9147
  19.           temp[0]=0X02;
  20.           GT9147_WR_Reg(GT_CTRL_REG,temp,1);//软复位GT9147
  21.           GT9147_RD_Reg(GT_CFGS_REG,temp,1);//读取GT_CFGS_REG寄存器
  22.           if(temp[0]<0X60) { //默认版本比较低,需要更新flash配置
  23.   //         printf("Default Ver:%d\r\n",temp[0]);
  24.               if(lcddev.id==0X5510)
  25.                   GT9147_Send_Cfg(1);//仅4.3寸MCU屏,更新并保存配置
  26.           }
  27.           delay_ms(10);
  28.           temp[0]=0X00;
  29.           GT9147_WR_Reg(GT_CTRL_REG,temp,1);//结束复位
  30.           return 0;
  31.       }
  32.       return 1;
  33.   }
复制代码
以上代码,GT9147_Init用于初始化GT9147,该函数通过读取0X8140~0X8143这4个寄存器,并判断是否是:“9147”,来确定是不是GT9147芯片,在读取到正确的ID后,软复位GT9147,然后根据当前芯片版本号,确定是否需要更新配置,通过GT9147_Send_Cfg函数,发送配置信息(一个数组),配置完后,结束软复位,即完成GT9147初始化。
  1.   //扫描触摸屏(采用查询方式)
  2.   //mode:0,正常扫描.
  3.   //返回值:当前触屏状态.
  4.   //0,触屏无触摸;1,触屏有触摸
  5.   u8 GT9147_Scan(u8 mode)
  6.   {
  7.      u8 buf[4];
  8.      u8 i=0;
  9.      u8 res=0;
  10.      u16 temp;
  11.      u16 tempsta;
  12.      static u8t=0;//控制查询间隔,从而降低CPU占用率
  13.      t++;
  14.      if((t%10)==0||t<10)//空闲时,每进入10次CTP_Scan函数才检测1次,从而节省CPU使用率
  15.      {
  16.          GT9147_RD_Reg(GT_GSTID_REG,&mode,1);    //读取触摸点的状态
  17.          if(mode&0X80&&((mode&0XF)<6))
  18.          {
  19.              i=0;
  20.              GT9147_WR_Reg(GT_GSTID_REG,&i,1);//清标志
  21.          }
  22.          if((mode&0XF)&&((mode&0XF)<6))
  23.          {
  24.              temp=0XFFFF<<(mode&0XF);    //将点的个数转换为1的位数,匹配tp_dev.sta定义
  25.              tempsta=tp_dev.sta;         //保存当前的tp_dev.sta值
  26.              tp_dev.sta=(~temp)|TP_PRES_DOWN|TP_CATH_PRES;
  27.              tp_dev.x[4]=tp_dev.x[0];    //保存触点0的数据
  28.              tp_dev.y[4]=tp_dev.y[0];
  29.              for(i=0;i<5;i++)
  30.              {
  31.                  if(tp_dev.sta&(1<<i))   //触摸有效?
  32.                  {
  33.                      GT9147_RD_Reg(GT9147_TPX_TBL[i],buf,4); //读取XY坐标值
  34.                       if(lcddev.id==0X5510)   //4.3寸800*480 MCU屏
  35.                       {
  36.                           if(tp_dev.touchtype&0X01)//横屏
  37.                           {
  38.                               tp_dev.y[i]=((u16)buf[1]<<8)+buf[0];
  39.                               tp_dev.x[i]=800-(((u16)buf[3]<<8)+buf[2]);
  40.                           }else
  41.                           {
  42.                               tp_dev.x[i]=((u16)buf[1]<<8)+buf[0];
  43.                               tp_dev.y[i]=((u16)buf[3]<<8)+buf[2];
  44.                           }
  45.                       }else if(lcddev.id==0X4342) //4.3寸480*272RGB屏
  46.                       {
  47.                           if(tp_dev.touchtype&0X01)//横屏
  48.                           {
  49.                               tp_dev.x[i]=(((u16)buf[1]<<8)+buf[0]);
  50.                               tp_dev.y[i]=(((u16)buf[3]<<8)+buf[2]);
  51.                           }else
  52.                           {
  53.                               tp_dev.y[i]=((u16)buf[1]<<8)+buf[0];
  54.                               tp_dev.x[i]=272-(((u16)buf[3]<<8)+buf[2]);
  55.                           }
  56.                       }
  57.                      //printf("x[%d]:%d,y[%d]:%d\r\n",i,tp_dev.x,i,tp_dev.y);
  58.                  }
  59.              }
  60.              res=1;
  61.              if(tp_dev.x[0]>lcddev.width||tp_dev.y[0]>lcddev.height)//非法数据(坐标超出了)
  62.              {
  63.                  if((mode&0XF)>1)        //有其他点有数据,则复第二个触点的数据到第一个触点.
  64.                  {
  65.                      tp_dev.x[0]=tp_dev.x[1];
  66.                      tp_dev.y[0]=tp_dev.y[1];
  67.                      t=0;                //触发一次,则会最少连续监测10次,从而提高命中率
  68.                  }else                  //非法数据,则忽略此次数据(还原原来的)
  69.                  {
  70.                      tp_dev.x[0]=tp_dev.x[4];
  71.                      tp_dev.y[0]=tp_dev.y[4];
  72.                      mode=0X80;
  73.                      tp_dev.sta=tempsta; //恢复tp_dev.sta
  74.                  }
  75.              }else t=0;                 //触发一次,则会最少连续监测10次,从而提高命中率
  76.          }
  77.      }
  78.      if((mode&0X8F)==0X80)//无触摸点按下
  79.      {
  80.          if(tp_dev.sta&TP_PRES_DOWN)     //之前是被按下的
  81.          {
  82.              tp_dev.sta&=~TP_PRES_DOWN;  //标记按键松开
  83.          }else                           //之前就没有被按下
  84.          {
  85.              tp_dev.x[0]=0xffff;
  86.              tp_dev.y[0]=0xffff;
  87.              tp_dev.sta&=0XE000;         //清除点有效标记
  88.          }
  89.      }
  90.      if(t>240)t=10;//重新从10开始计数
  91.      return res;
  92.   }
复制代码

GT9147_Scan函数用于扫描电容触摸屏是否有按键按下,由于我们不是用的中断方式来读取GT9147的数据的,而是采用查询的方式,所以这里使用了一个静态变量来提高效率,当无触摸的时候,尽量减少对CPU的占用,当有触摸的时候,又保证能迅速检测到。对GT9147数据的读取,先读取手势ID寄存器(GT_GSTID_REG),判断是不是有有效数据,如果有,则读取,否则直接忽略,继续后面的处理。
软件部分就介绍到这里,接下来看看下载验证。
1.5 下载验证
讲完了软件工程,接下来我们就将该实验下载至我们的领航者开发板进行验证。
首先我们将4.3寸的ATK-4.3’TFTLCD与开发板上的TFTLCD接口连接。再将下载器一端连电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。
我们在SDK软件中将程序下载至我们的领航者开发板,手指在触摸屏上划动时显示划动的轨迹(写一个“正”字),结果如下图所示。
image019.jpg
25.5.1 实验结果图
至此,我们的TFT LCD触摸屏实验就完成了。





正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-10-3 13:22

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表