STM32第十课(FSMC,LCD,HAL)

STM32F407 或 STM32F417 系列芯片都带有 FSMC 接口,能够与同步或异步存储器和 16 位 PC 存储器卡连接,包括 SRAM、 NAND FLASH、 NOR FLASH 和 PSRAM 等存储器。

STM32F4 的 FSMC 将外部设备分为 2 类: NOR/PSRAM 设备、NAND/PC 卡设备。
共用地址数据总线等信号,他们具有不同的 CS 以区分不同的设备,
TFTLCD 就是用的 FSMC_NE4 做片选,其实就是将 TFTLCD 当成 SRAM 来控制。

为什么可以把 TFTLCD 当成 SRAM 设备用:
外部 SRAM 的控制一般有:地址线(如 A0~A18)、数据线(如 D0~D15)、写信号(WE)、
读信号(OE)、片选信号(CS),如果 SRAM 支持字节控制,那么还有 UB/LB 信号。

TFTLCD的信号包括: RS、 D0-D15、 WR、 RD、 CS、 RST 和 BL 等,其中真正在操作 LCD 的时候需要用到的就只有: RS、 D0-D15、 WR、 RD 和 CS。其操作时序和 SRAM的控制完全类似,唯一不同就是 TFTLCD 有 RS 信号,但是没有地址信号。

RS 信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如我们把 RS 接在 A6上面,那么当 FSMC 控制器写地址 0x00-0x3F 的时候,A6 为 0,对 TFTLCD 来说,就是写命令。而 FSMC 写地址线0x40-0x7F的时候, A6 将会变为 1,对 TFTLCD 来说,就是写数据。

FSMC 支持 8/16/32 位数据宽度,我们这里用到的 LCD 是 16 位宽度的,所以在设置的时候,选择 16 位宽就 OK 了。

FSMC 将外部存储器划分为固定大小为 256M 字节的四个存储块,FSMC 总共管理 1GB 空间,拥有 4 个存储块(Bank),
当STM32的内核程序,读写的地址位于某个Bank之中的时候,会自动生成对应的合适的接口时序信号。
例如,
读写地址位于bank1,FSMC会生成PSRAM或者NORFLASH的接口时序信号。
读写地址位于bank2,FSMC会生成NANDFLASH的接口时序信号。
读写地址位于bank3,FSMC会生成NANDFLASH的接口时序信号。
读写地址位于bank4,FSMC会生成NANDFLASH的接口时序信号。

Bank1,对应地址区间是0X6000_0000–0x6FFF_FFFF,
Bank2,对应地址区间是0X7000_0000–0x7FFF_FFFF,
Bank3,对应地址区间是0X8000_0000–0x8FFF_FFFF,
Bank4,对应地址区间是0X9000_0000–0x9FFF_FFFF,

每个bank被分为4个region,每个sector对应一个片选。例如,对于bank1,
Bank1_1,对应输出的片选是FSMC_NE1,地址区间是0X6000_0000–0x63FF_FFFF,
Bank1_2,对应输出的片选是FSMC_NE2,地址区间是0X6400_0000–0x67FF_FFFF,
Bank1_3,对应输出的片选是FSMC_NE3,地址区间是0X6800_0000–0x6BFF_FFFF,
Bank1_4,对应输出的片选是FSMC_NE4,地址区间是0X6C00_0000–0x6FFF_FFFF,

ARM内核是字节寻址的,所以,
当 Bank1 接的是 16 位宽度存储器的时候: HADDR[25:1]→ FSMC_A[24:0]。
当 Bank1 接的是 8 位宽度存储器的时候: HADDR[25:0]→ FSMC_A[25:0]。

++++++++++++++++++++++++++++++++++++++++++++++++++
初始化 FSMC 主要是初始化三个寄存器 FSMC_BCRx, FSMC_BTRx,FSMC_BWTRx, 在 HAL 库中提供了 FSMC 初始化函数为

HAL_StatusTypeDef HAL_SRAM_Init(SRAM_HandleTypeDef *hsram,
								FMC_NORSRAM_TimingTypeDef *Timing, 
								FMC_NORSRAM_TimingTypeDef *ExtTiming)

SRAM_HandleTypeDef 类 型 指 针 变 量,这是SRAM的句柄。
FMC_NORSRAM_TimingTypeDef 类型指针变量,是时序描述符的句柄。

typedef struct
{
	uint32_t AddressSetupTime;
	uint32_t AddressHoldTime;
	uint32_t DataSetupTime;
	uint32_t BusTurnAroundDuration;
	uint32_t CLKDivision;
	uint32_t DataLatency;
	uint32_t AccessMode;
}FSMC_NORSRAM_TimingTypeDef;
 

+++++++++++++++++++++++++++++++++++++++++++++++
TFTLCD 的 RS 接在 FSMC的 A6 上面, CS 接在 FSMC_NE4 上,并且是 16 位数据总线。即我们使用的是 FSMC 的bank 1的sector4,即bank1_4.

我们采用Memory Map的方式,对LCD的读写区域进行访问。
所以需要定义LCD_BASE,

#define LCD_BASE ((u32)(0x6C00007E))

为了方便对LCD的数据读写进行管理,我们定义了一个结构体

typedef struct
{
	volatile uint16_t LCD_REG;
	volatile uint16_t LCD_RAM;
} LCD_TypeDef;

将REG读写单元和SRAM读写单元整合在一起。

为了方便引用这个MMRegion,
我们将LCD_BASE强转为LCD_TypeDef类型的指针。

#define LCD  ((LCD_TypeDef *) LCD_BASE)

当我们要往 LCD 写命令/数据的时候,可以这样写:

LCD->LCD_REG=cmd; //写命令
LCD->LCD_RAM=data; //写数据

cmd = LCD->LCD_REG;//读 LCD 寄存器
data = LCD->LCD_RAM;//读 LCD 数据

在MM的工作方式下,CS、 WR、 RD 和 IO 口方向都是由 FSMC 控制。

这样,就可以封装最基本的读写函数了,

void LCD_WR_REG(vu16 regval)
{ 
	regval=regval; //使用-O2 优化的时候,必须插入的延时
	LCD->LCD_REG=regval;//写入要写的寄存器序号
}
//写 LCD 数据
//data:要写入的值
void LCD_WR_DATA(vu16 data)
{ 
	data=data; //使用-O2 优化的时候,必须插入的延时
	LCD->LCD_RAM=data;
}
//读 LCD 数据
//返回值:读到的值
u16 LCD_RD_DATA(void)
{ 
	vu16 ram; //防止被优化
	ram=LCD->LCD_RAM;
	return ram;
}
//写寄存器
//LCD_Reg:寄存器地址
//LCD_RegValue:要写入的数据
void LCD_WriteReg(vu16 LCD_Reg, vu16 LCD_RegValue)
{ 
	LCD->LCD_REG = LCD_Reg; //写入要写的寄存器序号
	LCD->LCD_RAM = LCD_RegValue; //写入数据
}
//读寄存器
//LCD_Reg:寄存器地址
//返回值:读到的数据
u16 LCD_ReadReg(vu16 LCD_Reg)
{ 
	LCD_WR_REG(LCD_Reg); //写入要读的寄存器序号
	delay_us(5);
	return LCD_RD_DATA(); //返回读到的值
}
//开始写 GRAM
void LCD_WriteRAM_Prepare(void)
{ 
	LCD->LCD_REG=lcddev.wramcmd;
}
//LCD 写 GRAM
//RGB_Code:颜色值
void LCD_WriteRAM(u16 RGB_Code)
{ 
	LCD->LCD_RAM = RGB_Code;//写十六位 GRAM
}

++++++++++++++++++++++++++++++++++++++
为了对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 参数
extern _lcd_dev lcddev; //管理 LCD 重要参数

+++++++++++++++++++++++++++++++++++++++++++
来看看一个调用基本的读写函数实现功能的功能级函数,

//设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{
	if(lcddev.id==0X9341||lcddev.id==0X5310){
		LCD_WR_REG(lcddev.setxcmd);
		LCD_WR_DATA(Xpos>>8);
		LCD_WR_DATA(Xpos&0XFF);
		LCD_WR_REG(lcddev.setycmd);
		LCD_WR_DATA(Ypos>>8);
		LCD_WR_DATA(Ypos&0XFF);
	}
	else if(lcddev.id==0X6804){
		if(lcddev.dir==1)
			Xpos=lcddev.width-1-Xpos;//横屏时处理
		LCD_WR_REG(lcddev.setxcmd);
		LCD_WR_DATA(Xpos>>8);
		LCD_WR_DATA(Xpos&0XFF);
		LCD_WR_REG(lcddev.setycmd);
		LCD_WR_DATA(Ypos>>8);
		LCD_WR_DATA(Ypos&0XFF);
	}
	else if(lcddev.id==0X5510){
		LCD_WR_REG(lcddev.setxcmd);
		LCD_WR_DATA(Xpos>>8);
		LCD_WR_REG(lcddev.setxcmd+1);
		LCD_WR_DATA(Xpos&0XFF);
		LCD_WR_REG(lcddev.setycmd);
		LCD_WR_DATA(Ypos>>8);
		LCD_WR_REG(lcddev.setycmd+1);
		LCD_WR_DATA(Ypos&0XFF);
	}
	else{
		if(lcddev.dir==1)
			Xpos=lcddev.width-1-Xpos;//横屏其实就是调转 x,y 坐标
		LCD_WriteReg(lcddev.setxcmd, Xpos);
		LCD_WriteReg(lcddev.setycmd, Ypos);
	}
}

该函数实现将 LCD 的当前操作点设置到指定坐标(x,y)。

更进一步的功能函数,就是调用别的功能级函数。

//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
	LCD_SetCursor(x,y); //设置光标位置
	LCD_WriteRAM_Prepare(); //开始写入 GRAM
	LCD->LCD_RAM=POINT_COLOR;
}

该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。
其中 POINT_COLOR 是我们定义的一个宏,用于存放画笔颜色,
顺带介绍一下另外一个宏: BACK_COLOR,该变量代表 LCD 的背景色。

读取 TFTLCD 模块数据的函数为LCD_ReadPoint,该函数直接返回读到的 GRAM 值。

//读取个某点的颜色值
//x,y:坐标
//返回值:此点的颜色
u16 LCD_ReadPoint(u16 x,u16 y)
{
	vu16 r=0,g=0,b=0;
	if(x>=lcddev.width||y>=lcddev.height)
		return 0; //超过了范围,直接返回
	LCD_SetCursor(x,y);
	
	if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310)
		LCD_WR_REG(0X2E);
	//9341/6804/3510 发送读 GRAM 指令
	else if(lcddev.id==0X5510)
		LCD_WR_REG(0X2E00);//5510 发送读 GRAM 指令
	else 
		LCD_WR_REG(R34); //其他 IC 发送读 GRAM 指令
		
	if(lcddev.id==0X9320)
		opt_delay(2); //FOR 9320,延时 2us
	LCD_RD_DATA(); //dummy Read
	opt_delay(2);
	r=LCD_RD_DATA(); //实际坐标颜色
	
	if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)
	{ 	//9341/NT35310/NT35510 要分 2 次读出
		opt_delay(2);
		b=LCD_RD_DATA();
		g=r&0XFF;//9341/5310/5510 等,第一次读取的是 RG 的值,R 在前,G 在后,各占 8 位
		g<<=8;
	}
	
	if(lcddev.id==0X9325||lcddev.id==0X4535||lcddev.id==0X4531||lcddev.id==0XB505||
							lcddev.id==0XC505)
		return r; //这几种 IC 直接返回颜色值
	else if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)
		return (((r>>11)<<11) |((g>>10)<<5)|(b>>11)); //ILI9341/NT35310/NT35510 需要公式转换一下
	else 
		return LCD_BGR2RGB(r); //其他 IC
}

其他几乎所有上层函数,都是通过调用这几个函数实现的。

++++++++++++++++++++++++++++++++++++++++++++
更进一步,我们需要更高层的功能函数,高层功能函数调用低层的功能函数。

//在指定位置显示一个字符
//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-' ';//得到偏移后的值
	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;
			}
		}
	}
}

++++++++++++++++++++++++++++++++++++++
我们在初始化的时候,需要获取LCD的设备参数,并存储在设备参数描述符中。
所以,我们需要设计一个功能级函数,它调用基本的读写函数。

void LCD_Init(void)
{
	uint16_t tmp_id;

	delay_ms(50); // delay 50 ms
	
	lcddev.id = 0xFFFF;
	
	//尝试 9341 ID 的读取
	LCD_WR_REG(0XD3);
	tmp_id=LCD_RD_DATA(); //dummy read
	tmp_id=LCD_RD_DATA(); //读到 0X00
	tmp_id=LCD_RD_DATA(); //读取 93
	tmp_id<<=8;
	tmp_id|=LCD_RD_DATA(); //读取 41
	
	if(tmp_id == 0x9341)
		lcddev.id = 0x9341;

	if(lcddev.id ==0XFFFF) //非 9341,尝试看看是不是 NT35310
	{
		LCD_WR_REG(0XD4);
		tmp_id=LCD_RD_DATA();//dummy read
		tmp_id=LCD_RD_DATA();//读回 0X01
		tmp_id=LCD_RD_DATA();//读回 0X53
		tmp_id<<=8;
		tmp_id|=LCD_RD_DATA(); //这里读回 0X10

		if(tmp_id == 0x5310)
			lcddev.id = 0x5310;
	}

	if(lcddev.id ==0XFFFF) //也不是 NT35310,尝试看看是不是 NT35510
	{
		LCD_WR_REG(0XDA00);
		tmp_id=LCD_RD_DATA(); //读回 0X00
		LCD_WR_REG(0XDB00);
		tmp_id=LCD_RD_DATA(); //读回 0X80
		tmp_id<<=8;
		LCD_WR_REG(0XDC00);
		tmp_id|=LCD_RD_DATA(); //读回 0X00
		
		if(tmp_id==0x8000)
			lcddev.id=0x5510;
		//NT35510 读回的 ID 是 8000H,为方便区分,我们强制设置为 5510	
	}
			
	if(lcddev.id ==0XFFFF) //也不是 NT5510,尝试看看是不是 SSD1963
	{
		LCD_WR_REG(0XA1);
		tmp_id=LCD_RD_DATA();
		tmp_id=LCD_RD_DATA(); //读回 0X57
		tmp_id<<=8;
		tmp_id |=LCD_RD_DATA(); //读回 0X61
		
		if(tmp_id==0X5761)
			lcddev.id=0X1963;
		//SSD1963 读回的 ID 是 5761H,为方便区分,我们强制设置为 1963
	}
	
	printf(" LCD ID:%x\r\n",lcddev.id); //打印 LCD ID
	
	if(lcddev.id==0X9341) //9341 初始化
	{
		……//9341 初始化寄存器序列
	}
	else if(lcddev.id==0xXXXX) //其他 LCD 初始化代码
	{
		……//其他 LCD 驱动 IC,初始化代码
	}
	
	LCD_Display_Dir(0); //默认为竖屏显示
	LCD_LED=1; //点亮背光
	LCD_Clear(WHITE);
}

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
在main函数中,使用了一个FSM来控制操作步。

u8 x=0;
u8 lcd_id[12]; //存放 LCD ID 字符串
...
LCD_Init(); //初始化 LCD FSMC 接口
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,"Explorer STM32F4");
	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,"2017/4/8");
	
	x++;
	if(x==12)
		x=0;
		
	LED0=!LED0;
	
	delay_ms(1000);
}

sprintf 的函数,该函数用法同 printf,只是 sprintf把打印内容输出到指定的字符串数组中。
+++++++++++++++++++++++++++++++++++++++
实例,
在cubemx中配置FSMC支持LCD。
选择SRAM1,
设置CS为NE1,
设置LCD REG SEL 为A6,
设置DATA为16bits,

在SRAM1 timing中 ,
设置access mode 为A,
设置ADDSET需要的HCLK数,为15,
设置DATAST需要的HCLK数,为60,
设置Bus turn around需要的HCLK数,为0,
这是控制的读时序,

再设置写时序,
设置extend access mode 为A,
设置extend ADDSET需要的HCLK数,为9,
设置extend DATAST需要的HCLK数,为9,
设置extend Bus turn around需要的HCLK数,为0,

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
对于驱动 TFT LCDFSMC(Flexible Static Memory Controller)在 STM32F407 上的使用,你可以按照以下步骤进行操作: 1. 配置 FSMC 控制器:首先,你需要配置 FSMC 控制器以与 TFT LCD 进行通信。这涉及到配置控制线、地址线和数据线等。你可以参考 STM32F407 参考手册中的 FSMC 章节,了解如何正确配置 FSMC 控制器。 2. 配置 TFT LCD 控制器:根据 TFT LCD 的规格书或者供应商提供的资料,你需要了解 TFT LCD 的时序要求和控制信号定义。然后,根据这些信息配置 FSMC 控制器的时序参数,以确保与 TFT LCD 的正确通信。 3. 编写驱动代码:在配置完 FSMC 控制器和 TFT LCD 控制器后,你需要编写驱动代码来实现绘制图形、显示文本等功能。这通常涉及到像素点的读写、区域填充、字体显示等操作。你可以使用 C 语言或者汇编语言来编写这些代码。 4. 调试和优化:一旦你完成了驱动代码的编写,你需要通过调试和优化来确保驱动的正确性和性能。你可以使用逻辑分析仪或者示波器来观察信号波形,以确保与 TFT LCD 的通信正常。 需要注意的是,TFT LCD 的驱动方式和接口可能因不同的型号而有所不同,因此在开始驱动之前,最好详细阅读 TFT LCD 的规格书,并参考 STM32F407 参考手册中的相关章节进行配置。此外,也可以参考一些开源项目或者社区提供的代码和资料,以便更好地理解和实现 TFT LCD 的驱动。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值