当前位置:首页 > 单片机 > 单片机
[导读]一、用STM32控制TFTLCD显示的编程方法,在编程驱动TFTLCD液晶显示器之前,我们先熟悉以下概念:1、色彩深度,这是一个与TFTLCD显存对应的概念;所谓色彩深度就是每个像素点需要多少位的RGB 数据表示该点的颜色信息。

一、用STM32控制TFTLCD显示的编程方法,在编程驱动TFTLCD液晶显示器之前,我们先熟悉以下概念:

1、色彩深度,这是一个与TFTLCD显存对应的概念;所谓色彩深度就是每个像素点需要多少位的RGB

数据表示该点的颜色信息。注意,不同的TFTLCD显示器的RGB的对应关系不一样,这个可以在LCD

控制芯片手册中找到答案。

例: 某LCD显示支持8、16、24位RGB,这些位数是指该像素点颜色由8、16、24位RGB构成,但是

RGB三种颜色各占的位数可以查看数据手册。

2、TFTLCD的操作分为两种:

A、对控制寄存器的读写操作(即程序员将要操作LCD显存寄存器的地址设置成可读或者可写)。

B、对显存寄存器的读写操作(即读写LCD显存寄存器)。

3、TFTLCD有一个索引寄存器,对控制寄存器操作前,需要对索引寄存器进行定入操作,用以指明

寄存器读写是针对那个寄存器的,具体操作步骤如下:

RS为低电平状态下,写入两个字节的数据,第一个字节为零,第二个字节为寄存器索引值。

RS为高电平状态下,读取两个字节数据,第一个字节为高八位,第二个字节为低八位。

二、实验平台STM32F103RCT6与ILI9341 TFTLCD驱动模块

硬件采用 16 位的并方式与外部连接,之所以不采用 8 位的方式,是因为彩屏的数据量比较大,

尤其在显示图片的时候,如果用 8 位数据线,就会比 16 位方式慢一倍以上,我们当然希望速

度越快越好,所以我们选择 16 位的80 并口。有如下一些信号线:
CS:TFTLCD 片选信号。
WR:向 TFTLCD 写入数据。
RD:从 TFTLCD 读取数据。
D[15:0]:16 位双向数据线。
RST:硬复位 TFTLCD。
RS:命令/数据标志(0,读写命令;1,读写数据)。

在 16 位模式下,ILI9341 采用 RGB565 格式存储颜色数据,接下来看一下ILI9341 的几个重要命令

1、0XD3,用于读取 LCD 控制器的 ID。

2、0X36,这是存储访问控制指令,可以控制 ILI9341 存储器的读写方向,简单的说,就是在连续写

GRAM 的时候,可以控制 GRAM 指针的增长方向,从而控制显示方式。

3、0X2A,这是列地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置

横坐标(x 坐标)。

4、0X2B,是页地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置纵

坐标(y 坐标)。

5、0X2C,该指令是写 GRAM 指令,在发送该指令之后,我们便可以往 LCD的 GRAM 里面写入颜色

数据了,该指令支持连续写,在收到指令 0X2C 之后,数据有效位宽变为 16 位,我们可以连续写入

LCDGRAM 值,而 GRAM 的地址将根据 MY/MX/MV 设置的扫描方向进行自增。

6、0X2E, 该指令是读 GRAM 指令,用于读取 ILI9341 的显存(GRAM)。



三、软件编程

lcd.h 里面的一个重要结构体:

//LCD重要参数集
typedef struct
{
u16 width;//LCD 宽度
u16 height;//LCD 高度
u16 id;//LCD ID
u8 dir;//横屏还是竖屏控制:0,竖屏;1,横屏。
u16wramcmd;//开始写gram指令
u16 setxcmd;//设置x坐标指令
u16 setycmd;//设置y坐标指令
}_lcd_dev;

//LCD参数
extern _lcd_dev lcddev; //管理LCD重要参数


该结构体用于保存一些 LCD 重要参数信息,比如 LCD 的长宽、LCD ID(驱动 IC 型号)、
LCD 横竖屏状态等,这个结构体虽然占用了 14 个字节的内存,但是却可以让我们的驱动函数
支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。有
了以上了解,下面我们开始介绍 ILI93xx.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;
}


//写数据函数
//可以替代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_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;
//读取数据(读寄存器时,并不需要读2次)
LCD_RD_CLR;
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)LCD_WR_REG(0X2E);//9341/6804/5310发送读GRAM指令
else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);//5510 发送读GRAM指令
else LCD_WR_REG(R34); //其他IC发送读GRAM指令
GPIOB->CRL=0X88888888; //PB0-7 上拉输入
GPIOB->CRH=0X88888888; //PB8-15 上拉输入
GPIOB->ODR=0XFFFF; //全部输出高


LCD_RS_SET;
LCD_CS_CLR;
//读取数据(读GRAM时,第一次为假读)
LCD_RD_CLR;
delay_us(1);//延时1us
LCD_RD_SET;
//dummy READ
LCD_RD_CLR;
delay_us(1);//延时1us
r=DATAIN; //实际坐标颜色
LCD_RD_SET;
if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)//9341/NT35310/NT35510要分2次读出
{
LCD_RD_CLR;
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)

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

在嵌入式开发中,STM32的时钟系统因其灵活性和复杂性成为开发者关注的焦点。然而,看似简单的时钟配置背后,隐藏着诸多易被忽视的陷阱,轻则导致系统不稳定,重则引发硬件损坏。本文从时钟源选择、PLL配置、总线时钟分配等关键环...

关键字: STM32 时钟系统

在嵌入式系统开发中,STM32系列微控制器的内部温度传感器因其低成本、高集成度特性,广泛应用于设备自检、环境监测等场景。然而,受芯片工艺差异和电源噪声影响,其原始数据存在±1.5℃的固有误差。本文从硬件配置、校准算法、软...

关键字: STM32 温度传感器

在能源效率与智能化需求双重驱动下,AC-DC转换器的数字控制技术正经历从传统模拟方案向全数字架构的深刻变革。基于STM32微控制器的PFM(脉冲频率调制)+PWM(脉冲宽度调制)混合调制策略,结合动态电压调整(Dynam...

关键字: AC-DC STM32

当前智能家居产品需求不断增长 ,在这一背景下 ,对现有浇花装置缺陷进行了改进 ,设计出基于STM32单片机的全 自动家用浇花机器人。该设计主要由机械结构和控制系统构成 ,机械结构通过麦克纳姆轮底盘与喷洒装置的结合实现机器...

关键字: STM32 麦克纳姆轮 安全可靠 通过性强

用c++编程似乎是让你的Arduino项目起步的障碍吗?您想要一种更直观的微控制器编程方式吗?那你需要了解一下Visuino!这个图形化编程平台将复杂电子项目的创建变成了拖动和连接块的简单任务。在本文中,我们将带您完成使...

关键字: Visuino Arduino ESP32 STM32

基于STM32与LoRa技术的无线传感网络凭借其低功耗、广覆盖、抗干扰等特性,成为环境监测、工业自动化等场景的核心解决方案。然而,如何在复杂电磁环境中实现高效休眠调度与动态信道优化,成为提升网络能效与可靠性的关键挑战。本...

关键字: STM32 LoRa

在实时控制系统、高速通信协议处理及高精度数据采集等对时间敏感的应用场景中,中断响应延迟的优化直接决定了系统的可靠性与性能上限。STM32系列微控制器凭借其灵活的嵌套向量中断控制器(NVIC)、多通道直接内存访问(DMA)...

关键字: STM32 DMA

数字电源技术向高功率密度、高效率与高动态响应方向加速演进,STM32微控制器凭借其基于DSP库的算法加速能力与对LLC谐振变换器的精准控制架构,成为优化电源动态性能的核心平台。相较于传统模拟控制或通用型数字控制器,STM...

关键字: STM32 数字电源

STM32微控制器凭借其针对电机控制场景的深度优化,成为高精度、高可靠性驱动系统的核心选择。相较于通用型MCU,STM32在电机控制领域的核心优势集中体现在FOC(磁场定向控制)算法的硬件加速引擎与PWM死区时间的动态补...

关键字: STM32 电机控制

无线充电技术加速渗透消费电子与汽车电子领域,基于Qi协议的无线充电发射端开发成为智能设备能量补给的核心课题。传统模拟控制方案存在响应滞后、参数调整困难等问题,而基于STM32的数字PID控制结合FOD(Foreign O...

关键字: STM32 无线充电
关闭