STM32 FSMC 驱动16脚12864lcd液晶显示屏

写在前面

在学习了两天STM32 FSMC后,总算是拿这个东西做了第一个应用,关于FSMC是什么东西怎么用,CSDN中有很多介绍,但是,估计新手刚看都是一头雾水(我就是),不过,你仍必须反反复复地看,看了很多很多次后,你最终就能理解它。视频方面,不推荐看正点原子的FSMC的视频,而野火的FSMC讲得很好,推荐看用FSMC读写SRAM那一节。
请记住,如果你完全不懂FSMC,那么你高概率看不懂后面的代码。

FSMC驱动12864lcd简介

12864lcd液晶屏

这个液晶屏是16脚的,看到那么多引脚,可以猜测到这东西大概率是并口通讯,液晶屏的图片和资料见这篇https://blog.csdn.net/kabuto_hui/article/details/46911055

FSMC驱动原理

简单来说就是利用FSMC控制SRAM的时序和并口控制12864液晶屏类似这个特性。关于RS引脚可用地址线A0(对应STM32F103的PF0引脚)模拟。当你定义完一个0x60000000的指针p1,如果你向p1所指向的单元中写入一个数据,此时,A0引脚会拉低,代表写命令;如果你定义完一个0x60000001的指针p2,如果你向p2所指向的单元中写入一个数据,此时,A0引脚会拉高,代表写数据。

引脚对应关系

液晶屏引脚——FSMC引脚——IO口引脚
RS——A0——PF0
RD——FSMC_NOE——PD4
WR——FSMC_NWE——PD5
D0——FSMC_D0——PD14
D1——FSMC_D1——PD15
D2——FSMC_D2——PD0
D3——FSMC_D3——PD1
D4——FSMC_D4——PE7
D5——FSMC_D5——PE8
D6——FSMC_D6——PE9
D7——FSMC_D7——PE10
CS——FSMC_NE1——PD7
RESET——PG9

代码

包含的一些delay.h、usart.h、sys.h头文件是正点原子的(事实上只用了sys.h),lcd初始化设置及测试代码用的是普中科技的代码(主要是懒),另外,我的C语言写得也很烂,有问题就别吐槽了。

文件LCD12864.c

#include "stm32f10x_gpio.h"
#include "stm32f10x_fsmc.h"
#include "stm32f10x_rcc.h"
#include "LCD12864.h"
#include "charcode.h"

/*******************************************************
* 函 数 名:void lcd_gpio_init(void)
* 功能描述:初始化lcd12864需要使用io口,引脚对应关系(lcd——FSMC功能引脚——GPIO口)
*			RS——A0——PF0			RD——FSMC_NOE——PD4
*			WR——FSMC_NWE——PD5	D0——FSMC_D0——PD14
*			D1——FSMC_D1——PD15	D2——FSMC_D2——PD0
*			D3——FSMC_D3——PD1		D4——FSMC_D4——PE7
*			D5——FSMC_D5——PE8		D6——FSMC_D6——PE9
*			D7——FSMC_D7——PE10	CS——FSMC_NE1——PD7
*			RESET——PG9
* 调用函数:无
* 全局变量:无
* 输入参数:无
* 返 回 值:无
* 设 计 者:sddfsAv          日期:24/07/2019
*******************************************************/
void lcd_gpio_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/*GPIOF、D、E、G时钟使能*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOG, ENABLE);
	
	/*RS推挽复用输出*/
	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);
	
	/*RD、WR、D0、D1、D2、D3、CS推挽复用输出*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_14|GPIO_Pin_15|
								  GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_7;
	GPIO_Init(GPIOD, &GPIO_InitStructure);
	
	/*D4、D5、D6、D7推挽复用输出*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;
	GPIO_Init(GPIOE, &GPIO_InitStructure);
	
	/*RESET推挽输出*/
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_Init(GPIOG, &GPIO_InitStructure);	
	
}

/*******************************************************
* 函 数 名:void lcd_fsmc_init(void)
* 功能描述:用fsmc初始化配置
* 调用函数:无
* 全局变量:无
* 输入参数:无
* 返 回 值:无
* 设 计 者:sddfsAv          日期:24/07/2019
*******************************************************/
void lcd_fsmc_init(void)
{
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
	FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; 
	FSMC_NORSRAMTimingInitTypeDef  writeTiming;
	
	/*FSMC时钟使能*/
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);
	
	/*读时序设置*/
	readWriteTiming.FSMC_AddressSetupTime = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
	readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
	readWriteTiming.FSMC_DataSetupTime = 0x0f;		 //数据保存时间为16个HCLK
	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 = 0x06;		 //数据保存时间为6个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_NORSRAM1;//使用NE1
	FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; //不复用数据地址
	FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM;  //SRAM   
	FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b;//存储器数据宽度为8bit   
	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_NORSRAM1, ENABLE);  // 使能BANK1 
}

/*******************************************************
* 函 数 名:void LcdSt7565_WriteCmd(void)
* 功能描述:写一个命令到12864
* 调用函数:无
* 全局变量:无
* 输入参数:u8 cmd, 8位命令
* 返 回 值:无
* 设 计 者:sddfsAv          日期:25/07/2019
*******************************************************/
void LcdSt7565_WriteCmd(u8 cmd)
{
	WRITECOMMAND=cmd;
}

/*******************************************************
* 函 数 名:void LcdSt7565_WriteCmd(void)
* 功能描述:写一个数据到12864
* 调用函数:无
* 全局变量:无
* 输入参数:u8 dat, 8位命令
* 返 回 值:无
* 设 计 者:sddfsAv          日期:25/07/2019
*******************************************************/
void LcdSt7565_WriteData(u8 dat)
{
	WRITEDATA=dat;
}

/*******************************************************
* 函 数 名:void Lcd12864_Init(void)
* 功能描述:写一个数据到12864
* 调用函数:lcd_gpio_init();lcd_fsmc_init()
* 全局变量:无
* 输入参数:无
* 返 回 值:无
* 设 计 者:sddfsAv          日期:25/07/2019
*******************************************************/
void Lcd12864_Init(void)
{
	u8 i;
	lcd_gpio_init();
	lcd_fsmc_init();
	
	LCD12864_RSET = 0;
	for (i=0; i<100; i++);
	LCD12864_CS = 0;
	LCD12864_RSET = 1;

	//----------------Star Initial Sequence-------//
	//------程序初始化设置,具体命令可以看文件夹下---//

	//--软件初始化--//
	LcdSt7565_WriteCmd(0xE2);  //reset
	for (i=0; i<100; i++);           //延时一下

	//--表格第8个命令,0xA0段(左右)方向选择正常方向(0xA1为反方向)--//
	LcdSt7565_WriteCmd(0xA0);  //ADC select segment direction 

	//--表格第15个命令,0xC8普通(上下)方向选择选择反向,0xC0为正常方向--// 
	LcdSt7565_WriteCmd(0xC8);  //Common direction 
					  
	//--表格第9个命令,0xA6为设置字体为黑色,背景为白色---//
	//--0xA7为设置字体为白色,背景为黑色---//
	LcdSt7565_WriteCmd(0xA7);  //reverse display

	//--表格第10个命令,0xA4像素正常显示,0xA5像素全开--//
	LcdSt7565_WriteCmd(0xA4);  //normal display

	//--表格第11个命令,0xA3偏压为1/7,0xA2偏压为1/9--//
	LcdSt7565_WriteCmd(0xA2);  //bias set 1/9

	//--表格第19个命令,这个是个双字节的命令,0xF800选择增压为4X;--//
	//--0xF801,选择增压为5X,其实效果差不多--//        
	LcdSt7565_WriteCmd(0xF8);  //Boost ratio set
	LcdSt7565_WriteCmd(0x01);  //x4

	//--表格第18个命令,这个是个双字节命令,高字节为0X81,低字节可以--//
	//--选择从0x00到0X3F。用来设置背景光对比度。---/
	LcdSt7565_WriteCmd(0x81);  //V0 a set
	LcdSt7565_WriteCmd(0x23);

	//--表格第17个命令,选择调节电阻率--//
	LcdSt7565_WriteCmd(0x25);  //Ra/Rb set

	//--表格第16个命令,电源设置。--//
	LcdSt7565_WriteCmd(0x2F);
	for (i=0; i<100; i++);

	//--表格第2个命令,设置显示开始位置--//
	LcdSt7565_WriteCmd(0x40);  //start line

	//--表格第1个命令,开启显示--//
	LcdSt7565_WriteCmd(0xAF);  // display on
	for (i=0; i<100; i++);

}

/*******************************************************************************
* 函 数 名         : LCD12864_ClearScreen
* 函数功能                   : 清屏12864
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void Lcd12864_ClearScreen(void)
{
	u8 i, j;

	for(i=0; i<8; i++)
	{
		//--表格第3个命令,设置Y的坐标--//
		//--Y轴有64个,一个坐标8位,也就是有8个坐标--//
		//所以一般我们使用的也就是从0xB0到0x07,就够了--//        
		LcdSt7565_WriteCmd(0xB0+i); 

		//--表格第4个命令,设置X坐标--//
		//--当你的段初始化为0xA1时,X坐标从0x10,0x04到0x18,0x04,一共128位--//
		//--当你的段初始化为0xA0时,X坐标从0x10,0x00到0x18,0x00,一共128位--//
		//--在写入数据之后X坐标的坐标是会自动加1的,我们初始化使用0xA0所以--//
		//--我们的X坐标从0x10,0x00开始---//
		LcdSt7565_WriteCmd(0x10); 
		LcdSt7565_WriteCmd(0x04);                                                           
		
		//--X轴有128位,就一共刷128次,X坐标会自动加1,所以我们不用再设置坐标--//
		for(j=0; j<128; j++)
		{
			LcdSt7565_WriteData(0x00);  //如果设置背景为白色时,清屏选择0XFF
		}
	}
}

/*******************************************************************************
* 函 数 名       : LCD12864_Write16CnCHAR
* 函数功能		   : 在12864上面书写16X16的汉字
* 输    入       : Page, Column, cn
*******************************************************************************/
u8 Lcd12864_Write16CnCHAR(u8 Page, u8 Column, u8 *cn)
{
	u8 j, x1, x2, wordNum;
	
	if(Page > 7)  return 0;      //页坐标只能从0到7,大于则直接返回
	if(Column > 128) 	return 0;  //列的坐标只能从0到127,大于则直接返回
	
  Page += 0xB0;	             //求取页坐标的值 不能放在while里面

	while ( *cn != '\0')	     //在C语言中字符串结束以‘\0’结尾
	{	
		LcdSt7565_WriteCmd(Page);    //设置页坐标

		x1 = (Column >> 4)& 0x0F;       //先取出高4位  注释1
		x2 = Column & 0x0F;             //取低四位 
		LcdSt7565_WriteCmd(0x10 + x1);  //设置列坐标 高4
		LcdSt7565_WriteCmd(0x00 + x2);	//           低4

		for (wordNum=0; wordNum<50; wordNum++)
		{
		    //--查询要写的字在字库中的位置--//
			if ((CN16CHAR[wordNum].Index[0] == *cn) && (CN16CHAR[wordNum].Index[1] == *(cn+1)))
			{
				for (j=0; j<32; j++) //写一个字
				{		
					if (j == 16) //16X16用到两个页坐标,当大于等于16时,切换页坐标
					{
			   		LcdSt7565_WriteCmd(Page + 1);    //设置页坐标			
						LcdSt7565_WriteCmd(0x10 + x1);   //高4位 设置列坐标
						LcdSt7565_WriteCmd(0x00 + x2);	 //低4位
					}
					LcdSt7565_WriteData(CN16CHAR[wordNum].Msk[j]);
				}
				Column += 16; //下一个字的新的列地址
				break;  //我们理解:一旦字库中找到字符,余下字库不用再找
			}  
		} 	
		cn += 2;
	}	
	return 1;
}

LCD12864.h

#ifndef __LCD12864_H
#define __LCD12864_H

#include "stm32f10x.h"
#include "sys.h"

//---包含字库头文件
#define CHAR_CODE

#define WRITECOMMAND *((volatile u8 *)0x60000000)	//在A0~A15全部低电平,代表地址0x0000
#define WRITEDATA *((volatile u8 *)0x60000001)	//仅在A0上为高电平,代表地址0x0001
#define LCD12864_CS PDout(7)
#define LCD12864_RSET PGout(9)

void lcd_gpio_init(void);
void lcd_fsmc_init(void);
void Lcd12864_Init(void);
void Lcd12864_ClearScreen(void);
void LcdSt7565_WriteData(u8 dat);
void LcdSt7565_WriteCmd(u8 cmd);
u8 Lcd12864_Write16CnCHAR(u8 Page, u8 Column, u8 *cn);
#endif

charcode.h

#ifndef __CHARCODE_H
#define __CHARCODE_H

// ------------------  汉字字模的数据结构定义 ------------------------ //
struct Cn16CharTypeDef                  // 汉字字模数据结构 
{
	unsigned char  Index[2];      // 汉字内码索引,一个汉字占两个字节	
	unsigned char  Msk[32];       // 点阵码数据(16*16有32个数据) 
};

struct Cn16CharTypeDef CN16CHAR[]=
{

/*--  文字:  液  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"液",0x10,0x60,0x02,0x8C,0x00,0x84,0xE4,0x1C,0x05,0xC6,0xBC,0x24,0x24,0xE4,0x04,0x00,
0x04,0x04,0x7E,0x01,0x00,0x00,0xFF,0x82,0x41,0x26,0x18,0x29,0x46,0x81,0x80,0x00,

/*--  文字:  晶  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"晶",0x00,0x00,0x00,0x00,0x7F,0x49,0x49,0x49,0x49,0x49,0x7F,0x00,0x00,0x00,0x00,0x00,
0x00,0xFF,0x49,0x49,0x49,0x49,0xFF,0x00,0xFF,0x49,0x49,0x49,0x49,0xFF,0x00,0x00,

/*--  文字:  显  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"显",0x00,0x00,0x00,0xFE,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0xFE,0x00,0x00,0x00,0x00,
0x40,0x42,0x44,0x58,0x40,0x7F,0x40,0x40,0x40,0x7F,0x40,0x50,0x48,0x46,0x40,0x00,

/*--  文字:  示  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"示",0x40,0x40,0x42,0x42,0x42,0x42,0x42,0xC2,0x42,0x42,0x42,0x42,0x42,0x40,0x40,0x00,
0x20,0x10,0x08,0x06,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x02,0x04,0x08,0x30,0x00,

/*--  文字:  文  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"文",0x08,0x08,0x08,0x38,0xC8,0x08,0x09,0x0E,0x08,0x08,0xC8,0x38,0x08,0x08,0x08,0x00,
0x80,0x80,0x40,0x40,0x20,0x11,0x0A,0x04,0x0A,0x11,0x20,0x40,0x40,0x80,0x80,0x00,

/*--  文字:  字  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"字",0x10,0x0C,0x04,0x24,0x24,0x24,0x25,0x26,0xA4,0x64,0x24,0x04,0x04,0x14,0x0C,0x00,
0x02,0x02,0x02,0x02,0x02,0x42,0x82,0x7F,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x00,

/*--  文字:  测  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"测",0x10,0x60,0x02,0x8C,0x00,0xFE,0x02,0xF2,0x02,0xFE,0x00,0xF8,0x00,0xFF,0x00,0x00,
0x04,0x04,0x7E,0x01,0x80,0x47,0x30,0x0F,0x10,0x27,0x00,0x47,0x80,0x7F,0x00,0x00,

/*--  文字:  试 --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
"试",0x40,0x40,0x42,0xCC,0x00,0x90,0x90,0x90,0x90,0x90,0xFF,0x10,0x11,0x16,0x10,0x00,
0x00,0x00,0x00,0x3F,0x10,0x28,0x60,0x3F,0x10,0x10,0x01,0x0E,0x30,0x40,0xF0,0x00,

};
#endif

main.c

#include "delay.h"
#include "usart.h"
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "LCD12864.h"

int main(void)
{
	delay_init();
	Lcd12864_Init();
	Lcd12864_ClearScreen();
	while(1)
	{
		//页地址  列地址   显示文字
		Lcd12864_Write16CnCHAR(0, 0, "液晶显示文字测试");
	}
}
/******************* (C) COPYRIGHT 2019 sddfsAv*****文件结束****/

结果

STM32 FSMC 驱动16脚12864lcd液晶显示屏_第1张图片
看上去显示不是很完美,不过至少说明FSMC基本正常工作。
好吧,个人觉得浪费10几个IO口来驱动这样一个LCD完全不划算。

你可能感兴趣的:(嵌入式STM32,STM32,12864,FSMC,LCD)