基于战舰V3的4.3寸TFTLCD的原理详述

基于战舰V3的4.3寸TFTLCD的原理详述

8080时序简介

NT35510支持多种时序读写操作,我们这里使用的是8080并口操作,读/写时序如下:

表格 1

操作

RD

WR

RS

写入命令

1

0

0

读取命令

0

1

0

写入数据

1

0

1

读取数据

0

1

1

注意:这里的数据传输线D[0:15]是双向的,且RD读使能,WR写使能低电平有效。

FSMC到底是干什么用的?

不同存储器在FSMC中的地址映射如下所示:

FSMC与外部存储器的映射关系如下:

我们可以好好地品味上图的含义,FSMC一个块有64M的存储空间,存储空间里存储的是数据吗?不是的,存储的是外设地址,当我们在存储块中的访问单元序号+1,对应的外设存储单元的访问地址就自加8(如果外设数据存储的数据宽度为8b的话)。由此,我们可以得到FSMC的工作机理:

我们一定要区分“地址映射“和”实际地址“之间的不同。FSMC的地址映射和外部存储器的实际地址关系如下所示(以8b为例):

FSMC如何寻址?

STM32的FSMC存储块1(Bank1)用于驱动NOR FLASH/SRAM/PSRAM,被分为4个区,每个区管理64M字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。Bank1的256M字节空间由28根地址线(HADDR[27:0])寻址。

这里HADDR,是内部AHB地址总线,其中,HADDR[25:0]来自外部存储器地址FSMC_A[25:0],而HADDR[26:27]对4个区进行寻址。如下表所示:

注意:

  1. 当Bank1接的是16位宽度存储器的时候:HADDR[25:1]接FSMC_A[24:0];
  2. 当Bank1接的是8位宽度存储器的时候:HADDR[25:0]接FSMC_A[25:0];

内存数量级的关系

  1. 1KB=1024B 1024是2的10次方
  2. 1MB=1024X1024B 是2的20次方
  3. 1GB=1024X1024X1024B 是2的30次方
  4. 1TB=1024X1024X1024X1024B 是2的40次方

FSMC的地址映射范围因数据长度而异?

从上表可以看出,FSMC最大访问的存储器位数就是512M,例如Bank1的第1区的寻址范围是0X60000000~0X63FFFFFF,对应的外部存储器的地址为0~0X03FFFFFF,即我们可以访问的位的地址从0~512M。

此时,我们可以用FSMC一个数据存储块最大管理的位数反推FSMC地址映射范围,可得如下结果:

表格 2

数据宽度

最大访问的数据个数

最大访问存储器空间

8b

512M/8=64M

512M

16b

512M/16=32M

512M

HADDR的[25:0]位所表示的地址为0~64M,但是当数据长度为16b时,需要访问的地址为0~32M,因此HADDR中只用到了25位。当数据长度为16b时,HADDR的有效位为[25:1]而且外部存储器的地址线接收到的地址必须从0开始寻址,因此要求HADDR向低位偏移一位,如下图所示:

当数据长度为16b时,FSMC中的地址映射仍是一个地址寻址一个字节的数据,但是FSMC地址自增就变了,变成了每次自加2,而非原来的自加1,16b的地址映射关系如下:

FSMC如何进行读写操作?

读写LCD本质上是对寻址空间的某一个地址进行读写操作,例如:

读:var = *(uint32_t *)0x6C000080;

写:*(uint32_t *)0x6C000080 = var;

读写控制是FSMC自动控制的,你向指定地址写数据(如 *(uint32_t *)0x6C000080 = var),FSMC会自动控制相应的引脚(CS、RS、RD、WR等)发送写数据时序,读也是一样的。

为了更清楚,我们可以看一下相应代码的汇编:

因此,如果是读LCD,则使用的一定是LDR指令(Load Register);而写LCD,则使用的是STR指令(Store Register)。这是由于不同的指令会对应不同的总线访问时序。

从上面可以得出结论,只要是访问一个存储单元并且写入数据(将变量的值赋值给存储单元),那么FSMC就会控制CS、RS、RD、WR等对这个存储单元进行写操作;我们访问一个存储单元并进行读操作(把存储单元的数据读出来并赋值给变量),那么FSMC就会控制CS、RS、RD、WR等对这个存储单元执行读操作。

TFTLCD为什么能用到FSMC?

FSMC听起来很高大上,其实FSMC就是一个MCU与外部存储器(SRAM,FLASH等)读写数据的一个接口,我们可以配置FSMC的寄存器从而实现MCU可以根据特定的时序与外部存储器进行数据的交互(存储/读取)。有人会疑问:TFTLCD又不是存储器不具备存储功能但是却可以与MCU通过读写时序交互信息?

TFTLCD确实不是SRAM这样的数据存储器,不具备数据存储功能,但是FSMC的扩展功能就在于此,TFTLCD的读写时序和SRAM大致一样,因此我们使用FSMC去充当MCU与TFTLCD的沟通桥梁。不止TFTLCD是这样,其他的具有和FSMC支持的存储器相同/类似时序的设备都通过FSMC与MCU建立起数据沟通的桥梁。

使用FSMC时,用到的位带操作是什么?

位带操作,乍一听名字,也很高大上。但是位带操作实质上就是一个寻址的手段而已。在我们做一件事情时,总喜欢先顶层规划,然后再细分各个工作流程。C语言中常用的寻址方式就是位带操作,我们再C语言中访问一个结构体变量元素的地址,一般常常确定这个结构体变量的地址,再根据结构体中的元素相对于结构体首地址的偏移量确定其具体位置,并且访问该地址的信息。

在lcd.c文件中,有如下代码:

//LCD地址结构体  
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) 

0x6C000000代表的是FSMC中的Bank1的基地址,而0x000007FE代表的是Bank1的存储块1相对于Bank1的地址偏移量。这样操作就使得LCD_BASE代表FSMC中Bank1的存储块1的地址,然后我们使用强制类型转换,就在LCD_BASE地址上建立了一个结构体变量LCD,其内存分布如下所示:

一定要注意:由于数据是16位的,因此地址自增2,即0X6C008000=0X6C0007FE+0X02。

在TFTLCD实验中,FSMC读写时序切换的原理

像这种地址线和数据线分开的数据存储器,一般的向数据存储器写数据的时序如下所示:

首先,我们要向数据存储器写入指令:

紧接着,我们要输入指令所需的数据:

我们前面说过FSMC进行读写操作的原理,但是当我们读FSMC中的地址映射单元时,FSMC会输出如下图所示的读时序:

这并不是8080时序,而且我们疑问的是TFTLCD中只有地址线A[25:0],而没有RS信号来决定收发数据还是收发命令。这就很懵了,RS的0/1分别代表着命令/数据,但是我上哪里找0/1呢?此时换位思考一下,惊奇的发现地址线A[25:0]可以帮到我们:

假设TFTLCD只有两个寄存器:数据操作寄存器/命令操作寄存器。当TFTLCD收到寄存器地址”addr= 1“时,此时命令操作寄存器响应;同理,当TFTLCD收到寄存器地址“addr=0”时,此时数据操作寄存器响应。以上一波操作就可以把FSMC读写SRAM的时序神奇地转化为8080读写时序了。

我们将A[10]作为RS引脚用于控制TFTLCD是操作命令还是操作数据。如何使得TFTLCD操作数据时A[10]=1,操作命令时A[10]=0是关键:

Bank1.sector4就是从地址0X6C000000开始,而0X000007FE,则是A[10]的偏移量。以A[10]为例,A[10]相对于Bank1.sector4的地址0X7FE换成二进制为:0b0111 1111 1110,而16位数据时,地址右移一位对齐,对应到地址引脚就是:A[10:0]=0b0011 1111 1111,此时A[10]是0,但是如果16位地址加1(对应到8位地址是加2,即0X7FE+0X02),那么:A[10:0]=0b0100 0000 0000,此时A[10]就是1了,即实现了对RS的0和1的控制。

//LCD地址结构体  
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_TypeDef结构体地址,那么可以得到LCD->LCD_REG的

地址就是0X6C00,07FE,对应A[10]的状态为0(即RS=0),而LCD-> LCD_RAM的地址就是

0X6C00,0800(结构体地址自增),对应A[10]的状态为1(即RS=1),从而实现对RS的控

制。

如何计算一个字符对应点阵集所占的字节数?

字体大小分为1608,2412,1206三种形式,例如1608代表字符长16个位宽8个位,如下所示:

在正点原子给lcd.c中有个函数位LCD_ShowChar,这个函数是最基本的显示字符的函数,他是显示一切ASCII字符串的“鼻祖”,其中有个计算ASCII字符所占字节个数的代码如下所示:

u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数  

我们可以这样来看:

size是整形变量,表示一个ASCII字符的长度,由于整型变量相除还是整型变量,C语言默认向0取整,因此size/8+(size%8?1:0)代表了1列也就是16个位所需的字节数,然后我们知道宽=列/2,因此size/2代表了一行共有多少列,因此列数*每列所需的字节数=一个ASCII字符显示所需的字节数。

为何可以在font.h中定义二维数组?

我们学过C语言的同学都熟知:.h文件中只能包含.c文件中的函数声明和一些宏定义,自定义数据类型的定义。但是正点原子的LCD的专属字库文件font.h中定义了用于存放字库数据的二维const类型数组。首先,在C语言中如果这个.h文件为某个.c文件所专属(比如font.h文件就是供lcd.c专用的),我们不建议这样做。比如,我如果在另一个.c文件中引用font.h字库文件,MDK会爆出以下错误:

多次引用同一个.h文件(这个.h文件中得定义有数据才会出现上述错误),相当于在多个.c文件中多次定义该变量,这会导致变量重定义错误。如果要在多个文件中引用.h文件中的变量,只需将font.h中的变量声明成static类型的即可,运行成功如下图所示:

但在C++中,const类型隐含着带有static类型的属性,因此在C++中只需将.h文件中的变量声明成const类型即可。

.h文件中的每个变量拥有了static类型之后,当某个.c文件引用该.h文件时,该.h文件中的所有变量会变成这个.c文件的专属变量,即该.c文件的局部变量。相同名称的全局变量会起冲突,而相同名称但是存在于不同.c文件中的局部变量不会起冲突。

编译器的-O2优化

首先,我们要知道-O2是干什么的,-O2是一个优化等级。在MDK5中,我们可以自选优化等级:

该优化选项会牺牲部分编译速度,除了执行-O1所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用以提高目标代码的运行速度。·其中,最直接的优化算法就是“去掉中间变量,一步到位”,具体示例如下:

#include <stdio.h>  
void delay(long val);  
int main(){  
                      
    delay(1000000);  
   
    return 0;  
   
}  
void delay(volatile long val){  
   
    while(val--);  
} 

我们虽然加了volatile,但是这只能保证在频繁的改变一个变量时,编译器不会将其认为“仅仅改变了一次”。-O2执行的是空间上的优化,即代码精简,与volatile八竿子打不着。当我们使用-O0和-O2优化时,会出现如下结果:

MDK5中4中不同的等级如下:

-O0:不做任何优化,这是默认的编译选项;

-O1:对程序做部分编译优化,对于大函数,优化编译占用稍微多的时间和相当大的内存。使用本项优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化;

-O2:该优化选项会牺牲部分编译速度,除了执行-O1所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用来精简代码所占用的空间长度,以提高目标代码的运行速度;

-O3:该选项除了执行-O2所有的优化选项之外,一般都是采取很多向量化算法,提高代码的并行执行程度(时间域上的优化),利用现代CPU中的流水线,Cache等。

STM32F4的白屏问题

当我们提高优化等级时,之伴随的是执行效率的提高和更多的BUG。我们将MDK的优化等级从-O0提高至-O2时,编译后的程序可能会出现以下问题:

  1. 编译乱序引起的问题,程序当中没有放置正确的内存屏障,编译优化引起的乱序执行导致程序出现bug;
  2. 时序引起的问题,优化后的代码执行速度要比优化前快得多,这可能引起时序不符合要求,最终导致bug;

STM32F4白屏的原因就是因为FSMC速度过快,导致编译乱序问题。我们要解决的方法无非就是延迟一下使得程序慢下来,在正点原子的程序中,延迟的原理是“找一个中间变量,数据是通过中间变量传给目标变量,这样数据的传输过程就经过了两个寄存器”,代码实现如下:

volatile到底有什么用?

volatile修饰是告诉编译器, 这个变量有可能被其他子程序改变,所以不能从寄存器里读取变量的值,要从内存里读取。 就是说修饰的确实是一个变量,而且是一个容易变的变量。在每次取这个变量值的时候,要求不是取它上次在某个时候取的临时缓存变量(比如说暂存在某个寄存器中),而是直接到内存中取。 volatile变量能防止优化,别如说你在某个地方可能连续调用了好几次这个函数,于是编译器优化后,可能就调用一次,其他几次就采用这一次调用的返回值,而volatile修饰后,要让每一次都进行函数调用, 而不采用寄存器里的暂存值。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥肥胖胖是太阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值