前言:TFT-LCD模块作为人们日常生活中常见屏幕类型之一,使用的受众面非常广阔。例如:显示各个传感器数值显示精美界面多级化菜单系统等等都不离不开他的身影。可以说学会TFT-LCD模块是嵌入式开发必须掌握的驱动开发技能之一,同时,也是嵌入式开发调试配置的重要手段与技巧!(文章结尾会有代码开源

实验硬件:STM32F103C8T6;2.4寸TFT-LCD(240×320)

硬件实物图:

效果图:

引脚连接:

VCC –> 3.3V

GND –> GND

CS –> PB11

Reset –> PB12

DC –> PB10

SDI –> PB15

SCK –> PB13

LED –> PB9控制LCD背光,可以同PWM调节改变LCD亮暗

一、TFT-LCD模块简介

TFT-LCDThin Film Transistor)液晶显示屏是薄膜晶体管型液晶显示屏,也就是“真彩”(TFT)。TFT液晶为每个像素都设有一个半导体开关,每个像素都可以通过点脉冲直接控制,因而每个节点都相对独立,并可以连续控制,不仅提高了显示屏的反应速度,同时可以精确控制显示色阶,所以TFT液晶的色彩更真

TFT液晶显示屏的特点亮度好对比度高层次感强颜色鲜艳,但也存在着比较耗电成本过高的不足。TFT液晶技术加快了手机彩屏的发展。新一代的彩屏手机中很多都支持65536色显示,有的甚至支持16万色显示,这时TFT的高对比度,色彩丰富的优势就非常重要了。

市面上的TFT-LCD有需要的芯片驱动类型(不同的驱动芯片,其显存大小与其驱动时的传输LCD初始化数据不一样。其显示功能的API函数可以互通),市面上常见的芯片驱动有:ILI9341/ ILI9325/ RM68042/ RM68021/ ILI9320/ ILI9328/ LGDP4531/ LGDP4535/ SPFD5408/ SSD1289/ 1505/ B505/ C505/ NT35310/ NT35510 等。

笔者所用的TFTLCD驱动芯片为常见的ILI9341,这里就以ILI9341给大家为例讲诉(需要其他驱动芯片资料的可以评论留言,笔者基本上市面上常见的都有其代码与资料)。

ILI9341液晶控制器自带显存,其显存总大小为 172800(240*320*18/8),即 18 位模式(26万色)下的显存量。在 16 位模式下,ILI9341 采用 RGB565 格式存储颜色数据(在更高级的32位RGB储存颜色中还有RGBA888,Linux开发板中较为常见),此时 ILI9341的 18 位数据线与 MCU 的 16 位数据线以及 LCD GRAM 的对应关系如图 所示:

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

注意:不同的TFT-LCD模块的引脚可能不同,这里原因为该LCD的硬件通讯方式的不同。较为常见的TFTLCD通讯方式有:串行通讯SPI,LVDS、EDP、MIPI等。(较多的为标红

笔者这块TFT-LCD模块采用了SPI的通讯方式,故此接下来就以SPI下的TFT-LCD驱动为讲解。

一般 TFTLCD 模块的使用流程如图:

二、SPI简介

SPI 协议是由摩托罗拉公司提出的通讯协议 (Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC设备LCD 等设备与 MCU 间,要求通讯速率较高的场合。

2.1SPI通讯系统

SPI 通讯使用3条总线片选线,3 条总线分别为 SCK、MOSI、MISO,片选线为 SS/CS,它们的作用介绍如下:

(1) SS/CS( Slave Select)

从设备选择信号线,常称为片选信号线,也称为 NSSCS,以下用 NSS表示。当有多个 SPI 从设备与 SPI 主机相连时,设备的其它信号线 SCK、MOSI 及 MISO同时并联到相同的 SPI 总线上,即无论有多少个从设备,都共同只使用这 3 条总线;而每个从设备都有独立的这一条 NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;

而SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以 SPI 通讯以 NSS 线置低电平为开始信号,以NSS 线被拉高作为结束信号。(在LCD中,片选线有很多名称,CS,SS,NSS都是指片选

(2) SCK (Serial Clock):

时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备

(3) MOSI (Master Output,Slave Input):

主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。 (IIC相比,这个就是信号线,由主机向从机发送数据,即SDA

(4) MISO(Master Input,,Slave Output):

主设备输入/从设备输出引脚。主机从这条信线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。 (从机向主机发送数据,使用触摸屏时需要这根线。如果单纯使用LCD来显示,这根线可以不接)。

多设备的SPI通讯接线:

2.2 SPI时序图

SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:
①、CPOL=0,串行时钟空闲状态为低电平。
②、CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具
体的传输协议。
③、CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
④、CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。

三、CubexMX配置

1、RCC配置外部高速晶振(精度更高)——HSE;

2、SYS配置:Debug设置成Serial Wire否则可能导致芯片自锁);

3、GPIO配置:将PB9,PB10,PB11,PB12都设置为GPIO_OUTPUT,速度为:Hight;

4、SPI配置:配置使用SPI2作为TFT-LCD通讯方式

5、时钟树配置:

6、工程配置

四、代码实现与实验效果

4.1 TFT-LCD基础的初始化

以下代码读者朋友可以参考各自TFT-LCD的datasheet文本,不同类型的TFT-LCD屏幕的初始化写入的数据可能不同,但是主要还是对那几个功能寄存器写入对应的数值。

lcd.h

/***************************************************************************** 名称:voidSPIv_WriteData(u8 Data)* 功能:STM32_模拟SPI写一个字节数据底层函数* 入口参数:Data* 出口参数:无* 说明:STM32_模拟SPI读写一个字节数据底层函数****************************************************************************///voidSPIv_WriteData(u8 Data)//{//unsigned char i=0;//for(i=8;i>0;i--)//{//if(Data&0x80)//{//LCD_SDA_SET; //" />TFT-LCD的SPI软件通讯模式下常见的代码,其中不同驱动芯片下的LCD_Init函数会不同,基本一致的函数有:void SPIv_WriteData(u8 Data),void Lcd_WriteIndex(u8 Index),void Lcd_WriteData(u8 Data),void LCD_WriteReg(u8 Index,u16 Data),void Lcd_WriteData_16Bit(u16 Data),void Lcd_Reset(void)。

引脚定义:

//define LCD PIN#defineLCD_CS_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_SET)#defineLCD_CS_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_RESET)#defineLCD_RS_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_SET)#defineLCD_RS_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET)#defineLCD_SDA_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET)#defineLCD_SDA_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET)#defineLCD_SCL_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_SET)#defineLCD_SCL_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_RESET)#defineLCD_RST_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET)#defineLCD_RST_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET)#defineLCD_LED_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET)#defineLCD_LED_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET)

4.2 SPI函数重写

因为笔者采用了HAL库去使用SPI通讯,所以需要去重写SPI函数

spi.h:

/* Includes ------------------------------------------------------------------*/#include "main.h"/* USER CODE BEGIN Includes */uint8_t SPI_WriteByte(uint8_t *TxData,uint16_t size);/* USER CODE END Includes */

spi.c:

/* USER CODE BEGIN 1 */uint8_t SPI_WriteByte(uint8_t *TxData,uint16_t size){return HAL_SPI_Transmit(&hspi2,TxData,size,1000);}/* USER CODE END 1 */

4.3 TFT-LCD显示的基础函数

TFT-LCD的显示需要依赖几个基础功能函数,这几个函数也时通用的。

/*************************************************函数名:LCD_Set_XY功能:设置lcd显示起始点入口参数:xy坐标返回值:无*************************************************/void Lcd_SetXY(u16 Xpos, u16 Ypos){Lcd_WriteIndex(0x2A);Lcd_WriteData_16Bit(Xpos);Lcd_WriteIndex(0x2B);Lcd_WriteData_16Bit(Ypos);Lcd_WriteIndex(0x2c);} /*************************************************函数名:LCD_Set_Region功能:设置lcd显示区域,在此区域写点数据自动换行入口参数:xy起点和终点返回值:无*************************************************///设置显示窗口void Lcd_SetRegion(u16 xStar, u16 yStar,u16 xEnd,u16 yEnd){Lcd_WriteIndex(0x2A);Lcd_WriteData_16Bit(xStar);Lcd_WriteData_16Bit(xEnd);Lcd_WriteIndex(0x2B);Lcd_WriteData_16Bit(yStar);Lcd_WriteData_16Bit(yEnd);Lcd_WriteIndex(0x2c);}/*************************************************函数名:LCD_DrawPoint功能:画一个点入口参数:xy坐标和颜色数据返回值:无*************************************************/void Gui_DrawPoint(u16 x,u16 y,u16 Data){Lcd_SetXY(x,y);Lcd_WriteData_16Bit(Data);}/*************************************************函数名:Lcd_Clear功能:全屏清屏函数入口参数:填充颜色COLOR返回值:无*************************************************/void Lcd_Clear(u16 Color) { unsigned int i; Lcd_SetRegion(0,0,X_MAX_PIXEL-1,Y_MAX_PIXEL-1); LCD_CS_CLR; LCD_RS_SET; for(i=0;i>8);SPIv_WriteData(Color); } LCD_CS_SET;}

总的lcd.h函数:

#ifndef __LCD_H#define __LCD_H#include "main.h"#define u8unsigned char #define u16 unsigned int /用户配置区/// //支持横竖屏快速定义切换#define USE_HORIZONTAL0//定义是否使用横屏 0,不使用.1,使用.//-----------------------------SPI 总线配置--------------------------------------//#define USE_HARDWARE_SPI 0//1:Enable Hardware SPI;0:USE Soft SPI//-------------------------屏幕物理像素设置--------------------------------------//#define LCD_X_SIZE240#define LCD_Y_SIZE320#if USE_HORIZONTAL//如果定义了横屏 #define X_MAX_PIXELLCD_Y_SIZE#define Y_MAX_PIXELLCD_X_SIZE#else#define X_MAX_PIXELLCD_X_SIZE#define Y_MAX_PIXELLCD_Y_SIZE#endif// #define RED0xf800#define GREEN0x07e0#define BLUE 0x001f#define WHITE0xffff#define BLACK0x0000#define YELLOW0xFFE0#define GRAY0 0xEF7D //灰色0 3165 00110 001011 00101#define GRAY1 0x8410//灰色100000 000000 00000#define GRAY2 0x4208//灰色21111111111011111//define LCD PIN#defineLCD_CS_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_SET)#defineLCD_CS_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_RESET)#defineLCD_RS_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_SET)#defineLCD_RS_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET)#defineLCD_SDA_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET)#defineLCD_SDA_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET)#defineLCD_SCL_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_SET)#defineLCD_SCL_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_RESET)#defineLCD_RST_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET)#defineLCD_RST_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET)#defineLCD_LED_SETHAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET)#defineLCD_LED_CLRHAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET)void Lcd_Reset(void);void Lcd_WriteIndex(u8 Index);void Lcd_WriteData(u8 Data);void Lcd_WriteReg(u8 Index,u8 Data);u16 Lcd_ReadReg(u8 LCD_Reg);void Lcd_Reset(void);void Lcd_Init(void);void Lcd_Clear(u16 Color);void Lcd_SetXY(u16 x,u16 y);void Gui_DrawPoint(u16 x,u16 y,u16 Data);unsigned int Lcd_ReadPoint(u16 x,u16 y);void Lcd_SetRegion(u16 xStar, u16 yStar,u16 xEnd,u16 yEnd);void Lcd_WriteData_16Bit(u16 Data);#endif

4.4 TFT-LCD显示的API函数

LCDAPI.h:

#ifndef __LCDAPI_H#define __LCDAPI_H#include "main.h"#define u8 unsigned char#define u16 unsigned intvoid Gui_Circle(u16 X,u16 Y,u16 R,u16 fc); void Gui_DrawLine(u16 x0, u16 y0,u16 x1, u16 y1,u16 Color);void Gui_box(u16 x, u16 y, u16 w, u16 h,u16 bc);void Gui_box2(u16 x,u16 y,u16 w,u16 h, u8 mode);void DisplayButtonDown(u16 x1,u16 y1,u16 x2,u16 y2);void DisplayButtonUp(u16 x1,u16 y1,u16 x2,u16 y2);void Gui_DrawFont_GBK16(u16 x, u16 y, u16 fc, u16 bc, u8 *s);void Gui_DrawFont_GBK24(u16 x, u16 y, u16 fc, u16 bc, u8 *s);void Gui_DrawFont_Num32(u16 x, u16 y, u16 fc, u16 bc, u16 num);void LCD_DrawPoint(u16 x,u16 y);unsigned long oled_pow(u8 m,u8 n);void LCD_Showdecimal(u8 x,u8 y,float num,u8 z_len,u8 f_len,u8 size2);void LCD_ShowChar(u16 x,u16 y,u8 num,u8 mode);void Address_set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2);void showhanzi(unsigned int x,unsigned int y,unsigned char index);void showimage(const unsigned char *p);void LCD_ShowNum(u16 x,u16 y,unsigned long num,u8 len);void picture();#endif

4.4.1 TFT-LCD显示字符和汉字的API函数

笔者这里写了一个共用函数即可显示字符也可以显示汉字

LCD的汉字与字符显示与OLED类似,也是需要去取字模的,但是TFT-LCD需要设定字体颜色

不同的显示汉字字符API函数的取模方式不一样!!!(如果取模和程序画点顺序不一致,会导致显示字符为乱码)

笔者这里的取字模方式:

LCDAPI.c:

//display 16 zitivoid Gui_DrawFont_GBK16(u16 x, u16 y, u16 fc, u16 bc, u8 *s){unsigned char i,j;unsigned short k,x0;x0=x;while(*s) {if((*s) 32) k-=32; else k=0;for(i=0;i<16;i++)for(j=0;j>j))Gui_DrawPoint(x+j,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);}}x+=8;}s++;}else {for (k=0;k<hz16_num;k++) {if ((hz16[k].Index[0]==*(s))&&(hz16[k].Index[1]==*(s+1))){ for(i=0;i<16;i++){for(j=0;j>j))Gui_DrawPoint(x+j,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);}}for(j=0;j>j))Gui_DrawPoint(x+j+8,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j+8,y+i,bc);}}}}}s+=2;x+=16;} }}//display 24 zitivoid Gui_DrawFont_GBK24(u16 x, u16 y, u16 fc, u16 bc, u8 *s){unsigned char i,j;unsigned short k;while(*s) {if( *s 32) k-=32; else k=0;for(i=0;i<16;i++)for(j=0;j>j))Gui_DrawPoint(x+j,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);}}s++;x+=8;}else {for (k=0;k<hz24_num;k++) {if ((hz24[k].Index[0]==*(s))&&(hz24[k].Index[1]==*(s+1))){ for(i=0;i<24;i++){for(j=0;j>j))Gui_DrawPoint(x+j,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);}}for(j=0;j>j))Gui_DrawPoint(x+j+8,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j+8,y+i,bc);}}for(j=0;j>j))Gui_DrawPoint(x+j+16,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j+16,y+i,bc);}}}}}s+=2;x+=24;}}}

main.h函数:

//Font display 24 and 16Gui_DrawFont_GBK24(0,0,GREEN,WHITE,"»ì·Ö¾ÞÊÞÁúijij");Gui_DrawFont_GBK16(0,30,BLUE,WHITE,"»ì·Ö¾ÞÊÞÁúijij");//string displayGui_DrawFont_GBK16(0,50,BLACK,WHITE,"black sneak");

显示效果:

4.4.2TFT-LCD显示数字和含小数的API函数

动态的数字显示和含有小数的数字显示也是日常嵌入式开发中经常会遇到的,学会使用该API函数也是至关重要的。

常规数字显示函数:

void LCD_ShowNum(u16 x,u16 y,unsigned long num,u8 len){ u8 t,temp;u8 enshow=0;num=(u16)num;for(t=0;t<len;t++){temp=(num/oled_pow(10,len-t-1))%10;if(enshow==0&&t<(len-1)){if(temp==0){LCD_ShowChar(x+8*t,y,' ',1);continue;}else enshow=1; } LCD_ShowChar(x+8*t,y,temp+48,1); }} 

含有小数的数字显示函数:

void LCD_Showdecimal(u8 x,u8 y,float num,u8 z_len,u8 f_len,u8 size2){ u8 t,temp;u8 enshow;int z_temp,f_temp;z_temp=(int)num;//" />

4.4.3TFT-LCD特殊字符或数码管API显示函数

一般特殊的字符或是数码管等也时通过取模软件进行特殊取模以呈现的效果,这里给大家介绍一个类似数码管数字的显示API函数,可以以后作为时钟显示使用。

数码管数字显示函数:

void Gui_DrawFont_Num32(u16 x, u16 y, u16 fc, u16 bc, u16 num){unsigned char i,j,k,c;//lcd_text_any(x+94+i*42,y+34,32,32,0x7E8,0x0,sz32,knum[i]);//w=w/8;for(i=0;i<32;i++){for(j=0;j<4;j++) {c=*(sz32+num*32*4+i*4+j);for (k=0;k>k))Gui_DrawPoint(x+j*8+k,y+i,fc);else {if (fc!=bc) Gui_DrawPoint(x+j*8+k,y+i,bc);}}}}}

显示效果:

4.4.4TFT-LCD图片显示API函数

TFT-LCD图片显示需要使用到Img2Lcd软件,需要该软件的读者朋友,可以评论留言邮箱。

Img2Lcd软件使用方法很简单,对于RGB565的真彩选择:16位真彩,其余选择如下图:

特别注意:由于MCU的显存通常有限,特别时C8T6等最小系统板的存储更是有限。如果取模图片之后,编译出现如下:Error: L6406E: No space in execution regions with .ANY selector matching xxx,那是你MCU炸内存了。

解决方法:

①换开发板:换一个大容量的开发板,比如ZET6等;

②从SD卡读图片:部分拓展实验,比如LCD显示动画,都是采用的这方法;

③改写MCU的malloc:这种方法属于大佬使用的,十分考验对MCU存储概念的理解;

图片显示API函数:

//显示图片void showimage(const unsigned char *p) {//" />

4.4.4 main函数

/* Initialize all configured peripherals */MX_GPIO_Init();MX_SPI2_Init();/* USER CODE BEGIN 2 */Lcd_Init();LCD_LED_SET;//ͨ¹ýIO¿ØÖƱ³¹âÁLCD_RST_SET;Lcd_Clear(WHITE);//LCD_RST_SET;/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 *///Font display 24 and 16Gui_DrawFont_GBK24(0,0,GREEN,WHITE,"混分巨兽龙某某");Gui_DrawFont_GBK16(0,30,BLUE,WHITE,"混分巨兽龙某某");//string displayGui_DrawFont_GBK16(0,50,BLACK,WHITE,"black sneak");//Number displayLCD_ShowNum(0,180,z,4);LCD_Showdecimal(0,200,t,2,2,16);t += 0.01;z += 1;//special thing displayfor(n = 0;n <10 ;n++){Gui_DrawFont_Num32(0,100,RED,WHITE,num[n]);if(n == 10){n = 0;}}//PNG displaypicture();}/* USER CODE END 3 */

五、实验效果

TFT-LCD各种显示

六、代码开源

读者这里提供是2.4寸ILI9341驱动芯片的LCD显示代码,如果需要其他尺寸大小和芯片驱动的LCD代码可以评论区留言邮箱点个关注笔者免费提供

链接:https://pan.baidu.com/s/1tNzOOUUK7ioekmVSy5TqYA 提取码:yi93