12864ST7565P液晶驱动.docx
《12864ST7565P液晶驱动.docx》由会员分享,可在线阅读,更多相关《12864ST7565P液晶驱动.docx(29页珍藏版)》请在冰豆网上搜索。
![12864ST7565P液晶驱动.docx](https://file1.bdocx.com/fileroot1/2022-11/22/7b41648a-b565-46f4-84ab-b68737794eb7/7b41648a-b565-46f4-84ab-b68737794eb71.gif)
12864ST7565P液晶驱动
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
//建立最基本的传输函数
02
SPI_Send{unsigned char Data}{}
03
04
//建立传输数据函数
05
Send_Data(unsigned char Data)
06
{
07
A0=1;SPI_Send(Data);
08
......
09
}
10
11
//建立传输命令函数
12
Send_Command(unsigned char Data)
13
{
14
A0=0;SPI_Send(Data);
15
......
16
}
17
18
//建立初始化函数
19
Initial_Function()
20
{
21
//液晶显示初始化配置
22
Send_Command(0xaf); //液晶使能
23
Send_Command(0x40); //开始显示
24
Send_Command(0xa6); //此命令表达1=点亮,0=点灭
25
26
//扫描次序配置
27
Send_Command(0xa0); //列扫描向左至右
28
Send_Command(0xc8); //也扫描从上至下
29
30
//内部电源配置
31
Send_Command(0xa4);
32
Send_Command(0xa2);
33
Send_Command(0x2f);
34
Send_Command(0x24);
35
Send_Command(0x81); //背光LED配置命令
36
Send_Command(0x24); //背光LED配置值
37
}
38
39
//绘图函数
40
Draw_Fucntion()
41
{
42
for( int page=0;page<8;page++)
43
{
44
Send_Command(0xb0|page); //设置页地址
45
Send_Command(0x10); //设置列地址“高四位”-0000
46
Send_Command(0x00); //设置列地址“第四位”-0000
47
48
for( int x=0;x<128;x++)Send_Data(*pic++);
49
}
50
}
51
52
//主函数
53
int main( void )
54
{
55
Initial_Function();
56
Draw_Function();
57
58
whiel
(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]
CS
A0
Data
SPI_Out
[3]
[2]
[1]
[0]
CS
A0
SCL
SI
在这里需要重申几个常常容易被疏忽的重点:
我们知道SPI的时钟信号在“上升沿”的时候是“锁存数据”,在时钟信号的“下降沿”是“设置数据”。
但是在单片机上编写SPI写函数,或者调用单片机SPI硬件资源来执行SPI写操作,我们常常会忽略了这些具体的细节。
(那些有关使用单片机SPI硬件资源的事儿,我什么都不想说,因为这样的做法什么也学不到。
)
SPI_Send(unsignedcharData)
{
CS=0;SCL=0;
for(inti=0;i<8;i++)
{
if(Data&0x80)SI=1;
elseSI=0;
Data<<=1;
SCL=0;
SCL=1;
}
}
SPI_Send(unsignedcharData)
{
CS=0;SCL=1;
for(inti=0;i<8;i++)
{
SCL=0;
if(Data&(7-i))SI=1;
elseSI=0;
SCL=1;
}
}
上面有两个SPI_Send函数,左边的写法是最常用,但是也是最容易忽略小细节。
相比右边的写法比较谨慎,以最低的方法去符合一写小细节。
对于SPI时钟信号,在空闲的时候总是处于高电平(几乎所有与上升沿有关的信号,在默认状态下都是处于高电平)。
SPI时钟信号在下降沿“设置”SI数据(主机数据移位操作),反之SPI时钟信号在上升沿“锁存”数据(从机读取数据操作)。
很明显左边的写法没有符合这个规则,然而右边的写法却符合这个规则。
无论是左边的写法还是右边的写法,都忽略了一个致命的细节,两种写法都无法确定SPI时钟信号的时钟频率。
当然可以基于上述的写法产生更笨拙的写法,如下:
01
SPI_Send(unsigned char Data)
02
{
03
CS=0;SCL=1;
04
05
for( int i=0;i<8;i++)
06
{
07
SCL=0;Delay_US(10); //添加延迟函数
08
09
if(Data&(7-i))SI=1;
10
else SI=0;
11
12
SCL=1; Delay_US(10); //添加延迟函数
13
}
14
}
哦!
这样的此法只会浪费单片机宝贵的处理资源...除非这个单片机有置入实时操作系统,否则那样的活儿将会是非常的糟糕。
虽然顺序操作的语言在“结构性”和“简易性”上,远远领先VerilogHDL语言。
但是你别忘了我们可以利用VerilogHDL语言来“模仿”顺序操作。
可能读者会误会“仿顺序操作”只是在外形上模仿“顺序操作”而已。
但是实际上,我们可以借与VerilogHDL语言本身的特性,只要稍微用心去发挥一下,读者不仅可以模仿“顺序操作”的“操作概念”,而且还可以发挥出超越“顺序操作”本身的极限。
虽然spi_write_module.v终究仅是模仿SPI_Send()函数这个部分而已,但是这不是代表我们可以拥有“只要目的,不要细节”这种盲目的态度。
spi_write_module.v
SCL的时钟频率定义为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传输的规则中上升沿的时候,从机锁存数据,从机自己单纯的将rCLK拉高即可。
然后i递增以示下一个步骤。
上述的步骤会一直重复到第八次,直到一字节的数据发送完毕。
最后会产生一个完成信号(59~63行)。
这里有一个表达式需要说明一下:
i>>1:
表示i除与2。
因为右移操作也是代表除与j^2,j是右移次数。
假设8>>2,亦即8/2^2等于2。
8>>3,亦即8/2^3等于1。
最后还有一个重点就是SPI_Out的驱动(70行)。
在上面我已经重复过SPI_Out是占4位的输出。
而且每一个位都有意义。
SPI_Out第3位:
表示了CS,所以直接由SPI_Data的第9位驱动。
SPI_Out第2位:
表示了A0,同样也是直接由SPI_Data的第8位驱动。
SPI_Out第1位:
表示了SCL,以寄存器rCLK来驱动。
SPI_Out第0位:
表示了SI,以寄存器rDO来驱动。
这样的目的是简化连线的复杂度。
我们知道VerilogHDL语言的位操作是很强大。
懂得善用,会对建模提到很大的帮助。
初始化模块
乍看initial_module.v既包含了initial_control_module.v和spi_write_module.v。
spi_write_module.v前面已经说过了,至于initial_control_module.v吗~我们知道我们需要一个控制模块来执行,初始化的步骤,而该模块就是这个初衷。
initial_control_module.v
第11~17行定义了输出和输入口相关的信息,具体和图形一样。
在22行定义了rData寄存器,它是用来驱动SPI_Data(94行)。
第23行定义了isSPI_Start标志寄存器,如命名般一样,是用来驱动SPI_Start_Sig,换句话就是SPI发送模块的是能信号。
第26~88是该模块的核心部分。
当上一层将Start_Sig拉高的时候(注意:
initial_control_module.v的Start_Sig外部连线是Initial_Start_Sig),该模块就开始工作(35行)。
全核心部分都是使用“仿顺序操作”的写法。
前三个命令是液晶的“显示配置命令”(38~48行),然而我们知道要对液晶写数据的时候,CS和A0都必须拉低,由于SPI_Data位分配的关系。
rData寄存器第9..8位都是赋予2'b00。
假设i等于0。
那么机会发送第一个命令,亦即0xaf,
(39行)一开始由于条件if没有达到,(40行)rData会被赋予2'b00,8'haf,并且isSPI_Start会设置位逻辑1,这时候SPI发送模块就会开始工作。
直到SPI发送模块发送一字节数据,并且反馈一个完成信号的高脉冲(SPI_Done_Sig),if条件就会成立(39行),然后isSPI_Start就会被设置为逻辑0,然后i递增以示下一步步骤。
类似上面的操作会一直重复,直到完成发送3个“显示配置命令”,2个“扫描次序配置命令”,和6个“内部电源配置命令”(38~80行)。
直到最后该模块会反馈一个完成信号给上一层模块(82~86行),并且(83行)复位rData寄存器(前两位必须设置为逻辑1,而后八位可以是任意值)。
initial_module.v
initial_module.v是initial_control_module.v和spi_write_module的组合模块。
连线关系基本上和“图形一样”。
有一点可能会使读者们困惑。
因为“低级建模”的全部功能不可能在一个模块中完成,多多少少,读者们会对模块与模块之间的关系会有“不解”的情况。
笔者在这里要求读者们要保持平常心去理解,因为VerilogHDL语言的建模本来就需要很强的逻辑性。
目前面对的难题就当做是为日后的修行吧。
绘图模块
draw_module.v是一个组合模块,同样draw_module.v有包含spi_write_module.v。
此外draw_module.v也含有draw_control_module.v和pika_rom_module.v,pika_rom_module.v是一个8bitsx1024words的rom。
draw_control_module.v控制模块主要是控制绘图的所有操作步骤,然而pika_rom_module.v包含了所需要的图片资料。
该控制模块对spi_write_module.v的链接也和initial_control_module.v一样。
draw_control_module.v
第13~20行的定义基本上都和“图形”一样,除了Start_Sig和Done_Sig比较特别,它们在外部的连线时Draw_Start_Sig和Draw_Done_Sig。
第31~67行是该模块的核心部分,但是别被它吓到了,它不过是充气胖子。
在这里我们简单复习一下在“顺序操作”中的Draw_Function()的操作。
01
Draw_Fucntion()
02
{
03
for( int page=0;page<8;page++)
04
{
05
Send_Command(0xb0|page); //页地址配置
06
Send_Command(0x10); //列地址高四位配置
07
Send_Command(0x00); //列地址第四位配置
08
09
for( int x=0;x<128;x++)Send_Data(*p++); //发送128次列填充
10
}
11
}
上述的一段函数代码中,一个Draw_Function()函数的功能表达的一了百了,而且该函数中最大作用就是for循环,很可惜VerilogHDL语言是不推荐使用for循环。
(不要问我为什么,很多的参考书上都是这样写的,如果以我的角度说,我表示for循环不适合VerilogHDL语言的风格)。
在Draw_Function()函数之中,第一个for循环控制page,亦即页。
并且在每一个页的开始都重新配置列地址。
至于第二个for循环是用于控制128次的列填充。
那么VerilogHDL语言该如何呢?
在34~39行中,i控制执行步骤,x控制列扫描地址(列填充次数),y控制页扫描次序,rData是用来驱动SPI_Out(73行),而isSPI_Start是用来驱动SPI_Start_Sig。
当Start_Sig被拉高的时候(41行),该控制模块就开始工作。
我们先假设一个情况:
当i等于0的时候,由于if条件不成立(45行)。
由于“顺序操作”关系,必须先设置页地址,rData被赋予2'b00(CS=0,A0=0,亦即发送命令)和y寄存器的值,Y寄存器复位值是8'd0。
然后isSPI_Start寄存器被设置为逻辑1(46行)。
此时SPI发送模块开始工作。
当SPI发送完一字节的数据,就会反馈一个高脉冲至完成信号SPI_Done_Sig。
此时if条件就会成立(45行),isSPI_Start寄存器被设置为0,然后i递增以示下一步步骤。
当页地址设置完毕后,接下来的操作就要设置列地址。
48~50行是设置列地址的高四位,52~54行是设置列地址的第四位。
具体操作和设置也地址一样。
不一样的是,每一次设置“新一页”,列地址都必须复原为0。
当页地址和列地址设置okay后,接下来就是128次的列填充操作了。
x寄存是用