完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子STM32mini开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第十六章 TFTLCD 显示实验 上一章我们介绍了 OLED 模块及其显示,但是该模块只能显示单色/双色,不能显示彩色, 而且尺寸也较小。本章我们将介绍 ALIENTEK 2.8 寸 TFT LCD 模块,该模块采用 TFTLCD 面 板,可以显示 16 位色的真彩图片。在本章中,我们将使用 MiniSTM32 开发板上的 LCD 接口, 来点亮 TFTLCD,并实现 ASCII 字符和彩色的显示等功能,并在串口打印 LCD 控制器 ID,同 时在 LCD 上面显示。本章分为如下几个部分: 16.1 TFTLCD 简介 16.2 硬件设计 16.3 软件设计 16.4 下载验证 16.1 TFTLCD 简介 本章我们将通过 STM32 的普通 IO 口模拟 8080 总线来控制 TFTLCD 的显示。 TFT-LCD 即薄膜晶体管液晶显示器。其英文全称为:Thin Film Transistor-Liquid Crystal Display。TFT-LCD 与无源 TN-LCD、STN-LCD 的简单矩阵不同,它在液晶显示屏的每一个象 素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特 性与扫描线数无关,因此大大提高了图像质量。TFT-LCD 也被叫做真彩液晶显示器。 上一章介绍了 OLED 模块,本章,我们给大家介绍 ALIENTEK TFTLCD 模块,该模块有 如下特点: 1,2.4’/2.8’/3.5’/4.3’/7’ 5 种大小的屏幕可选。 2,320×240 的分辨率(3.5’分辨率为:320*480,4.3’和 7’分辨率为:800*480)。 3,16 位真彩显示。 4,自带触摸屏,可以用来作为控制输入。 本章,我们以 2.8 寸的 ALIENTEK TFTLCD 模块为例介绍,该模块支持 65K 色显示,显示 分辨率为 320×240,接口为 16 位的 80 并口,自带触摸屏。 该模块的外观图如图 16.1.1 所示: 图 16.1.1 ALIENTEK 2.8 寸 TFTLCD 外观图 模块原理图如图 16.1.2 所示: 图 16.1.2 ALIENTEK 2.8 寸 TFTLCD 模块原理图 TFTLCD 模块采用 2*17 的 2.54 公排针与外部连接,接口定义如图 16.1.3 所示: 图 16.1.3 ALIENTEK 2.8 寸 TFTLCD 模块接口图 从图 16.1.3 可以看出,ALIENTEK TFTLCD 模块采用 16 位的并方式与外部连接,之所以 不采用 8 位的方式,是因为彩屏的数据量比较大,尤其在显示图片的时候,如果用 8 位数据线, 就会比 16 位方式慢一倍以上,我们当然希望速度越快越好,所以我们选择 16 位的接口。图 16.1.3 还列出了触摸屏芯片的接口,关于触摸屏本章我们不多介绍,后面的章节会有详细的介绍。该 模块的 80 并口有如下一些信号线: CS:TFTLCD 片选信号。 WR:向 TFTLCD 写入数据。 RD:从 TFTLCD 读取数据。 D[15:0]:16 位双向数据线。 RST:硬复位 TFTLCD。 RS:命令/数据标志(0,读写命令;1,读写数据)。 80 并口在上一节我们已经有详细的介绍了,这里我们就不再介绍,需要说明的是,TFTLCD 模块的 RST 信号线是直接接到 STM32 的复位脚上,并不由软件控制,这样可以省下来一个 IO 口。另外我们还需要一个背光控制线来控制 TFTLCD 的背光。所以,我们总共需要的 IO 口数 目为 21 个。这里还需要注意,我们标注的 DB1~DB8,DB10~DB17,是相对于 LCD 控制 IC 标 注的,实际上大家可以把他们就等同于 D0~D15(按从小到大顺序),这样理解起来简单点。 ALIENTEK 提供 2.8/3.5/4.3/7 寸等不同尺寸的 TFTLCD 模块,其驱动芯片有很多种类型, 比如有:ILI9341/ILI9325/RM68042/RM68021/ILI9320/ILI9328/LGDP4531/LGDP4535/SPFD5408 /SSD1289/1505/B505/C505/NT35310/NT35510/SSD1963 等(具体的型号,大家可以通过下载本章 实验代码,通过串口或者 LCD 显示查看),这里我们仅以 ILI9341 控制器为例进行介绍,其他 的控制基本都类似,我们就不详细阐述了。 ILI9341 液晶控制器自带显存,其显存总大小为 172800(240*320*18/8),即 18 位模式(26 万色)下的显存量。在 16 位模式下,ILI9341 采用 RGB565 格式存储颜色数据,此时 ILI9341 的 18 位数据线与 MCU 的 16 位数据线以及 LCD GRAM 的对应关系如图 16.1.4 所示: 图 16.1.4 16 位数据与显存对应关系图 从图中可以看出,ILI9341 在 16 位模式下面,数据线有用的是: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 等驱动器不一 样,必须加以注意。 接下来,我们介绍一下 ILI9341 的几个重要命令,因为 ILI9341 的命令很多,我们这里就 不全部介绍了,有兴趣的大家可以找到 ILI9341 的 datasheet 看看。里面对这些命令有详细的介 绍。我们将介绍:0XD3,0X36,0X2A,0X2B,0X2C,0X2E 等 6 条指令。 首先来看指令:0XD3,这个是读 ID4 指令,用于读取 LCD 控制器的 ID,该指令如表 16.1.1 所示: 表 16.1.1 0XD3 指令描述 从上表可以看出,0XD3 指令后面跟了 4 个参数,最后 2 个参数,读出来是 0X93 和 0X41, 刚好是我们控制器 ILI9341 的数字部分,从而,通过该指令,即可判别所用的 LCD 驱动器是什 么型号,这样,我们的代码,就可以根据控制器的型号去执行对应驱动 IC 的初始化代码,从而 兼容不同驱动 IC 的屏,使得一个代码支持多款 LCD。 接下来看指令:0X36,这是存储访问控制指令,可以控制 ILI9341 存储器的读写方向,简 单的说,就是在连续写 GRAM 的时候,可以控制 GRAM 指针的增长方向,从而控制显示方式 (读 GRAM 也是一样)。该指令如表 16.1.2 所示: 表 16.1.2 0X36 指令描述 从上表可以看出,0X36 指令后面,紧跟一个参数,这里我们主要关注:MY、MX、MV 这三个位,通过这三个位的设置,我们可以控制整个 ILI9341 的全部扫描方向,如表 16.1.3 所 示: 表 16.1.3 MY、MX、MV 设置与 LCD 扫描方向关系表 这样,我们在利用 ILI9341 显示内容的时候,就有很大灵活性了,比如显示 BMP 图片, BMP 解码数据,就是从图片的左下角开始,慢慢显示到右上角,如果设置 LCD 扫描方向为从 左到右,从下到上,那么我们只需要设置一次坐标,然后就不停的往 LCD 填充颜色数据即可, 这样可以大大提高显示速度。 接下来看指令:0X2A,这是列地址设置指令,在从左到右,从上到下的扫描方式(默认) 下面,该指令用于设置横坐标(x 坐标),该指令如表 16.1.4 所示: 表 16.1.4 0X2A 指令描述 在默认扫描方式时,该指令用于设置 x 坐标,该指令带有 4 个参数,实际上是 2 个坐标值: SC 和 EC,即列地址的起始值和结束值,SC 必须小于等于 EC,且 0≤SC/EC≤239。一般在设 置 x 坐标的时候,我们只需要带 2 个参数即可,也就是设置 SC 即可,因为如果 EC 没有变化, 我们只需要设置一次即可(在初始化 ILI9341 的时候设置),从而提高速度。 与 0X2A 指令类似,指令:0X2B,是页地址设置指令,在从左到右,从上到下的扫描方式 (默认)下面,该指令用于设置纵坐标(y 坐标)。该指令如表 16.1.5 所示: 表 16.1.5 0X2B 指令描述 在默认扫描方式时,该指令用于设置 y 坐标,该指令带有 4 个参数,实际上是 2 个坐标值: SP 和 EP,即页地址的起始值和结束值,SP 必须小于等于 EP,且 0≤SP/EP≤319。一般在设置 y 坐标的时候,我们只需要带 2 个参数即可,也就是设置 SP 即可,因为如果 EP 没有变化,我 们只需要设置一次即可(在初始化 ILI9341 的时候设置),从而提高速度。 接下来看指令:0X2C,该指令是写 GRAM 指令,在发送该指令之后,我们便可以往 LCD 的 GRAM 里面写入颜色数据了,该指令支持连续写,指令描述如表 16.1.6 所示: 表 16.1.6 0X2C 指令描述 从上表可知,在收到指令 0X2C 之后,数据有效位宽变为 16 位,我们可以连续写入 LCD GRAM 值,而 GRAM 的地址将根据 MY/MX/MV 设置的扫描方向进行自增。例如:假设设置 的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过 SC,SP 设置)后,每写入 一个颜色值,GRAM 地址将会自动自增 1(SC++),如果碰到 EC,则回到 SC,同时 SP++,一 直到坐标:EC,EP 结束,其间无需再次设置的坐标,从而大大提高写入速度。 最后,来看看指令:0X2E,该指令是读 GRAM 指令,用于读取 ILI9341 的显存(GRAM), 该指令在 ILI9341 的数据手册上面的描述是有误的,真实的输出情况如表 16.1.7 所示: 表 16.1.7 0X2E 指令描述 该指令用于读取GRAM,如表16.1.7所示,ILI9341在收到该指令后,第一次输出的是dummy 数据,也就是无效的数据,第二次开始,读取到的才是有效的 GRAM 数据(从坐标:SC,SP 开始),输出规律为:每个颜色分量占 8 个位,一次输出 2 个颜色分量。比如:第一次输出是 R1G1,随后的规律为:B1R2→G2B2→R3G3→B3R4→G4B4→R5G5... 以此类推。如果我们只 需要读取一个点的颜色值,那么只需要接收到参数 3 即可,如果要连续读取(利用 GRAM 地址 自增,方法同上),那么就按照上述规律去接收颜色数据。 以上,就是操作 ILI9341 常用的几个指令,通过这几个指令,我们便可以很好的控制 ILI9341 显示我们所要显示的内容了。 一般 TFTLCD 模块的使用流程如图 16.1.4: 图 16.1.4 TFTLCD 使用流程 任何 LCD,使用流程都可以简单的用以上流程图表示。其中硬复位和初始化序列,只需要 执行一次即可。而画点流程就是:设置坐标→写 GRAM 指令→写入颜色数据,然后在 LCD 上 面,我们就可以看到对应的点显示我们写入的颜色了。读点流程为:设置坐标→读 GRAM 指令 →读取颜色数据,这样就可以获取到对应点的颜色数据了。 以上只是最简单的操作,也是最常用的操作,有了这些操作,一般就可以正常使用 TFTLCD 了。接下来我们将该模块用来来显示字符和数字,通过以上介绍,我们可以得出 TFTLCD 显示 需要的相关设置步骤如下: 1)设置 STM32 与 TFTLCD 模块相连接的 IO。 这一步,先将我们与 TFTLCD 模块相连的 IO 口进行初始化,以便驱动 LCD。这里需要根 据连接电路以及 TFTLCD 模块的设置来确定。 2)初始化 TFTLCD 模块。 即图 16.1.4 的初始化序列,这里我们没有硬复位 LCD,因为 MiniSTM32 开发板的 LCD 接 口,将 TFTLCD 的 RST 同 STM32 的 RESET 连接在一起了,只要按下开发板的 RESET 键,就 会对 LCD 进行硬复位。初始化序列,就是向 LCD 控制器写入一系列的设置值(比如伽马校准), 这些初始化序列一般 LCD 供应商会提供给客户,我们直接使用这些序列即可,不需要深入研究。 在初始化之后,LCD 才可以正常使用。 3)通过函数将字符和数字显示到 TFTLCD 模块上。 这一步则通过图 16.1.4 左侧的流程,即:设置坐标→写 GRAM 指令→写 GRAM 来实现, 但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而 达到显示字符/数字的目标,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数, 就可以实现数字/字符的显示了。 16.2 硬件设计 本实验用到的硬件资源有: 1) 指示灯 DS0 2) TFTLCD 模块 TFTLCD 模块的电路在前面已有详细说明了,这里我们介绍 TFTLCD 模块与 ALIETEK MiniSTM32 开发板的连接,MiniSTM32 开发板底板的 LCD 接口和 ALIENTEK TFTLCD 模块直 接可以对插(靠右插!),连接如图 16.2.1 所示: 图 16.2.1 TFTLCD 与开发板连接示意图 图 16.2.1 中圈出来的部分就是连接 TFTLCD 模块的接口,板上的接口比液晶模块的插针要 多 2 个口,液晶模块在这里是靠右插的。多出的 2 个口是给 OLED 用的,所以 OLED 模块在接 这里的时候,是靠左插的,这个要注意。在硬件上,TFTLCD 模块与 MiniSTM32 开发板的 IO 口对应关系如下: LCD_LED 对应 PC10; LCD_CS 对应 PC9; LCD _RS 对应 PC8; LCD _WR 对应 PC7; LCD _RD 对应 PC6; LCD _D[17:1]对应 PB[15:0]; 这些线的连接,MiniSTM32 开发板的内部已经连接好了,我们只需要将 TFTLCD 模块插 上去就好了。 16.3 软件设计 软件设计我们依旧在之前的工程上面增加,不过没用到 OLED,所以先去掉 oled.c(注意, 此时 HARDWARE 组仅剩:led.c),然后在 HARDWARE 文件夹下新建一个 LCD 的文件夹。然 后打开 USER 文件夹下的工程,新建一个 lcd.c 的文件和 lcd.h 的头文件,保存在 LCD 文件夹下, 并将 LCD 文件夹加入头文件包含路径。在 lcd.c 里面要输入的代码比较多,我们这里就不贴出 来了,只针对几个重要的函数进行讲解。 首先,我们介绍一下 lcd.h 里面的一个重要结构体: //LCD 重要参数集 typedef struct { u16 width; //LCD 宽度 u16 height; //LCD 高度 u16 id; //LCD ID u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。 u16 wramcmd; //开始写 gram 指令 u16 setxcmd; //设置 x 坐标指令 u16 setycmd; //设置 y 坐标指令 }_lcd_dev; //LCD 参数 extern _lcd_dev lcddev; //管理 LCD 重要参数 该结构体用于保存一些 LCD 重要参数信息,比如 LCD 的长宽、LCD ID(驱动 IC 型号)、 LCD 横竖屏状态等,这个结构体虽然占用了 14 个字节的内存,但是却可以让我们的驱动函数 支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。有 了以上了解,下面我们开始介绍 lcd.c 里面的一些重要函数。 第一个是 LCD_WR_DATA 函数,该函数在 lcd.h 里面,通过宏定义的方式申明。该函数通 过 80 并口向 LCD 模块写入一个 16 位的数据,使用频率是最高的,这里我们采用了宏定义的方 式,以提高速度。其代码如下 //写数据函数 #define LCD_WR_DATA(data){ LCD_RS_SET; LCD_CS_CLR; DATAOUT(data); LCD_WR_CLR; LCD_WR_SET; LCD_CS_SET; } 上面函数中的‘’是 C 语言中的一个转义字符,用来连接上下文,因为宏定义只能是一个 串,而当你的串过长(超过一行的时候),就需要换行了,此时就必须通过反斜杠来连接上下文。 这里的‘’正是起这个作用。在上面的函数中,LCD_RS_SET/ LCD_CS_CLR/ LCD_WR_CLR/ LCD_WR_SET/ LCD_CS_SET 等是操作 RS/CS/WR 的宏定义,均是采用 STM32 的快速 IO 控制 寄存器实现的,从而提高速度。 第二个是:LCD_WR_DATAX 函数,该函数在 ILI93xx.c 里面定义,功能和 LCD_WR_DATA 一模一样,该函数代码如下: //写数据函数 //可以替代 LCD_WR_DATAX 宏,拿时间换空间. //data:寄存器值 void LCD_WR_DATAX(u16 data) { LCD_RS_SET; LCD_CS_CLR; DATAOUT(data); LCD_WR_CLR; LCD_WR_SET; LCD_CS_SET; } 我们知道,宏定义函数的好处就是速度快(直接嵌到被调用函数里面去了),坏处就是占空 间大。在 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)。该函数代码如下: //写寄存器函数 //data:寄存器值 void LCD_WR_REG(u16 data) { LCD_RS_CLR;//写地址 LCD_CS_CLR; DATAOUT(data); LCD_WR_CLR; LCD_WR_SET; LCD_CS_SET; } 既然有写寄存器命令函数,那就有读寄存器数据函数。接下来介绍 LCD_RD_DATA 函数, 该函数用来读取 LCD 控制器的寄存器数据(非 GRAM 数据),该函数代码如下: //读 LCD 寄存器数据 //返回值:读到的值 u16 LCD_RD_DATA(void) { u16 t; GPIOB->CRL=0X88888888; //PB0-7 上拉输入 GPIOB->CRH=0X88888888; //PB8-15 上拉输入 GPIOB->ODR=0X0000; //全部输出 0 LCD_RS_SET; LCD_CS_CLR; LCD_RD_CLR; //读取数据(读寄存器时,并不需要读 2 次) if(lcddev.id==0X8989)delay_us(2);//FOR 8989,延时 2us t=DATAIN; LCD_RD_SET; LCD_CS_SET; GPIOB->CRL=0X33333333; //PB0-7 上拉输出 GPIOB->CRH=0X33333333; //PB8-15 上拉输出 GPIOB->ODR=0XFFFF; //全部输出高 return t; } 以上 4 个函数,用于实现 LCD 基本的读写操作,接下来,我们介绍 2 个 LCD 寄存器操作 的函数,LCD_WriteReg 和 LCD_ReadReg,这两个函数代码如下: //写寄存器 //LCD_Reg:寄存器编号 //LCD_RegValue:要写入的值 void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue) { LCD_WR_REG(LCD_Reg); LCD_WR_DATA(LCD_RegValue); } //读寄存器 //LCD_Reg:寄存器编号 //返回值:读到的值 u16 LCD_ReadReg(u16 LCD_Reg) { LCD_WR_REG(LCD_Reg); //写入要读的寄存器号 return LCD_RD_DATA(); } 这两个函数函数十分简单,LCD_WriteReg 用于向 LCD 指定寄存器写入指定数据,而 LCD_ReadReg 则用于读取指定寄存器的数据,这两个函数,都只带一个参数/返回值,所以, 在有多个参数操作(读取/写入)的时候,就不适合用这两个函数了,得另外实现。 第七个要介绍的函数是坐标设置函数,该函数代码如下: //设置光标位置 //Xpos:横坐标 //Ypos:纵坐标 void LCD_SetCursor(u16 Xpos, u16 Ypos) { if(lcddev.id==0X9341||lcddev.id==0X5310) { LCD_WR_REG(lcddev.setxcmd); LCD_WR_DATA(Xpos>>8); LCD_WR_DATA(Xpos&0XFF); LCD_WR_REG(lcddev.setycmd); LCD_WR_DATA(Ypos>>8); LCD_WR_DATA(Ypos&0XFF); }else if(lcddev.id==0X6804) { if(lcddev.dir==1)Xpos=lcddev.width-1-Xpos;//横屏时处理 LCD_WR_REG(lcddev.setxcmd); LCD_WR_DATA(Xpos>>8); LCD_WR_DATA(Xpos&0XFF); LCD_WR_REG(lcddev.setycmd); LCD_WR_DATA(Ypos>>8); LCD_WR_DATA(Ypos&0XFF); }else if(lcddev.id==0X5510) { LCD_WR_REG(lcddev.setxcmd); LCD_WR_DATA(Xpos>>8); LCD_WR_REG(lcddev.setxcmd+1); LCD_WR_DATA(Xpos&0XFF); LCD_WR_REG(lcddev.setycmd); LCD_WR_DATA(Ypos>>8); LCD_WR_REG(lcddev.setycmd+1); LCD_WR_DATA(Ypos&0XFF); }else { if(lcddev.dir==1)Xpos=lcddev.width-1-Xpos;//横屏其实就是调转 x,y 坐标 LCD_WriteReg(lcddev.setxcmd, Xpos); LCD_WriteReg(lcddev.setycmd, Ypos); } } 该函数实现将 LCD 的当前操作点设置到指定坐标(x,y)。因为不同 LCD 的设置方式不一定 完全一样,所以代码里面有好几个判断,对不同的驱动 IC 进行不同的设置。 接下来我们介绍第八个函数:画点函数。该函数实现代码如下: //画点 //x,y:坐标 //POINT_COLOR:此点的颜色 void LCD_DrawPoint(u16 x,u16 y) { LCD_SetCursor(x,y); //设置光标位置 LCD_WriteRAM_Prepare(); //开始写入 GRAM LCD_WR_DATA(POINT_COLOR); } 该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。其中 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 的代码如下: //读取个某点的颜色值//x,y:坐标 //返回值:此点的颜色 u16 LCD_ReadPoint(u16 x,u16 y) { u16 r,g,b; if(x>=lcddev.width||y>=lcddev.height)return 0; //超过了范围,直接返回 LCD_SetCursor(x,y); if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310||lcddev.id==0X1963) LCD_WR_REG(0X2E);//9341/6804/3510/1963 发送读 GRAM 指令 else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);//5510 发送读 GRAM 指令 else LCD_WR_REG(0X22); //其他 IC 发送读 GRAM 指令 GPIOB->CRL=0X88888888; //PB0-7 上拉输入 GPIOB->CRH=0X88888888; //PB8-15 上拉输入 GPIOB->ODR=0XFFFF; //全部输出高 LCD_RS_SET; LCD_CS_CLR; LCD_RD_CLR; //读取数据(读 GRAM 时,第一次为假读) opt_delay(2); //延时 r=DATAIN; //实际坐标颜色 LCD_RD_SET; if(lcddev.id==0X1963) { LCD_CS_SET; GPIOB->CRL=0X33333333; //PB0-7 上拉输出 GPIOB->CRH=0X33333333; //PB8-15 上拉输出 GPIOB->ODR=0XFFFF; //全部输出高 return r; //1963 直接读就可以 } LCD_RD_CLR; //dummy READ opt_delay(2); //延时 r=DATAIN; //实际坐标颜色 LCD_RD_SET; if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)//这几个 IC 要分 2 次读出 { LCD_RD_CLR; opt_delay(2);//延时 b=DATAIN;//读取蓝色值 LCD_RD_SET; g=r&0XFF;//对于 9341,第一次读取的是 RG 的值,R 在前,G 在后,各占 8 位 g<<=8; }else if(lcddev.id==0X6804) { LCD_RD_CLR; LCD_RD_SET; r=DATAIN;//6804 第二次读取的才是真实值 } LCD_CS_SET; GPIOB->CRL=0X33333333; //PB0-7 上拉输出 GPIOB->CRH=0X33333333; //PB8-15 上拉输出 GPIOB->ODR=0XFFFF; //全部输出高 if(lcddev.id==0X9325||lcddev.id==0X4535||lcddev.id==0X4531||lcddev.id==0X8989|| lcddev.id==0XB505)return r; //这几种 IC 直接返回颜色值 else if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510) return (((r>>11)<<11)|((g>>10)<<5)|(b>>11));//这几个 IC 需要公式转换一下 else return LCD_BGR2RGB(r); //其他 IC } 在 LCD_ReadPoint 函数中,因为我们的代码不止支持一种 LCD 驱动器,所以,我们根据 不同的 LCD 驱动器((lcddev.id)型号,执行不同的操作,以实现对各个驱动器兼容,提高函数 的通用性。 第十个要介绍的是字符显示函数 LCD_ShowChar,该函数同前面 OLED 模块的字符显示函 数差不多,但是这里的字符显示函数多了一个功能,就是可以以叠加方式显示,或者以非叠加 方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显示。 该函数实现代码如下: //在指定位置显示一个字符 //x,y:起始坐标 //num:要显示的字符:" "--->"~" //size:字体大小 12/16/24 //mode:叠加方式(1)还是非叠加方式(0) void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode) { u8 temp,t1,t; u16 y0=y; u8 csize=(size/8+((size%8)?1:0))*(size/2);//得到字体一个字符对应点阵集所占字节数 //设置窗口 num=num-' ';//得到偏移后的值 for(t=0;t if(size==12)temp=asc2_1206[num][t]; //调用 1206 字体 else if(size==16)temp=asc2_1608[num][t]; //调用 1608 字体 else if(size==24)temp=asc2_2412[num][t]; //调用 2412 字体 else return; //没有的字库 for(t1=0;t1<8;t1++) { if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR); else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR); temp<<=1; y++; if(x>=lcddev.width)return; //超区域了 if((y-y0)==size) { y=y0; x++; if(x>=lcddev.width)return; //超区域了 break; } } } } 在 LCD_ShowChar 函数里面,我们采用快速画点函数 LCD_Fast_DrawPoint 来画点显示字 符,该函数同 LCD_DrawPoint 一样,只是带了颜色参数,且减少了函数调用的时间,详见本例 程源码。该代码中我们用到了三个字符集点阵数据数组 asc2_2412、asc2_1206 和 asc2_1608, 这几个字符集的点阵数据的提取方式,同十五章介绍的方法是一模一样的。详细请参考第十五 章。 最后,我们再介绍一下 TFTLCD 模块的初始化函数 LCD_Init,该函数先初始化 STM32 与 TFTLCD 连接的 IO 口,然后读取 LCD 控制器的型号,根据控制 IC 的型号执行不同的初始化 代码,其简化代码如下: //初始化 lcd //该初始化函数可以初始化各种 ALIENTEK 出品的 LCD 液晶屏 //本函数占用较大 flash,用户可以根据自己的实际情况,删掉未用到的 LCD 初始化代码.以节 省空间. void LCD_Init(void) { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_GPIOB_CLK_ENABLE(); //开启 GPIOB 时钟 __HAL_RCC_GPIOC_CLK_ENABLE(); //开启 GPIOC 时钟 //PC6,7,8,9,10 GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8| GPIO_PIN_9|GPIO_PIN_10; GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速 HAL_GPIO_Init(GPIOC,&GPIO_Initure); //PB0~15 GPIO_Initure.Pin=GPIO_PIN_All; //PB 推挽输出 HAL_GPIO_Init(GPIOB,&GPIO_Initure); __HAL_AFIO_REMAP_SWJ_DISABLE(); //禁止 JTAG delay_ms(50); // delay 50 ms LCD_WriteReg(0x0000,0x0001); delay_ms(50); // delay 50 ms lcddev.id = LCD_ReadReg(0x0000); if(lcddev.id<0XFF||lcddev.id==0XFFFF||lcddev.id==0X9300)//读到 ID 不正确, //新增 lcddev.id==0X9300 判断,因为 9341 在未被复位的情况下会被读成 9300 { //尝试 9341 ID 的读取 LCD_WR_REG(0XD3); LCD_RD_DATA(); //dummy read LCD_RD_DATA(); //读到 0X00 lcddev.id=LCD_RD_DATA(); //读取 93 lcddev.id<<=8; lcddev.id|=LCD_RD_DATA(); //读取 41 if(lcddev.id!=0X9341) //非 9341,尝试是不是 6804 { LCD_WR_REG(0XBF); LCD_RD_DATA(); //dummy read LCD_RD_DATA(); //读回 0X01 LCD_RD_DATA(); //读回 0XD0 lcddev.id=LCD_RD_DATA();//这里读回 0X68 lcddev.id<<=8; lcddev.id|=LCD_RD_DATA();//这里读回 0X04 if(lcddev.id!=0X6804) //也不是 6804,尝试看看是不是 NT35310 { LCD_WR_REG(0XD4); LCD_RD_DATA(); //dummy read LCD_RD_DATA(); //读回 0X01 lcddev.id=LCD_RD_DATA(); //读回 0X53 lcddev.id<<=8; lcddev.id|=LCD_RD_DATA(); //这里读回 0X10 if(lcddev.id!=0X5310) //也不是 NT35310,尝试看看是不是 NT35510 { LCD_WR_REG(0XDA00); LCD_RD_DATA(); //读回 0X00 LCD_WR_REG(0XDB00); lcddev.id=LCD_RD_DATA();//读回 0X80 lcddev.id<<=8; LCD_WR_REG(0XDC00); lcddev.id|=LCD_RD_DATA();//读回 0X00 if(lcddev.id==0x8000)lcddev.id=0x5510;//NT35510 读回的 ID 是 //8000H,为方便区分,我们强制设置为 5510 if(lcddev.id!=0X5510)//也不是 NT5510,尝试看看是不是 SSD1963 { LCD_WR_REG(0XA1); lcddev.id=LCD_RD_DATA(); lcddev.id=LCD_RD_DATA(); //读回 0X57 lcddev.id<<=8; lcddev.id|=LCD_RD_DATA(); //读回 0X61 if(lcddev.id==0X5761)lcddev.id=0X1963;//SSD1963 读回的 ID 是 //5761H,为方便区分,我们强制设置为 1963 } } } } } printf(" LCD ID:%xrn",lcddev.id); //打印 LCD ID if(lcddev.id==0X9341) //9341 初始化 { ……//9341 初始化代码 }else if(lcddev.id==0xXXXX) //其他 LCD 初始化代码 { ……//其他 LCD 驱动 IC,初始化代码 } LCD_Display_Dir(0); //默认为竖屏显示 LCD_LED=1; //点亮背光 LCD_Clear(WHITE); } 该函数先对 STM32 与 LCD 连接的相关 IO 进行初始化,之后读取 LCD 控制器型号(LCD ID), 根据读到的 LCD ID,对不同的驱动器执行不同的初始化代码,其中 else if(lcddev.id==0xXXXX), 是省略写法,实际上代码里面有十几个这种 else if 结构,从而可以支持十多款不同的驱动 IC 执 行初始化操作,这样大大提高了整个程序的通用性。大家在以后的学习中应该多使用这样的方 式,以提高程序的通用性、兼容性。 特别注意:本函数使用了 printf 来打印 LCD ID,所以,如果你在主函数里面没有初始化串 口,那么将导致程序死在 printf 里面!!如果不想用 printf,那么请注释掉它。 保存 lcd.c,并将该代码加入到 HARDWARE 组下。在介绍完了 lcd.c 的内容之后,然后我 们在 lcd.h 里面输入如下内容: #ifndef __LCD_H #define __LCD_H #include "sys.h" #include "stdlib.h" //LCD 重要参数集 typedef struct { u16 width; //LCD 宽度 u16 height; //LCD 高度 u16 id; //LCD ID u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。 u16 wramcmd; //开始写 gram 指令 u16 setxcmd; //设置 x 坐标指令 u16 setycmd; //设置 y 坐标指令 }_lcd_dev; //LCD 参数 extern _lcd_dev lcddev; //管理 LCD 重要参数 //LCD 的画笔颜色和背景色 extern u16 POINT_COLOR;//默认红色 extern u16 BACK_COLOR; //背景颜色.默认为白色 //LCD 端口定义,使用快速 IO 控制 #define LCD_LED PCout(10) //LCD 背光 PC10 #define LCD_CS_SET GPIOC->BSRR=1<<9 //片选端口 PC9 #define LCD_RS_SET GPIOC->BSRR=1<<8 //数据/命令 PC8 #define LCD_WR_SET GPIOC->BSRR=1<<7 //写数据 PC7 #define LCD_RD_SET GPIOC->BSRR=1<<6 //读数据 PC6 #define LCD_CS_CLR GPIOC->BRR=1<<9 //片选端口 PC9 #define LCD_RS_CLR GPIOC->BRR=1<<8 //数据/命令 PC8 #define LCD_WR_CLR GPIOC->BRR=1<<7 //写数据 PC7 #define LCD_RD_CLR GPIOC->BRR=1<<6 //读数据 PC6 //PB0~15,作为数据线 #define DATAOUT(x) GPIOB->ODR=x; //数据输出 #define DATAIN GPIOB->IDR; //数据输入 ////////////////////////////////////////////////////////////////////////////////// //扫描方向定义 #define L2R_U2D 0 //从左到右,从上到下 #define L2R_D2U 1 //从左到右,从下到上 #define R2L_U2D 2 //从右到左,从上到下 #define R2L_D2U 3 //从右到左,从下到上 #define U2D_L2R 4 //从上到下,从左到右 #define U2D_R2L 5 //从上到下,从右到左 #define D2U_L2R 6 //从下到上,从左到右 #define D2U_R2L 7 //从下到上,从右到左 #define DFT_SCAN_DIR L2R_U2D //默认的扫描方向 //画笔颜色 #define WHITE 0xFFFF ……//省略部分 #define LBBLUE 0X2B12 //浅棕蓝色(选择条目的反色) void LCD_Init(void); //初始化 ……//省略部分函数定义 void LCD_Set_Window(u16 sx,u16 sy,u16 width,u16 height);//设置窗口 //SSD1963 驱动 LCD 面板参数 //LCD 分辨率设置 #define SSD_HOR_RESOLUtiON 800 //LCD 水平分辨率 #define SSD_VER_RESOLUTION 480 //LCD 垂直分辨率 //LCD 驱动参数设置 #define SSD_HOR_PULSE_WIDTH 1 //水平脉宽 #define SSD_HOR_BACK_PORCH 210 //水平前廊 #define SSD_HOR_FRONT_PORCH 45 //水平后廊 #define SSD_VER_PULSE_WIDTH 1 //垂直脉宽 #define SSD_VER_BACK_PORCH 34 //垂直前廊 #define SSD_VER_FRONT_PORCH 10 //垂直前廊 //如下几个参数,自动计算 #define SSD_HT (SSD_HOR_RESOLUTION+SSD_HOR_PULSE_WIDTH+ SSD_HOR_BACK_PORCH+SSD_HOR_FRONT_PORCH) #define SSD_HPS (SSD_HOR_PULSE_WIDTH+SSD_HOR_BACK_PORCH) #define SSD_VT (SSD_VER_PULSE_WIDTH+SSD_VER_BACK_PORCH+ SSD_VER_FRONT_PORCH+SSD_VER_RESOLUTION) #define SSD_VSP (SSD_VER_PULSE_WIDTH+SSD_VER_BACK_PORCH) #endif 代码里里面的_lcd_dev 结构体,在前面有已有介绍,其他的相对就比较简单了。另外这段 代码对颜色和驱动器的寄存器进行了很多宏定义,限于篇幅考虑,我们没有完全贴出来,省略 了其中绝大部分。此部分我们就不多说了。接下来,我们在 test.c 里面修改 main 函数如下: int main(void) { u8 x=0; u8 lcd_id[12]; //存放 LCD ID 字符串 HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(115200); //初始化串口 LED_Init(); //初始化 LED LCD_Init(); POINT_COLOR=RED; sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将 LCD ID 打印到 lcd_id 数组。 while(1) { switch(x) { case 0:LCD_Clear(WHITE);break; case 1:LCD_Clear(BLACK);break; case 2:LCD_Clear(BLUE);break; case 3:LCD_Clear(RED);break; case 4:LCD_Clear(MAGENTA);break; case 5:LCD_Clear(GREEN);break; case 6:LCD_Clear(CYAN);break; case 7:LCD_Clear(YELLOW);break; case 8:LCD_Clear(BRRED);break; case 9:LCD_Clear(GRAY);break; case 10:LCD_Clear(LGRAY);break; case 11:LCD_Clear(BROWN);break; } POINT_COLOR=RED; LCD_ShowString(30,40,200,24,24,"Mini STM32 ^_^"); LCD_ShowString(30,70,200,16,16,"TFTLCD TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,lcd_id); //显示 LCD ID LCD_ShowString(30,130,200,12,12,"2019/11/15"); x++; if(x==12)x=0; LED0=!LED0; delay_ms(1000); } } 该部分代码将显示一些固定的字符,字体大小包括 24*12、16*8 和 12*6 等三种,同时显示 LCD 驱动 IC 的型号,然后不停的切换背景颜色,每 1s 切换一次。而 LED0 也会不停的闪烁, 指示程序已经在运行了。其中我们用到一个 sprintf 的函数,该函数用法同 printf,只是 sprintf 把打印内容输出到指定的内存区间上,sprintf 的详细用法,请百度。 另外特别注意:uart_init 函数,不能去掉,因为在 LCD_Init 函数里面调用了 printf,所以一 旦你去掉这个初始化,就会死机了。实际上,只要你的代码有用到 printf,就必须初始化串口, 否则都会死机,即停在 usart.c 里面的 fputc 函数,出不来。 在编译通过之后,我们开始下载验证代码。 16.4 下载验证 将程序下载到 MiniSTM32 后,可以看到 DS0 不停的闪烁,提示程序已经在运行了。同时 可以看到 TFTLCD 模块的显示如图 16.4.1 所示: 图 16.4.1 TFTLCD 显示效果图 我们可以看到屏幕的背景是不停切换的,同时 DS0 不停的闪烁,证明我们的代码被正确的 执行了,达到了我们预期的目的。另外,本例程除了不支持 CPLD 方案的 7 寸屏模块,其余所 有的 ALIENTEK TFTLCD 模块都可以支持,直接插上去即可使用。 |
|
相关推荐
|
|
使用stm32l451片子,对ad7606进行3通道100k采样值跳动问题?
543 浏览 1 评论
STM32进行DMA数据转运,为什么只有A组最后一位数据被转运到B组去?
1370 浏览 2 评论
1682 浏览 3 评论
1292 浏览 0 评论
嵌入式开发 Win11安装ST-Link 提示 设备描述符请求失败 怎么处理?
1421 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-20 17:17 , Processed in 0.351106 second(s), Total 32, Slave 25 queries .
Powered by 电子发烧友网
© 2015 www.ws-dc.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号