首先在明白操作LCD真正用到的引脚有:
1)数据或命令 RS
2)数据传输线: D0-D15
3)写信号: WR
4)读信号: RD
5)片选信号: CS
LCD的操作时序与SRAM控制类似,唯一不同的是是TFTCLD只有RS信号,没有地址信号,这里再次强调,SRAM有地址控制信号,而TFTLCD没有!原则上SRAM的地址信号对
TFTLCD没有什么作用,对TFTLCD来说我只需找到一个引脚,这个引脚用来控制RS(即是写命令还是写数据)即可。那既然SRAM中的地址线已经引出来了,那么我们就用呗
,“随便”选一根来用!那就选A10(对于STM32F1)或A6(对于STM32F4),那么我们怎样才能使它变高或变低呢?(就是如何才能对它写1或写0?)又因为它是地址线中
的某一根线,而对于TFTLCD来说,我们要使用的是SRAM中的A模式中的4区,也就是寻址的地址从0x6C00 0000 开始,到0x6CFF FFFF止,那么我们试着看看能不能从这些地
址中找到能满足某种条件的这么一个地址:使地址线A[0]–A[25]中的A[10]变为0?或使A[10]变为1?(这里只对STM32F1来说)当然能找到,而且还很多:只要在这25根
地址线上传输信号能满足A[10]这一位是0或是1就可啦,即:如果地址线上传输的数据是这种格式:0x0110 1100 XXXX XXXX XXXX X0XX XXXX XXXX,即bit10位为0也就是A
[10]为0!注意要从0x6C00 0000 开始不然就找到别的区里去啦!我们就可以拉低RS,就是传命令了;同理:只要满足这种格式:0x0110 1100 XXXX XXXX XXXX X1XX XXXX
XXXX即bit10位为1,也就是A[10]为1就是传数据了!于是我们就可以这样定义两个地址,一个用来传命令(含A[10]为0),一个用来传数据(含A[10]为1):
#define LCD_REG (u32)0x6C00 0000
#define LCD_RAM (u32)0x6C00 0400
…
为了让它们好看点,我们再去找这么一个地址:两个地址相邻,一个能让A[10]默认为0,而加1之后又使A[10]变成了1?有没有这么两个相邻的地址?当然有!它就是:
0x6C00 0000|0x00 07FE和0x6C00 0000|0x0000 07FF(注意最后一位的区别,于是我们就可以这么定义:
#define LCD_REG (u32)0x6C00 0000|0x0000 07FE
#define LCD_RAM (u32)0x6C00 0000|0x0000 07FF
但要注意,这两个定义只是定义了地址数据,如果要往这个地址上写数据时还要这样处理:
(uint16_t)LCD_REG = cmd;
(uint16_t)LCD_RAM = data;
这样写有点麻烦,还有点难懂,这时我们还可以这么定义:
#define LCD_REG __attribute__((at (0X6C0007FE)))
#define LCD_REG __attribute__((at(0X6C0007FF)))
这里用了关键字arrtibute来指定某一个变量的地址,这个地址正好在SRAM那里,所以往这个地址上写数据的时候CPU就会过内部的数据总线(AHB)将地址和数据一起传送给
FSMC(片上外设),FSMC以我们设定好的时序(就是FSMC的读或写时序)对外部存储设备或LCD设备等进行数据交换,从而达到控制外部设备的目的。
我们知道结构体成员在内存中都是相邻存储的,所以还可以将上边两个定义再优化一下:使它们变成一个结构体:
typedef struct
{
u16 LCD_REG;
u16 LCD_RAM;
} LCD_TypeDef;
然后再这样定义:
#define LCD_BASE ((u32)(0x6C000000 | 0x000007FE))
#define LCD ((LCD_TypeDef *) LCD_BASE)
这就是正点原子文档里边的用到的结构体!强制将LCD_BASE这个数据转换为结构体LCD_TypeDef的入口地址,然后将这个入口地址重新命名为LCD,LCD_REG正好就是
0X0x6C0007FE,LCD_RAM正好是0X6C0007FFF.为了一根信号线能够输出0或1,费了这般劲,不容易呀!我们往这个地址上写数据的时候就可以这么做:
LCD->LCD_REG = cmd;
LCD->LCD_RAM = data;
这样就变得好看多了!
这时,我想到OLED:
做过OLED实验的人都知道,IIC驱动的OLED它的片选地址是0x78,写命令指令0x00,写数据指令是0x40,大家看出其中规律了没有?看不出来?
好吧,我们把这俩个16进制的数据变成二进制:0x00就是:0000 0000, 0x40就是:0100 0000,到这里看出规律了没有?第6位是写命令或写数据位!如果不相信的话你可
心大胆地做一下实验,将两个数据更改一下,只要保证它的第6位正确,那么它就能正常工作!神奇吧?
而TFTLCD与OLED类似,都是数据线和地址线共用SRAM正好相反,数据线和地址线分开!但不影响TFTLCD呀,我们又不用地址线。
至于为什么要右移一位,其实不明白也没关系,刚才说了,我们又不用地址线,而花那么多时间精力去理解,我们也只是为了解决A[10]这一位能够让它准确地表达在RS上
!其实理解起来也很简单:
因为SRAM是以一个字节为一个地址的,而TFTLCD数据宽度是16位的,就是说它要用FSMC的两个地址上的数据,才能表达出它本身一个地址上的值!我们为了让它们能够重
新一一对应起来,只有将FSMC内的地址“重新编号“,将它们编号缩小一半,再简单一点来就是就将地址1和地址2合并成一个地址,并叫它为地址1,那宽度就是16位了对
吧?那这样的话HADDR的26根地址线(A[25:0])可以访问的字节元素个数变成了原来的1/2!但是它的宽度变成了原来的2倍(原来是8位,现在是16位)。因此才出现了当传
输线的宽度为16位时,我们要将HADDR右移一们以对应FSMC_A[24:0]。不防大胆地去设想:那要是32位呢?再移一位就好了。总之就是可访问的地址值要比原来的缩小一半
!那就是除以2了,除以2不就是往右边移一位嘛!要记住这一点:HADDR最终改变的是FSMC的地址。而FSMC的A[0]永远对应着外部设备的A[0]!HADDR就是AHB数据总线,它
上边的数据就是我们写入并要传给FSMC的数据!再次强调:在这里费了老半天劲,我们要的效果也仅仅是为了能够使A[10]能够准确地表达在RS这根命令/数据线上而已!
不相信?
我们再回到LCD_REG和LCDRAM这两人定义:
#define LCD_REG (u32)0x6C00 0000|0x0000 07FE
#define LCD_RAM (u32)0x6C00 0000|0x0000 07FF
其中0x00 07FE展开来就是:0000 0000 0000 0111 1111 1110。我们要写的本来是这个数据,但是经过FSMC传到TFTLCD的时候却变成这个:0000 0000 0000 0011 1111
1111,A[10]为0有没有?再加1A[10]就变成1,有没有?再深入一点去说就是HADDR访问的地址一定是SFMC上的偶地址!(这里是对于16位来说的),换一种说法吧,16位数
据地址宽度的外设与8位数据的地址MCU连接,那么你就得两个地址两个地址给我传,才能凑成16位,我(指16位)的地址0是你的(指8位)地址0,我的地址1却变成了你的地
址2了,我的地址2却变成了你的地址4.……你也许会问:那我8位地址8位地址地给你传行不行?然后再将两个地址上的数据合并成16位就成了你的地址上的数据了!这样
也行,某些设备也确实是这样做了,但是只能通过软件去干这件事,(通过先传高位字节,再传低位字节),硬件可不懂!再说了,放着那么多根地址线你不用?(28根!
),却去自找苦头吃?我跟我朋友说过这一件事,他开玩笑地说:“没错了,因为我比你长!我要访问你的地址1其实是访问了你的地址2!我要访问你的地址4其实是要访
问你的址8!”
不理解就PASS吧,没关系的,反正TFTLCD又不用这些地址线!大不了换个引脚当RS。
解决了地址偏移问题,那剩下的就和OLED没有太大的区别了,只是TFTLCD的内容丰富了点而已(多了RGB颜色),FSMC的存在就当做是为了给TFTLCD创造合适TFTLCD的某种
时序,或者可以把它理解成IIC或SPI都可以,(只不过FSMC是并口而已!)其它的应该也就没有什么了吧!
对于TFTLCD来说真正用到FSMC的只有两个结构体,一个是FSMC基本配置结构体:FSMC_NORSRAMInitTypeDef,另一个是读写时序结构体:FSMC_NORSRAMTimingInitTypeDef
由于一些外设本身的原因,导致读和写的速度不一样,对于TFTLCD,如果芯片是ILI9341,那么它的写速度将近是读速度的两倍之多,所以我们可以在FSMC的基本配置中将
读和写时序分开来设置,这样就是使能FSMC_NORSRAMInitTypeDef结构体成员:FSMC_ExtendedMode让它变成Enable,这样我们又要多配置另一个结构体数据了,分别是读
时序,写时序。
对于FSMC来说真正要配置的成员其实就那7个:
1)FSMC_Bank
2)FSMC_ExtendedMode
3)FSMC_MemoryDataWidth
4)FSMC_MemoryType
5)FSMC_WriteOperation
6)FSMC_ReadWriteTimingStruct
7)FSMC_WriteTimingStruct
其它的都是Disable就可以了!
理解了上边的内容,那么LCD的驱动应该问题不大了。