FSMC驱动8080接口液晶屏
显示器是计算机的一个主要输出设备。对于一个嵌入式的系统而言,往往不需要显示器。但是一旦涉及到比较复杂的人机交互过程, 就需要有一个显示器输出一些提示信息以及系统状态。目前触摸屏已经很普及了,它不仅是一个输出设备了, 而且是一个输入设备,多点触摸的手机已经不是什么新鲜玩意儿了,很多显示器也已经具备触摸功能的了。这使得人机交互过程更加便捷。
显示器也是由多个模块构成的,为了满足用户的需求,厂商设计和制造了各种接口形式的显示器,目前市面上见到的基本都是LCD显示器了。 他把LCD控制器、驱动器和显示屏集成在一起,用户只需要把LCD控制器的接口与处理器的接口简单连接起来,并通过LCD的指令系统编写程序, 就可以完成复杂界面的显示工作了。
LCD的全称是Liquid Crystal Display,它通过液晶和彩色滤光器过滤光源,进而在平板上显示图像。 在探索者的开发板上,有一个8080接口的液晶屏。 本文就结合原开发板的例程,了解一下液晶屏的驱动过程。
1. 液晶显示的基本原理
根据维基的说法, 1988年奥地利的植物学家和化学家Friedrich Reinitzer从胡萝卜中提取出了一种化合物。这种化合物具有两个不同温度的熔点, 在一定的温度下具有固体和液体的双重特性,之后人们就将之称为"液晶"。
之后的几十年的时间里,也有一些科学家研究液晶的各种特性。直到1960年代,美国RCA公司的工程师们发现不同的电压可以改变液晶分子的排列状态, 并让射入的光线发生偏转。利用这一特性,他们发明了世界上第一台液晶屏。
将液晶材料置于两块光轴垂直的偏光板之间,如果两块偏光板之间没有形成电场,光线从一侧偏光板射入后,会沿着液晶旋转的方向前进到达另一侧的偏光板射出。 当两块偏光板之间有电场,就会改变液晶分子的排列状态,使得光线无法从另一侧的偏光板射出。这一现象称作扭转式向列场效应(Twisted Nematic Feild Effect, TNFE)。
使用液晶屏就需要一个背板光源,再加上彩色滤光板。我们可以用一个阵列的形式,通过控制阵列中各个单元的电压,就可以控制各个像素的明亮和色彩。 最后就可以形成想要的图像了。根据驱动形式的不同,有扭转式向列型(Twisted Nematic, TN)、超扭转式向列型(Super Twisted Nematic, STN)、 薄膜式晶体管型(Thin Film Transistor, TFT)。
2. ATK-4.3' TFTLCD电容触摸屏
这里我们选用为探索者配套的ATK-4.3' TFTLCD电容触摸屏(以后简称ATK4.3)为研究对象入门液晶屏的驱动控制。它是一块由NT35510驱动的,16位真彩800×480的液晶屏。 由于NT35510自带GTRAM,所以不需要额外的器件,MCU就可以直接驱动。该模块还有支持5点触摸,我们将在后续章节中予以介绍。
NT35510是一款带显存的集驱动器与控制器于一身的TFT LCD的单芯片解决方案。它支持多种分辨率,因为它内置了480×864×24位的显存, 所以可以为480RGB×864、480RGB×854、480RGB×800、480RGB×720和480RGB×640提供内置的CGRAM。而对于为480RGB×1024,NT35510提供了扩展接口可以通过旁路CGRAM予以支持。
NT35510支持MDDI接口、MIPI接口、16/18/24位RGB接口、8/16/24位80系统接口、SPI和I2C接口。使得我们可以根据需要选择合适的接口控制LCD。 ATK4.3采用16位的8080并口与外部连接,其在探索者开发板上的接口原理图如下图所示。
|
|
图 1. 探索者LCD原理图 |
- 根据将要读写的数据类型,设置RS电平
- 拉低片选信号CS,选中LCD驱动器
- 如果需要读取数据则将RD拉低,如果需要写入数据将WR拉低。
- 在RD的上升沿读取数据线D[15:0]上锁存的数据,在WR的上升沿写数据到D[15:0]上。
表 1 8080控制引脚电平信号
RS | CS | WR | RD | |
---|---|---|---|---|
写命令 | L | L | ↑ | H |
读状态 | L | L | H | ↑ |
写数据 | H | L | H | ↑ |
读数据 | H | L | ↑ | H |
在8080的控制接口定义中,读操作和写操作分别用WR和RD控制着, 在探索者的原理图中它们被分别接到了FSMC_NWE和FSMC_NOE上,整个控制时序与RAM的时序类似。所以在探索者开发板的设计中,是希望将之看作是一块内存, 通过FSMC来访问NT35510。
再看原理图,我们可以看到LCD模组的16位数据线都接到了FSMC的数据线上了,而且是一一对应的。LCD的片选信号接到了FSMC_NE4上,这意味着LCD被映射到了BANK1的区域4, 对应的首地址为0x6C000000。但是只接有一个地址线A6与RS连接,而数据总线又是16位,所以地址的第8位决定了对LCD的一次访问是指令操作还是数据通信, 即0x6C000080是数据通信,0x6C00007E则是指令操作。
此外,根据液晶屏的原理介绍我们知道,要让液晶屏能够正常的工作还需要一个背光,所以在液晶屏模组上还有一个BL的引脚接到了F407的一个IO端口上。在模组上, 已经为BL引脚添加了一个100k的下拉电阻,所以默认情况下背光是关闭的,需要在MCU上通过一个引脚控制打开背光。
3. 模组的背光控制
模组的背光实际上就是一个LED,完全可以参照通用IO端口驱动LED灯操作LCD的背光。 下面通过函数lcd_init_bl()完成初始化操作,查看原理图可以看到LCD_BL接到了PB15引脚上。在函数中,我们首先开启GPIOB的驱动时钟,再配置PB15引脚工作在输出模式下。 因为在模组上已经有了一个下拉电阻,所以这里我们以推挽输出的方式驱动背光,不再开启IO端口的上下拉电阻了。
void lcd_init_bl(void) {
RCC->AHB1ENR.bits.gpiob = 1;
GPIOB->MODER.bits.pin15 = GPIO_Mode_Out;
GPIOB->OTYPER.bits.pin15 = GPIO_OType_PP;
GPIOB->PUPDR.bits.pin15 = GPIO_Pull_No;
GPIOB->OSPEEDR.bits.pin15 = GPIO_OSpeed_Very_High;
}
然后我们为LCD_BL定义一个宏
#define LCD_BL (PBout(15))
以后,就可以通过这个宏来开关LCD的背光了:
LCD_BL = 1; // 打开背光
LCD_BL = 0; // 关闭背光
4. FSMC访问NT35510
根据对原理图的分析,我们需要配置20个引脚来通过FSMC访问NT35510,其中16个数据总线,1个地址总线,以及3个控制信号。一共涉及到D、E、F、G四个GPIO端口。 我们先在函数lcd_init_gpio()中完成对引脚的初始化工作,首先开启四个GPIO端口和FSMC的驱动时钟:
void lcd_init_gpio(void) {
RCC->AHB1ENR.bits.gpiod = 1;
RCC->AHB1ENR.bits.gpioe = 1;
RCC->AHB1ENR.bits.gpiof = 1;
RCC->AHB1ENR.bits.gpiog = 1;
RCC->AHB3ENR.bits.fsmc = 1;
接着配置相关引脚的工作方式为FSMC,下面截取了部分引脚的功能映射的代码:
GPIOD->AFR.bits.pin0 = 12; // PD0 -> FSMC_D2
GPIOD->AFR.bits.pin1 = 12; // PD1 -> FSMC_D3
GPIOD->AFR.bits.pin4 = 12; // PD4 -> FSMC_NOE
GPIOD->AFR.bits.pin5 = 12; // PD5 -> FSMC_NWE
// 其它相关引脚的复用功能配置省略了,详细参见源码
最后,配置各个引脚工作在推挽复用的模式下,并开启上拉电阻:
struct gpio_pin_conf pincof;
pincof.mode = GPIO_Mode_Af;
pincof.otype = GPIO_OType_PP;
pincof.pull = GPIO_Pull_Up;
pincof.speed = GPIO_OSpeed_Very_High;
gpio_init(GPIOD, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_8
| GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 | GPIO_Pin_15, &pincof);
gpio_init(GPIOE, GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11
| GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15, &pincof);
gpio_init(GPIOF, GPIO_Pin_12, &pincof);
gpio_init(GPIOG, GPIO_Pin_12, &pincof);
}
完成了对各个引脚的初始化之后,我们就可以配置FSMC的寄存器了。因为LCD挂在Bank1的第4个区域,所以在函数lcd_init_fsmc()中我们只对BCR4,BTR4和BWTR4进行配置。 首先我们需要开启FSMC的驱动时钟并对寄存器清零。
void lcd_init_fsmc(void) {
RCC->AHB3ENR.bits.fsmc = 1;
// 寄存器清零
FSMC_Bank1->bcr4.all = 0;
FSMC_Bank1->btr4.all = 0;
FSMC_Bank1E->bwtr4.all = 0;
根据探索者开发教程的说法,TFTLCD的读操作一般比较慢,而写操作比较快。
如果两者使用相同的时序设定,要么让写操作与读操作一致也以较慢的时序工作,要么频繁地为读写操作配置FSMC过程比较繁琐。好在FSMC地模式A支持独立地读写操作,所以这里需要使用扩展功能。
FSMC_Bank1->bcr4.bits.WREN = 1; // 写使能
FSMC_Bank1->bcr4.bits.EXTMOD = 1; // 使用扩展功能
FSMC_Bank1->bcr4.bits.MWID = FSMC_BCR_MWID_16b; // 总线宽度
接下来配置读操作的时序,拉长它的数据保持时间
// 读时序操作
FSMC_Bank1->btr4.bits.ACCMOD = FSMC_BTR_ACCMOD_A; // 模式A
FSMC_Bank1->btr4.bits.ADDSET = 15; // 地址建立时间15个HCLK, 15/168M ≈ 90ns
FSMC_Bank1->btr4.bits.DATAST = 60; // 数据保持时间60个HCLK, 约为360ns
配置写操作的时序,并使能FSMC。
// 写时序操作
FSMC_Bank1E->bwtr4.bits.ACCMOD = FSMC_BTR_ACCMOD_A; // 模式A
FSMC_Bank1E->bwtr4.bits.ADDSET = 9; // 地址建立时间9个HCLK, 约为54ns
FSMC_Bank1E->bwtr4.bits.DATAST = 8; // 数据保持时间8个HCLK, 约为48ns
// 使能Bank1,区域4
FSMC_Bank1->bcr4.bits.MBKEN = 1;
}
在分析原理图的时候我们就说过,只用了一个地址线A6,所以本质上我们的LCD只有命令和数据两个寄存器,分别对应地址0x6C00007E和0x6C000080。 参照其它片上外设的设定,我们定义如下的结构体和宏定义:
struct LcdDev {
volatile uint16 cmd;
volatile uint16 data;
};
#define LCD_BASE ((uint32)(0x6C000000 | 0x0000007E))
#define LCD ((struct LcdDev *) LCD_BASE)
如此一来我们就可以方便的向LCD写入并读出指令和数据了,类似如下的语句:
LCD->cmd = COMMAND;
LCD->data = DATA;
COMMAND = LCD->cmd;
DATA = LCD->data;
接下来,我们写一个简单的例程,控制显示器的所有像素点亮并熄灭。首先,调用刚才提到的三个初始化函数,完成系统的配置功能。
int main(void) {
lcd_init_bl();
lcd_init_gpio();
lcd_init_fsmc();
然后延迟一段时间,等待NT35510上电初始化结束,然后打开背光。
delay(1680000);
delay(1680000);
LCD_BL = 1;
根据NT35510第261页的描述,读出芯片的ID。
默认读出来的结果就是,ID1=0x00, ID2=0x80, ID3=0x00。
LCD->cmd = 0x0400;
id1 = LCD->data;
LCD->cmd = 0x0401;
id2 = LCD->data;
LCD->cmd = 0x0402;
id3 = LCD->data;
在NT35510的文档中说,上电之后NT35510需要先完成硬件的初始化,需要延迟5ms以上。
然后通过指令SLPOUT(0x1100)开启液晶驱动的DC/DC转换器和内部的显示晶振。再延迟5ms以上等待系统稳定之后,
通过指令DISPON(0x2900)开启显示功能,之后就可以控制液晶屏按照需要显示了。这里先后通过指令ALLPON(0x2300)和ALLPOFF(0x2200)控制所有像素点亮或者熄灭,
关于其它指令和像素控制方法可以参考官方文档的说法,我们以后也会有专题介绍。
LCD->cmd = 0x1100; // SLPOUT
delay(1680000); delay(1680000);
LCD->cmd = 0x2900; // DISPON
delay(1680000); delay(1680000);
LCD->cmd = 0x2200; // all pixel off
delay(1680000); delay(1680000);
LCD->cmd = 0x2300; // all pixel on
最后,我们关闭液晶背光结束例程。
LCD_BL = 0;
while (1) { }
}
5. 总结
在本文中,我们通过STM32的FSMC来实现一个8080的接口驱动NT35510。现在不知道总结个啥了,以后再修改吧。