Bootstrap

STM32实战总结:HAL之FSMC控制TFT-LCD

什么是FSMC?

FSMC(Flexible Static Memory Controller,可变静态存储控制器)是STM32系列采用的一种新型的存储器扩展技术。在外部存储器扩展方面具有独特的优势,可根据系统的应用需要,方便地进行不同类型大容量静态存储器的扩展。

FSMC(Flexible Static Memory Controller,可变静态存储控制器)是STM32系列中内部集成256 KB以上FlaSh、后缀为xC、xD和xE的高存储密度微控制器特有的存储控制机制。之所以称为“可变”,是由于通过对特殊功能寄存器的设置,FSMC能够根据不同的外部存储器类型,发出相应的数据/地址/控制信号类型以匹配信号的速度,从而使得STM32系列微控制器不仅能够应用各种不同类型、不同速度的外部静态存储器,而且能够在不增加外部器件的情况下同时扩展多种不同类型的静态存储器,满足系统设计对存储容量、产品体积以及成本的综合要求。

FSMC技术优势

①支持多种静态存储器类型。STM32通过FSMC可以与SRAM、ROM、PSRAM、NOR Flash和NANDFlash存储器的引脚直接相连。

②支持丰富的存储操作方法。FSMC不仅支持多种数据宽度的异步读/写操作,而且支持对NOR/PSRAM/NAND存储器的同步突发访问方式。

③支持同时扩展多种存储器。FSMC的映射地址空间中,不同的BANK是独立的,可用于扩展不同类型的存储器。当系统中扩展和使用多个外部存储器时,FSMC会通过总线悬空延迟时间参数的设置,防止各存储器对总线的访问冲突。

④支持更为广泛的存储器型号。通过对FSMC的时间参数设置,扩大了系统中可用存储器的速度范围,为用户提供了灵活的存储芯片选择空间。

⑤支持代码从FSMC扩展的外部存储器中直接运行,而不需要首先调入内部SRAM。

FSMC内部结构

STM32微控制器之所以能够支持NOR Flash和NAND Flash这两类访问方式完全不同的存储器扩展,是因为FSMC内部实际包括NOR Flash和NAND/PC Card两个控制器,分别支持两种截然不同的存储器访问方式。在STM32内部,FSMC的一端通过内部高速总线AHB连接到内核Cortex-M3,另一端则是面向扩展存储器的外部总线。内核对外部存储器的访问信号发送到AHB总线后,经过FSMC转换为符合外部存储器通信规约的信号,送到外部存储器的相应引脚,实现内核与外部存储器之间的数据交互。FSMC起到桥梁作用,既能够进行信号类型的转换,又能够进行信号宽度和时序的调整,屏蔽掉不同存储类型的差异,使之对内核而言没有区别。

FSMC映射地址空间

FSMC管理1 GB的映射地址空间。该空间划分为4个大小为256 MB的BANK,每个BANK又划分为4个64 MB的子BANK,如表1所列。FSMC的2个控制器管理的映射地址空间不同。NOR Flash控制器管理第1个BANK,NAND/PC Card控制器管理第2~4个BANK。由于两个控制器管理的存储器类型不同,扩展时应根据选用的存储设备类型确定其映射位置。其中,BANK1的4个子BANK拥有独立的片选线和控制寄存器,可分别扩展一个独立的存储设备,而BANK2~BANK4只有一组控制寄存器。

什么是PC卡?

PC卡,又称PCMCIA卡,被笔记本电脑广泛用作存储媒体。

一般来说较耗电,但成本较低。

PCMCIA插槽是笔记本电脑上最重要的设备扩展接口,可以用来插入传真卡/网卡/存储卡/声霸卡等等,一些专业杂志甚至预测PCMCIA会成为今后台式电脑、汽车以及家用电器上的标准接口。PCMCIA的主要优势是可以带电插拔,配合适当软件后可以实现即插即用。

除了笔记型计算机可使用 PCMCIA 规格的卡片外,还有 PDA、数字相机、数字电视、机顶盒 ( set-top boxes )…等等也都有对应的产品可以使用PCMCIA规格的卡片。

LCD

什么是LCD?

LCD(Liquid Crystal Display)俗称液晶。液晶是一种材料,这种材料具有一种特点:可以在电信号的驱动下液晶分子进行旋转,旋转时会影响透光性,因此我们可以在整个液晶面板后面用白光照(称为背光),可以通过不同电信号让液晶分子进行选择性的透光,此时在液晶面板前面看到的就是各种各样不同的颜色,这就是LCD显示。


被动发光和主动发光。

有些显示器(譬如LED显示器、CRT显示器)自己本身会发光称为主动发光,有些(LCD)本身不会发光只会透光,需要背光的协助才能看起来是发光的,称为被动发光。


 

液晶应用领域:电视机、电脑显示屏、手机显示屏、工业显示屏等····

其他主流显示设备(LED、CRT、等离子、OLED)
(1) CRT:阴极摄像管显示器。早期电视机的大屁股。寿命短,笨重。
(2) 等离子显示:未成为主流
(3) OLED:目前未成为主流,但是很有市场潜力
(4) LED:(自发光)主要用在户外大屏幕(LCD易受光照影响,比如在亮处看不清屏幕)
(5) LCD:目前是主流显示器

LCD的显示原理和特点(液晶分子透光+背光)

LCD屏幕的工作原理是怎样的?_哔哩哔哩_bilibili
 

LCD的发展史和种类(TN/STN/TFT)
(1) TN最早。坏处是响应性不够好,有拖尾现象。
(2) STN是TN的升级版。有效解决拖尾现象,显示更清晰。
(3) TFT的最大特点就是超薄。
(4) TFT技术之上发展出来很多更新的技术。


LCD如何显示图像
像素(pixel)
(1)像素就是组成图像的最基本元素,或者说显示中可以被控制的最小单位,整个图像就是由很多个像素组成的。
(2)像素可以被单独控制,或控制其亮或不亮(单色屏)、或控制其亮度强弱(譬如亮50%,35%,这样叫灰度屏,以前的黑白电视机)、或控制其显示一定的颜色(这就是我们现在最常用的彩色显示屏)。
总结:像素很重要,整个显示图像是由一个个的像素组成的。我们要在显示器上显示一个图像,就是把这个图像离散化成一个一个的点,然后把各个点的颜色对应在显示器的像素上。

扫描
(1)扫描是一个动作而不是一个名字,扫描就是依次将颜色数值放入屏幕中所有的像素的这个过程。
(2)扫描这个词是由最早的CRT显示器遗留下来的,到LCD显示器的年代本来已经失去意义了,但是我们还是延续着这么叫。
(3)显示器的扫描显示原理依赖于人眼的视觉暂留。只要显示器扫描频率大于人眼的发现频率,人眼看到的图像就是恒定的。如果扫描频率偏小人眼就会看到闪动。(扫描频率的概念就叫做刷新率)

驱动器&控制器
LCD驱动器一般和LCD显示面板集成在一起(本来是分开的,做面板的是只做面板的,譬如说三星、LG、台湾的友达、奇美都是做面板的;驱动器也由专门的IC厂商生产;集成厂商买来面板和驱动器后集成在一起做成LCD屏幕),面板只负责里面的液晶分子旋转透光,面板需要一定的模拟电信号来控制液晶分子;LCD驱动器芯片负责给面板提供控制液晶分子的模拟电信号,驱动器的控制信号(数字信号)来自于自己的数字接口,这个接口就是LCD屏幕的外部接口
LCD控制器一般集成在单片机端,他负责通过数字接口向远端的LCD驱动器提供控制像素显示的数字信号。LCD控制器的关键在于时序,它必须按照一定的时序和LCD驱动器通信;LCD控制器受单片机控制,单片机会从内存中拿像素数据给LCD控制器并最终传给LCD驱动器。有的会直接将控制器和驱动器都集成到LCD中。具体要看产品设计。

显示内存(简称:显存)
(1) SoC在内存中挑选一段内存,然后通过配置将LCD控制器和这一段内存(以后称为显存)连接起来构成一个映射关系。一旦这个关系建立之后,LCD控制器就会自动从显存中读取像素数据传输给LCD驱动器。这个显示的过程不需要CPU的参与。
(2) 显示体系建立起来后,CPU就不用再管LCD控制器、驱动器、面板这些东西了;以后CPU就只关心显存了,因为我只要把要显示的图像的像素数据丢到显存中,硬件就会自动响应(屏幕上就能自动看到显示的图像了)。

总结:LCD显示是分为2个阶段的:第一个阶段就是建立显示体系的过程,目的就是CPU初始化LCD控制器使其和显存联系起来构成映射;第二个阶段就是映射建立之后,此阶段主要任务是将要显示的图像丢到显存中去。


LCD显示单位:帧(frame)
(1) 显示器上一整个画面的内容成为一个帧(frame),整个显示器工作时是一帧一帧的在显示。
(2) 电影实际就是以每秒种24帧的速度在播放图片。
(3) 帧内数据:一帧分为多行,一行分为多像素,因此一帧图像其实就是多个像素组成的矩阵。
(4) 帧外数据:整个视频由很多个帧构成,最终播放视频时逐个播放各个图像帧即可。

LCD显示一帧图像的过程
(1)首先把帧分为行,然后再把行分为像素,然后逐个像素去显示。(显示像素:其实就是LCD驱动器按照接收到的LCD控制器给的显示数据,驱动一个像素的液晶分子旋转,让这个像素显示出相应的颜色值的过程)
(2)关键点:LCD控制器和驱动器之间一次只能传一个像素点的显示数据。所以一帧图像在屏幕上其实是串行的依次被显示上去的,不是同一时间显示出来的。

LCD显示的主要相关概念

像素(pixel)
(1)整个图像是由一个个的像素组成的,像素就是一个显示点。

像素间距(pitch)
(1)pitch是连续2个像素的像素中心的距离。一般的像素是方形的,所以横向pitch和纵向的pitch一样的。但是也有不一样的。
(2)像素间距会影响屏幕的最佳观看距离。像素间距大的适合远距离看,像素间距小的适合近距离看。

分辨率(resolution)
(1)整个屏幕的横向和纵向的像素个数就叫分辨率,譬如X210开发板用的屏幕是800×480.
(2)屏幕尺寸和分辨率无关的,像开发板的屏幕尺寸是7寸的(纯屏幕对角线尺寸是7英寸)。
(3)屏幕尺寸和分辨率和像素间距三者之间有关联。

清晰度
(1)清晰度是一个主观概念,是人眼对显示效果的一个主观判断。说白了就是人看起来感觉清晰不清晰。
(2)客观来讲,清晰度由分辨率和像素间距共同决定。一般的,屏幕尺寸固定时分辨率越高越清晰,分辨率越低就越不清晰;分辨率固定下,屏幕尺寸越小越清晰,越大越不清晰。
(3)清晰度还由其他很多因素共同决定。

像素深度(bits per pixel,简称bpp)
(1)一个像素在计算机中由多少个字节数据来描述。
(2)计算机中用二进制位来表示一个像素的数据,用来表示一个像素的数据位越多,则这个像素的颜色值更加丰富、分的更细,颜色深度就更深。
(3)一般来说像素深度有这么几种:1位、8位、16位、24位、32位。

颜色在计算机中的表示
颜色的本质
(1) 颜色是主观存在,颜色其实是自然光在人的眼睛中和大脑中产生的一种映像。
(2) 颜色的本质决定于光的波长。

自然光的颜色是连续的
光的波长是连续的,导致颜色也是连续的。理论上,只要你的眼睛分辨能力足够好,可以在自然界中发现无数种颜色。

计算机中的颜色是离散的
(1)计算机中不可能存储无数种颜色,所以必须将颜色有限化,所以就用有限种颜色来代表自然界中的无限种颜色。这个理论非常类似于之前学过的AD转换。
(2)这种离散化表达颜色的缺点是不够真实,漏掉了很多种颜色。因此计算机中所能表达的颜色没有自然界中丰富(计算机屏幕上显示的图像和真实图像有差别)
(3)计算机所能表达的颜色种类个数,这个参数叫:像素深度bpp。

常见像素深度:1位、8位、16位、24位、32位
1位:用1个二进制位来表示颜色,这种就叫单色显示。示例就是小饭店、理发店门口的LED屏。
8位:用8个二进制位来表示颜色,此时能表示256种颜色。这种叫灰度显示。这时候是黑白的,没有彩色,我们把纯白到纯黑分别对应255到0,中间的数值对应不同的灰。示例就是以前的黑白电视机。
16位:用16个二进制位表示颜色,此时能表示65536种颜色。这时候就可以彩色显示了,一般是RGB565的颜色分布(用5位二进制表示红色、用6位二进制表示绿色、用5位二进制表示蓝色)。这种红绿蓝都有的颜色表示法就是一种模拟自然界中所有颜色的表示方式。但是因为RGB的颜色表达本身二进制位数不够多(导致红绿蓝三种颜色本身分的都不够细致),所以这样显示的彩色失真比较重,人眼能明显看到显示的不真实。

注意,红色在高位。
24位:用24个二进制位来表示颜色,此时能表示16777216种颜色。这种表示方式和16位色原理是一样的,只是RGB三种颜色各自的精度都更高了(RGB各8位),叫RGB888。此时颜色比RGB565更加真实细腻,虽然说比自然界无数种颜色还是少了很多,不过由于人眼的不理想性所以人眼几乎不能区分1677万种颜色和无数种颜色的差别了。于是乎就把这种RGB888的表示方法叫做真彩色。(RGB565就是假彩色)
32位:总共用32位二进制来表示颜色,其中24位表示红绿蓝三元色(还是RGB888分布),剩下8位表示透明度。这种显色方式就叫ARGB(A是阿尔法,表示透明度),现在PC机中一般都用ARGB表示颜色。

补充:颜色的组成,三元色(三基色)是RGB,也就是说所有的颜色都可以由红绿蓝三种颜色组成。

TFT-LCD

TFT(Thin Film Transistor)是薄膜晶体管的缩写。

TFT式显示屏是各类笔记本电脑和台式机上的主流显示设备,该类显示屏上的每个液晶像素点都是由集成在像素点后面的薄膜晶体管来驱动,从而可以做到高速度高亮度高对比度显示屏幕信息。TFT式显示器是最好的LCD彩色显示器之一,具有高响应度、高亮度、高对比度等优点。是多数液晶显示器的一种。

同时,TFT式屏幕也普遍应用于中高端彩屏手机中,分65536 色、16 万色,1600万色三种,其显示效果非常出色。

随着九十年代初TFT技术的成熟,彩色液晶平板显示器迅速发展,不到10年的时间,TFT-LCD迅速成长为主流显示器,这与它具有的优点是分不开的。主要特点是:

使用特性好

低压应用,低驱动电压,固体化使用安全性和可靠性提高;平板化,又轻薄,节省了大量原材料和使用空间;低功耗,它的功耗约为CRT显示器的十分之一,反射式TFT-LCD甚至只有CRT的百分之一左右,节省了大量的能源;TFT-LCD产品还有规格型号、尺寸系列化,品种多样,使用方便灵活、维修、更新、升级容易,使用寿命长等许多特点。显示范围覆盖了从1英寸至40英寸范围内的所有显示器的应用范围以及投影大平面,是全尺寸显示终端;显示质量从最简单的单色字符图形到高分辨率,高彩色保真度,高亮度,高对比度,高响应速度的各种规格型号的视频显示器;显示方式有直视型,投影型,透视式,也有反射式。

环保特性好

无辐射、无闪烁,对使用者的健康无损害。特别是TFT-LCD电子书刊的出现,将把人类带入无纸办公、无纸印刷时代,引发人类学习、传播和记栽文明方式的革命。

适用范围宽

从-20℃到+50℃的温度范围内都可以正常使用,经过温度加固处理的TFT-LCD低温工作温度可达到零下80℃。既可作为移动终端显示,台式终端显示,又可以作大屏幕投影电视,是性能优良的全尺寸视频显示终端。

自动化程度

大规模工业化生产特性好。TFT-LCD产业技术成熟,大规模生产的成品率达到90%以上。TFT-LCD易于集成化。是大规模半导体集成电路技术和光源技术的完美结合,继续发展潜力很大。有非晶、多晶和单晶硅TFT-LCD,将来会有其它材料的TFT,既有玻璃基板的又有塑料基板。

FSMC控制TFT-LCD

我们使用FSMC的块1来控制TFT

新出现的存储器PSRAM(pseudo SRAM),称之为伪静态随机存取器。

它具有SRAM的接口协议:给出地址、读写指令,就可以实现数据的存取;相比DRAM的实现,它不需要复杂的memory controller来控制内存单元去定期刷新数据。

为什么FSMC可以控制LCD?因为非复用信号的PSRAM的接口功能和8080接口基本吻合。

其实,只要引脚数量足够,手动模拟时序来读取也可以。

这里是因为刚好FSMC的信号和时序都能匹配得上8080接口,所以直接使用能大大提升效率。

硬件接口

这里的LCD显示采用的是8080接口。用到的LCD驱动器是ILI9341。

ILI9341内部结构如下:

左上角一块为控制器接口,和单片机相连。右侧接口则和LCD相连接。

单片机控制LCD显示,实际上就是和LCD的驱动器进行通信。

LCD常用接口参考:LCD常见接口总结_路溪非溪的博客-CSDN博客

MX配置FSMC

模式选择选项中,就是FSMC的存储块选择。

前四个选项对应块1的四个分区;

NAND Flash1对应块2;

NAND Flash2对应块3;

Compact Flash对应块4。

我们这里选择NOR Flash/PSRAM/SRAM/ROM/LCD1即可(也可以选另外三个分区)

Chip Select

Memory type

LCD的数据/命令选择端口

可以选择任意一根地址线,但是要看实际的硬件到底是连到了哪个引脚。

数据位宽

选完后,下方默认即可。

这里的2、5、0也保持默认,影响的是FSMC的时序周期。

另外,背光是单独控制的,配置其连接的引脚即可。

驱动程序

要想LCD显示,先要调通驱动控制。

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/
static void LCD_Init(void);                                                 //LCD屏幕初始化
static void LCD_FillColor(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t); //LCD屏幕填充颜色

/* Public variables-----------------------------------------------------------*/
TFT_LCD_t TFT_LCD = 
{
	0,
	
	LCD_Init,
	LCD_FillColor
};

/* Private function prototypes------------------------------------------------*/
static uint32_t LCD_ReadID(void);                                //LCD读取ID
static void LCD_Disp_Direction(void);                            //LCD显示方向
static void LCD_SetWindows(uint16_t,uint16_t,uint16_t,uint16_t); //设置LCD显示窗口

/*
	* @name   LCD_ReadID
	* @brief  LCD读取ID
	* @param  None
	* @retval LCD_ID -> 返回LCD屏幕ID    
*/
static uint32_t LCD_ReadID(void)
{
	uint32_t LCD_ID = 0;
	uint32_t buf[4];
	
	LCD_Write_CMD(0xD3);
	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];
	return LCD_ID;
}

/*
	* @name   LCD_Init
	* @brief  LCD屏幕初始化
	* @param  None
	* @retval None      
*/
static void LCD_Init(void)
{
	//读取LCD屏幕ID
	TFT_LCD.ID = LCD_ReadID();
	printf("The ID of TFT 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); 
	LCD_Write_DATA(0x00);
	LCD_Write_DATA(0x00);
	LCD_Write_DATA(0x01);
	LCD_Write_DATA(0x3f);
	LCD_Write_CMD(0x2A); 
	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; //打开背光	 
}

/*
	* @name   LCD_Disp_Directio
	* @brief  LCD显示方向
	* @param  None
	* @retval None      
*/
static void LCD_Disp_Direction()
{
	switch(LCD_DIRECTION)
	{
		case 1:	LCD_Write_CMD(0x36); LCD_Write_DATA(1<<3); break;
		case 2: LCD_Write_CMD(0x36); LCD_Write_DATA((1<<3)|(1<<5)|(1<<6)); break;
		case 3: LCD_Write_CMD(0x36); LCD_Write_DATA((1<<3)|(1<<7)|(1<<4)|(1<<6)); break;
		case 4: LCD_Write_CMD(0x36); LCD_Write_DATA((1<<3)|(1<<7)|(1<<5)|(1<<4)); break;
		default:LCD_Write_CMD(0x36); LCD_Write_DATA(1<<3); break;			
	}
}

/*
	* @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_SETxOrgin);	
	LCD_Write_DATA(xStar>>8);
	LCD_Write_DATA(0x00FF&xStar);		
	LCD_Write_DATA((xStar+xWidth-1)>>8);
	LCD_Write_DATA((xStar+xWidth-1)&0xFF);

	LCD_Write_CMD(LCD_CMD_SETyOrgin);
	LCD_Write_DATA(yStar>>8);
	LCD_Write_DATA(0x00FF&yStar);		
	LCD_Write_DATA((yStar+yHeight-1)>>8);
	LCD_Write_DATA((yStar+yHeight-1)&0xFF);

	LCD_Write_CMD(LCD_CMD_WRgram); //开始写入GRAM		
}	

/*
	* @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<100;k++);
		}
	}
}
/********************************************************
  End Of File
********************************************************/

LCD显示的关键是写入各种字符或者图片的“模”,将对应的数据码传输给驱动器。

整体来看:LCD驱动是如何实现绘制图案的?

通常,会有个显存,驱动会根据一定的时序将显存中的内容显示到显示屏上。

我们要做的,就是将要写的数据传输到显存中。

那么,显存中的数据是如何和屏幕像素点对应的呢?

在一般情况下,是这样的。

ILI9341 控制器为例,屏幕尺寸320*240,显存的大小会和屏幕对应。

屏幕横向有320个点,竖向有240个点,假设使用的是16bpp,也就是每个点需要2个字节来点亮,那么显存大小就会是320*240*2个字节大小。

我们知道屏幕是由一个一个的像素点组成的,其实就可以将其看做一个个内存单元,因为二者是一一对应的。

关键的是:我们通过x和y坐标来定位到某个像素点,其实就是将数据写到对应的地址位置,驱动器就会将对应位置的数据显示到对应的屏幕区域,而不是整个地刷新。

通常,我们会需要设置xS/yS/xE/yE,其中(xS, yS)(xE, yE)分别为扫描开始的点和扫描结束的点。

控制器示意图:

控制器一般都会有如下接口:片选、硬复位、数据串口、数据并口、背光控制、读、写、时钟、模式选择,比如:

具体可查阅对应的数据手册,各类控制器大差不差。

所需功能

屏幕初始化:主要用来设置屏幕的方向、位深度等初始化参数(直接使用厂家示例代码即可);
设置窗口:通过命令来设置要绘制的区域;
画点:在指定的区域发送一个显示数据;
有了这几个基本功能,就可以延伸出其他所需要的功能,比如:
填充色块:把指定区域所有的点都填充满;

填充窗口:如果想要抹除之前的屏幕显示内容或者设置屏幕背景,就可以通过全屏填充来实现;
画圆:在指定区域画一个圆;
绘制ASCII字符、英文、中文、图标、图像等。

关于字模提取 

以前在做LED点阵时,一个像素点由一个位来控制,比如给1就点亮,给0就不亮。

这种情况,一个字节就可以控制8个像素点,此时,像素位深度为1。

到了TFT-LCD时,常用的是16bpp,也就是说,此时,为了尽可能多地显示各种颜色,需要用2个字节来表示一个像素点。

这里面有一些问题需要注意。

显示的内容,一般有颜色、字体、图标、图形、图片等。

颜色:颜色的模不必使用软件来取,直接根据红绿蓝的混合即可。以RGB565为例,11111/111111/11111,纯红纯绿纯蓝混合,就是白色;00000/000000/00000,红绿蓝都没有就是黑色,对应的十六进制数据就是0xFFFF和0x0000,其他颜色类似。

图标/图形/图片:需要使用专用的图片取模工具,来获取其完整的模,如果一个图片的尺寸为100*80,也就是有8000个像素点,每个像素点由2个字节来表示,那么就需要16000个字节才能表示一张图片。

字体:这个是最容易搞错的,为什么?因为我在对字体取模时,发现了问题,比如:

我建立的字体大小为16*16,也就是一共有256个像素点,如果按照位深度16bpp来说,一共需要256*2=512个字节来表示。

可是,当我取模时,它却只有32个字节

显然,这是很矛盾的。

其实,从这个取的模可以看出来,这里是一个点表示一个像素点。

看起来是不是挺矛盾的?

其实,仔细想想,过程是这样的。

取模软件,取的就是某个像素点亮不亮。

在我们写软件时,根据模是位1和位0去判断,根据判断,给目标字体和背景分别填充不同的颜色,此时,这里的颜色数据才是真正写到显存中去的数据。这里的模,并不是实际要写到显存中去的数据。

这样一来,取模时的参数选择,其实跟硬件关系不大,关键看你的软件是按照什么逻辑去判断的。硬件就是将显存中的数据依次写到屏幕上去,一般都是从左到右从上到下(当然有其他的显示方式),不过,他跟这里取模的参数设置是没有关系的。

字模选项都是啥意思?

点阵格式:阴码时亮的点为1,不亮的点为0,这个是软件判断的重要逻辑,根据高低位分别赋予不同的颜色。

取模方式:就是取模是从左到右取还是从上到下取等等,比如测试的“测”字

如果是逐行取模,那么头两个字节就是00000000/00000100,即0x00和0x04,但是,发现和实际取到的模不一致

哪里出了问题呢?

因为还有个参数需要共同决定

取模走向:低位在前,就是取的模的字节第一位表示的是字节像素最低位,可以看做是按照这样的顺序来取的模

也就是按照逐行+逆向,前四个字节为:00000000/00100000/11100100/00100011,即0x00/0x20/0xE4/0x23

如果是逐行+顺向取模,则前四个字节应该为00000000/00000100/00100111/11000100,即

0x00/0x04/0x27/0xc4

可见,是一致的。

每行显示数据-点阵:这个就是生成的模分几行显示,比如测试的测,一共32个字节,每行8个字节,就会分4行显示;每行16个,就分2行显示;每行32个,就直接1行显示。不影响软件编程。索引没啥用,不用管。

液晶面板仿真-像素大小:是调整该软件的视图显示,不影响软件编程。

自定义格式:一般选C51,就是调整数据显示的格式,不太重要。

综上所述,最重要的三个参数为:点阵格式、取模走向、取模方式。

点阵格式——决定1时是目标字体还是背景,一般都是选阴码(通常会有很多0x00,阳码通常会有很多0xFF)

取模走向——决定我在软件编程时,是从LSB开始判断还是MSB开始判断。一般选逆向,低位在前,即字模的低位表示的是前面的像素,从从最低位开始判断。再反过来想,先扫描到的点就放到模的最低位。

取模方式——这个跟硬件其实有点关系,扫描方向和屏幕保持一致,一般都选逐行扫描。

注意:每取8个点作为一个字节,如果最后不足8个点就补满8位。

比如,横向有12个像素点,那么,第一次取8个位,第二次还剩4个位,但是会补满8位,所以我们在软件里判断时需要注意去掉这多余的位。

注意:某中文字体对应的英文,宽度是中文的一半。

比如,如果英文字体的大小要求是7*16,那么取模的时候,尺寸就要设置成14*16

图片怎么显示呢?

需要取图片的模,一般可以用Image2Lcd这个软件来取

Image2lcd这款软件专用于图像模型,支持JPG、BMP、EMF、WBMP、GIF、ICO等多种格式图片的输入,可以自动将我们选取的图片以二进制、C语言数组、BMP、WBMP、Sigmate的形式输出出来。根据实际使用需要,可以对图象的扫描模式、输出灰度(颜色)、输出图片大小等参数进行调整。

左边是输入的图片,右边是效果图

图片的模直接写到LCD里即可。

PCtoLCD也能给图片取模,但是只能取黑白色,也就是亮和不亮,反正就是无法实现效果,所以一般不会使用这个来取图片的模。

注意:取模的数据一般都会修饰成const的,因为取模数据比较大,用const修饰,就是一个常量了,是放到flash中的,取模数据不会改变,放到内存中没有必要,还特别占空间。

那么,图形怎么显示呢?

图形的显示就不需要取模了,而是直接画出来的。

通常,首先要实现画点,有了点,就能画线、画圆、画矩形,如果不需要填充就画个边界形状,如果需要填充,就范围内所有的点都画上。关于画圆、画矩形、画三角形等,网上都有相应的算法,可自行去找资料。

可参考:

【玩转嵌入式屏幕显示】(三)TFT-LCD屏幕打点 + 画线 + 画矩形 + 画圆Bresenham算法实现(基于打点函数,算法可移植到任何屏幕的驱动程序之上)_51CTO博客_嵌入式lcd显示图形

;