如何使用STM32 HAL库驱动TFT-LCD实现手画板功能

Posted 三明治开发社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用STM32 HAL库驱动TFT-LCD实现手画板功能相关的知识,希望对你有一定的参考价值。

使用STM32 HAL库和TFT-LCD通信

本文将教大家如何使用STM32HAL库去驱动TFT-LCD屏。

实现功能:实现手画板的功能。

程序下载路径:demo程序

一.STM32L4简介

STM32L476RGT6是64脚的单片机,GPIO的数量有50多个,这么多的GPIO,拿来驱动一个26个引脚的8080端口TFT彩屏是可以的。STM32的C、K、R系列的芯片是不带FSMC(可变静态存储控制器),因此这三类STM32单片机只能采用IO模拟方式驱动TFT彩屏;STM32L476RGT6内部的Flash大小为1MB,也就是说可以存储的const数组大小大约为0.9MB,可以存储0.9MB的const数组是什么概念呢,一张320x240的16位色BMP图片占用为320x240x16/8=153600B=150KB,而800x600的16位色BMP图片占用为800x600x16/8=937.5KB=0.91MB,就是说这个L476RGT6可以存放一张0.91MB的800x600的16位色BMP图片的const数组于内部Flash里面。

二.TFT-LCD简介

我们通过STM32的普通IO口模拟8080总线来控制TFT-LCD的显示。我们使用的是正点原子的2.8寸ALIENTEK TFT-LCD模块, 16位真彩显示,自带触摸屏,可以用来作为控制输入。该模块支持65K色显示,显示分辨率为320×240,接口为16位的8080并口,自带触摸屏(电阻触摸屏)。

  • 模块实物图如下所示:

  • 原理图如下所示(参考正点原子官方的文档)

  • 模块接口图如下所示:TFTLCD模块采用2*17的2.54公排针与外部连接。

ALIENTEK TFTLCD模块采用16位的并口方式与外部连接,之所以不采用8位的方式,是因为彩屏的数据量比较大,尤其在显示图片的时候,如果用8位数据线,就会比16位方式慢一倍以上。

三.TFT-LCD显示实验

触摸屏芯片的接口如下所示:

  • CS:TFTLCD片选信号。
  • WR:向TFTLCD写入数据。
  • RD:从TFTLCD读取数据。
  • D[15:0]:16位双向数据线。
  • RST:硬复位TFTLCD。
  • RS:命令/数据标志(0,读写命令;1,读写数据)。

模块的RST信号线是直接接到STM32的复位脚上,并不由软件控制,这样可以省下来一个IO口。另外我们还需要一个背光控制线来控制TFTLCD的背光。所以,我们总共需要的IO口数目为21个。这里还需要注意,标注的DB1~DB8,DB10~DB17,是相对于LCD控制IC标注的,实际上可以把他们就等同D0~D15(按从小到大顺序)。

数据线有用的是:D17~D13和D11~D1,D0和D12没有用到,实际上在LCD模块里面,ILI9341的D0和D12压根就没有引出来,ILI9341的D17~D13和D11~D1对应MCU的D15~D0。 这样MCU的16位数据,最低5位代表蓝色,中间6位为绿色,最高5位为红色。

数值越大,表示该颜色越深。另外,特别注意ILI9341所有的指令都是8位的(高8位无效),且参数除了读写GRAM的时候是16位,其他操作参数,都是8位的,这个和ILI9320等驱动器不一样,必须加以注意。

这里指令不在做详细叙述了,正点原子官方网站可以下载。

TFTLCD使用流程如下所示:

任何LCD,使用流程都可以简单的用以上流程图表示:

  • 硬复位和初始化序列,只需要执行一次即可。
  • 画点流程为:设置坐标 > GRAM指令 > 写入颜色数据,完成后就能在LCD上看到对应的点显示我们写入的颜色了。
  • 读点流程为:设置坐标  > 读GRAM指令 > 读取颜色数据,这样就可以获取到对应点的颜色数据了。

以上只是最简单的操作,也是最常用的操作,有了这些操作,一般就可以正常使用TFTLCD了。接下来我们将该模块用来来显示字符和数字,通过以上介绍,我们可以得出TFTLCD显示需要的相关设置步骤如下:

  1. 设置STM32与TFT-LCD模块相连接的IO。

这一步,先将我们与TFTLCD模块相连的IO口进行初始化,以便驱动LCD。这里需要根据连接电路以及TFTLCD模块的设置来确定。

     2. 初始化TF-TLCD模块。

这里我们没有硬复位LCD,因为我们将TFT-LCD的RST同STM32的RESET连接在一起了,只要按下开发板的RESET键,就会对LCD进行硬复位。初始化序列,就是向LCD控制器写入一系列的设置值(比如伽马校准),这些初始化序列一般LCD供应商会提供给客户,我们直接使用这些序列即可,不需要深入研究。在初始化之后,LCD才可以正常使用。

    3.通过函数将字符和数字显示到TFT-LCD模块上。

即:设置坐标,写GRAM指令,写GRAM来实现,但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而达到显示字符/数字的目标,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数,就可以实现数字/字符的显示了。

     4.TFTLCD模块与IO口的对应关系如下所示。

      5. 软件设计

首先,我们介绍一下lcd.h里面的一个重要结构体:

该结构体用于保存一些LCD重要参数信息,比如LCD的长宽、LCD ID(驱动IC型号)、LCD横竖屏状态等,这个结构体虽然占用了14个字节的内存,但是却可以让我们的驱动函数支持不同尺寸的LCD,同时可以实现LCD横竖屏切换等重要功能,所以还是利大于弊的。

6.ILI93xx.c函数介绍

有了以上了解,下面我们开始介绍ILI93xx.c里面的一些重要函数。

第一个是LCD_WR_DATA函数:该函数在lcd.h里面,通过宏定义的方式申明。该函数通过8080并口向LCD模块写入一个16位的数据,使用频率是最高的,这里我们采用了宏定义的方式,以提高速度。其代码如下

上面函数中的‘\\’是C语言中的一个转义字符,用来连接上下文,因为宏定义只能是一个串,而当你的串过长(超过一行的时候),就需要换行了,此时就必须通过反斜杠来连接上下文。这里的‘\\’正是起这个作用。在上面的函数中,LCD_RS_SET/ LCD_CS_CLR/ LCD_WR_CLR/ LCD_WR_SET/ LCD_CS_SET等是操作RS/CS/WR的宏定义,均是采用STM32的快速IO控制寄存器实现的,从而提高速度。

第二个是:LCD_WR_DATAX函数:该函数在lcd.c里面定义,功能和LCD_WR_DATA一模一样,该函数代码如下:

我们知道,宏定义函数的好处就是速度快(直接嵌到被调用函数里面去了),坏处就是占空间大。在LCD_Init函数里面,有很多地方要写数据,如果全部用宏定义的LCD_WR_DATA函数,那么就会占用非常大的flash,所以我们这里另外实现一个函数:LCD_WR_DATAX,专门给LCD_Init函数调用,从而大大减少flash占用量。

第三个是LCD_WR_REG函数:该函数是通过8080并口向LCD模块写入寄存器命令,因为该函数使用频率不是很高,我们不采用宏定义来做(宏定义占用FLASH较多),通过LCD_RS来标记是写入命令(LCD_RS=0)还是数据(LCD_RS=1)。该函数代码如下:

既然有写寄存器命令函数,那就有读寄存器数据函数。

第四个是LCD_RD_DATA函数:该函数用来读取LCD控制器的寄存器数据(非GRAM数据),该函数代码如下:

以上4个函数,用于实现LCD基本的读写操作。

接下来,我们介绍2个LCD寄存器操作的函数。

第五个是LCD_WriteReg和LCD_ReadReg函数,这两个函数代码如下:

这两个函数函数十分简单,LCD_WriteReg用于向LCD指定寄存器写入指定数据,而LCD_ReadReg则用于读取指定寄存器的数据,这两个函数,都只带一个参数/返回值,所以,在有多个参数操作(读取/写入)的时候,就不适合用这两个函数了。

第六个是坐标设置函数,该函数代码如下:

该函数实现将LCD的当前操作点设置到指定坐标(x,y)。因为不同LCD的设置方式不一定完全一样,所以代码里面有好几个判断,对不同的驱动IC进行不同的设置。

第七个是画点函数。该函数实现代码如下:

该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。其中POINT_COLOR是我们定义的一个全局变量,用于存放画笔颜色,顺带介绍一下另外一个全局变量:BACK_COLOR,该变量代表LCD的背景色。LCD_DrawPoint函数虽然简单,但是至关重要,其他几乎所有上层函数,都是通过调用这个函数实现的。

第八个是读点函数,用于读取LCD的GRAM,这里说明一下,为什么OLED模块没做读GRAM的函数,而这里做了。因为OLED模块是单色的,所需要全部GRAM也就1K个字节,而TFTLCD模块为彩色的,点数也比OLED模块多很多,以16位色计算,一款320×240的液晶,需要320×240×2个字节来存储颜色值,也就是也需要150K字节,这对任何一款单片机来说,都不是一个小数目了。

我们在图形叠加的时候,可以先读回原来的值,然后写入新的值,在完成叠加后,我们又恢复原来的值。这样在做一些简单菜单的时候,是很有用的。这里我们读取TFTLCD模块数据的函数为LCD_ReadPoint,该函数直接返回读到的GRAM值。该函数使用之前要先设置读取的GRAM地址,通过LCD_SetCursor函数来实现。LCD_ReadPoint的代码如下:

在LCD_ReadPoint函数中,因为我们的代码不止支持一种LCD驱动器,所以,根据不同的LCD驱动器((lcddev.id)型号,执行不同的操作,以实现对各个驱动器兼容,提高函数的通用性。

第9个是字符显示函数LCD_ShowChar,该函数同前面OLED模块的字符显示函数差不多,但是这里的字符显示函数多了一个功能,就是可以以叠加方式显示,或者以非叠加方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显示。该函数实现代码如下:

在LCD_ShowChar函数里面,我们采用快速画点函数LCD_Fast_DrawPoint来画点显示字符,该函数同LCD_DrawPoint一样,只是带了颜色参数,且减少了函数调用的时间,详见本例程源码。该代码中我们用到了三个字符集点阵数据数组asc2_2412、asc2_1206和asc2_1608。

第十个是TFTLCD模块的初始化函数LCD_Init,该函数先初始化STM32与TFTLCD连接的IO口,然后读取LCD控制器的型号,根据控制IC的型号执行不同的初始化代码,其简化代码如下:

该函数先对STM32与LCD连接的相关IO进行初始化,之后读取LCD控制器型号(LCD ID),根据读到的LCD ID,对不同的驱动器执行不同的初始化代码,其中else if(lcddev.id==0xXXXX),是省略写法,实际上代码里面有十几个这种else if结构,从而可以支持十多款不同的驱动IC执行初始化操作,这样大大提高了整个程序的通用性。大家在以后的学习中应该多使用这样的方式,以提高程序的通用性、兼容性。

注意:本函数使用了printf来打印LCD ID,所以,如果你在主函数里面没有初始化串口,那么将导致程序死在printf里面!!如果不想用printf,那么请注释掉它。

接下来,看看主函数代码:

四、TFTLCD触摸屏实验

1.原理简介

我们将介绍如何使用STM32来驱动触摸屏,NUCLEO-L476RG开发板本身并没有触摸屏控制器,但是它支持触摸屏,可以通过外接带触摸屏的LCD模块(比如ALIENTEK TFTLCD模块),来实现触摸屏控制。

在本章中,我们将向大家介绍STM32控制ALIENTKE TFTLCD模块(包括电阻触摸与电容触摸),实现触摸屏驱动,最终实现一个手写板的功能。

  • 电阻式触摸屏利用压力感应进行触点检测控制,需要直接应力接触,通过检测电阻来定位触摸位置。
  • 电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。

电阻触摸屏的优点:精度高、价格便宜、抗干扰能力强、稳定性好。 电阻触摸屏的缺点:容易被划伤、透光性不太好、不支持多点触摸。

电容触摸屏的优点:手感好、无需校准、支持多点触摸、透光性好。 电容触摸屏的缺点:成本高、精度不高、抗干扰能力差。

注意:这里特别提醒大家电容触摸屏对工作环境的要求是比较高的,在潮湿、多尘、高低温环境下面,都是不适合使用电容屏的。

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。

2.功能简介

开机的时候先初始化LCD,读取LCD ID,随后,根据LCD ID判断是电阻触摸屏还是电容触摸屏,如果是电阻触摸屏,则先读取flash的数据判断触摸屏是否已经校准过,如果没有校准,则执行校准程序,校准过后再进入电阻触摸屏测试程序,如果已经校准了,就直接进入电阻触摸屏测试程序。

电阻触摸屏测试程序和电容触摸屏测试程序基本一样,只是电容触摸屏支持最多5点同时触摸,电阻触摸屏只支持一点触摸,其他一模一样。测试界面的右上角会有一个清空的操作区域(RST),点击这个地方就会将输入全部清除,恢复白板状态。

3.硬件设计

所有这些资源与STM32的连接图,在前面都已经介绍了,这里我们只针对TFTLCD模块与STM32的连接端口再说明一下,TFTLCD模块的触摸屏(电阻触摸屏)总共有5根线与STM32连接,连接电路图如下所示:

4.软件设计

打开工程可以发现,我们在工程中添加了touch.c、touch.h等文件,并保存在Drive分组下面。其中,touch.c和touch.h是电阻触摸屏部分的代码。顺带兼电容触摸屏的管理控制,其他则是电容触摸屏部分的代码。

打开touch.c文件,在里面输入与触摸屏相关的代码(主要是电阻触摸屏的代码),这里我们也不全部贴出来了,仅介绍几个重要的函数。 首先我们要介绍的是TP_Read_XY2这个函数,该函数专门用于从电阻式触摸屏控制IC读取坐标的值(0~4095),TP_Read_XY2的代码如下:

该函数采用了一个非常好的办法来读取屏幕坐标值,就是连续读两次,两次读取的值之差不能超过一个特定的值(ERR_RANGE),通过这种方式,我们可以大大提高触摸屏的准确度。另外该函数调用的TP_Read_XY函数,用于单次读取坐标值。TP_Read_XY也采用了一些软件滤波算法,具体见源码。接下来,我们介绍另外一个函数TP_Adjust,该函数源码如下:

TP_Adjust是此部分最核心的代码,在这里,给大家介绍一下我们这里所使用的触摸屏校正原理:我们传统的鼠标是一种相对定位系统,只和前一次鼠标的位置坐标有关。而触摸屏则是一种绝对坐标系统,要选哪就直接点哪,与相对定位系统有着本质的区别。绝对坐标系统的特点是每一次定位坐标与上一次定位坐标没有关系,每次触摸的数据通过校准转为屏幕上的坐标,不管在什么情况下,触摸屏这套坐标在同一点的输出数据是稳定的。不过由于技术原理的原因,并不能保证同一点触摸每一次采样数据相同,不能保证绝对坐标定位,点不准,这就是触摸屏最怕出现的问题:漂移。对于性能质量好的触摸屏来说,漂移的情况出现并不是很严重。所以很多应用触摸屏的系统启动后,进入应用程序前,先要执行校准程序。 通常应用程序中使用的LCD坐标是以像素为单位的。比如说:左上角的坐标是一组非0的数值,比如(20,20),而右下角的坐标为(220,300)。这些点的坐标都是以像素为单位的,而从触摸屏中读出的是点的物理坐标,其坐标轴的方向、XY值的比例因子、偏移量都与LCD坐标不同,所以,需要在程序中把物理坐标首先转换为像素坐标,然后再赋给POS结构,达到坐标转换的目的。

校正思路:在了解了校正原理之后,我们可以得出下面的一个从物理坐标到像素坐标的转换关系式: LCDx=xfacPx+xoff; LCDy=yfacPy+yoff;

其中(LCDx,LCDy)是在LCD上的像素坐标,(Px,Py)是从触摸屏读到的物理坐标。xfac,yfac分别是X轴方向和Y轴方向的比例因子,而xoff和yoff则是这两个方向的偏移量。

这样我们只要事先在屏幕上面显示4个点(这四个点的坐标是已知的),分别按这四个点就可以从触摸屏读到4个物理坐标,这样就可以通过待定系数法求出xfac、yfac、xoff、yoff这四个参数。我们保存好这四个参数,在以后的使用中,我们把所有得到的物理坐标都按照这个关系式来计算,得到的就是准确的屏幕坐标。达到了触摸屏校准的目的。

TP_Adjust就是根据上面的原理设计的校准函数,注意该函数里面多次使用了lcddev.width和lcddev.height,用于坐标设置,主要是为了兼容不同尺寸的LCD(比如320x240、480x320和800x480的屏都可以兼容)。

接下来看看触摸屏初始化函数:TP_Init,该函数代码如下:

该函数比较简单,重点说一下:tp_dev.scan,这个结构体函数指针,默认是指向TP_Scan的,如果是电阻屏则用默认的即可。

上述代码,我们重点看看_m_tp_dev结构体,这个结构体用于管理和记录触摸屏(包括电阻触摸屏与电容触摸屏)相关信息。通过结构体,在使用的时候,我们一般直接调用tp_dev的相关成员函数/变量屏即可达到需要的效果,这种设计简化了接口,且方便管理和维护。

下面分别介绍一下三个重要的函数。

  • 电阻屏测试函数

rtp_test,该函数用于电阻触摸屏的测试,该函数代码比较简单,就是扫描触摸屏,如果触摸屏有按下,则在触摸屏上面划线,如果按中“RST”区域,则执行清屏。

  • 主函数:

main函数,则比较简单,初始化相关外设,然后去选择执行rtp_test。

5.功能实现

手写板展示效果图:

以上是关于如何使用STM32 HAL库驱动TFT-LCD实现手画板功能的主要内容,如果未能解决你的问题,请参考以下文章

STM32CubeMX | 41 - 使用LTDC驱动TFT-LCD屏幕(RGB屏)

STM32 HAL USART 驱动程序:这种语法是如何工作的?

基于STM32F103C8T6最小系统板HAL库CubeMX驱动HC-SR501红外人体传感模块

基于STM32F103C8T6最小系统板HAL库CubeMX驱动HC-SR501红外人体传感模块

基于STM32F103C8T6最小系统板HAL库CubeMX驱动HC-SR501红外人体传感模块

STM32 HAL USART驱动程序:此语法如何工作?