12864ST7565P液晶驱动.docx

上传人:b****5 文档编号:3434423 上传时间:2022-11-23 格式:DOCX 页数:29 大小:1.15MB
下载 相关 举报
12864ST7565P液晶驱动.docx_第1页
第1页 / 共29页
12864ST7565P液晶驱动.docx_第2页
第2页 / 共29页
12864ST7565P液晶驱动.docx_第3页
第3页 / 共29页
12864ST7565P液晶驱动.docx_第4页
第4页 / 共29页
12864ST7565P液晶驱动.docx_第5页
第5页 / 共29页
点击查看更多>>
下载资源
资源描述

12864ST7565P液晶驱动.docx

《12864ST7565P液晶驱动.docx》由会员分享,可在线阅读,更多相关《12864ST7565P液晶驱动.docx(29页珍藏版)》请在冰豆网上搜索。

12864ST7565P液晶驱动.docx

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寄存是用

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 学科竞赛

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1