STM322完全学习——FSMC控制LCD显示屏
一、GPIO初始化
首先这个功能只有大容量的STM32系列有,C8T6是没有的。再就是FSMC这个使用的是GPIO的复用功能,下面先完成我们需要使用的GPIO的初始化
void TFTLCD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD|RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF|RCC_AHB1Periph_GPIOG, ENABLE);//使能PD,PE,PF,PG时钟
GPIO_PinAFConfig(GPIOD,GPIO_PinSource14,GPIO_AF_FSMC); //FSMC_D0-FSMC_D15
GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource1,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource7,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource8,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource10,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource12,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource13,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource14,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource15,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource8,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource9,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource10,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource4,GPIO_AF_FSMC); //FSMC_NOE
GPIO_PinAFConfig(GPIOD,GPIO_PinSource5,GPIO_AF_FSMC); //FSMC_NWE
GPIO_PinAFConfig(GPIOF,GPIO_PinSource12,GPIO_AF_FSMC); //FSMC_A6
GPIO_PinAFConfig(GPIOG,GPIO_PinSource12,GPIO_AF_FSMC); //FSMC_NE4
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;//PD0,1,4,5,8,9,10,14,15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
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;//PE7~15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOE, &GPIO_InitStructure); //初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PF12,FSMC_A6
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOF, &GPIO_InitStructure); //初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PG12,FSMC_NE4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOG, &GPIO_InitStructure); //初始化
}
主要看的是你的这些接口接的是哪里,这个每个开发板的设置可能都不太一一样但是也大差不差,仔细核对好。
二、FSMC初始化
void TFTLCD_FSMC_Init(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef FSMC_ReadNORSRAMTiming;
FSMC_NORSRAMTimingInitTypeDef FSMC_WriteNORSRAMTiming;
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE);//使能FSMC时钟
FSMC_ReadNORSRAMTiming.FSMC_AddressSetupTime = 0X01; //地址建立时间(ADDSET)为2个HCLK 1/168M=6ns*2=12ns
FSMC_ReadNORSRAMTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(ADDHLD)模式A未用到
FSMC_ReadNORSRAMTiming.FSMC_DataSetupTime = 0x0f; //数据保存时间为16个HCLK 因为液晶驱动IC的读数据的时候,速度不能太快
FSMC_ReadNORSRAMTiming.FSMC_BusTurnAroundDuration = 0x00;
FSMC_ReadNORSRAMTiming.FSMC_CLKDivision = 0x00;
FSMC_ReadNORSRAMTiming.FSMC_DataLatency = 0x00;
FSMC_ReadNORSRAMTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A
FSMC_WriteNORSRAMTiming.FSMC_AddressSetupTime =0x03; //地址建立时间(ADDSET)为1个HCLK
FSMC_WriteNORSRAMTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(A
FSMC_WriteNORSRAMTiming.FSMC_DataSetupTime = 0x02; //数据保存时间为6ns*9个HCLK=54ns
FSMC_WriteNORSRAMTiming.FSMC_BusTurnAroundDuration = 0x00;
FSMC_WriteNORSRAMTiming.FSMC_CLKDivision = 0x00;
FSMC_WriteNORSRAMTiming.FSMC_DataLatency = 0x00;
FSMC_WriteNORSRAMTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;// 这里我们使用NE4 ,也就对应BTCR[6],[7]。
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 不复用数据地址
FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM; //SRAM
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
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_Enable; // 读写使用不同的时序
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_ReadNORSRAMTiming; //读写时序
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_WriteNORSRAMTiming; //写时序
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); //初始化FSMC配置
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE); // 使能BANK1
}
上面初始化赋值的这些参数也不是一成不变,自己可以不断地尝试一些不同的设置,你会发现他还是可以正常工作。
三、实现TFT写命令和数据的函数
我们使用FSMC的方法来控制LCD,那你就要忘记我们使用IO模拟时序许的方法,因为在这里我们只能控制FSMC然后通过他去控制LCD,而不是还要想着控制GPIO怎样去发送数据,因为FSMC不是专门被设计用来控制LCD的因此我们只是用到这个模块其中的一些功能我们是将他控制SRAM的方法修改了一下。
既然我们使用的是SRAM这个模块的控制器,那么我们到时候控制的地址就在上面这个范围里面 ,但是这个块里面还有四个小块。这个主要看你的开发板是怎么接的,NE4就是使用的里面的第四个小块那么他的地址范围就是0x6C00 0000-0x6FFF FFFF这个地址很重要一定要记得。还需要注意那个FSMC_A6这个引脚,每个开发板也会不一样,这个也很很重要,关系到写指令和写数据的时候需要写的地址。
下面关于一些地址偏移进行一些说明,主要是将数据手册的下面这一部分说清楚:
第一种8位的我们就不说了,地址就是一一对应的。对于我们这种数据总线16的来说,我们来看下面这个情况,这个说法也不太严谨,简单的来说,就是他们两个之间有一个1比2的关系。在STM32内部每4个地址构成1个32位数据位,而外部存储器每两个地址构成一个16位的数据。注意每个地址都只有1个字节。
会存在上面的对应关系,因为左边的地址只有偶数,而右边的地址是连续的因此你会发现将左边的地址右移一位便可以得到右边的地址上面我这个移位只考虑了,后面几位。你懂这个意思就可以啦。因此当我们想要操作的右边的地址假如是0x1002 那么当你在STM32中控制FSMC里面的地址的时候一定要先将这个地址左移一位,然后在和我们FSMC里面的基地址在进行位或,这样的话到时候FSMC会将这个地址右移一位,就刚好是我们想要操作的那个地址了。正如上面所说的,我们呢使用的是块1里面的第四个小块,他的起始地址是0X6C00 0000 而我们用来选择写数据还是写命令的那个线接到了,FSMC里面的第10根地址线,我们还知道RS线上0是写命令,1是写数据,因此这边将RS信号线接到了A10这根地址线上面,因此我们在写命令的是时候,需要给0x6c00 0000这个地址里面写就相当于写命令,给0x6c00 0800里面写入东西就相当于写数据,这个地址里面第11位是1,也就是到时候FSMC会右移一下,这样这个地址的第10位就是1,就相当于RS=1。这样就达到了目的。
#define LCD_CMD (*(uint32_t *) 0X6C000000)
#define LCD_DATA (*(uint32_t *) 0X6C000800)
你只需要将自己要写的入的命令或数据直接赋值给上面的宏定义即可。当然这样处理是可以的但是不是唯一的地址,这样只是看起来好看一点,通俗的来讲只要是第11位地址是0其他位无论什么都可以当作写命令的地址,第11位是1其他位无论是什么都可以当作写数据的地址。下面这个使用的是结构体的方式,这里的RS连接的是A6也就是第6根数据线,这里需要注意一下。
//TFTLCD地址结构体
typedef struct
{
u16 LCD_CMD;
u16 LCD_DATA;
}TFTLCD_TypeDef;
//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A6作为数据命令区分线
//注意设置时STM32内部会右移一位对齐! 111 1110=0X7E
//当结构体内部的地址进行对齐的时候,0x6c00 007e首先会加2 也就是0x6c00 0080
//这样你就会发现两个地址在第7位分别是0和1.
#define TFTLCD_BASE ((u32)(0x6C000000 | 0x0000007E))
#define TFTLCD ((TFTLCD_TypeDef *) TFTLCD_BASE)
//当然这样的处理也不是唯一的 例如你将结构体里面的两个数据颠倒写一下,也就是数据写到第一位,命令写到第二位,就会有下面的这个
/*
//TFTLCD地址结构体
typedef struct
{
u16 LCD_DATA;
u16 LCD_CMD;
}TFTLCD_TypeDef;
//注意这里还是A6作为数据命令区分线
//首先数据写在前面也就是A7必须先为1,然后通过地址对齐在为0
//这样地址就是1111 1110 = 0XFE
#define TFTLCD_BASE ((u32)(0x6C000000 | 0x000000FE))
#define TFTLCD ((TFTLCD_TypeDef *) TFTLCD_BASE)
*/
//写寄存器函数
//cmd:寄存器值
void LCD_WriteCmd(u16 cmd)
{
TFTLCD->LCD_CMD=cmd;
}
//写数据
//data:要写入的值
void LCD_WriteData(u16 data)
{
TFTLCD->LCD_DATA=data;
}
void LCD_WriteData_Color(u16 color)
{
TFTLCD->LCD_DATA=color;
}
当然我上面结构体的这种方法使用的是A6地址线,下面我修改一下使用A10地址线,同样也是两种方法;
//TFTLCD地址结构体
typedef struct
{
u16 LCD_CMD;
u16 LCD_DATA;
}TFTLCD_TypeDef;
//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A10作为数据命令区分线
//注意设置时STM32内部会右移一位对齐! 111 1111 1110=0X7FE
//当结构体内部的地址进行对齐的时候,0x6c00 07FE首先会加2 也就是0x6c00 0800
//这样你就会发现两个地址在第11位分别是0和1.
#define TFTLCD_BASE ((u32)(0x6C000000 | 0x000007FE))
#define TFTLCD ((TFTLCD_TypeDef *) TFTLCD_BASE)
//当然这样的处理也不是唯一的 例如你将结构体里面的两个数据颠倒写一下,也就是数据写到第一位,命令写到第二位,就会有下面的这个
/*
//TFTLCD地址结构体
typedef struct
{
u16 LCD_DATA;
u16 LCD_CMD;
}TFTLCD_TypeDef;
//注意这里还是A10作为数据命令区分线
//首先数据写在前面也就是A11必须先为1,然后通过地址对齐在为0
//这样地址就是1111 1111 1110 = 0XFFE
#define TFTLCD_BASE ((u32)(0x6C000000 | 0x00000FFE))
#define TFTLCD ((TFTLCD_TypeDef *) TFTLCD_BASE)
*/
//写寄存器函数
//cmd:寄存器值
void LCD_WriteCmd(u16 cmd)
{
TFTLCD->LCD_CMD=cmd;
}
//写数据
//data:要写入的值
void LCD_WriteData(u16 data)
{
TFTLCD->LCD_DATA=data;
}
void LCD_WriteData_Color(u16 color)
{
TFTLCD->LCD_DATA=color;
}
四、TFTLCD的初始化
每个开发板使用的LCD显示屏是不同的,因此每个芯片的初始化序列可能有些不一样,我这里的LCD驱动芯片型号是ILI9481,下面是初始化代码这些如果想详细了解需要看数据手册,我这里直接将别人写好的移植过来直接使用,我主要是用,因此只要能工作就行。
void TFTLCD_Init(void)
{
TFTLCD_GPIO_Init();
TFTLCD_FSMC_Init();
delay_ms(10);
/*****************省略掉了50毫秒的延时**************/
LCD_WriteCmd(0xFF);
LCD_WriteCmd(0xFF);
/*****************省略掉了5毫秒的延时**************/
delay_ms(5);
LCD_WriteCmd(0xFF);
LCD_WriteCmd(0xFF);
LCD_WriteCmd(0xFF);
LCD_WriteCmd(0xFF);
/*****************省略掉了10毫秒的延时**************/
delay_ms(10);
LCD_WriteCmd(0xB0);
LCD_WriteData(0x00);
LCD_WriteCmd(0xB3);
LCD_WriteData(0x02);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteCmd(0xC0);
LCD_WriteData(0x13);
LCD_WriteData(0x3B);//480
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x01);
LCD_WriteData(0x00);//NW
LCD_WriteData(0x43);
LCD_WriteCmd(0xC1);
LCD_WriteData(0x08);
LCD_WriteData(0x1B);//CLOCK
LCD_WriteData(0x08);
LCD_WriteData(0x08);
LCD_WriteCmd(0xC4);
LCD_WriteData(0x11);
LCD_WriteData(0x01);
LCD_WriteData(0x73);
LCD_WriteData(0x01);
LCD_WriteCmd(0xC6);
LCD_WriteData(0x00);
LCD_WriteCmd(0xC8);
LCD_WriteData(0x0F);
LCD_WriteData(0x05);
LCD_WriteData(0x14);
LCD_WriteData(0x5C);
LCD_WriteData(0x03);
LCD_WriteData(0x07);
LCD_WriteData(0x07);
LCD_WriteData(0x10);
LCD_WriteData(0x00);
LCD_WriteData(0x23);
LCD_WriteData(0x10);
LCD_WriteData(0x07);
LCD_WriteData(0x07);
LCD_WriteData(0x53);
LCD_WriteData(0x0C);
LCD_WriteData(0x14);
LCD_WriteData(0x05);
LCD_WriteData(0x0F);
LCD_WriteData(0x23);
LCD_WriteData(0x00);
LCD_WriteCmd(0x35);
LCD_WriteData(0x00);
LCD_WriteCmd(0x44);
LCD_WriteData(0x00);
LCD_WriteData(0x01);
LCD_WriteCmd(0xD0);
LCD_WriteData(0x07);
LCD_WriteData(0x07);//VCI1
LCD_WriteData(0x1D);//VRH
LCD_WriteData(0x03);//BT
LCD_WriteCmd(0xD1);
LCD_WriteData(0x03);
LCD_WriteData(0x5B);//VCM
LCD_WriteData(0x10);//VDV
LCD_WriteCmd(0xD2);
LCD_WriteData(0x03);
LCD_WriteData(0x24);
LCD_WriteData(0x04);
LCD_WriteCmd(0x2A);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x01);
LCD_WriteData(0x3F);//320
LCD_WriteCmd(0x2B);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x01);
LCD_WriteData(0xDF);//480
LCD_WriteCmd(0x36);
LCD_WriteData(0x00);
LCD_WriteCmd(0xC0);
LCD_WriteData(0x13);
LCD_WriteCmd(0x3A);
LCD_WriteData(0x55);
LCD_WriteCmd(0x11);
/*****************省略掉了150毫秒的延时**************/
delay_ms(10);
LCD_WriteCmd(0x29);
/*****************省略掉了30毫秒的延时**************/
delay_ms(10);
LCD_WriteCmd(0x2C);
LCD_Display_Dir(TFTLCD_DIR); //0:竖屏 1:横屏 默认竖屏
LCD_Clear(WHITE);
}
//清屏函数
//color:要清屏的填充色
void LCD_Clear(u16 color)
{
uint16_t i, j ;
LCD_Set_Window(0, 0, tftlcd_data.width-1, tftlcd_data.height-1); //作用区域
for(i=0; i<tftlcd_data.width; i++)
{
for (j=0; j<tftlcd_data.height; j++)
{
LCD_WriteData_Color(color);
}
}
}
//设置LCD显示方向
//dir:0,竖屏;1,横屏
void LCD_Display_Dir(u8 dir)
{
if (dir==0) //默认竖屏方向
{
LCD_WriteCmd(0x36); //设置彩屏显示方向的寄存器
LCD_WriteData(0x00);
tftlcd_data.height=480;
tftlcd_data.width=320;
tftlcd_data.dir=0;
}
else
{
LCD_WriteCmd(0x36); //设置彩屏显示方向的寄存器
LCD_WriteData(0x60);
tftlcd_data.height=320;
tftlcd_data.width=480;
tftlcd_data.dir=1;
}
}
//设置窗口,并自动设置画点坐标到窗口左上角(sx,sy).
//sx,sy:窗口起始坐标(左上角)
//width,height:窗口宽度和高度,必须大于0!!
//窗体大小:width*height.
void LCD_Set_Window(u16 sx,u16 sy,u16 width,u16 height)
{
LCD_WriteCmd(0x2A);
LCD_WriteData(sx/256);
LCD_WriteData(sx%256);
LCD_WriteData(width/256);
LCD_WriteData(width%256);
LCD_WriteCmd(0x2B);
LCD_WriteData(sy/256);
LCD_WriteData(sy%256);
LCD_WriteData(height/256);
LCD_WriteData(height%256);
LCD_WriteCmd(0x2C);
}
//在指定区域内填充单个颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色
void LCD_Fill(u16 xState,u16 yState,u16 xEnd,u16 yEnd,u16 color)
{
uint16_t temp;
if((xState > xEnd) || (yState > yEnd))
{
return;
}
LCD_Set_Window(xState, yState, xEnd, yEnd);
xState = xEnd - xState + 1;
yState = yEnd - yState + 1;
while(xState--)
{
temp = yState;
while (temp--)
{
LCD_WriteData_Color(color);
}
}
}
//快速画点
//x,y:坐标
//color:颜色
void LCD_DrawFRONT_COLOR(u16 x,u16 y,u16 color)
{
LCD_Set_Window(x, y, x, y);
LCD_WriteData_Color(color);
}
作者:小A159