Stm32FSMC及TFTLED屏笔记
截图的图片出自正点原子的参考手册和网络资料,如有侵权,请联系我删除(因为没怎么写过博客,有注明出处,但对版权的具体细节不清楚)
1.FSMC*(Flexiable Static Memory Controler)*
1)FSMC的应用:
-
以上是STM32官方手册的内容,可以认为,FSMC,是芯片内部专门可以用于读取外部接入的(SRAM,PC卡等)需要并口协议读写的存储器的一个控制器,起到一个连接器(或者是桥)的作用。
只要我们按芯片要求将外部SRAM等于GPIO连接好,并配置好FSMC的相关配置,就可以给这些SRAM配置一个向内部寄存器那样的地址,就可以直接向SRAM的具体地址写入数据(软件上和寄存器操作一样,直接给对应地址赋值,但实际是通过并口数据线将数据写入)这样就可以是我们对外部SRAM的使用效率提升,而不用再使用GPIO去模拟对应的时序*(可以直接向内部寄存器一样赋值)*。
- 从上面的框图也可以看出,因为并口信号线FSMC[15:0]是公用的,所以一次只能同时外接一个SRAM类的存储器。
- 如上图,实际上,是内部连接的AHB将FSMC可控制的寄存器区域划分为以下这四个,其中NOR/PSRAM内再划分为Block1-4四块。FSMC在这片区域寻址(这片区域的地址在配置完成后,将会称为外设的地址 即把外设地址命名为这些地址)时,使用HADDR地址线(共有28位[27:0]),HADDR[27:26]两位决定NOR/PSRAM内部的的Block1-4(00-1;01-2;10-3;11-4)。
2)NOR/SRAM:
(1)映象部分:
-
值得注意的是在外部SRAM连接的FSMC_A在存储器位数不同时,地址线的对齐方式不同。
因为在内部AHB的地址里(即HADDR[25:0])是以字节为单位的,即一个地址可以写8位数据;
当外部是以半字(16位)为单位时,这时候每当在外部地址写完一次(16位数据)的过程中,内部AHB对应的地址应该是移动两位的(2*8位);所以为了使用方便,这儿有一个很巧妙(也很常用)的方法就是将HADDR[25:0]左移一位与FSMC_A对齐;
-
举例如下:当在外设FSMC_A的第……0001*(地址为bit0 = 1处的地址,前面的0省略)写数据时,HADDR中对应是第……001 _(地址为bit1 = 1处的地址,下划线表示:因左移一位对齐而失效的HADDR [0] )*; 这样在外设写……0001;这移位时,内部HADDR 实际上走了……0010,和0011两个8位数据
-
所以这样的对齐方式,使FSMC_A的一位会对应HADDR的两位:
FSMC_A(16位单位外设地址) HADDR(实际内部的8位单位地址) 000 00 0 和 00 1(下划线表示因左移而失效的HADDR[0]) 001 01 0 和 01 1 010 10 0 和 10 1
-
-
可以看出刚好是左移一位对齐,而且000 位仍有对应 000位地址,即起始地址仍一样。
(2)信号线及8080并口协议:
-
以下将SRAM常用的8080并口协议的连接线与FSMC的输出信号做些整理
SRAM*(以IS62WV51216为例)* FSMC 说明 CS NE[x] 片选,低电平表示选中。 OE NOE 输出使能,即(对主机MCU来说)读使能 WE NWE 写使能 UB/LB NBL[1]/NBL[0] 高位/地位数据掩码(是否允许访问高8位/低8位数据,低电平允许) A0-A18 A[25:0] 地址总线 I/O0-7;I/O8-15 D[15:0] 双向数据线(可选8位或16位) -
8080并口协议时序:(这里只做简单介绍)
在CS选中后,地址线发送对应地址信息表示该地址;(结束时拉高CS)
-
读时;NWE拉高,禁止写;后拉低NOE写使能,SRAM数据放于数据口上,NOE拉高,上升沿数据读出;
-
写时;NOE拉高,禁止读;后拉低NWE写使能,主机MCU数据放于数据口上,NWE拉高,上升沿数据写入;
-
当只需要高8位/低8位用于数据传输时,可使用地址掩码,NBL[1:0]进行控制,低电平表示允许访问。
-
-
而在FMSC内当我们按照要求连接好硬件电路后,可以直接向写读内部寄存器一样,直接写 ”xxxx*(地址)* = 0xyyyyy (数据) “ 即可。FSMC会自动帮我们完成上述对各种信号线的操作;
(3)硬件连接:
(FSMC只有大容量,多引脚封装的stm32有,我用的是STM32ZET6)具体的引脚连接可参照下图、下表,或者上面的参考博客
(4)寄存器:
主要有FSMC_BCRx*(片选控制寄存器),FSMC_BTRx(读时序控制寄存器),FSMC——BWTRx(写时序控制寄存器)*
按如下组合进行访问:
(5)库函数使用:(见如下代码及注释,参考正点原子)
void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct);
//需要填入一个结构体的NORSRAM初始化;
typedef struct
{
uint32_t FSMC_Bank; //来设置使用到的存储块标号和区号,
//如NOR/PSRAM是块1;我们用其中(有4块)的块4 FSMC_Bank1_NORSRAM4。
uint32_t FSMC_DataAddressMux;
// 地址/数据复用使能,若设置为使能,那么地址的低 16位和数据将共用数据总线,
uint32_t FSMC_MemoryType; // 来设置存储器类型,如SRAM FSMC_MemoryType_SRAM。
uint32_t FSMC_MemoryDataWidth;//用来设置数据宽度,如16位FSMC_MemoryDataWidth_16b
uint32_t FSMC_BurstAccessMode;
uint32_t FSMC_AsynchronousWait;
uint32_t FSMC_WaitSignalPolarity;
uint32_t FSMC_WrapMode;
uint32_t FSMC_WaitSignalActive;
uint32_t FSMC_WriteOperation;// 用来设置写使能,
uint32_t FSMC_WaitSignal;
uint32_t FSMC_ExtendedMode; //扩展模式使能位,扩展模式下,可以读写采用不同的时序
uint32_t FSMC_WriteBurst;
FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;
FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
} FSMC_NORSRAMInitTypeDef;
关于读写时序的结构体参数如下*(即上面结构体的最后两个成员变量)*:
typedef struct
{
//主要是设置地址及数据的建立时间,根据所接外设SRAM的不同而设置;
uint32_t FSMC_AddressSetupTime; //地址建立时间;
uint32_t FSMC_AddressHoldTime; //地址维持时间,在同步模式下才使用
uint32_t FSMC_DataSetupTime; //数据建立时间
uint32_t FSMC_BusTurnAroundDuration;
uint32_t FSMC_CLKDivision; //时钟分频系数;
uint32_t FSMC_DataLatency;
uint32_t FSMC_AccessMode; //访问模式有四种,ABCD,异步访问SRAM用模式A
}FSMC_NORSRAMTimingInitTypeDef;
FSMC的使能函数如下:(较简单)
void FSMC_NORSRAMCmd(uint32_t FSMC_Bank, FunctionalState NewState);
void FSMC_NANDCmd(uint32_t FSMC_Bank, FunctionalState NewState);
void FSMC_PCCARDCmd(FunctionalState NewState);
2.TFTLED屏(ILI9341驱动)
1)ILI9341驱动:
(1)接口时序:8080并口:
接口线 | 作用简介 |
---|---|
CS | 片选 |
WR | 写使能 |
RD | 读使能· |
D[15:0] | 16位双向数据线 |
RST | 硬件复位线 |
RS/DCX | 数据1*(写入9341的SRAM)/命令0(写入9341的寄存器)* 控制线 |
在接线上,原子用的方法比较巧妙;
-
将RST复位接在系统的复位上;
-
将RS线接在FSMC的任意一个地址线上(A10上)如下:
typedef struct { vu16 LCD_REG; vu16 LCD_RAM; } LCD_TypeDef; //使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A10作为数据命令区分线 //注意设置时STM32内部会右移一位对其! #define LCD_BASE ((u32)(0x6C000000 | 0x000007FE)) #define LCD ((LCD_TypeDef *) LCD_BASE)
-
利用结构体地址自增的特性:
将LCD这个结构体定义做LCD_BASE*(A10)的地址*;***(注意:16位数据传输时,将外设地址右移一对齐位,所以实际是 HADDR[11] 位)***,这样:
LCD_REG == 0x6C0007FE ,A10位为0(7为0111);
LCD_RAM == 0x6C000800,A10位为1(8为1000)这里的A10均指外设的A10即FSMC_A[10].
如此在,对地址LCD_REG = 0xyyyy*(数据)时就是,写入命令(写道9341的寄存器中)(因为此时,RS=0)*;
同理,对地址LCD_RAM = 0xyyyy*(数据)时就是,写入数据(写到9341的SRAM上)(因为此时,RS=1)*。
2)ILI9341的部分命令:
-
0xD3:读ID.
-
0x36:控制GRAM的指针增长方向。
-
0x2A:设置列坐标,(即x轴横坐标)
0x2B:与2A一样,设置的是行坐标(即y轴纵坐标)
注:以下的SC可以理解为 Start Column;EC可以理解为Ending Column.
-
0x2C:填入颜色数据(RGB565格式)
RGB565格式如下:
同时,我们从高亮那部分可以得知,为什么我们之前法横纵坐标的数据的时候,每个坐标(如SC)必须用16位(为了兼容足够大的屏幕),却被拆成两个8位来发的原因。
-
0x2E:读GRAM,即读处对应点的颜色:
3)原子代码使用:
原子帮我们将许多底层的代码都封装好了,但在使用时有一些注意事项:
-
使用时,需先执行LCD_Init();函数进行初始化;
-
**因为LCD_Init()函数里默认将芯片信号printf到串口1输出;所以在执行LCD_Init()初始化函数前,需先执行串口初始化uart_init();或者直接将LCD_Init()内的printf();注释掉,否则程序会卡死在printf中 **
-
将原子的LCD.h中可以调用的函数整理在这儿,方便自己查找:
void LCD_Init(void); //初始化 void LCD_DisplayOn(void); //开显示 void LCD_DisplayOff(void); //关显示 void LCD_Clear(u16 Color); //清屏 void LCD_SetCursor(u16 Xpos, u16 Ypos); //设置光标 void LCD_DrawPoint(u16 x,u16 y); //画点 void LCD_Fast_DrawPoint(u16 x,u16 y,u16 color); //快速画点 u16 LCD_ReadPoint(u16 x,u16 y); //读点 void LCD_Draw_Circle(u16 x0,u16 y0,u8 r); //画圆 void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2); //画线 void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2); //画矩形 void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color); //填充单色 void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color); //填充指定颜色 void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode); //显示一个字符 void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size); //显示一个数字 void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode); //显示 数字(高位用0补充) void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p); //显示一个字符串,12/16/24字体,若想字符串不覆盖显示,将其中的Showchar(mode=1)即可