STM32F103 TFTLCD显示实验(二)

文章内容

在上一章的基础上,将讲解部分代码。

STM32F103 TFTLCD显示实验(一) https://blog.csdn.net/qq_40318498/article/details/97111069




相关代码

我们知道 TFTLCD 的 RS 接在 FSMC 的 A10 上面,CS 接在 FSMC_NE4 上,并且是 16 位数据总线。即我们使用的是 FSMC 存储器 1 的第 4 区。

//LCD地址结构体
typedef struct
{
	vu16 LCD_REG;
	vu16 LCD_RAM;
} LCD_TypeDef;
#define LCD_BASE        ((u32)(0x6C000000 | 0x000007FE))
#define LCD             ((LCD_TypeDef *) LCD_BASE)

bank1.sector4的地址就是从0X6C000000开始,而0x000007FE,则是A10的偏移量,将这个地址强制转换成LCD_TypeDef结构体地址,那么可以得到 LCD->LCD_REG 的地址就是 0X6C00,07FE,对应 A10 的状态为 0(即 RS=0),而 LCD-> LCD_RAM 的地址就是 0X6C00,0800(结构体地址自增),对应 A10 的状态为 1(即 RS=1)。



写寄存器值函数

//写寄存器函数
//regval:寄存器值
void LCD_WR_REG(u16 regval)
{   
	LCD->LCD_REG=regval;//写入要写的寄存器序号	 
	//FSMC自动控制片选信号等等,如果没有FSMC的话,比如,先拉低片选cs,在WR的上升沿写入数据等,比较麻烦。
	//这里指对应没有FSMC的,比如mini版的和OLED这种
}

写数据函数

//写LCD数据
//data:要写入的值
void LCD_WR_DATA(u16 data)
{	 
	LCD->LCD_RAM=data;		//跟写寄存器值函数类似	 
}


读数据函数

//读LCD数据
//返回值:读到的值
u16 LCD_RD_DATA(void)
{
	vu16 ram;			//防止被优化
	ram=LCD->LCD_RAM;	
	return ram;	 
	//return LCD->LCD_RAM
}


写寄存器内容函数

//写寄存器
//LCD_Reg:寄存器地址
//LCD_RegValue:要写入的数据
void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue)
{	
	LCD->LCD_REG = LCD_Reg;		//写入要写的寄存器序号	 
	LCD->LCD_RAM = LCD_RegValue;//写入数据	    		 
}


读寄存器内容函数

//读寄存器
//LCD_Reg:寄存器地址
//返回值:读到的数据
u16 LCD_ReadReg(u16 LCD_Reg)
{										   
	LCD_WR_REG(LCD_Reg);		//写入要读的寄存器序号
	delay_us(5);		  
	return LCD_RD_DATA();		//返回读到的值
}   


开始写GRAM函数

//开始写GRAM
void LCD_WriteRAM_Prepare(void)
{
 	LCD->LCD_REG=lcddev.wramcmd;	  
}	


写GRAM函数

//LCD写GRAM
//RGB_Code:颜色值
void LCD_WriteRAM(u16 RGB_Code)
{							    
	LCD->LCD_RAM = RGB_Code;//写十六位GRAM
}

上面7个函数是比较简单也比较基础的,下面介绍一下坐标设置函数


坐标设置函数

//LCD重要参数集
typedef struct  
{										    
	u16 width;			//LCD 宽度
	u16 height;			//LCD 高度
	u16 id;				//LCD ID
	u8  dir;			//横屏还是竖屏控制:0,竖屏;1,横屏。	
	u16	wramcmd;		//开始写gram指令
	u16  setxcmd;		//设置x坐标指令
	u16  setycmd;		//设置y坐标指令 
}_lcd_dev; 	  
//管理LCD重要参数
//默认为竖屏
_lcd_dev lcddev;
//设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
lcddev.dir=0;	//竖屏
lcddev.width=240;
lcddev.height=320;
lcddev.wramcmd=0X2C;	//写GRAM,写入函数数据
lcddev.setxcmd=0X2A;	//列地址
lcddev.setycmd=0X2B;  	//页地址
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{	
	LCD_WR_REG(lcddev.setxcmd); 	//向寄存器写入指令
	LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF); 	
	//因为坐标是16为的,先传高8位,后传低8位		 
	LCD_WR_REG(lcddev.setycmd); 
	LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);
}


画点函数

//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
	LCD_SetCursor(x,y);		//设置光标位置,这不是窗口,而是一个点
	LCD_WriteRAM_Prepare();	//开始写入GRAM
	LCD->LCD_RAM=POINT_COLOR; 
}
//快速画点
//x,y:坐标
//color:颜色
void LCD_Fast_DrawPoint(u16 x,u16 y,u16 color){
	LCD_WR_REG(lcddev.setxcmd); 	//写控制x命令.
	LCD_WR_DATA(x>>8);LCD_WR_DATA(x&0XFF); 	//写入数据		 
	LCD_WR_REG(lcddev.setycmd); 		//写入y命令
	LCD_WR_DATA(y>>8);LCD_WR_DATA(y&0XFF); 	
	LCD->LCD_REG=lcddev.wramcmd; 		//写GRAM
	LCD->LCD_RAM=color;	//写颜色
	//相对于上面,应该省去了调用函数的时间,本质是一样的。
}

该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。其中 POINT_COLOR 是我们 定义的一个全局变量,用于存放画笔颜色。



画直线矩形

//画线
//x1,y1:起点坐标
//x2,y2:终点坐标  
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2)
{
	//比如x1=10,y1=10,x2=10,y2=20,也就是竖直线,dalta_x=0,delta_y=10.
	//incx=0,incy=1,distance=delta_y(这么多的距离,也就是点)
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,distance; 
	int incx,incy,uRow,uCol; 
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1; 
	uRow=x1; 
	uCol=y1; 
	if(delta_x>0)incx=1; //设置单步方向 
	else if(delta_x==0)incx=0;//垂直线 
	else {incx=-1;delta_x=-delta_x;} 
	
	if(delta_y>0)incy=1; 
	else if(delta_y==0)incy=0;//水平线 
	else{incy=-1;delta_y=-delta_y;} 
	
	if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y; 
	for(t=0;t<=distance+1;t++ )//画线输出 
	{  
		LCD_DrawPoint(uRow,uCol);//画点 
		xerr+=delta_x ; 	//xerr +=delta_x=0
		yerr+=delta_y ; 	//yerr+=delta_y=10,个人没发现xerr的用处。
		//yerr + = 10=10,那么yerr>distance就不会成立,因此,t=0跟t=1,重复画一个点。
		//t=1时,yerr+=10=20,那么yerr-=distance=20-10=10,uCol+=incy=下一个点,所以后面是正常的。
		//也就是这里+1的原因吧。
		if(xerr>distance) 
		{ 
			xerr-=distance; 
			uRow+=incx; 
		} 
		if(yerr>distance) 
		{ 
			yerr-=distance; 
			uCol+=incy; 
		} 
	}  
}    
//画矩形	  
//(x1,y1),(x2,y2):矩形的对角坐标
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2)
{
	LCD_DrawLine(x1,y1,x2,y1);
	LCD_DrawLine(x1,y1,x1,y2);
	LCD_DrawLine(x1,y2,x2,y2);
	LCD_DrawLine(x2,y1,x2,y2);
}

有了画点,当然还需要有读点的函数,那就还有读点函数,用于读取 LCD 的 GRAM,这里说明一下,为什么 OLED 模块没做读 GRAM 的函数,而这里做了。因为 OLED 模块是单色的,所需要全部 GRAM 也就 1K 个字节,而 TFTLCD 模块为彩色的,点数也比 OLED 模块多很多,以 16 位色计算,一款 320×240 的液晶,需要 320×240×2 个字节来存储颜色值, 也就是也需要 150K 字节,这对任何一款单片机来说,都不是一个小数目了。而且我们在图形 叠加的时候,可以先读回原来的值,然后写入新的值,在完成叠加后,我们又恢复原来的值。 这样在做一些简单菜单的时候,是很有用的。这里我们读取 TFTLCD 模块数据的函数为 LCD_ReadPoint,该函数直接返回读到的 GRAM 值。该函数使用之前要先设置读取的 GRAM 地址,通过 LCD_SetCursor 函数来实现。LCD_ReadPoint 的代码如下:


读点函数

u16 LCD_ReadPoint(u16 x,u16 y)
{
	u16 r=0,g=0,b=0;
	if(x>=lcddev.width||y>=lcddev.height)return 0;	//超过了范围,直接返回		   
	LCD_SetCursor(x,y);	  							//设置点的坐标
	LCD_WR_REG(0X2E);								//9341/6804/3510/1963 发送读GRAM指令
	r=LCD_RD_DATA();								//dummy Read,假读,参考显示实验一的指令0x2e
	opt_delay(2);	  	
 	r=LCD_RD_DATA();  		  						//实际坐标颜色 
 	opt_delay(2);	  
	b=LCD_RD_DATA(); 
	g=r&0XFF;										//对于9341/5310/5510,第一次读取的是RG的值,R在前,G在后,各占8位
	g<<=8; 
	return (((r>>11)<<11)|((g>>10)<<5)|(b>>11));	//ILI9341/NT35310/NT35510需要公式转换一下
	//r是高5位,这里右移11位,也就是将低11位全部置0,不是相当于没移。
	//g=r&0xFF,也就是相当于取低8位,只有高6位是颜色,若r = 1111 1000 1111 1100,g = r&0XFF=0000 0000 1111 1100
	//g<<=8 = 1111 1100 0000 0000>>10 = 0000 0000 0011 1111<<5 = 0000 0011 1110 0000
	//b占据了16位中的高5位,右移11位,相当于让b占据了16位中的低5位。
	//这样,将读取到的数据拼凑成16位的数据,RGB565的形式,读者可以尝试一下:
	//r = 1111 1000 1111 1100
	//b = 1111 1000 0000 0000
	//应该可以改写成 (r&F800)|(g<<3) | (b>>1);

下面要介绍的是字符显示函数 LCD_ShowChar,该函数同前面 OLED 模块的字符显示 函数差不多,但是这里的字符显示函数多了 1 个功能,就是可以以叠加方式显示,或者以非叠 加方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显 示。(比如一张图片,叠加方式可以在背景上显示字符,而非叠加会覆盖掉原背景)。



显示字符函数

//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{  							  
    u8 temp,t1,t;
	u16 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数	
 	num=num-' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
	for(t=0;t<csize;t++)
	{   
		if(size==12)temp=asc2_1206[num][t]; 	 	//调用1206字体
		else if(size==16)temp=asc2_1608[num][t];	//调用1608字体
		else if(size==24)temp=asc2_2412[num][t];	//调用2412字体
		else return;								//没有的字库
		for(t1=0;t1<8;t1++)
		{			    
			if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
			else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
			temp<<=1;
			y++;
			if(y>=lcddev.height)return;		//超区域了
			if((y-y0)==size)
			{
				y=y0;
				x++;
				if(x>=lcddev.width)return;	//超区域了
				break;
			}
		}  	 
	}  	    	   	 	  
} 


初始化函数

	GPIO_InitTypeDef GPIO_InitStructure;
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
	FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; 
	FSMC_NORSRAMTimingInitTypeDef  writeTiming;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);	//使能FSMC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOG,ENABLE);//使能PORTB,D,E,G以及AFIO复用功能时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				 //PB0 推挽输出 背光
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//PORTD复用推挽输出  
	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;				 //	//PORTD复用推挽输出  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD, &GPIO_InitStructure); 
	
	//PORTE复用推挽输出  
	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;				 //	//PORTD复用推挽输出  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOE, &GPIO_InitStructure);    	    	 											 
	
	//	//PORTG12复用推挽输出 A0	,LCD_CS ->PG12,LCD_RS->PG0
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_12;	 //	//PORTD复用推挽输出  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOG, &GPIO_InitStructure); 
	
	readWriteTiming.FSMC_AddressSetupTime = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
	readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
	readWriteTiming.FSMC_DataSetupTime = 0x0f;		 // 数据保存时间为16个HCLK,因为液晶驱动IC的读数据的时候,速度不能太快,尤其对1289这个IC。
	readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
	readWriteTiming.FSMC_CLKDivision = 0x00;
	readWriteTiming.FSMC_DataLatency = 0x00;
	readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 
	
	
	writeTiming.FSMC_AddressSetupTime = 0x00;	 //地址建立时间(ADDSET)为1个HCLK  
	writeTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(A		
	writeTiming.FSMC_DataSetupTime = 0x03;		 ////数据保存时间为4个HCLK	
	writeTiming.FSMC_BusTurnAroundDuration = 0x00;
	writeTiming.FSMC_CLKDivision = 0x00;
	writeTiming.FSMC_DataLatency = 0x00;
	writeTiming.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 = &readWriteTiming; //读写时序
	FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &writeTiming;  //写时序
	
	FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置
	
	FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  // 使能BANK1 
	
	
	delay_ms(50); 					// delay 50 ms 
	lcddev.id=LCD_ReadReg(0x0000);	//读ID(9320/9325/9328/4531/4535等IC)   
	LCD_WR_REG(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 
	//下面的初始化函数,由厂家提供,代码比较长,这里就不一一贴出。	

在这里插入图片描述
在这里插入图片描述
由此,相应的IO口初始化也就不难理解了。


main.c

//画笔颜色
#define WHITE         	 0xFFFF
#define BLACK         	 0x0000	  
#define BLUE         	 0x001F  
#define BRED             0XF81F
#define GRED 			 0XFFE0
#define GBLUE			 0X07FF
#define RED           	 0xF800
#define MAGENTA       	 0xF81F
#define GREEN         	 0x07E0
#define CYAN          	 0x7FFF
#define YELLOW        	 0xFFE0
#define BROWN 			 0XBC40 //棕色
#define BRRED 			 0XFC07 //棕红色
#define GRAY  			 0X8430 //灰色
int main(void)
{	 
	u8 x=0;
	u8 lcd_id[12];			//存放LCD ID字符串
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
	LED_Init();			     //LED端口初始化
	LCD_Init();
	POINT_COLOR=RED;
	sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将LCD ID打印到lcd_id数组。 
	while(1) 
	{		 
	switch(x)
	{
		case 0:LCD_Clear(WHITE);break;
		case 1:LCD_Clear(BLACK);break;
		case 2:LCD_Clear(BLUE);break;
		case 3:LCD_Clear(RED);break;
		case 4:LCD_Clear(MAGENTA);break;
		case 5:LCD_Clear(GREEN);break;
		case 6:LCD_Clear(CYAN);break; 
		case 7:LCD_Clear(YELLOW);break;
		case 8:LCD_Clear(BRRED);break;
		case 9:LCD_Clear(GRAY);break;
		case 10:LCD_Clear(LGRAY);break;
		case 11:LCD_Clear(BROWN);break;
	}
	POINT_COLOR=RED;	  
	LCD_ShowString(30,40,210,24,24,"Elite STM32F1 ^_^"); 
	LCD_ShowString(30,70,200,16,16,"TFTLCD TEST");
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,lcd_id);		//显示LCD ID	      					 
	LCD_ShowString(30,130,200,12,12,"2015/1/14");	      					 
	x++;
	if(x==12)x=0;
	LED0=!LED0;				   		 
	delay_ms(1000);	
	} 
}
void LCD_Clear(u16 color)
{
	u32 index=0;      
	u32 totalpoint=lcddev.width;
	totalpoint*=lcddev.height; 			//得到总点数
	LCD_SetCursor(0x00,0x0000);	//设置光标位置 
	LCD_WriteRAM_Prepare();     		//开始写入GRAM	 	  
	for(index=0;index<totalpoint;index++)
	{
		LCD->LCD_RAM=color;	
	}
}  


实验结果

在这里插入图片描述


总结

一开始看TFTLCD没看懂,在看了3遍文档和视频的讲解,终于有了大概的认识。也许在多做几次这样的实验,才能加深我对TFTLCD和FSMC的认知。


猜你喜欢

转载自blog.csdn.net/qq_40318498/article/details/97126489