TFT-LCD屏幕填充颜色
手上的TFT-LCD屏幕为2.8寸屏, 驱动IC为ILI9341
TFT-LCD屏幕显示内容前要设置一个显示窗口,窗口的参数有X坐标,Y坐标,宽度,高度,设置一个正方形或者长方形窗口,就可在窗口内显示内容,如果要显示一个点,则X、Y坐标也要设置,只不过宽度和高度都为1
TFT-LCD驱动流程
CubeMX配置
打开CubeMX软件,点击Connectivity下的FSMC
在模式中可以看到有多种选择,前四个对应了NOR Flash的四个区,因为硬件的LCD_CS引脚接到了FSMC_NE1,所以用第1个区
LCD的片选引脚CS接到了NE1,所以选择NE1
内存类型选择LCD接口,其实就是TFT_LCD驱动芯片里的GRAM,与SRAM差不多,只不过FSMC常用于LCD,所以就有这个LCD的接口选择
LCD的数据/命令选择线D/CX线接到了FSMC_A0,所以这里的LCD Register Select选择A0
数据位选择16位,这要看屏幕,有些屏幕数据8位的就选8位
配置按默认值即可,时钟周期可按下面的来配置
最后LCD屏幕的背光引脚初始化为推挽输出,默认低电平即可
程序
TFT_LCD.h
#ifndef __TFT_LCD_H_
#define __TFT_LCD_H_
/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"
//屏幕背光控制
#define TFT_LCD_BL_ON HAL_GPIO_WritePin(TFT_LCD_BL_GPIO_Port,TFT_LCD_BL_Pin,GPIO_PIN_SET)
#define TFT_LCD_BL_OFF HAL_GPIO_WritePin(TFT_LCD_BL_GPIO_Port,TFT_LCD_BL_Pin,GPIO_PIN_RESET)
/******************************************************************************
NOR/PSRAM存储区地址:
64MB:FSMC_Bank1_NORSRAM1:0x6000 0000 ~ 0x63FF FFFF
64MB:FSMC_Bank1_NORSRAM2:0x6400 0000 ~ 0x67FF FFFF
64MB:FSMC_Bank1_NORSRAM3:0x6800 0000 ~ 0x6BFF FFFF
64MB:FSMC_Bank1_NORSRAM4:0x6C00 0000 ~ 0x6FFF FFFF
选择BANK1-BORSRAM1 连接 TFT,地址范围为0x6000 0000 ~ 0x63FF FFFF
实验板选择 FSMC_A0 接LCD的D/CX(命令/数据选择)脚
命令地址 = 0x6000 0000 相当于A0 = 0,此时是发送命令
数据地址 = 0x6000 0002 = 0x6000 0000+(1<<(0+1)) 想让A0 = 1,本来是0x6000 0001的,但数据要左移一位,才是HADDR[25:1]的地址
如果电路设计时选择不同的地址线时,地址要重新计算
eg:选择A10,则数据基地址 = 0x6000 0000+(1<<(10+1)) = 0x6000 0800
*******************************************************************************/
#define FSMC_LCD_CMD_ADDR ((uint32_t)0x60000000) //FSMC_Bank1_NORSRAM1用于LCD命令操作的地址
#define FSMC_LCD_DATA_ADDR ((uint32_t)0x60000002) //FSMC_Bank1_NORSRAM1用于LCD数据操作的地址
//LCD读写函数宏定义
//先把地址强制类型转换为指针,再解引用,就得到数据,然后把命令或数据直接赋值过去就行
#define LCD_WRITE_CMD(CMD) *((__IO uint16_t*)FSMC_LCD_CMD_ADDR) = (uint16_t)CMD
#define LCD_WRITE_DATA(DATA) *((__IO uint16_t*)FSMC_LCD_DATA_ADDR) = (uint16_t)DATA
#define LCD_READ_DATA(DATA) *((__IO uint16_t*)FSMC_LCD_DATA_ADDR)
//显示方向选择,共有4个方向
#define LCD_DIRECTION 1 //竖屏,原点在屏幕左上角 X*Y = 240*320
//#define LCD_DIRECTION 2 //横屏,原点在屏幕右上角 X*Y = 320*240
//#define LCD_DIRECTION 3 //竖屏,原点在屏幕右下角 X*Y = 240*320
// #define LCD_DIRECTION 4 //横屏,原点在屏幕左下角 X*Y = 320*240
//LCD屏幕的长度与宽度,根据显示方向来定
#if (LCD_DIRECTION==1)||(LCD_DIRECTION==3)
#define LCD_WIGHT 240 //X轴长度
#define LCD_HIGHT 320 //Y轴长度
#else
#define LCD_WIGHT 320 //X轴长度
#define LCD_HIGHT 240 //Y轴长度
#endif
//LCD命令
#define LCD_CMD_SET_X_ORGIN 0x2A //设置X轴坐标,也就是列地址
#define LCD_CMD_SET_Y_ORGIN 0x2B //设置Y轴坐标,也就是行地址
#define LCD_CMD_WRGRAM 0x2C //写GRAM命令
#define LCD_CMD_REGRAM 0x2E //读GRAM命令
//定义颜色枚举类型
typedef enum
{
Color_BLACK = 0x0000, //黑色
Color_WHITE = 0xFFFF, //白色
Color_RED = 0xF800, //红色
Color_GREEN = 0x07E0, //绿色
Color_BLUE = 0x001F, //蓝色
Color_YELLOW = 0xFFE0, //黄色
Color_GRAY = 0x8430, //灰色
}LCD_Color_t;
//定义结构体类型
typedef struct
{
uint32_t ID; //屏幕ID
void (*Init)(void); //屏幕初始化
void (*LCD_FillColor)(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t); //屏幕填充颜色
}TFT_LCD_t;
/* extern variables-----------------------------------------------------------*/
extern TFT_LCD_t TFT_LCD;
/* extern function prototypes-------------------------------------------------*/
#endif
/********************************************************
End Of File
********************************************************/
头文件中主要对LCD写命令、写数据和读数据的操作用宏定义来说明,因为屏幕是接到FSMC的引脚上的,FSMC已经将通信时序做好,代码中就只需要操作数据或命令的地址就行
TFT_LCD.c
读取屏幕的ID,命令为0xD3,返回值为0x009341
/**
* @name LCD_ReadID
* @brief 读取LCD屏幕的ID号
* @param None
* @retval 返回屏幕ID号
*/
static uint32_t LCD_ReadID(void)
{
uint32_t LCD_ID = 0;
uint32_t buf[4];
LCD_WRITE_CMD(0xD3); //发送读取ID号的指令
buf[0] = LCD_READ_DATA(); //第一个读取数据无效
buf[1] = LCD_READ_DATA()&0x00FF; //只有低8位的数据才有效
buf[2] = LCD_READ_DATA()&0x00FF; //只有低8位的数据才有效
buf[3] = LCD_READ_DATA()&0x00FF; //只有低8位的数据才有效
LCD_ID = (buf[1]<<16)+(buf[2]<<8)+buf[3]; //组合成ID号
return LCD_ID; //0x009341
}
屏幕初始化函数
该函数内容一般厂商会给,可以直接拿来用,有些初始化硬照着数据手册来看都不是很懂,也可以对某些命令进行修改,比如初始化一般会设置固定的显示方向,这里可以改为用自己写的控制LCD显示方向的函数,可以随意更改不同方向;最后记得打开显示以及背光
/**
* @name LCD_Init
* @brief TFT_LCD屏幕初始化
* @param None
* @retval None
*/
static void LCD_Init(void)
{
TFT_LCD.ID = LCD_ReadID();
printf("The ID of LCD is 0x%.6X\r\n",TFT_LCD.ID);
//2.8inch ILI9341初始化
LCD_WRITE_CMD(0xCF); //电源控制
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0xC9); //C1
LCD_WRITE_DATA(0x30);
LCD_WRITE_CMD(0xED); //通电顺序控制
LCD_WRITE_DATA(0x64);
LCD_WRITE_DATA(0x03);
LCD_WRITE_DATA(0X12);
LCD_WRITE_DATA(0X81);
LCD_WRITE_CMD(0xE8); //驱动时基控制
LCD_WRITE_DATA(0x85);
LCD_WRITE_DATA(0x10);
LCD_WRITE_DATA(0x7A);
LCD_WRITE_CMD(0xCB); //电源(功率)控制
LCD_WRITE_DATA(0x39);
LCD_WRITE_DATA(0x2C);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x34);
LCD_WRITE_DATA(0x02);
LCD_WRITE_CMD(0xF7);
LCD_WRITE_DATA(0x20);
LCD_WRITE_CMD(0xEA);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x00);
LCD_WRITE_CMD(0xC0); //Power control
LCD_WRITE_DATA(0x1B); //VRH[5:0]
LCD_WRITE_CMD(0xC1); //Power control
LCD_WRITE_DATA(0x00); //SAP[2:0];BT[3:0] 01
LCD_WRITE_CMD(0xC5); //VCM control
LCD_WRITE_DATA(0x30); //3F
LCD_WRITE_DATA(0x30); //3C
LCD_WRITE_CMD(0xC7); //VCM control2
LCD_WRITE_DATA(0XB7);
//LCD_WRITE_CMD(0x36); // Memory Access Control 不用固定的显示方式,改为自己写的设置显示方向函数
//LCD_WRITE_DATA(0x08);
LCD_Disp_Direction(); //设置LCD显示方向
LCD_WRITE_CMD(0x3A); //像素格式设置
LCD_WRITE_DATA(0x55);
LCD_WRITE_CMD(0xB1); //帧速率控制
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x1A);
LCD_WRITE_CMD(0xB6); // Display Function Control
LCD_WRITE_DATA(0x0A);
LCD_WRITE_DATA(0xA2);
LCD_WRITE_CMD(0xF2); // 3Gamma Function Disable
LCD_WRITE_DATA(0x00);
LCD_WRITE_CMD(0x26); //Gamma curve selected
LCD_WRITE_DATA(0x01);
LCD_WRITE_CMD(0xE0); //Set Gamma
LCD_WRITE_DATA(0x0F);
LCD_WRITE_DATA(0x2A);
LCD_WRITE_DATA(0x28);
LCD_WRITE_DATA(0x08);
LCD_WRITE_DATA(0x0E);
LCD_WRITE_DATA(0x08);
LCD_WRITE_DATA(0x54);
LCD_WRITE_DATA(0XA9);
LCD_WRITE_DATA(0x43);
LCD_WRITE_DATA(0x0A);
LCD_WRITE_DATA(0x0F);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x00);
LCD_WRITE_CMD(0XE1); //Set Gamma
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x15);
LCD_WRITE_DATA(0x17);
LCD_WRITE_DATA(0x07);
LCD_WRITE_DATA(0x11);
LCD_WRITE_DATA(0x06);
LCD_WRITE_DATA(0x2B);
LCD_WRITE_DATA(0x56);
LCD_WRITE_DATA(0x3C);
LCD_WRITE_DATA(0x05);
LCD_WRITE_DATA(0x10);
LCD_WRITE_DATA(0x0F);
LCD_WRITE_DATA(0x3F);
LCD_WRITE_DATA(0x3F);
LCD_WRITE_DATA(0x0F);
//自己添加的初始化代码
LCD_WRITE_CMD(0x2B); //设置Y轴
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x01);
LCD_WRITE_DATA(0x3f);
LCD_WRITE_CMD(0x2A); //设置X轴
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0x00);
LCD_WRITE_DATA(0xef);
LCD_WRITE_CMD(0x11); //Exit Sleep
HAL_Delay(120);
LCD_WRITE_CMD(0x29); //display on
TFT_LCD_BL_ON; //打开背光
}
控制LCD显示方向
根据头文件中的宏定义LCD_DIRECTION来设置不同的方向,需要根据屏幕手册来设置,后面有显示方向实物图
BGR位要始终设置为1,不然根据RGB565数据格式设置的颜色数据会不正确,例如想显示红色的却显示了蓝色
注意:屏幕显示部分面向自己,插针朝上,此时屏幕为竖屏1,而竖屏2就是屏幕旋转180度来看
/**
* @name LCD_Disp_Direction
* @brief LCD显示方向
* @param None
* @retval None
*/
static void LCD_Disp_Direction(void)
{
switch (LCD_DIRECTION)
{
//竖屏1
case 1: LCD_WRITE_CMD(0x36); LCD_WRITE_DATA(1<<3); break; //BGR=1
//横屏1
case 2: LCD_WRITE_CMD(0x36); LCD_WRITE_DATA((1<<3)|(1<<5)|(1<<6)); break; //BGR=1,MV=1,MX=1
//竖屏2
case 3: LCD_WRITE_CMD(0x36); LCD_WRITE_DATA((1<<3)|(1<<7)|(1<<6)); break; //BGR=1,MY=1,MX=1
//横屏2
case 4: LCD_WRITE_CMD(0x36); LCD_WRITE_DATA((1<<3)|(1<<7)|(1<<5)); break; //BGR=1,MY=1,MV=1
default:LCD_WRITE_CMD(0x36); LCD_WRITE_DATA(1<<3); break;
}
}
设置LCD显示窗口
显示任何内容前,都要先绘制一个显示窗口,主要是设置X轴坐标(列地址),Y轴坐标(行地址),窗口宽度,窗口高度;
要显示一个点则X轴坐标和Y轴坐标也要设置,只不过宽度和高度都为1
/**
* @name LCD_SetWindows
* @brief 设置LCD显示窗口
* @param xStar:窗口起点x轴坐标
* yStar:窗口起点y轴坐标
* xWidth:窗口宽度
* yHeight:窗口高度
* @retval None
*/
static void LCD_SetWindows(uint16_t xStar,uint16_t yStar,uint16_t xWidth,uint16_t yHeight)
{
LCD_WRITE_CMD(LCD_CMD_SET_X_ORGIN); //发送设置X轴坐标的命令0x2A
//参数SC[15:0] -> 设置起始列地址,也就是设置X轴起始坐标
LCD_WRITE_DATA(xStar>>8); //先写高8位
LCD_WRITE_DATA(xStar&0x00FF); //再写低8位
//参数EC[15:0] -> 设置窗口X轴结束的列地址,因为参数xWidth是窗口长度,所以要转为列地址再发送
LCD_WRITE_DATA((xStar+xWidth-1)>>8); //先写高8位
LCD_WRITE_DATA((xStar+xWidth-1)&0x00FF); //再写低8位
LCD_WRITE_CMD(LCD_CMD_SET_Y_ORGIN); //发送设置Y轴坐标的命令0x2B
//参数SP[15:0] -> 设置起始行地址,也就是设置Y轴起始坐标
LCD_WRITE_DATA(yStar>>8); //先写高8位
LCD_WRITE_DATA(yStar&0x00FF); //再写低8位
//参数EP[15:0] -> 设置窗口Y轴结束的列地址,因为参数yHeight是窗口高度,所以要转为行地址再发送
LCD_WRITE_DATA((yStar+yHeight-1)>>8); //先写高8位
LCD_WRITE_DATA((yStar+yHeight-1)&0x00FF); //再写低8位
LCD_WRITE_CMD(LCD_CMD_WRGRAM); //开始往GRAM里写数据,写GRAM的指令为0x2C
}
设置列地址指令0x2A
起始列地址就由16位的SC确定,停止列地址由16位的EC确定,EC减去SC就是窗口的宽度
SC <= EC 0<=SC EC<=239
设置行地址的指令0x2B
起始行地址由16位的SP来确定,停止行地址由16位的EP来确定
SP <= EP 0<=SP EP<=319
LCD屏幕填充颜色
要先设置显示窗口,然后窗口就相当于二维数组,用两个for循环往窗口内写入颜色数据,颜色数据为头文件定义的颜色枚举类型
可以用一个for循环来进行延时,动态观看屏幕显示颜色的过程
/**
* @name LCD_FillColor
* @brief LCD屏幕填充颜色
* @param xStar:窗口起点x轴坐标
* yStar:窗口起点y轴坐标
* xWidth:窗口宽度
* yHeight:窗口高度
* FillColor:填充的颜色
* @retval None
*/
static void LCD_FillColor(uint16_t xStar,uint16_t yStar,uint16_t xWidth,uint16_t yHeight,LCD_Color_t FillColor)
{
uint16_t i,j;
uint16_t k;
LCD_SetWindows(xStar,yStar,xWidth,yHeight); //设置显示窗口
for(i=xStar;i<(xStar+xWidth);i++)
{
for(j=0;j<(yStar+yHeight);j++)
{
LCD_WRITE_DATA(FillColor);
//延时一会,动态观看屏幕显示过程
for(k=0;k<20000;k++);
}
}
}
屏幕显示方向
竖屏1
首先屏幕有插针的一边朝上,屏幕对自己,这样看就是竖屏,上图中的显示效果就始终以这个方向为基准
屏幕颜色填充方向:从原点开始,从左到右,从上到下
/*
* @name Run
* @brief 系统运行
* @param None
* @retval None
*/
static void Run()
{
TFT_LCD.LCD_FillColor(0,0,LCD_WIGHT,LCD_HIGHT,Color_RED); //显示红色
HAL_Delay(1000);
}
在主函数中调用填充屏幕颜色的函数,X坐标为0,Y坐标为0,即从原点开始,LCD_WIGHT为240,LCD_HIGHT为320,即窗口为整个屏幕,下载到实验板后可以看到屏幕逐渐显示红色以及其显示过程
横屏1
不旋转屏幕,则颜色填充方向:从上到下,从右到左
旋转屏幕,则颜色填充方向:从左到右,从上到下
TFT_LCD.LCD_FillColor(0,0,LCD_WIGHT,LCD_HIGHT,Color_GREEN); //显示绿色
此时LCD_WIGHT宽度就是320,LCD_HIGHT高度就是240
竖屏2
不旋转屏幕,则颜色填充方向:从右到左,从下到上
旋转屏幕,则颜色填充方向:从原点开始,从左到右,从上到下
TFT_LCD.LCD_FillColor(0,0,LCD_WIGHT,LCD_HIGHT,Color_BLUE); //显示蓝色
横屏2
不旋转屏幕,则颜色填充方向:从下到上,从左到右
旋转屏幕,则颜色填充方向:从原点开始,从左到右,从上到下
TFT_LCD.LCD_FillColor(0,0,LCD_WIGHT,LCD_HIGHT,Color_YELLOW); //显示黄色
读GRAM颜色数据
ILI9341手册不详细,下图中指令说明更清晰