在完成IO驱动彩屏的试验后,就准备着手使用FSMC来驱动彩屏,先了解一下预备知识
一、所谓的FSMC机制
简单介绍FSMC在这篇博文里面很清楚,推荐一下 http://blog.csdn.net/king_bingge/article/details/8718566
然后还有就是这篇学习笔记,也还行 http://www.cnblogs.com/hduxyc/archive/2011/05/17/2048099.html
个人觉得有了这两篇博文再加上我们的参考手册足够搞定FSMC驱动彩屏了
二、FSMC之我见
开始只是谈到别人对FSMC的理解,注意这里只讨论FSMC控制TFT,也就是在FSMC的NOR\PSRAM模式控制LCD,所以我们以下的分析都是基于这种模式的。
1、我们之前通过使用GPIO来模拟8080/6800时序从而达到驱动彩屏的,同样需要明白的一点就是我们也只是使用FSMC来模拟8080/6800时序,只不过这个读写速度有些快(使用了总线嘛),仅此而已!如果不明白8080/6800时序是怎样的或许在这个文库里面能找到你想要的http://wenku.baidu.com/view/a8c98600cc1755270722083e.html
简单一点就是:8080是通过“读使能(RE)”和“写使能(WE)”两条控制线进行读写操作。 6800是通过“总使能(E)”和“读写选择(W/R)”两条控制线进行
2、那么了解到FSMC的三总线如下!
数据线:这个可以分为8位的和16位,这个不难理解,就是之指一次穿上红8位还是16位数据,我的是16位的,8位的有一个懒得用。
地址线:既然我们访问的外NOR FLASH,那么一定会有相应的地址线,那么这些地址线在哪里呢?肯定是通过GPIO引脚复用的。有A0 -- A23 24根,能够控制访问16M的空间,也就是一个子bank;
控制总线:它的控制总线只有三根:读使能信号,写使能信号,片选信号。所以这里和我们8080时序相比,少了复位信号线和数据/命令控制线,怎么办?继续看!
3、了解了FSMC会有这三总线的概念,那么接下来就是如何转化为我们需要的时序了。
对比一下FSMC访问外nor flash和8080访问时序如下
差别似乎很小是吧,简单说就是在数据/指令选择和复位信号上的区别。
4、在这里我们使用的软件方法来完善FSMC转化为8080的读写时序
在参考手册上的存储系统能找到,芯片留给我们外扩的存储器(NOR FLASH、PSRAM这类可直接寻址的器
件)地址是从0x60000000开始的,意思就是当我们访问0x60000000的时候,那就是相当于访问外部nor flash了(我们只讨论这种情况),那么他就会自动产生FSMC的时序
在这里,我们所需要添加的就是D/C选择信号,如何实现呢?我们是通过,一根地址线来实现的,当我们把A0多对应的GPIOF0(引脚默认复用)接到TFT的RS端,
然后执行访问0x60000000的指令,那么RS是否就是低电平选择为数据呢?又加入我们访问的地址是0x60000001的时候,那么RS是否就是高电平,从而选择的就是指令传送呢?答案当然是肯定的!所以我们就解决了这个问题,复位信号就更好解决了,直接和开发板的复位引脚接在一起就好了,就这么简单!
三、说了这么久的理论,来个实例分析更加形象了,首先硬件连线要明白
在原理图或者开发手册上面能够确定引脚复用问题
地址引脚:
(A0-A5 :PF0 - PF5) (A6-A9: F12-F15 ) (A10-A15:PG0-PG5)
(A16-A18:PD11-PD13) (A19-A22:PE3-PE6) (A23-PE2)
片选信号(NEx:PG12)因为我选择的是block4
写使能(NWR:PD5)
读使能(NOE:PD4)
至此控制引脚基本完成
下面就是数据引脚:
PD14-FSMC-D0 ----LCD-DB0
PD15-FSMC-D1 ----LCD-DB1
PD0-FSMC-D2 ----LCD-DB2
PD1-FSMC-D3 ----LCD-DB3
PE7-FSMC-D4 ----LCD-DB4
PE8-FSMC-D5 ----LCD-DB5
PE9-FSMC-D6 ----LCD-DB6
PE10-FSMC-D7 ----LCD-DB7
PE11-FSMC-D8 ----LCD-DB8
PE12-FSMC-D9 ----LCD-DB9
PE13-FSMC-D10 ----LCD-DB10
PE14-FSMC-D11 ----LCD-DB11
PE15-FSMC-D12 ----LCD-DB12
PD8-FSMC-D13 ----LCD-DB13
PD9-FSMC-D14 ----LCD-DB14
PD10-FSMC-D15 ----LCD-DB15
有了这些硬件连线是没有任何问题的
四、正式分析程序
1、硬件引脚配置函数
void LCD_CtrlLinesConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable FSMC, GPIOD, GPIOE, GPIOF, GPIOG and AFIO clocks */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); //使能FSMC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE |
RCC_APB2Periph_GPIOF | RCC_APB2Periph_GPIOG |
RCC_APB2Periph_AFIO, ENABLE);
//IO口复用功能时钟
/* Set PD.00(D2), PD.01(D3), PD.04(NOE), PD.05(NWE), PD.08(D13), PD.09(D14),
PD.10(D15), PD.14(D0), PD.15(D1) as alternate
function push pull */
/*D端口初始化*/
GPIO_InitStructure.GPIO_Pin = 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;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/*E端口初始化*/
/* Set PE.07(D4), PE.08(D5), PE.09(D6), PE.10(D7), PE.11(D8), PE.12(D9), PE.13(D10),
PE.14(D11), PE.15(D12) as alternate function push pull */
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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure); //将配置写入GPIOE管脚
/*A0地址线*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOF, &GPIO_InitStructure);
/* Set PG.12(NE4 (LCD/CS)) as alternate function push pull - CE3(LCD /CS) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOG, &GPIO_InitStructure);
/*复位端口PE6*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_ResetBits(GPIOE,GPIO_Pin_6); //复位脚
DelayMs(50); //延时50ms
GPIO_SetBits(GPIOE, GPIO_Pin_6); //将复位脚拉高
}
细心观察,能够发现就是上面说的那些引脚嘛!
2、接下来的就是比较重要的FSMC的配置了
先上代码,然后慢慢分析吧
void LCD_FSMCConfig(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef p;
/*-- FSMC Configuration ------------------------------------------------------*/
/*----------------------- SRAM Bank 4 ----------------------------------------*/
/* FSMC_Bank1_NORSRAM4 configuration */
p.FSMC_AddressSetupTime = 0;
p.FSMC_AddressHoldTime = 0;
p.FSMC_DataSetupTime = 2;
p.FSMC_BusTurnAroundDuration = 0;
p.FSMC_CLKDivision = 0;
p.FSMC_DataLatency = 0;
p.FSMC_AccessMode = FSMC_AccessMode_A;
/*
Color LCD configuration ------------------------------------
LCD configured as follow:
- Data/Address MUX = Disable
- Memory Type = SRAM
- Data Width = 16bit
- Write Operation = Enable
- Extended Mode = Enable
- Asynchronous Wait = Disable
*/
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
//FSMC_NORSRAMInitStructure.FSMC_AsyncWait = FSMC_AsyncWait_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
/* BANK 4 (of NOR/SRAM Bank 1~4) is enabled */
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);
}
首先是这个结构体FSMC_NORSRAMTimingInitTypeDef,找到他的定义:
成员变量有
uint32_t FSMC_AccessMode
uint32_t FSMC_AddressHoldTime
uint32_t FSMC_AddressSetupTime
uint32_t FSMC_BusTurnAroundDuration
uint32_t FSMC_CLKDivision
uint32_t FSMC_DataLatency
uint32_t FSMC_DataSetupTime
FSMC_AccessMode:Specifies the asynchronous access mode,用于同步模式,它的取值有以下几种,参考手册上面显示:模式A —— SRAM/PSRAM(CRAM) OE翻转,所以这里我们选择的是模式A
|
|||||||||
FSMC_AddressHoldTime:Defines the number of HCLK cycles to configure the duration of the address hold time. This parameter can be a value between 0 and 0xF.地址保持的时钟周期! | |||||||||
FSMC_AddressSetupTime:Defines the number of HCLK cycles to configure the duration of the address setup time. This parameter can be a value between 0 and 0xF. 明显是地址建立飞时间周期 | |||||||||
FSMC_BusTurnAroundDuration:Defines the number of HCLK cycles to configure the duration of the bus turnaround. This parameter can be a value between 0 and 0xF.这个应该是指总线翻转周期么,不是很了解 | |||||||||
FSMC_CLKDivision:Defines the period of CLK clock output signal, expressed in number of HCLK cycles. This parameter can be a value between 1 and 0xF 明显是指HCLK的分频系数 |
|||||||||
FSMC_DataLatency:Defines the number of memory clock cycles to issue to the memory before getting the first data. The value of this parameter depends on the memory type as shown below:
|
|||||||||
FSMC_DataSetupTime:这个相应的就是数据的建立时间了 |
通过对比,发现上述配置是可行的,不过大家也可以按照要求更改。理论上,在速度要求不是很高的场合,大一点是没有关系的,但是下限得注意,具体是多少,我也走不知道,等以后再说吧!
然后就是看这个结构体了FSMC_NORSRAMInitStructure,成员如下:
uint32_t | FSMC_AsynchronousWait |
uint32_t | FSMC_Bank |
uint32_t | FSMC_BurstAccessMode |
uint32_t | FSMC_DataAddressMux |
uint32_t | FSMC_ExtendedMode |
uint32_t | FSMC_MemoryDataWidth |
uint32_t | FSMC_MemoryType |
FSMC_NORSRAMTimingInitTypeDef * | FSMC_ReadWriteTimingStruct |
uint32_t | FSMC_WaitSignal |
uint32_t | FSMC_WaitSignalActive |
uint32_t | FSMC_WaitSignalPolarity |
uint32_t | FSMC_WrapMode |
uint32_t | FSMC_WriteBurst |
uint32_t | FSMC_WriteOperation |
FSMC_NORSRAMTimingInitTypeDef * | FSMC_WriteTimingStruct |
似乎有些复杂,同时也说明了它的功能强大吧!
FSMC_AsynchronousWait:Enables or disables wait signal during asynchronous transfers, valid only with asynchronous Flash memories,明显就是使能等待同步信号否? | |||||||||
FSMC_Bank:这个应该是bank的选择吧,明显取值有以下几种:
|
|||||||||
FSMC_BurstAccessMode:Enables or disables the burst access mode for Flash memory, valid only with synchronous burst Flash memories.这个什么呢?不懂继续看吧! | |||||||||
FSMC_DataAddressMux:Specifies whether the address and data values are multiplexed on the databus or not,数据地址引脚是否复用,明显这里我们不需要!! | |||||||||
FSMC_ExtendedMode:Enables or disables the extended mode.,外扩模式否?应该是不用的 | |||||||||
FSMC_MemoryDataWidth:位宽,我使用的是16位的TFT,所以应该是16,看看取值 果然:
|
|||||||||
FSMC_MemoryType:Specifies the type of external memory attached to the corresponding memory bank.意思应该是当我们外扩存储器的时候,分配哪一个bank吧,看取值。应该是0-3吧!看结果似乎有些问题,它是指外扩的存储器类型 | |||||||||
|
|||||||||
FSMC_WaitSignal:Enables or disables the wait-state insertion via wait signal等待信号,等待状态与否 | |||||||||
FSMC_WaitSignalActive:Specifies if the wait signal is asserted by the memory one clock cycle before the wait state or during the wait state, valid only when accessing memories in burst mode,似乎有些搞混淆了,先这样继续看吧! | |||||||||
FSMC_WaitSignalPolarity:Specifies the wait signal polarity, valid only when accessing the Flash memory in burst mode,是指奇偶校验信号么?但是这个只需在flash的burst模式里面设置。 | |||||||||
FSMC_WrapMode:Enables or disables the Wrapped burst access mode for Flash memory翻转模式? | |||||||||
FSMC_WriteBurst :Enables or disables the write burst operation写操作的 | |||||||||
FSMC_WriteOperation:Enables or disables the write operation in the selected bank by the FSMC | |||||||||
FSMC_WriteTimingStruct FSMC_ReadWriteTimingStruct还有两个这样的结构体! Timing Parameters for write and read access if the ExtendedMode is not used ,当我们没有使用外扩模式的时候需要配置,显然这里需要配置的! 这两个结构体就是上面分析了的。所以我们只要执行这个语句,就能完成FSMC的配置了 FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); 然后就是简单的初始化TFT了 个人觉得有几点需要注意的地方,也总结一下! 关于使用16位宽的时候的地址的问题! 用的第17位,插16位的线 假如我们这样访问*(volatile unsigned short int *)(0x60020000)=val |
现在明白了我们的bit17对应的是FSMC的bit16,也就是A16了,所以我们的硬件连线应该是PA16了,也就是说HADDR
信号线是需要转换到外部存储器的内部AHB地址线,是字节地址。
给地址、将地址映射到外存储器(由HADDR的A25--A1映射到A24---A0)
注意点就是这个地方了,其他的时序问题和前面用IO模拟是差不多的,唯一的好处就是,我们有很多控制信号就不需要管了。最后贴上代码吧
/*******************************************************************************
* Function Name : LCD_WriteReg
* Description : Writes to the selected LCD register.
* Input : - LCD_Reg: address of the selected register.
* - LCD_RegValue: value to write to the selected register.
* Output : None
* Return : None
*******************************************************************************/
void LCD_WriteReg(unsigned char LCD_Reg,unsigned int LCD_RegValue)
{
/* Write 16-bit Index, then Write Reg */
LCD->LCD_REG = LCD_Reg; //这里表示写寄存器
/* Write 16-bit Reg */
LCD->LCD_RAM = LCD_RegValue; //这里表示写寄存器的值(也就是数据)
}
/*******************************************************************************
* Function Name : LCD_ReadReg
* Description : Reads the selected LCD Register.
* Input : None
* Output : None
* Return : LCD Register Value.
*******************************************************************************/
u16 LCD_ReadReg(unsigned char LCD_Reg)
{
/* Write 16-bit Index (then Read Reg) */
LCD->LCD_REG = LCD_Reg; //这里表示先写寄存器
/* Read 16-bit Reg */
return (LCD->LCD_RAM); //读取值
}
/*******************************************************************************
* Function Name : LCD_WriteRAM_Prepare
* Description : Prepare to write to the LCD RAM.
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void LCD_WriteRAM_Prepare(void)
{
LCD->LCD_REG = R34;
}
/*******************************************************************************
* Function Name : LCD_WriteRAM
* Description : Writes to the LCD RAM.
* Input : - RGB_Code: the pixel color in RGB mode (5-6-5).
* Output : None
* Return : None
*******************************************************************************/
void LCD_WriteRAM(u16 RGB_Code)
{
/* Write 16-bit GRAM Reg */
LCD->LCD_RAM = RGB_Code;
}
/*******************************************************************************
* Function Name : LCD_ReadRAM
* Description : Reads the LCD RAM.
* Input : None
* Output : None
* Return : LCD RAM Value.
*******************************************************************************/
unsigned int LCD_ReadRAM(void)
{
unsigned int dummy;
/* Write 16-bit Index (then Read Reg) */
LCD->LCD_REG = R34; /* Select GRAM Reg */
/* Read 16-bit Reg */
dummy = LCD->LCD_RAM;
return dummy;
}
/*******************************************************************************
* Function Name : LCD_SetCursor
* Description : Sets the cursor position.
* Input : - Xpos: specifies the X position.
* - Ypos: specifies the Y position.
* Output : None
* Return : None
*******************************************************************************/
void LCD_SetCursor(unsigned int Xpos, unsigned int Ypos)
{
LCD_WriteReg(0x06,Ypos>>8);
LCD_WriteReg(0x07,Ypos);
LCD_WriteReg(0x02,Xpos>>8);
LCD_WriteReg(0x03,Xpos);
}