12864(ST7565P)液晶驱动显示概念含有ST7565P芯片的液晶,是没有文库支持的功能,但是没有就没有啦!液晶可以给我画画,那么它就是好东西了。液晶的“显示”,液晶的“扫描次序”全部都与CGRAM分配有很大的关系。我们先了解“扫描次序”吧。宏观上一副液晶是“64高x128宽”。微观上由芯片ST7565P驱动的一副12864液晶是由“8个8高x128宽的页”组成。至于液晶的“扫描次序”就与4个命令有关系。上图表示了,当命令为0xA0列扫描是“自左向右”,如果命令式0xA1列扫描是“自右向左”。总归,这两个命令控制了“列扫描次序”除了控制列扫描的命令以外,当然还有控制“页扫描次序”的命令。如上图,命令0xC0控制页扫描是“从下至上”,然而命令0xc8控制页扫描“又上至下”。无论页扫描的次序是“从上至下”还是“从下至上”,然而每一页的列填充,都是“低位开始高位结束”关于列扫描就有列填充的问题。我们知道每“一页”都是由“8高x128宽”组成。换句话说,这里没有“行扫描”的概念,因为“一页”都是由“一个字节数据,列填充128次”成为一页。如上图中所示。假设“页扫描次序”是由上至下,填充的值是0x0f,那么经过128次的“列扫描”以后,一页的扫描结果会是如上图所示。关于ST7565P芯片,命令,和液晶扫描它们之间的关系而已,我们简单来总结一下:(一)CGRAM分布是由8页组成。(二)每一页是由一个字节填充和128次列扫描组成。(三)列扫描次序与命令0xA0与0xA1有关。(四)页扫描次序与命令0xC0与0xC8有关。(五)列填充字节的高位低位关系与页扫描命令有关。(六)不存在行扫描概念。上图所示是“页扫描”由上至下,“列扫描”由左至右,列填充值是0x0f。在CGRAM分布方面。CGRAM可以说是由8bitsx1024words,如果以“页”去分配,也就是说8pagex8bitsx128words,那么“页”的偏移量就是128。这一点要好好的记住。那么关于“列地址”和“页地址”又是如何呢?事实上CGRAM的建立不可能是8pagex8bitsx128words那么完美的,必定有而外的列和页是不在显示的范围内,亦即第8页和第128~131列(如果页和列从0开始计算)。虽然说完成一次列填充,列地址会自动递增,然而ST7565P对于列地址的控制显得很笨蛋。假设一开始我们设置“页地址0和列地址0作为起始地址”,当列填充到127(如果从0开始计算),列地址会自动递增至128,这显然不是显示范围了(红色部分)。所以呀,每一次完成128次的列填充,就要“重新设置列起始地址和下一个页地址”。关于设置也地址的命令很简单,就是0xb?。“?”页地址的设置。假设输入0xb0,也就是页地址0。那么关于设置列地址的命令是0x1?,和0x0?。命令0x1?的“?”是列地址的“高四位”,0x0?的“?”是列地址的“低四位”。假设输入0x10,0x00,也就是说列地址是8'b0000_0000,亦即0。假设我要设置页地址1(00000001),和列地址65(01000001)。那么我需要输入:0xb1;0x14;0x01;通过几页的内容,我只是要读者明白ST7565P芯片驱动液晶的规则和一些基本的概念,真正的好戏儿在后头。上图是在黑金开发板上的12864液晶原理图。对于串行输入模式的液晶来说,重要的引脚有P/S,CS,A0,DB6(SCL)和DB7(SDI)而已。ST7565P芯片可以支持3种传输模式,当然最简单的传输模式还是SPI模式,然而控制“传输模式的引脚”就是P/S。当P/S被拉低时就是表示“串行传输模式”。CS是使能信号(低电平有效)。A0是命令或者数据决定信号(0=命令,1=数据)。SCL是串行时钟信号,SI是串行输入信号。至于其他的引脚属性自己去查相关的数据手册吧,这里只说重要的引脚而已。上图是ST7565P芯片,SPI传输的时序图。从图中我们可以明白,SI读取数据都是在SCL信号的上升沿。在这里我再重复一下:CS是使能信号。SI是串行数据输入信号。SCL是串行时钟信号。AO是决定当前的SI信号上的是命令还是数据(1=数据,0=命令)。在顺序操作上(以C语言为例),ST7565P芯片液晶的简易驱动概念如下:01//建立最基本的传输函数02SPI_Send{unsignedcharData}{}0304//建立传输数据函数05Send_Data(unsignedcharData)06{07A0=1;SPI_Send(Data);08......09}1011//建立传输命令函数12Send_Command(unsignedcharData)13{14A0=0;SPI_Send(Data);15......16}1718//建立初始化函数19Initial_Function()20{21//液晶显示初始化配置22Send_Command(0xaf);//液晶使能23Send_Command(0x40);//开始显示24Send_Command(0xa6);//此命令表达1=点亮,0=点灭2526//扫描次序配置27Send_Command(0xa0);//列扫描向左至右28Send_Command(0xc8);//也扫描从上至下2930//内部电源配置31Send_Command(0xa4);32Send_Command(0xa2);33Send_Command(0x2f);34Send_Command(0x24);35Send_Command(0x81);//背光LED配置命令36Send_Command(0x24);//背光LED配置值37}3839//绘图函数40Draw_Fucntion()41{42for(intpage=0;page8;page++)43{44Send_Command(0xb0|page);//设置页地址45Send_Command(0x10);//设置列地址“高四位”-000046Send_Command(0x00);//设置列地址“第四位”-00004748for(intx=0;x128;x++)Send_Data(*pic++);49}50}5152//主函数53intmain(void)54{55Initial_Function();56Draw_Function();5758whiel(1);//停止59}在顺序操作中,我们会先建立最基本的SPI_Send()函数,然后基于SPI_Send()函数又建立Send_Data()和Send_Command()等函数。接下来,会基于Send_Command()函数建立Initial_Function()函数,和基于Send_Data()函数建立Draw_Fucntion()函数。最后在主函数中调用Initial_Function()和Draw_Function()函数。在4-1章我说过了,顺序操作如同吃饭那样,有“步骤的概念”,然而顺序操作的语言都是偏向高级语言,所以在编辑上占到许多好处。很多重要的指令都是被隐性处理,如函数的调用指令和返回指令等。在上述的内容中,一些高级函数无视了许多隐性指令,只要简单的多次嵌入低层函数,就能形成Initial_Function()和Draw_Function()等高级函数。此外函数的调用也有很方便。那么VerilogHDL语言要如何模仿顺序操作呢?SPI发送模块上图所示是要建立的功能模块,spi_write_module.v亦即spi发送模块。为了最大发挥VerilogHDL语言特性,SPI_Data和SPI_Out的位配置如下:SPI_Data[9][8][7..0]CSA0DataSPI_Out[3][2][1][0]CSA0SCLSI在这里需要重申几个常常容易被疏忽的重点:我们知道SPI的时钟信号在“上升沿”的时候是“锁存数据”,在时钟信号的“下降沿”是“设置数据”。但是在单片机上编写SPI写函数,或者调用单片机SPI硬件资源来执行SPI写操作,我们常常会忽略了这些具体的细节。(那些有关使用单片机SPI硬件资源的事儿,我什么都不想说,因为这样的做法什么也学不到。)SPI_Send(unsignedcharData){CS=0;SCL=0;SPI_Send(unsignedcharData){CS=0;SCL=1;for(inti=0;i8;i++){if(Data&0x80)SI=1;elseSI=0;Data=1;SCL=0;SCL=1;}for(inti=0;i8;i++){SCL=0;if(Data&(7-i))SI=1;elseSI=0;SCL=1;}}}上面有两个SPI_Send函数,左边的写法是最常用,但是也是最容易忽略小细节。相比右边的写法比较谨慎,以最低的方法去符合一写小细节。对于SPI时钟信号,在空闲的时候总是处于高电平(几乎所有与上升沿有关的信号,在默认状态下都是处于高电平)。SPI时钟信号在下降沿“设置”SI数据(主机数据移位操作),反之SPI时钟信号在上升沿“锁存”数据(从机读取数据操作)。很明显左边的写法没有符合这个规则,然而右边的写法却符合这个规则。无论是左边的写法还是右边的写法,都忽略了一个致命的细节,两种写法都无法确定SPI时钟信号的时钟频率。当然可以基于上述的写法产生更笨拙的写法,如下:01SPI_Send(unsignedcharData)02{03CS=0;SCL=1;0405for(inti=0;i8;i++)06{07SCL=0;Delay_US(10);//添加延迟函数0809if(Data&(7-i))SI=1;10elseSI=0;1112SCL=1;Delay_US(10);//添加延迟函数13}14}哦!这样的此法只会浪费单片机宝贵的处理资源...除非这个单片机有置入实时操作系统,否则那样的活儿将会是非常的糟糕。虽然顺序操作的语言在“结构性”和“简易性”上,远远领先VerilogHDL语言。但是你别忘了我们可以利用VerilogHDL语言来“模仿”顺序操作。可能读者会误会“仿顺序操作”只是在外形上模仿“顺序操作”而已。但是实际上,我们可以借与VerilogHDL语言本身的特性,只要稍微用心去发挥一下,读者不仅可以模仿“顺序操作”的“操作概念”,而且还可以发挥出超越“顺序操作”本身的极限。虽然spi_write_module.v终究仅是模仿SPI_Send()函数这个部分而已,但是这不是代表我们可以拥有“只要目的,不要细节”这种盲目的态度。spi_write_module.vSCL的时钟频率定义为1Mhz,也就是说一个周期是1us,半周期就是0.5us。如果以20Mhz来定时,那么计数的结果是10。在19行定义了0.5us第23~33行是0.5us的定时器。但是比较不同的是,这个定时器平时不工作,当Start_Sig拉高的时候才开始计数(第30行)。第37~64行是spi_write_module.v的核心功能。I寄存器表示操作步骤,rCLK寄存器表示SCL然而rDO寄存器表示SI。如同前面所述那样,SCL时钟信号,处于空闲状态时是出于高电平,所以rCLK复位与逻辑1(46行)。在这里稍微提醒一下:SPI_Data:第9位表示CS,第8位表示A0,第7..0位表示一字节数据。SPI_Out:第3位表示CS,第2位表示A0,第1位表示SCL,第0位表示SI。当Start_Sig拉高的同时,定时器开始计数(30行),该模块也开始执行(50行)。当第一个定时产生的时候(54行),也就是第一个时钟的前半周期,亦即下降沿,rCLK设置为逻辑0。根据SPI传输的规则,下降沿的时候主机设置数据,rDO赋予SPI_Data信号的第7位(SPI传输是从最高位开始,最低位结束),最后i递增以示下一个步骤。当i等于1的时候并且定时产生(56行),这表示第一个时钟的后半周期,亦即上升沿,rCLK设置为逻辑1。在SPI传输的规则中上升沿的时候,从机锁存数据,从机自己单纯的将rCL