1, 显示器分类(了解)
全彩显示,LCD具有更多的优势,适合在单片机上使用
2, LCD简介(了解)
Liquid Crystal Display,即液晶显示器,由:玻璃基板、背光、驱动IC等组成
全彩LCD,是一种全彩显示屏(RGB565、RGB888),可以显示各种颜色
1,低成本:低至几块钱的价格
2,高解析度 :可高达500ppi的解析度,显示细腻
PPI(Pixels Per Inch)[4],从字面意思理解就是每英寸像素,也可以理解为屏幕像素密度,因为像素并没有固定的大小,所以,PPI 越高,像素大小越小,也就越清晰。具体的计算公式如下:
3,高对比度: 可高达1000 : 1的对比度,色彩清晰艳丽
4,响应速度快:可高达1ms响应速度,显示效果好
对比度指的是一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,差异范围越大代表对比越大,差异范围越小代表对比越小,
LCD基本组成
1,玻璃基板
2,背光
3,驱动IC
LCD接口分类
MCU屏接口由于自带SRAM,驱动简单,大部分单片机都能驱动!
MCU接口:
2.8寸电阻触摸屏
3.5寸电阻触摸屏
4.3寸电容触摸屏
7寸电容触摸屏V2
RGB接口:
4.3寸480272触摸屏
4.3寸800480触摸屏
7寸800480触摸屏
7寸1024600触摸屏
10.1寸1280*800触摸屏
ILI9341芯片
ILI9341芯片支持多种通信接口。
MCU接口(8/9/16/18位)
3/4 线SPI接口
RGB接口(6/16/18位)
三基色原理
无法通过其他颜色混合得到的颜色,称之为:基本色
通过三基色混合,可以得到自然界中绝大部分颜色!
电脑一般用32位来表示一个颜色(ARGB888):
单片机一般用16/24位表示一个颜色(RGB565/RGB888):
3, LCD驱动原理(熟悉)
LCD屏(MCU接口)驱动的核心是:驱动LCD驱动芯片
LCD驱动基本知识:
1,8080时序,LCD驱动芯片一般使用8080时序控制,实现数据写入/读取
2,初始化序列(数组),屏厂提供,用于初始化特定屏幕,不同屏幕厂家不完全相同!
3,画点函数、读点函数(非必需),基于这两个函数可以实现各种绘图功能!
LCD驱动的一般过程
8080时序简介
并口总线时序,常用于MCU屏驱动IC的访问,由Intel提出,也叫英特尔总线
LCD 8080时序信号说明
8080写时序
数据(RS=1)/命令(RS=0)在WR的上升沿,写入LCD驱动IC,RD保持高电平
8080读时序
数据(RS=1)/命令(RS=0)在RD的上升沿,读取到MCU,WR保持高电平
8080读写简化代码
void lcd_wr_data(uint16_t data)
{
LCD_RS(1); /* 操作数据 */
LCD_CS(0); /* 选中 */
LCD_DATA_OUT(data); /* 数据 */
LCD_WR(0); /* WR低电平 */
LCD_WR(1); /* WR高电平 */
LCD_CS(1); /* 释放片选 */
}
uint16_t lcd_rd_data(void)
{
uint16_t ram; /* 定义变量 */
LCD_RS(1); /* 操作数据 */
LCD_CS(0); /* 选中 */
LCD_RD(0); /* RD低电平 */
ram = LCD_DATA_IN; /* 读取数据 */
LCD_RD(1); /* RD高电平 */
LCD_CS(1); /* 释放片选 */
return ram; /* 返回读数 */
}
4,LCD驱动芯片简介(熟悉)
用于控制LCD的各种显示功能和效果,整体功能较复杂。常见型号:ILI9341/ST7789等
一般我们只需要:6条指令即可完成对LCD的基本使用(以9341为例)
读ID指令(0XD3)
读取LCD控制器型号,通过型号可以执行不同LCD初始化,以兼容不同LCD
访问控制指令(0X36)
实现GRAM读写方向控制,即:控制GRAM自增方向,从而控制显示方向
MX,MY,MV:共同控制GRAM自增方向(扫描方向)
BGR位:可以控制RGB、BGR顺序
MX、MY、MV扫描方向控制关系
从左到右,从上到下
X坐标设置指令(0X2A)
SC:起始坐标
EC:结束坐标
设置关系:0≤SC≤EC≤239(LCD像素宽度)
Y坐标设置指令(0X2B)
页地址设置指令,一般用于设置Y坐标
SP:起始坐标
EP:结束坐标
设置关系:0≤SP≤EP≤319(LCD像素高度)
写GRAM指令(0X2C)
发送该指令后,数据线变成16位,可以开始写入GRAM数据,支持地址自增
每次写入1个像素点的颜色值(RGB565),地址自增方向由MX/MY/MV控制无需重新设置坐标,可实现连续写入,大大提高写入速度
读GRAM指令(0X2E)
发送该指令后,数据线变成16位,可以开始读取GRAM数据,支持地址自增
读1个点的颜色,要读3次
1,dummy
2,R1G1
3,B1R2
uint16_t r, g, b;
r = lcd_rd_data();
g = lcd_rd_data();
b = lcd_rd_data();
g = r & 0XFF;
return (((r >> 11) << 11) | ((g >> 2) << 5) | (b >> 11));
读点函数代码(精简)
uint16_t lcd_rd_data(void)
{
uint16_t ram; /* 定义变量 */
DATA_IN_MODE(); /* 设置数据输入 */
LCD_RS(1); /* 操作数据 */
LCD_CS(0); /* 选中 */
LCD_RD(0); /* RD低电平 */
ram = LCD_DATA_IN; /* 读取数据 */
LCD_RD(1); /* RD高电平 */
LCD_CS(1); /* 释放片选 */
DATA_OUT_MODE(); /* 设置数据输出 */
return ram; /* 返回读数 */
}
uint16_t lcd_read_point (uint16_t x, uint16_t y)
{
uint16_t r = 0, g = 0, b = 0; /* 定义变量 */
lcd_set_cursor(x, y); /* 设置坐标 */
lcd_wr_regno(0X2E); /* 发读点命令 */
r = lcd_rd_data(); /* 假读 */
r = lcd_rd_data(); /* 读rg */
b = lcd_rd_data(); /* 读b */
g = r & 0XFF; /* 得到g值 */
return (((r >> 11) << 11) | ((g >> 2) << 5) | (b >> 11));
}
5,LCD基本驱动实现(掌握)
目标:用最简单代码,点亮开发板LCD屏,实现任意位置画点和读点
1,确定IO连接关系 LCD模块原理图、开发板液晶接口原理图
2,初始化IO口 初始化连接LCD的各个IO口
3、初始化FSMC外设 可选,某些芯片是没有FSMC外设,MINI板是没有的
3,编写读写接口函数 lcd_wr_data、lcd_wr_regno、lcd_write_reg、lcd_rd_data
4,编写LCD初始化函数 编写lcd_init函数,完成初始化序列配置,点亮背光等
5,编写LCD画和读点函数
编写lcd_draw_point函数,实现LCD任意位置画点
6, 编程实战1(掌握)
1,使用8080时序在LCD上实现任意位置画点和读点
2,在LCD屏幕上能支持1212、1616、2424、3232大小的ASCII字符显示
任意字符显示的关键是要制作字库,有了字库就能实现任意字符显示
1,字库制作 根据字体大小(12/16/24/32),制作对应的字库
2,编写任意字符显示函数 根据字库生成方式,编写对应的字符显示函数
ASCII字库制作(16*16)
第1步,设置字体大小
第2步,设置字模选项
第3步,输入ASCII字符集(95个)
第4步,生成字模
7.1,FMC简介
FMC,Flexible Memory Controller,灵活的存储控制器。
用途:用于驱动NOR/PSRAM,NAND/PC卡,同步DRAM(SDRAM/Mobile LPSDR SDRAM)等。
配置好FMC,存储器当成普通外设使用。定义一个指向这些地址的指针,通过对指针操作就可以直接修改存储单元的内容,FMC自动完成读写命令和数据访问操作,不需要程序去实现时序。
FMC外设配置好就可以模拟出时序
FMC模拟8080时序控制LCD
我认为所谓的映射就是将0-25FMC地址线所接的外设,通过这26条地址总线将外接的存储器映射到stm32内部内存地址中,其中某一个总线可以映射为一个地址,可以操作这个地址进行读写
7.2,FMC框图介绍
知识补充stm32时钟
SYSCLK:系统时钟,由外/内部晶振/时钟源经过PLL(或不经过PLL)倍频得到的
HCLK:AHB总线,高速外设时钟 经过SYSCLK分频得到,其中H代表高速
PCLK1:外设时钟:总线APB1 经过HCLK分频得到
PCLK2:外设时钟:总线APB2 经过HCLK分频得到
① 时钟控制逻辑
FMC挂载在AHB总线上
时钟信号来自HCLK
② STM32内部的FMC控制单元
FMC配置寄存器
NOR和PSRAM控制器
NAND和PC卡控制器
SDRAM控制器
③ 通信引脚
不同类型存储器用到的信号引脚
公共信号引脚
FMC通信引脚介绍
用于连接硬件设备的引脚,控制不同类型的存储器会用不同的引脚。
LCD使用的是类似异步、地址与数据线独立的SRAM控制方式
使用FMC驱动LCD
7.3,FMC时序介绍
FMC是Flexible灵活的,可以产生多种时序来控制外部存储器。
NOR/PSRAM控制器产生的异步时序就有5种,总体分为两类:一类是模式1,其他为拓展模式。
拓展模式相对模式1来说读写时序时间参数设置可以不同,满足存储器读写时序不一样需求。
这里模拟8080时序驱动LCD选择模式A
写时序
引脚类比
NEx ——>>CS 片选
NOE ——>>RD 读
NWE ——>>WR 写
RS读写命令用地址线中的一根
数据总线用到其中一部分数据总线
ADDSET
DATAST+1 根据寄存器进行设置
写时序
9341写时序
读SRAM时序
读9341时序
重点时序:
读ID低电平脉宽(trdl)
读ID高电平脉宽(trdh)
读FM低电平脉宽(trdlfm)
读FM高电平脉宽(trdhfm)
写控制低电平脉宽(twrl)
写控制高电平脉宽(twrh)
ID:指LCD的ID号
FM指帧缓存即GRAM
RD/WR高电平持续时间即ADDSET
RD/WR低电平持续时间即DATAST
一个HCLK时钟周期:1/(系统主频Mhz)
注意:读和写时序存在不同,写时序中为DATAST+1
9341读写时钟
读写高低电平时间限制
写时钟周期最小66ns 高电平时间最小15ns 低电平时间最小15ns
读ID时钟周期最小160ns 高电平时间最小90ns 低电平时间最小45ns
读GRAM时钟周期最小450ns 高电平时间最小90ns 低电平时间最小355ns
我们进行读写时 以系统时钟168M也就是HCLK是168M,FSMC的时钟周期为1/168 = 6ns
写设置 地址建立时间ADDSET就是WR高电平时间 数据建立时间就是 WR地点评时间 这两个按照最低15ns 我们都设置为54ns
读设置 地址建立时间ADDSET就是OE高电平时间 数据建立时间就是 OE地点评时间 这两个按照最低90ns 我们都设置为355ns 这样既可以保证读ID也可以保证读FM 我们设置为90ns和360ns(经验值)
读写实际上就是对地址进行赋值和读取。
7.4,FMC地址映射
地址0x6000 0000-0xDFFF FFFF
使用FMC外接存储器,其存储单元是映射到STM32的内部寻址空间的。
从FMC角度看,把外部存储器划分为固定大小为256M字节的6个存储块(1.5GB)。
FMC存储块1被分为4个区,每个区管理64M字节空间
HADDR与FMC_A关系
HADDR总线是转换到外部存储器的内部AHB地址线
简单来说,从CPU通过AHB总线到外部信号线之间的关系。
HADDR是字节地址,而存储器访问不都是按字节访问,接到存储器的地址线与其数据宽度相关。
LCD使用16位数据线
注意:数据宽度为16位时,地址存在偏移
LCD的RS信号线与地址线关系
8080接口中RS(数据/命令选择线),用FMC的某根A地址线进行替换。
FMC_A19接到RS线上
当FMC_A19为高电平时(即RS为高电平),FMC_D[15:0]被理解为数据。
当FMC_A19为低电平时(即RS为低电平),FMC_D[15:0]被理解为命令。
究竟发送什么地址代替?
1、确认FMC_NE1基地址
0x6000 0000 NEx(x=1…4):0x6000 0000 + (0x400 0000 * (x - 1))
2、确认FMC_A19对应地址值
219 x 2 = 0x100000 FMC_Ay(y=0…24): 2y x 2 这里是不是FMC_A19对应HADDR_20
3、确认两个地址
代表LCD命令的地址:0x6000 0000
代表LCD数据的地址:0x6010 0000
#define FMC_ADDR_CMD ((uint32_t) 0x6000 0000)
#define FMC_ADDR_DATA ((uint32_t) 0x6010 0000)
7.5,FMC相关寄存器介绍
对于NOR_FLASH/PSRAM控制器(存储块1)配置工作,通过FMC_BCRx、FMC_BTRx和FMC_BWTRx寄存器设置(其中x=1~4,对应4个区)。
LCD的读写时序存在比较大差异,所以FMC的读写时序也得分开配置
SRAM/NOR闪存片选控制寄存器(FMC_BCRx)
EXTMOD:扩展模式使能位,控制是否允许读写不同的时序。读和写用不同的时序,该位设置为1
WREN:写使能位。向TFTLCD写入数据,该位设置1
MWID[1:0]:存储器数据总线宽度。00,表示8位数据模式;01表示16位数据模式;10和11保留。我们使用16位数据模式设置01
MTYP[1:0]:存储器类型。00表示SRAM、ROM;01表示PSRAM;10表示NOR FLASH;11保留。设置为00表示SRAM
MBKEN:存储块使能位。设置为1
SRAM/NOR闪存片选时序寄存(FMC_BTRx)
ACCMOD[1:0]:访问模式。00:模式A;01:模式B;10:模式C;11:模式D。设置为00
DATAST[7:0]:数据保持时间,等于DATAST个HCLK时钟周期,DATAST最大为255。
对于ILI9341来说,其实就是RD低电平持续时间,最小为355ns。
对于F429,一个HCLK = 5ns(1/180M),设置为70
对于F767,一个HCLK = 4.6ns(1/216M),设置为80
对于H743/H750,一个HCLK = 4.5ns(1/220M) ,设置为78
ADDSET[3:0]:地址建立时间。表示ADDSET个HCLK时钟周期,ADDSET最大为15。
对于ILI9341来说,相当于RD高电平持续时间,为90ns。
F429/F767/H743/H750设置15
SRAM/NOR闪存写入时序寄存器(FMC_BWTRx)
ACCMOD[1:0]:访问模式。00:模式A;01:模式B;10:模式C;11:模式D。
DATAST[7:0]:数据保持时间,等于DATAST个HCLK时钟周期,DATAST最大为255。
对于ILI9341来说,其实就是WR低电平持续时间,最小为15ns。
对于F429,一个HCLK = 5ns(1/180M),设置为15
对于F767,一个HCLK = 4.6ns(1/216M),设置为15
对于H743/H750,一个HCLK = 4.5ns(1/220M) ,设置为15
ADDSET[3:0]:地址建立时间。表示ADDSET(+1)个HCLK时钟周期,ADDSET最大为15。
对于ILI9341来说,相当于WR高电平持续时间,为15ns。
F429/F767/H743/H750设置15
FMC寄存器组合说明
在ST官方提供的寄存器定义里面,并没有定义FMC_BCRx、FMC_BTRx、FMC_BWTRx等这个单独的寄存器,而是将他们进行了一些组合,规则如下:
FMC_BCRx和FMC_BTRx,组合成BTCR[8]寄存器组,他们的对应关系如下:
BTCR[0]对应FMC_BCR1,BTCR[1]对应FMC_BTR1
BTCR[2]对应FMC_BCR2,BTCR[3]对应FMC_BTR2
BTCR[4]对应FMC_BCR3,BTCR[5]对应FMC_BTR3
BTCR[6]对应FMC_BCR4,BTCR[7]对应FMC_BTR4
FMC_BWTRx则组合成BWTR[7]寄存器组,他们的对应关系如下:
BWTR[0]对应FMC_BWTR1,BWTR[2]对应FMC_BWTR2,
BWTR[4]对应FMC_BWTR3,BWTR[6]对应FMC_BWTR4,
BWTR[1]、BWTR[3]和BWTR[5]保留,没有用到
7.6,FMC相关HAL库函数简介
本例程涉及HAL库相关函数如下:
__HAL_RCC_FMC_CLK_ENABLE(); /* 使能FMC时钟 */
HAL_StatusTypeDef HAL_SRAM_Init ( SRAM_HandleTypeDef *hsram,
FMC_NORSRAM_TimingTypeDef *Timing,
FMC_NORSRAM_TimingTypeDef *ExtTiming )
调用
SRAM_HandleTypeDef
typedef struct
{
FMC_NORSRAM_TypeDef *Instance; /* 寄存器基地址 */ FMC_NORSRAM_EXTENDED_TypeDef *Extended; /* 扩展模式寄存器基地址 */
FMC_NORSRAM_InitTypeDef Init; /* SRAM初始化结构体*/
HAL_LockTypeDef Lock; /* SRAM锁对象结构体 */
__IO HAL_SRAM_StateTypeDef State; /* SRAM设备访问状态 */
DMA_HandleTypeDef *hdma; /* DMA结构体 */
} SRAM_HandleTypeDef;
FSMC_NORSRAM_TimingTypeDef
typedef struct
{
uint32_t AddressSetupTime; /* 地址建立时间 */
uint32_t AddressHoldTime; /* 地址保持时间 */
uint32_t DataSetupTime; /* 数据建立时间 */
uint32_t BusTurnAroundDuration; /* 总线周转阶段的持续时间 */
uint32_t CLKDivision; /* CLK时钟输出信号的周期 */
uint32_t DataLatency; /* 同步突发NOR FLASH的数据延迟 */
uint32_t AccessMode; /* 异步模式配置 */
} FSMC_NORSRAM_InitTypeDef;
注意:1、引脚配置都配置成为复用FSMC
2、读写的时钟周期配置是经验值
void lcd_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
FSMC_NORSRAM_TimingTypeDef fsmc_read_handle;
FSMC_NORSRAM_TimingTypeDef fsmc_write_handle;
LCD_CS_GPIO_CLK_ENABLE(); /* LCD_CS脚时钟使能 */
LCD_WR_GPIO_CLK_ENABLE(); /* LCD_WR脚时钟使能 */
LCD_RD_GPIO_CLK_ENABLE(); /* LCD_RD脚时钟使能 */
LCD_RS_GPIO_CLK_ENABLE(); /* LCD_RS脚时钟使能 */
LCD_BL_GPIO_CLK_ENABLE(); /* LCD_BL脚时钟使能 */
gpio_init_struct.Pin = LCD_CS_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF12_FSMC; /* 复用为FSMC */
HAL_GPIO_Init(LCD_CS_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_CS引脚 */
gpio_init_struct.Pin = LCD_WR_GPIO_PIN;
HAL_GPIO_Init(LCD_WR_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_WR引脚 */
gpio_init_struct.Pin = LCD_RD_GPIO_PIN;
HAL_GPIO_Init(LCD_RD_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_RD引脚 */
gpio_init_struct.Pin = LCD_RS_GPIO_PIN;
HAL_GPIO_Init(LCD_RS_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_RS引脚 */
gpio_init_struct.Pin = LCD_BL_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
HAL_GPIO_Init(LCD_BL_GPIO_PORT, &gpio_init_struct); /* LCD_BL引脚模式设置(推挽输出) */
g_sram_handle.Instance = FSMC_NORSRAM_DEVICE;
g_sram_handle.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
g_sram_handle.Init.NSBank = FSMC_NORSRAM_BANK4; /* 使用NE4 */
g_sram_handle.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE; /* 地址/数据线不复用 */
g_sram_handle.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16; /* 16位数据宽度 */
g_sram_handle.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE; /* 是否使能突发访问,仅对同步突发存储器有效,此处未用到 */
g_sram_handle.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW; /* 等待信号的极性,仅在突发模式访问下有用 */
g_sram_handle.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS; /* 存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT */
g_sram_handle.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE; /* 存储器写使能 */
g_sram_handle.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE; /* 等待使能位,此处未用到 */
g_sram_handle.Init.ExtendedMode = FSMC_EXTENDED_MODE_ENABLE; /* 读写使用不同的时序 */
g_sram_handle.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE; /* 是否使能同步传输模式下的等待信号,此处未用到 */
g_sram_handle.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE; /* 禁止突发写 */
/* FSMC读时序控制寄存器 */
fsmc_read_handle.AddressSetupTime = 0x0F; /* 地址建立时间(ADDSET)为15个fsmc_ker_ck(1/168=6)即6*15=90ns */
fsmc_read_handle.AddressHoldTime = 0x00; /* 地址保持时间(ADDHLD) 模式A是没有用到 */
fsmc_read_handle.DataSetupTime = 60; /* 数据保存时间(DATAST)为60个fsmc_ker_ck=6*60=360ns */
/* 因为液晶驱动IC的读数据的时候,速度不能太快,尤其是个别奇葩芯片 */
fsmc_read_handle.AccessMode = FSMC_ACCESS_MODE_A; /* 模式A */
/* FSMC写时序控制寄存器 */
fsmc_write_handle.AddressSetupTime = 9; /* 地址建立时间(ADDSET)为9个fsmc_ker_ck=6*9=54ns */
fsmc_write_handle.AddressHoldTime = 0x00; /* 地址保持时间(ADDHLD) 模式A是没有用到 */
fsmc_write_handle.DataSetupTime = 9; /* 数据保存时间(DATAST)为9个fsmc_ker_ck=6*9=54ns */
/* 注意:某些液晶驱动IC的写信号脉宽,最少也得50ns */
fsmc_write_handle.AccessMode = FSMC_ACCESS_MODE_A; /* 模式A */
HAL_SRAM_Init(&g_sram_handle, &fsmc_read_handle, &fsmc_write_handle);
delay_ms(50);
/* 尝试9341 ID的读取 */
lcd_wr_regno(0xD3);
lcddev.id = lcd_rd_data(); /* dummy read */
lcddev.id = lcd_rd_data(); /* 读到0x00 */
lcddev.id = lcd_rd_data(); /* 读取93 */
lcddev.id <<= 8;
lcddev.id |= lcd_rd_data(); /* 读取41 */
}
总结:
FSMC用作模拟8080时序使用,是将TFT模拟成SRAM使用,其中将RS(写命令/地址接在一根地址线上比如A0上),这时可以将TFT认为一个只有2个地址的SRAM。A0=0时地址对应0 A0=1时地址对应1。给地址0写时A0(RS)就被拉低了就是写命令,给地址1写时A0(RS)就被拉高了就是写数据。数据输出在数据总线上给到GRAM。