STM32学习笔记6LCD的显示.docx
《STM32学习笔记6LCD的显示.docx》由会员分享,可在线阅读,更多相关《STM32学习笔记6LCD的显示.docx(26页珍藏版)》请在冰豆网上搜索。
STM32学习笔记6LCD的显示
STM32学习笔记(6):
LCD的显示
2011年4月14日LCD显示
1.LCD/LCM的基本概念
液晶显示器(LiquidCrystalDisplay:
LCD)的构造是在两片平行的玻璃当中放置液态的晶体,两片玻璃中间有许多垂直和水平的细小电线,透过通电与否来控制杆状水晶分子改变方向,将光线折射出来产生画面。
LCM(LCDModule)即LCD显示模组、液晶模块,是指将液晶显示器件,连接件,控制与驱动等外围电路,PCB电路板,背光源,结构件等装配在一起的组件。
在平时的学习开发中,我们一般使用的是LCM,带有驱动IC和LCD屏幕等多个模块。
2.FSMC的基本概念
在STM32上开发LCD显示,可以有两种方式来对LCD进行操作,一种是通过普通的IO口,连接LCM的相应引脚来进行操作,第2种是通过FSMC来进行操作。
可变静态存储控制器(FlexibleStaticMemoryController:
FSMC)是STM32系列中内部集成256KB以上FlaSh,后缀为xC、xD和xE的高存储密度微控制器特有的存储控制机制。
之所以称为“可变”,是由于通过对特殊功能寄存器的设置,FSMC能够根据不同的外部存储器类型,发出相应的数据/地址/控制信号类型以匹配信号的速度,从而使得STM32系列微控制器不仅能够应用各种不同类型、不同速度的外部静态存储器,而且能够在不增加外部器件的情况下同时扩展多种不同类型的静态存储器,满足系统设计对存储容量、产品体积以及成本的综合要求。
FSMC有很多优点:
1.支持多种静态存储器类型。
STM32通过FSMC可以与SRAM、ROM、PSRAM、NORFlash和NANDFlash存储器的引脚直接相连。
2.支持丰富的存储操作方法。
FSMC不仅支持多种数据宽度的异步读/写操作,而且支持对NOR、PSRAM、NAND存储器的同步突发访问方式。
3.支持同时扩展多种存储器。
FSMC的映射地址空间中,不同的BANK是独立的,可用于扩展不同类型的存储器。
当系统中扩展和使用多个外部存储器时,FSMC会通过总线悬空延迟时间参数的设置,防止各存储器对总线的访问冲突。
4.支持更为广泛的存储器型号。
通过对FSMC的时间参数设置,扩大了系统中可用存储器的速度范围,为用户提供了灵活的存储芯片选择空间。
5.支持代码从FSMC扩展的外部存储器中直接运行,而不需要首先调入内部SRAM。
FSMC包含两类控制器:
1.1个NOR闪存/SRAM控制器,可以与NOR闪存、SRAM和PSRAM存储器接口。
2.1个NAND闪存/PC卡控制器,可以与NAND闪存、PC卡,CF卡和CF+存储器接口。
控制器产生所有驱动这些存储器的信号时序:
1.16位数据线,用于连接8位或16位的存储器;
2.26位地址线,最多可连续64MB的存储器(这里不包括片选线);
3.5位独立的片选信号线;
4.1组适合不同类型存储器的控制信号线:
-控制读/写操作
-与存储器通信,提供就绪/繁忙信号和中断信号
-与所用配置的PC卡接口:
PC存储卡、PCI/O卡和真正的IDE接口
从FSMC的角度看,可以把外部存储器划分为固定大小为256MB的4个存储块
·存储块1用于访问最多4个NOR闪存或者PSRAM存储设备。
这个存储区被划分为4个NOR/PSRAM区,并有4个专用的片选。
·存储块2和3用于访问NAND闪存设备,每个存储块连接一个NAND闪存。
·存储块4用于访问PC卡设备
每一个存储块上的存储器类型是由用户在配置寄存器中定义的。
注意:
FSMC只是提供了一个控制器,并不提供相应的存储设备,至于外设接的是什么设备,完全是由用户自己选择,只要能用于FSMC控制,就可以,像本次实验中,我们接的就是LCM。
3.本例中FSMC的使用
由于本例只是利用FSMC对LCM进行操作,因此不用完全懂得FSMC的所有功能,而是懂得一部分相应的操作即可。
1.FSMC包括哪几个部分
FSMC包含以下4个模块:
·AHB接口(包含FSMC配置寄存器)
·NOR闪存和PSRAM控制器
·NAND闪存和PC卡控制器
·外部设备接口
需要注意的是,FSMC可以请求AHB进行数据宽度操作。
如果AHB操作的数据宽度大于外部设备(NOR或NAND或LCD)的宽度,此时FSMC将AHB操作分割成几个连续的较小的数据宽度,以适应外部设备的数据宽度。
2.FSMC对外部设备的地址映像
FSMC对外部设备的地址映像从0x60000000开始,到0x9FFFFFFF结束,一共4个地址块,每个地址块256MB,而每个地址块又分成4个分地址块,大小为64MB。
对于NOR的地址映像来说,我们可以通过选择HADDR[27:
26]来确定当前使用的是哪个64M的分地址块。
而这四个分存储块的片选,则使用NE[4:
1]来选择。
数据线/地址线/控制线是共享的。
这里的HADDR是需要转换到外部设备的内部AHB地址线,每个地址对应一个字节单元。
因此,若外部设备的地址宽度是8位的,则HADDR[25:
0]与STM32的CPU引脚FSMC_A[25:
0]一一对应,最大可以访问64M字节的空间。
若外部设备的地址宽度是16位的,则是HADDR[25:
1]与STM32的CPU引脚FSMC_A[24:
0]一一对应。
在应用的时候,可以将FSMC_A总线连接到存储器或其他外设的地址总线引脚上。
4.ILI9325
由于我们使用的是奋斗STM32V3开发板,其内部自带的是一个LCM,产品的编号是:
QD024CPS25-36AV0,其中的详细规格参数可以参考QD024CPS25-36AV0规格书中的记载。
而LCM中的驱动IC就是采用的ILI9325。
ILI9325的功能很多,在此无法一一说明,但是参考ILI9325的Datasheet我们发现有几个引脚还是非常重要的,而只要操作好了这几个引脚,基本上就可以实现简单的对LCM的控制了。
nCS:
IC的片选信号。
如果是低电平,则ILI9325是被选中,并且可以进行操作,如果是高电平,这不被选中。
RS:
寄存器选择信号。
如果是低电平,则选择的是索引或者状态寄存器,如果是高电平,则选择控制寄存器。
nWR/SCL:
写使能信号,低电平有效。
nRD:
读使能信号,低电平有效。
以上内容是从ILI9325的Datasheet里面找到的,但是根据我的实际操作发现,似乎高电平也是有效的。
而且,不管是高电平还是低电平,都可以成功驱动LCD,如果有了解情况的可以讨论一下。
ILI9325的寄存器非常多,详细的各个寄存器的功能请参考ILI9325的Datasheet。
在对ILI9325进行操作时,应该先写地址,然后再写数据,设置好各个寄存器之后,ILI9325就可以开始工作了。
5.电路设计
1.信号线的连接
STM32F10xFSMC有4个不同的banks,每一个64MB,可支持NOR以及其他类似的存储器。
这些外部设备的地址线、数据线和控制线是共享的。
每个设备的访问时通过片选信号来决定的,而每次只能访问一个设备。
我们的LCM就是连接在NOR的bank上面。
FSMC_D[15:
0]:
16bit的数据总线,连接ILI9325的数据线;
FSMC_NEx:
分配给NOR的256MB的地址空间还可以分为4个banks,每一个区用来分配一个外设,这4个外设分别就是NE1-NE4;
FSMC_NOE:
输出使能,连接ILI9325的nRD引脚;
FSMC_NWE:
写使能,连接ILI9325的nWR引脚;
FSMC_Ax:
用在LCD显示RAM和寄存器之间进行选择的地址线,这个和ILI9325的RS引脚相连。
该线可用任意一根地址线,范围是FSMC_A[25:
0]。
当RS=0时,表示读写寄存器,RS=1时,表示读写数据RAM。
其实关于RS的表述也并不完全准确,应该这么理解,RS=0的时候,向这个地址写的数表示了选择什么寄存器进行操作,然而要对寄存器进行什么操作,则要看当RS=1时,送入的数据了。
关于地址的计算,如果我们选择NOR的第一个存储区,并且使用FSMC_A16来控制ILI9325的RS引脚,则如果要访问寄存器地址(RS=0),那么地址是0x60000000(起始地址),如果要访问数据区(RS=1),那么基地址应该是0x60020000。
有人会问,为什么不是0x60010000呢?
因为FSMC_A16=1。
因为在前文中已经说过,若外部设备的地址宽度是16位的,则是HADDR[25:
1]与STM32的CPU引脚FSMC_A[24:
0]一一对应。
也就是说,内部产生的地址应该要左移一位,FSMC_A16=1,代表着第17位为1,而不是第16位为1。
如果外部设备的地址宽度是8位的话,则不会出现这个问题。
再举一个例子,如果选择NOR的第4个存储区,使用FSMC_A0来控制RS引脚,则访问数据区的地址为0x60000002,访问LCD寄存器的地址为:
0x60000000。
2.时序问题
一般使用模式2来做LCD的接口控制,不使用外扩模式。
并且读写操作的时序一样。
此种情况下,我们需要使用3个参数:
ADDSET、DATAST、ADDHOLD。
时序的计算需要根据NOR闪存存储器的特性和STM32F10x的时钟HCLK来计算这些参数。
写或读访问时序是存储器片选信号的下降沿与上升沿之间的时间,这个时间可以由FSMC时序参数的函数计算得到:
写/读访问时间=((ADDSET+1)+(DATAST+1))×HCLK
在写操作中,DATAST用于衡量写信号的下降沿与上升沿之间的时间参数:
写使能信号从低变高的时间=tWP=DATAST×HCLK
为了得到正确的FSMC时序配置,下列时序应予以考虑:
最大的读/写访问时间、不同的FSMC内部延迟、不同的存储器内部延迟
因此得到:
((ADDSET+1)+(DATAST+1))×HCLK=max(tWC,tRC)
DATAST×HCLK=tWP
DATAST必须满足:
DATAST=(tAVQV+tsu(Data_NE)+tv(A_NE))/HCLK–ADDSET–4
由于我没有找到ILI9325的这些时序的参数,所以就参考了一些以前别人写的程序里面的时序配置:
当HCLK的频率是72MHZ,使用模式B,则有如下时序:
地址建立时间:
0x1
地址保持时间:
0x0
数据建立时间:
0x5
6.程序编写步骤
对于程序的编写,一般步骤是:
1.初始化RCC;
2.初始化GPIO;
3.初始化FSMC;
4.初始化LCD;
5.往GRAM里面写入显示数据。
其中RCC、GPIO、FSMC的初始化函数在STM32的固件库中已经有相应的函数,在此就不一一赘述了,如果有不懂的,可以参考以前我写的学习笔记。
FSMC的初始化参数很多,而且基本上可以通用,因此在此也不对每一个参数具体有什么用进行解释了,一般来说,用通用参数就足够普通的开发了。
而对LCD的初始化,则需要自己编写相应的代码。
基本原则是,首先向寄存器地址写入需要操作的寄存器地址(代码),然后再根据Datasheet,向数据区地址写入相应的数据,以实现某些操作。
具体的操作在ILI9325的Datasheet第8节RegisterDescriptions中,有详细的解释。
而LCD的初始化只要按照Datasheet里面的,把每一个寄存器都给配置好了,就没有问题了。
而这些寄存器的配置,大部分都是通用的,只是有一些屏幕方向选择,坐标系等会略有差别。
LCD配置好之后,就可以往GRAM里面写入图像数据了,在这里推荐一个软件“Image2LCD”,这个软件能读取图像,然后生成C代码的数据,只要将这些生成的代码直接写入GRAM中,就可以显示出图像了。
不过要记住,在图像转换的时候,输出数据类型选择“C语言数组”,扫描模式选择“水平扫描”,输出灰度“16位真彩色”,最大宽度和高度“320”“240”勾选“高位在前(MSBFirst)”。
这些配置都是和ILI9325的寄存器配置相对应的,如果说ILI9325的配置和本文中的不一样,则需要相应的选择其他的选项。
7.程序源代码
main.c文件中的代码:
#include"stm32f10x_lib.h"
#include"stm32f10x_lcd.h"
externunsignedcharLCD_Image_BIT[];
externunsignedcharLCD_Image_HIT[];
voidRCC_cfg();
voidFSMC_cfg();
voidLCD_cfg();
voidGPIO_cfg();
voidLCD_Show(unsignedchar*LCD_Image);
intmain()
{
RCC_cfg();
GPIO_cfg();
FSMC_cfg();
LCD_cfg();
while
(1)
{
LCD_Show(LCD_Image_HIT);
Delay(100000000);
LCD_Show(LCD_Image_BIT);
Delay(100000000);
}
}
//RCC时钟配置
voidRCC_cfg()
{
//定义错误状态变量
ErrorStatusHSEStartUpStatus;
//将RCC寄存器重新设置为默认值
RCC_DeInit();
//打开外部高速时钟晶振
RCC_HSEConfig(RCC_HSE_ON);
//等待外部高速时钟晶振工作
HSEStartUpStatus=RCC_WaitForHSEStartUp();
if(HSEStartUpStatus==SUCCESS)
{
//设置AHB时钟(HCLK)为系统时钟
RCC_HCLKConfig(RCC_SYSCLK_Div1);
//设置高速AHB时钟(APB2)为HCLK时钟
RCC_PCLK2Config(RCC_HCLK_Div1);
//设置低速AHB时钟(APB1)为HCLK的2分频
RCC_PCLK1Config(RCC_HCLK_Div2);
//设置FLASH代码延时
FLASH_SetLatency(FLASH_Latency_2);
//使能预取指缓存
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
//设置PLL时钟,为HSE的9倍频8MHz*9=72MHz
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
//使能PLL
RCC_PLLCmd(ENABLE);
//等待PLL准备就绪
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);
//设置PLL为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//判断PLL是否是系统时钟
while(RCC_GetSYSCLKSource()!
=0x08);
}
//打开GPIO时钟,复用功能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_AFIO,ENABLE);
//打开FSMC时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);
}
//FSMC配置
voidFSMC_cfg()
{
FSMC_NORSRAMInitTypeDefFSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDefp;
//设置地址建立时间
p.FSMC_AddressSetupTime=0x02;
//设置地址保持时间
p.FSMC_AddressHoldTime=0x00;
//设置数据建立时间
p.FSMC_DataSetupTime=0x05;
//总线返转时间
p.FSMC_BusTurnAroundDuration=0x00;
//时钟分频
p.FSMC_CLKDivision=0x00;
//数据保持时间
p.FSMC_DataLatency=0x00;
//设置FSMC访问模式
p.FSMC_AccessMode=FSMC_AccessMode_B;
//选择设置的BANK以及片选信号(BANK1中的第一个block)
FSMC_NORSRAMInitStructure.FSMC_Bank=FSMC_Bank1_NORSRAM1;
//设置是否数据地址总线时分复用(No)
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux=FSMC_DataAddressMux_Disable;
//设置存储器类型(NOR)
FSMC_NORSRAMInitStructure.FSMC_MemoryType=FSMC_MemoryType_NOR;
//设置数据宽度(16bit)
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth=FSMC_MemoryDataWidth_16b;
//设置是否使用迸发访问模式(连续读写模式)(No)
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode=FSMC_BurstAccessMode_Disable;
//设置WAIT信号的有效电平(低电平有效)
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity=FSMC_WaitSignalPolarity_Low;
//设置是否使用还回模式(No)
FSMC_NORSRAMInitStructure.FSMC_WrapMode=FSMC_WrapMode_Disable;
//设置WAIT信号有效时机(在wait状态之前)
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive=FSMC_WaitSignalActive_BeforeWaitState;
//设置是否使能写操作(Yes)
FSMC_NORSRAMInitStructure.FSMC_WriteOperation=FSMC_WriteOperation_Enable;
//设置是否使用WAIT信号(No)
FSMC_NORSRAMInitStructure.FSMC_WaitSignal=FSMC_WaitSignal_Disable;
//设置是否使用扩展模式(读写时序相互独立)(No)
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode=FSMC_ExtendedMode_Disable;
//设置是否使用异步等待信号(No)
FSMC_NORSRAMInitStructure.FSMC_AsyncWait=FSMC_AsyncWait_Disable;
//设置是否使用迸发写模式(No)
FSMC_NORSRAMInitStructure.FSMC_WriteBurst=FSMC_WriteBurst_Disable;
//设定读写时序
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct=&p;
//设定写时序
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct=&p;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
//使能Bank1中的block1
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1,ENABLE);
}
//GPIO配置
voidGPIO_cfg()
{
GPIO_InitTypeDefGPIO_InitStructure;
//背光控制
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStructure);
//LCD复位
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_Init(GPIOE,&GPIO_InitStructure);
//打开FSMC的数据端口D[15:
0]
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_8|GPIO_Pin_9|
GPIO_Pin_10|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOD,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=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;
GPIO_Init(GPIOE,&GPIO_InitStructure);
//打开FSMC功能端口,PD.4=RD(nOE);PD.5=WR(nWE)
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5;
GPIO_Init(GPIOD,&GPIO_InitStructure);
//打开NE1设置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
GPIO_Init(GPIOD,&GPIO_InitStructure);
//打开RS设置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
G