学习情境2基于DS1302和LCD1602的可调数字钟

更新时间:2024-05-26 04:37:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

作者 张小波

学习情境2-可调式数字钟

基于DS1302和LCD1602的可调数字钟

前面的任务中我们学会了使用DS1302,知道了如何在单片机系统中的连接,也详细的学习了如何得到DS1302的时钟,并且我们使用了16个数码管把年月日和时分秒实时地显示出来。但数码管的显示毕竟有其自身的缺陷,现在在工业控制的各个环节,都使用液晶进行人机联系。

在日常生活中,我们对液晶显示器并不陌生。液晶显示模块已作为很多电子产品的通用器件,如在计算器、万用表、电子表及很多家用电子产品中都可以看到,显示的主要是数字、专用符号和图形。在单片机的人机交流界面中,一般的输出方式有以下几种:发光管、LED数码管、液晶显示器。发光管和LED数码管比较常用,软硬件都比较简单。

本任务中,我们使用LCD1602液晶显示器芯片作为时钟显示的硬件,这将大大简化电路结构,当然,在软件设计方面就比数码管复杂得多了。通过本任务的学习。希望能掌握LCD1602的软硬件设计方法,为以后学习功能更强大的液晶芯片作好知识储备。

3.1任务设计要求和设计原理 一、设计要求

1、利用专用时钟芯片DS1302获得秒、分、时、日、月、年等时间单位。 2、用液晶显示芯片LCD1602实时显示时间 3、能通过外部按键调节时间

4、利用Proteus软件设计电路原理图、PCB图、并结合软件进行仿真测试 5、利用Keil软件设计C语言源程序,通过编译、链接后生成HEX文件

二、设计原理

本任务主要由单片机AT89C52、时钟芯片DS1302、液晶显示芯片LCD1602等元器件组成。单片机是整个系统的主控芯片,主要负责对时钟芯片内部数据进行读写、并把读取的数据处理后实时送给数码管显示;DS1302时钟芯片则产生本任务所需的时间: 秒、分、时、日、月、年这6个时间单元,DS1302通过串行方式与单片机相连,在该芯片的时钟及复位引脚信号的控制下实现与单片机的数据通信,同时,利用4输入与门和4个独立按键实行对时间的调节,可调节年、月、日、时、分等5个时间单元,星期时间的调节会自动进行。在时间的显示方面,使用了LCD1602,这大大简化了电路结构,LCD1602通过P0口与单片机相连,显示的时间数据均由单片机的P0口输出,P0口外接上拉电阻。

当系统上电后,LCD1602显示时间(在仿真条件下,可设臵为显示电脑时间),反复按下K1按键,可依次调节年、月、日、时、分,按下K2按键,可对相应时间进行加调节,按

作者 张小波

下K3按键,可对相应时间进行的减调节,按下确定按键K4,表示时间调节完毕。

3.2 硬件设计 一、硬件电路设计

本硬件系统电路包括:单片机最小系统(包含时钟电路、复位电路和电源电路)、LCD显示电路、按键调节电路和DS1302时钟电路。系统原理框图如下图所示:

按键调节电路 图3-1系统框图

时钟电路、复位电路、电源电路 8051 单 片 机 LCD显示电路 DS1302时钟电路

系统各部分电路结构简单,在此不一一单独介绍,只简单介绍LCD显示电路,其电路设计原理图如图3-2所示。

图3-2 LCD1602液晶显示电路

从图可知,液晶芯片LCD1602的D0-D7与单片机的P0口相连接,且也外接上拉电阻。引脚RS、RW和E分别与P2口的P2.O、P2.1和P2.2连接,当然你愿意的话也可以和其他未使

作者 张小波

用的端口相连。

其余电路不作介绍,本任务整体系统电路原理图如图3-3所示。

图3-3 整体系统电路原理图

二、 LCD1602技术资料

在单片机系统中应用液晶显示器作为输出器件有以下几个优点:(1)显示质量高,由于液晶显示器每一个点在收到信号后就一直保持那种色彩和亮度,恒定发光,而不像阴极射线管显示器(CRT)那样需要不断刷新新亮点。因此,液晶显示器画质高且不会闪烁。(2)数字式接口,液晶显示器都是数字式的,和单片机系统的接口更加简单可靠,操作更加方便。(3)体积小、重量轻,液晶显示器通过显示屏上的电极控制液晶分子状态来达到显示的目的,在重量上比相同显示面积的传统显示器要轻得多。(4)功耗低,相对而言,液晶显示器的功耗主要消耗在其内部的电极和驱动IC上,因而耗电量比其它显示器要少得多。

1、液晶显示原理:液晶显示的原理是利用液晶的物理特性,通过电压对其显示区域进行控制,有电就有显示,这样即可以显示出图形。液晶显示器具有厚度薄、适用于大规模集成电路直接驱动、易于实现全彩色显示的特点,目前已经被广泛应用在便携式电脑、数字摄像机、PDA移动通信工具等众多领域。

2、液晶显示器的分类:液晶显示的分类方法有很多种,通常可按其显示方式分为段式、字符式、点阵式等。除了黑白显示外,液晶显示器还有多灰度有彩色显示等。如果根据驱动方式来分,可以分为静态驱动(Static)、单纯矩阵驱动(Simple Matrix)和主动矩阵驱动(Active Matrix)三种。

作者 张小波

3、液晶显示器各种图形的显示原理:

线段的显示:点阵图形式液晶由M×N个显示单元组成,假设LCD显示屏有64行,每行有128列,每8列对应1字节的8位,即每行由16字节,共16×8=128个点组成,屏上64×16个显示单元与显示RAM区1024字节相对应,每一字节的内容和显示屏上相应位臵的亮暗对应。例如屏的第一行的亮暗由RAM区的000H——00FH的16字节的内容决定,当(000H)=FFH时,则屏幕的左上角显示一条短亮线,长度为8个点;当(3FFH)=FFH时,则屏幕的右下角显示一条短亮线;当(000H)=FFH,(001H)=00H,(002H)=00H,……(00EH)=00H,(00FH)=00H时,则在屏幕的顶部显示一条由8段亮线和8条暗线组成的虚线。这就是LCD显示的基本原理。

字符的显示:用LCD显示一个字符时比较复杂,因为一个字符由6×8或8×8点阵组成,既要找到和显示屏幕上某几个位臵对应的显示RAM区的8字节,还要使每字节的不同位为“1”,其它的为“0”,为“1”的点亮,为“0”的不亮。这样一来就组成某个字符。但由于内带字符发生器的控制器来说,显示字符就比较简单了,可以让控制器工作在文本方式,根据在LCD上开始显示的行列号及每行的列数找出显示RAM对应的地址,设立光标,在此送上该字符对应的代码即可。

汉字的显示:汉字的显示一般采用图形的方式,事先从微机中提取要显示的汉字的点阵码(一般用字模提取软件),每个汉字占32B,分左右两半,各占16B,左边为1、3、5……右边为2、4、6……根据在LCD上开始显示的行列号及每行的列数可找出显示RAM对应的地址,设立光标,送上要显示的汉字的第一字节,光标位臵加1,送第二个字节,换行按列对齐,送第三个字节……直到32B显示完就可以LCD上得到一个完整汉字。

1602字符型LCD简介

字符型液晶显示模块是一种专门用于显示字母、数字、符号等点阵式LCD,目前常用16*1,16*2,20*2和40*2行等的模块。一般1602字符型液晶显示器实物如图3-4:

图3-4 1602字符型液晶显示器实物图

1602LCD分为带背光和不带背光两种,其控制器大部分为HD44780,带背光的比不带背光的厚,是否带背光在应用中并无差别。

1602LCD主要技术参数:显示容量:16×2个字符;芯片工作电压:4.5—5.5V;工作电流:2.0mA(5.0V);模块最佳工作电压:5.0V;字符尺寸:2.95×4.35(W×H)mm

引脚功能说明:

1602LCD采用标准的14脚(无背光)或16脚(带背光)接口,各引脚接口说明如表1所示:

第1脚:VSS为地电源。 第2脚:VDD接5V正电源。

第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度。

作者 张小波

第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。 第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平R/W为高电平时可以读忙信号,当RS为高电平R/W为低电平时可以写入数据。

第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。 第7~14脚:D0~D7为8位双向数据线。 第15脚:背光源正极。 第16脚:背光源负极。

表1 1602LCD引脚接口说明

编号 符号 1 2 3 4 VSS VDD 引脚说明 电源地 电源正极 编号 符号 引脚说明 9 10 D2 D3 D4 D5 D6 D7 数据 数据 数据 数据 数据 数据 VL 液晶显示偏压 11 RS 数据/命令选择 12 读/写选择 使能信号 数据 数据 13 14 5 R/ W 6 7 8 E D0 D1 15 BL A 背光源正极 16 BL K 背光源负极 1602LCD的指令说明及时序 1602液晶模块内部的控制器共有11条控制指令,如表2所示:

表2:控制命令表

序号 1 2 3 4 5 6 指令 复位显示器 光标返回 字符进入模式 显示开/关控制 光标或字符移位 功能设臵 RS R/W D7 D6 D5 D4 D3 D2 D1 D0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 1 功能 清屏,光标归位 变,光标移到左上角 * 设臵地址计数器清零,DDRAM数据不1 I/D S 设臵字符进入时的屏幕移位方式 D C B 设臵显示开关,光标开关,闪烁开关 * 设臵字符与光标移动 * 设臵DL,显示行数,字体 1 S/C R/L * DL N F * 7 设臵字符发生存贮器地址 0 8 9 设臵数据存贮器地址 读忙标志或地址 0 0 字符发生存贮器地址 设臵6位的CGRAM地址以读/写数据 显示数据存贮器地址 计数器地址 设臵7位的DDRAM地址以读/写数据 读忙标志及地址计数器 1 BF 0 10 写数据到CGRAM或DDRAM) 1 写入一字节数据,需要先设臵RAM地向CGRAM/DDRAM写入一字节的数据 址 读取入一字节数据,需要先设臵RAM从CGRAM/DDRAM读取一字节的数据 地址 11 从CGRAM或DDRAM读数据 1 1

作者 张小波

float a[10]; ???。。

} 是非法的!

(7)允许在同一个类型说明中,说明多个数组和多个变量; 例如: int a,b,c,d[10],e[5]; 2、一维数组元素的初始化

有下列方法初始化:

1)在定义数组时,对数组元素赋初值;

例如: int a[10]={0,1,2,3,4,5,6,7,8,9}; 上面的语句等价于 a[0]=0,a[1]=1,

2)可以只给一部分元素赋初值,例如:int a[10]={0,1,2,3,4};

表示只给数组的前5个元素赋初值,后5 个元素的值,系统自动默认为0 3)在对全部数组元素赋初值时,可以不指定数组长度;例如:int a[5]={0,1,2,3,4};可以改写为: int a[]={0,1,2,3,4};但是,int a[10] ={0,1,2,3,4}; 不能改写为: int a[]={0,1,2,3,4}; 3、一维数组的引用

数组必须先定义,后使用。C语言规定: 只能逐个引用数组元素,而不能一次引用整个数组!

数组的引用形式为: 数组名[下标] 其中,下标可以是整型常量也可以是整型表达式。例如: a[0]=a[5]+a[7]+a[2*3] 4、一维数组的程序举例

(1)读10个数存入数组中,输出数组中的所有数据。 main() {

int i,a[10];

for(i=0;i<=9;i++)

a[i]=i; /*顺序给数组元素赋初*/ for(i=0;i<=9;i++)

printf(“%d”,a[i]); /*顺序输出数组元素*/} (2)读10个整数存入数组中,输出平均值。 #include #define size 10 main() {

int x[size],i; float s=0,ave;

for(i=0;i

printf(“%d\\n”,ave);} 二、二维数组

1、二维数组的定义

作者 张小波

定义格式:类型说明符 数组名[常量表达式1][常量表达式2]

其中表达式1表示第一维下标的长度;表达式2表示第二维下标的长度;

例如:int a[2][3];说明了一个2行3列的数组,数组名为a,数组元素的类型为整型,该数组共有2*3个元素;即: a[0][0],a[0][1],a[0][2],a[1][0],a[1][1],a[1][2] 注意的是:二维数组在概念上是二维的,就是说其下标在两个方向上变化,数组元素在数组中的位置也处于一个平面中,并不向一维数组,所有的元素是线性的,但是,二维数组中的元素和一维数组中的元素一样,也是按线性存储的;

如何存储呢?

首先,它和一维数组一样,也是按线性存储的。在C语言中,二维数组是按行排列的,即存完一行后,顺序存入第二行;还是上面那个例子: int a[2][3];由于数组a说明为int型,所以数组的每个元素在内存中占两个字节的存储空间, 2、二维数组的引用

引用形式:数组名[下标][下标]

其中下标应该为整型常量或整型表达式;

例如:int a[3][4];a[2][3] 表示数组a的第三行第四列的元素。

注意:下标变量和数组说明在形式上有些相似,但这两者具有完全不同的含义;数组说明的方括号中给出的是某一维的长度;而数组元素中的下标是该元素在数组中的位置标识;数组说明中的方括号内只能是常量。而数组元素中方括号中的下标可以是常量,变量或表达式。

3、二维数组的初始化

二维数组初始化也是在类型说明时给各下标变量赋以初值。二维数组可以按行分段赋值,也可以按行连续赋值 1)按行分段赋值

int a[2][3]={{1,2,3},

{4,5,6}};

2)按行连续赋值

int a[2][3]={1,2,3,4,5,6}; 这两种赋值的结果是完全相同的; 说明:

1)可以只对部分元素赋值,未赋值的元素自动取0; 例如: int a[3][3]={{1},{2},{3}};

是对每行的第一列元素赋值,未赋值的元素自动取0

2)如果对所有的元素赋初值,则第一维的长度可以不给出; 例如: int a[3][3]={1,2,3,4,5,6,7,8,9}; 可以改为: int a[][3]={1,2,3,4,5,6,7,8,9};

3)数组是一种构造类型的数据,二维数组可以看作是一维数组的嵌套,设一维数组的每个元素又都是一个数组,就组成了一个二维数组,当然,前提是每个元素的类型必须一致;同理,一个二维数组也可以分解为多个一维数组,例如: a[3][4],可分解为3个一维数组,其数组名分别是: a[0],a[1],a[2];对这三个一维数组不需另作说明即可使用,这三个一维数组都有4个元素,例如: 一维数组a[0]的元素是a[0][0],a[0][1],a[0][2],a[0][3];必须强调的是,a[0],a[1],a[2]不能当作下标变量使用,因为它们是数组名,不是一个单纯的下标变量;

对于一个数组,例如: a[3][4]可以通过循环语句来赋值; for(i=0;i<3;i++)

作者 张小波

for(j=0;j<4;j++)

scanf(“%d”,&a[i][j]); 4、二维数组的使用举例

1)将一个二维数组的行和列元素互换,存到另一个二维数组中。 #include main() {

int a[2][3]= {{1,2,3},{4,5,6}}; int b[3][2],i,j; for(i=0;i<=1;i++) {

for(j=0;j<=2;j++) {

printf(“%d”,a[i][j]); b[j][i]=a[i][j]; }

printf(“\\n”); }

for(i=0;i<=2;i++) {

for(j=0;j<=1;j++)

printf(“%d”,b[i][j]); printf(“\\n”); } }

三、字符型数组

1、字符型数组的定义

定义: 每个数组元素的数据类型是字符型的一维数组。 字符数组的引用,存储,初始化的方法和一维数组相同

例如: 字符型数组的定义

char c[10];定义了字符型数组c,它的数组元素有10个,由于字符型和整型是相通的,因此,上面的定义也可以改为:int c[10];

例如: 字符型数组的初始化 char c[3]={?a?,?b?,?c?};

注意: 如果花括号提供的数组元素个数大于数组长度,则作语法错误处理;如果初值个数小于数组长度,则只将这些字符赋给前面的元素,其余的元素自动定为空字符(?\\0?); 比如:char c[5]={?a?,?b?,?c?}; a b c \\0 \\0 如果提供的初值个数和预定的数组长度相同,在定义时可以省掉数组长度,系统自动根据初值个数确定数组长度。 例如:char a[]={?a?,?b?,?c?,?d?,?e?}; 也可以用字符串常量赋值;

如:char ch[6]={“hello”};

char ch[6]=”hello”; char ch[]=”hello”;

作者 张小波

h e l l o \\0 2、字符型数组的引用 可以引用字符数组中的一个元素即得到一个字符! 例如: 输出一个字符串 Main() {

char a[5]={?a?,?b?,?c?,?d?,?e? }; int i;

for(i=0;i<5;i++) printf(“%c”,a[i];) printf(“\\n”); }

四、字符串 1、字符串定义

定义: C语言中用双引号括起来的任意字符序列就是字符串。

C语言中没有字符串变量,对程序中的字符串,系统用字符数组方式保存,连续,顺序的存放每一个字符,最后加上一个空字符”\\0”作为结束标志! 2、字符串的输入输出

1)通过逐个字符输入和输出: 使用%c; 2)通过整个字符串输入和输出:使用%s; 3、字符串数组

字符串数组就是数组中的每个元素又都是存放字符串的数组。例如: 可以将一个二维字符数组看成是一个字符串数组;char line[10][80]; 数组line共有10个元素,每个元素可以存放80个字符(79个普通字符,一个结束字符),第一个下标决定字符串的个数,第二个下标决定字符串的最大长度,line是有十个字符串的数组,这些字符串的最大长度是79。 字符串的初始化方法:

例如: char str[3][5]={“a”,”ab”,”abc”}; /*根据定义的大小初始化*/

char str[][5]={“a”,”ab”,”abc”}; /*根据右边字符串的个数,定义数组大小*/ 该数组的存储示意图为: a \\0 a a b b \\0 c \\0 3.4 软件硬件的仿真调试

1、首先利用ISIS-Professional软件设计好硬件电路图。

2、其次,利用KEIL软件建立工程项目,把用C语言编好的程序通过该软件平台进行编译,直至把C程序修改无错误为止,并生成HEX文件。

3、在ISIS-Professional软件平台下导入由KEIL软件生成的HEX文件,进行仿真,仔细观察产生的现象和效果,根据仿真的效果,边修改边调试,直到仿真效果与我们对项目的要求一致为止。

作者 张小波

本任务我们用单片机和时钟芯片DS1302和液晶LCD1602设计了一个电子时钟, 通过仿真,本系统能够准确的显示秒、分、时、星期、年、月、日等时间,由于使用了专门的时钟芯片和液晶显示芯片,比前一个任务无论视觉效果更佳。

3.5 知识拓展-I/O接口扩展(二)

一、I/O扩展常用芯片

(1)TTL/CMOS锁存器/缓冲器芯片: 如74LS377、74LS374、74LS373、74LS273、74LS244、74LS245等;

(2)通用可编程I/O接口芯片: 如8255、8155、8729等; (3)可编程阵列: 如GAL16V8、GAL20V8等。 I/O扩展中应注意的几个问题

(1)访问扩展I/O的方法与访问外部数据存储器完全相同,使用相同的指令。 (2)扩展多片I/O芯片或多个I/O设备时,注意总线的驱动能力问题;

(3)扩展I/O口的目的是为了单片机与外部设备进行信息交换而设臵的一个输入输出通道,I/O口最终与外设相连。

(4)在软件设计时,I/O口对应初始状态设臵、工作方式选择要与外接设备相匹配。 二、扩展简单并行接口 1.扩展并行输出口

(1)用74LS377扩展并行输出口

74LS377 是带有输出允许端的8D锁存器,有8个输入端口、8个输出端口、1个时钟输入端 CLK(上升沿有效)和1个允许控制端OE。如图3-9所示,OE与P2.7相连,74LS377的地址为7FFFH; 若与P2.0相连,则地址相应为0EFFH。

图3-9 MCS-51扩展输出口74LS377

【例3.1】 若以图3-8为接口电路,将片内RAM地址为50H单元的数据通过该电路输出。程序清单如下:

MOV DPTR,#7FFFH ;数据指针指向74LS377

MOV A,50H ;输出的50H单元数据送累加器A

作者 张小波

MOVX @DPTR,A ;P0口将数据通过74LS377输出 (2)用74LS374扩展并行输出口

74LS374是具有三态输出的8D边沿触发器,其功能与74LS377相似, 74LS374与单片机接口电路如图3-10所示,74LS374的地址为7FFFH。74LS374具有较强的驱动能力,输出低电平电流IOL最大可达24mA,是74LS377的3倍。在有较强驱动能力要求场合,可选用74LS374作为并行口扩展器件。

图3-10 74LS374与单片机接口电路

2.扩展并行输入口

并行输入扩展口比较简单,只需采用8位缓冲器即可。常用的缓冲器有74LS244, 74LS244为单向总线缓冲器,只能一个方向传输数据。并行输入接口与单片机连接如图3-11所示。

图3-11 扩展74LS244并行输入口

【例3.2】 如图3-10所示,扩展并行输入口,将输入口中的8位数据送片内51H单元。程序清单如下:

MOV DPTR,#7FFFH ;数据指针指向74LS244

作者 张小波

MOVX A,@DPTR ;外部数据经过74LS244送入累加器A MOV 51H,A ;数据送51H单元保存

将上述输入输出电路合并即如图3-12所示。在图3-12的输入输出接口电路中,输入采用三态门74HC244,输出采用8D触发器(锁存器)74HC374。P0口为双向数据线,既能从74HC244输入数据,又能将数据通过74HC374输出。输出控制信号由P2.0和WR合成,当两者同时为低电平时,或门输出0,将P0口数据锁存到74HC374,其输出控制着发光二极管LED。当某线输出为0时,该线上的LED发光。输入控制信号由P2.0和RD合成,当二者同时为低电平时,或门输出为0,选通74HC244,将外部信息输入到总线。与74HC244相连的按键开关没有按下时,输入全为1;若按下某键则所在的线输入为0。可见,输入输出都是在P2.0为0时有效,因此它们的口地址为0FEFFH,即占用相同的地址空间。但是由于分别用RD和WR信号控制,因此不会发生冲突。

图3-12 合并输入输出口

正如前面所提到的,扩展I/O口和扩展外部RAM一样,因此访问外部I/O口就像访问外部 RAM一样,用的是 MOVX类指令。对于图3-11,如果需要实现的功能是按下任意一个按键,对应的LED发光,则程序如下:

LOOP: MOV DPTR,#0FEFFH ;数据指针指向扩展I/O地址

MOVX A,@DPTR ;从74HC244读入数据,检测按键 MOVX @DPTR,A ;向74HC374输出数据,驱动LED SJMP LOOP ;循环

三、可编程并行I/O口的扩展

1、RAM/IO扩展芯片8155

Intel 8155是一个具有RAM、I/O和计数器的通用可编程接口多功能芯片。其具有的资源为:256B的静态RAM;两个可编程的8位并行I/O口PA和PB;一个可编程的6位并行I/O口PC;一个可编程14位减计数器TC;8位地址锁存器。 (1)8155的结构及引脚功能

8155的引脚如图3-13(a)所示,逻辑框图如图3-13(b)所示。8155引脚符号的含义和功能如下:

AD0~AD7: 地址数据线;

IO/M: IO/RAM选择信号端,输入高电平选择I/O操作,低电平选择访问片内RAM;

作者 张小波

CE: 片选信号输入端,低电平有效; RD: 读选通输入端,低电平有效; WR: 写选通输入端,低电平有效; TI: 计数器计数脉冲输入端;

TO:计数器的输出信号端,输出波形由内部定时工作方式决定; PA0~PA7: 8位并行I/O口; PB0~PB7: 8位并行I/O口; PC0~PC5: 6位并行I/O口;

ALE: 地址锁存信号输入端,其下降沿时,锁存AD0~AD7上的地址。 RESET: 复位输入引脚,高电平复位。 Vcc: 电源+5V; Vss: 接地。

图3-13 8155引脚及逻辑框图

(2)8155的RAM和I/O地址编码

8155的I/O端口及RAM地址在单片机应用系统中与外部数据存储器是统一编址的,其控制操作见表 3-4,对应I/O口寄存器的地址编码见表3-5。

8155当IO/M为0时,单片机对8155的RAM进行操作,共256B,低八位的地址为:00H—0FFH。8155当IO/M为1时,单片机对8155的I/O口进行操作。

表3-4 8155控制操作表 低八位地址 xxxxx000B xxxxx001B xxxxx010B xxxxx011B xxxxx100B xxxxx101B I/O口 命令/状态口 PA口 PB口 PC口 定时器/计数器低字节寄存器 定时器/计数器高字节寄存器

作者 张小波

表3-5 8155的端口地址编码 AD7 × × × × × × AD6 AD5 × × × × × × × × × × × × AD4 × × × × × × AD3 × × × × × × AD2 0 0 0 0 1 1 AD1 0 0 1 1 0 0 AD0 0 1 0 1 0 1 对应端口 命令/状态寄存器 A口 B口 C口 定时器/计数器低8位 定时器/计数器高8位 (3)命令/状态寄存器 8155的命令/状态寄存器物理上只有一个端口地址(见表3-5)。对该口写操作,命令字被写入命令寄存器; 对该口读操作,则从状态寄存器读出状态字。8155所提供的每个I/O口和定时器都是可编程的。I/O的工作方式选择,定时器/计数器的工作控制都是通过对8155内部命令寄存器设定命令控制字的方式来实现的,通过对状态字的读取来判别它们的工作状态。命令/状态寄存器共用一个口地址,通过读/写信号加以区分。命令字寄存器只能写不能读,状态寄存器只能读不能写。

8155命令字格式见表3-6; 8155状态字格式见表3-7。

表3-6 8155命令字格式表

TM2TM1IEBIEAPC2PC1PBPAP A口方 B 口方 00 11 01 10 A0:输入1:输出 B 00:空操作 01:停止计数 计数器 10:计满后停止 方式 11:开始计数 0:禁止中断1:允许中断方式1:A、B口基本I/O;C口输入 方式2:A、B口基本I/O;C口输出 方式3:A口选通I/O;B口基本I/O 方式4:A、B口选通I/O 信号说明:INTRA、INTRB: 中断请求输出线,高电平有效。

STBA、STBB: 设备选通信号输入线,低电平有效。 BFA、BFB: 口缓冲器满信号,高电平有效。

作者 张小波

表3-7 8155状态字格式表

(4)接口与编程

①硬件连接。

8155可以直接与MCS-51单片机连接,不需要任何外加逻辑。扩展一片8155系统将增加256B片外RAM,22位I/O口线及1个14位减法计数器。MCS-51与8155的连接方法如图3-14所示。P0口不需要加锁存器,可以直接与8155的 AD0~AD7相连,它既是低8位地址线也是8位数据线。8155的锁存信号ALE直接引自单片机ALE输出,用以在内部锁存地址。

图3-14 MCS-51与8155的一种接口逻辑

CE及IO/M与MCS-51的连接方式决定了8155的地址范围: ? RAM字节地址范围: 7E00H~7EFFH

? 命令/状态寄存器: 7F00H PA口: 7F01H

作者 张小波

? PB口: 7F02H ? PC口: 7F03H

? 定时器低8位寄存器: 7F04H ? 定时器高8位寄存器: 7F05H ②程序设计。

图3-13接口电路中,将单片机片内RAM50H~5FH单元的内容送8155片内的50H~5FH单元,设定8155的工作方式为: A口基本输入方式,B口基本输出方式,C口输入方式,定时器作方波发生器,对输入脉冲50分频。 程序清单如下:

D8155: MOV R0,50H ;将源数据单元首地址送寄存器R0中

MOV DPTR,#7E50H ;数据指针指向8155内部RAM单元

LP: MOV A,@R0 ;数据送累加器A

MOVX @DPTR,A ;数据从累加器A送8155内部RAM单元 INC DPTR ;指向下一个8155内部RAM单元 INC R0 ;指向下一个CPU内部RAM单元 CJNE R0,#60H,LP ;数据未传送完继续 MOV DPTR,#7F04H ;指向定时器低8位 MOV A,#32H ;分频系数32H(即50) MOVX @DPTR,A ;低8位初值装入 INC DPTR ;指向定时器高8位

MOV A,#40H ;设定时器方式为连续方波(40H=01000000B) MOVX @DPTR,A ;定时器/计数器方式及高6位初值装入 MOV DPTR,#7F00H ;数据指针指向控制字寄存器

MOV A,#0C2H ;设定A,B口方式,并启动定时器(0C2H=11000010B) MOVX @DPTR,A

RET ;程序返回

程序中,定时器2个字节的内容二进制形是: 0100000000110010B,最高2位01的作用是设定定时器输出方式: 连续方波;其余14位是计数初值0032H=50(十进制)。向控制寄存器写入的命令字的内容是11000010B,对应的十六进制形式是0C2H,其意义是: 最高2位11的作用是启动计数器,最低位D0=0设PA为输入,D1=1设PB为输出。D3D2=00设PA、PB为基本I/O,PC为输入。 ☆ 完整程序代码

1、LCD1602.c源程序

//---------------------LCD1602.c-------------------------- //液晶控制与显示驱动程序

//-------------------------------------------------------- #include #include

#define uchar unsigned char #define uint unsigned int

sbit RS=P2^0; // LCD寄存器选择 sbit RW=P2^1; // LCD读写控制 sbit EN=P2^2; // LCD启动 //*--------延时子程序--------*//

作者 张小波

void DelayMS(uint K) {

uchar i; while(K--) {

for(i=0;i<120;i++); } }

//----------读取LCD的状态-------*/ uchar Read_LCD_State() {

uchar state; RS=0; RW=1; EN=1;

DelayMS(2); state=P0; EN=0;

DelayMS(2); return state; }

//-------------忙检查--------------*/ void LCD_Busy_Wait() {

while((Read_LCD_State()&0x80)==0x80); DelayMS(5); }

//-----------写LCD命令-----------------*/ void Write_LCD_Command(uchar cmd) {

LCD_Busy_Wait(); RS=0; RW=0; EN=0; P0=cmd; EN=1;

DelayMS(2); EN=0; }

//-----------向LCD写入数据-----------------*/ void Write_LCD_Data(uchar dat) {

LCD_Busy_Wait(); RS=1;

作者 张小波

RW=0; EN=0; P0=dat; EN=1;

DelayMS(2); EN=0; }

//-----------LCD初始化-----------------*/ void Initialize_LCD1602() //液晶初始化函数 {

Write_LCD_Command(0x38);DelayMS(2); //功能设臵,数据长度为8位,双行显示,5×7点阵字体 Write_LCD_Command(0x01);DelayMS(2); //清屏

Write_LCD_Command(0x06);DelayMS(2); //字符进入模式:屏幕不动,字符后移 Write_LCD_Command(0x0c);DelayMS(2); // 显示开,关光标 }

//-----------设臵液晶显示位臵-----------------*/ void Set_LCD_Position(uchar pos) {

Write_LCD_Command(pos|0x80); //设臵7位的DDRAM地址值 }

//-----------在LCD上显示字符串-----------------*/ void LCD_Display(uchar p,uchar *str) {

uchar i;

Set_LCD_Position(p); for(i=0;i<16;i++) {

Write_LCD_Data(str[i]); DelayMS(2); } }

2、main.c源程序

//------------------------------------------------------------------------ // 名称: 用DS1302和1602液晶显示的实时时钟

//-------------------------------------------------------------------------

//说明:本程序运行时会以PC时间为默认时间开始运行,运行过程中可以通过K1按键选择调节对象,用K2、K3

// 按键进行加减,用K4按键保存。

//--------------------------------------------------------------------------------------- #include #include #include

#define uchar unsigned char #define uint unsigned int

作者 张小波

void Initialize_LCD1602(); //液晶初始化函数 void LCD_Display(uchar p,uchar *str); sbit SDA=P1^0; //DS1302数据线 sbit CLK=P1^1; //DS1302时钟线 sbit RST=P1^2; // DS1302 复位线 sbit k1=P3^3; // 选择按键 sbit k2=P3^4; // 加 sbit k3=P3^5; // 减 sbit k4=P3^6; // 确定 uchar tcount=0;

uchar MonthsDays[]={0,31,0,31,30,31,30,31,31,30,31,30,31}; // 一年中每个月的天数,二月的天数由年份决定

uchar *Week[]={\周日,周一到周六 // LCD显示缓冲

uchar LCD_DSY_BUFFER1[]={%uchar LCD_DSY_BUFFER2[]={%uchar DateTime[7];//所读取的日期时间

char Adjust_Index=-1; //当前调节的时间对象:秒,分,时,日,月,年(0,1,2,3,4,6) uchar Change_Flag[]=\分,时,日,月,年)不调节秒和周 //函数声明

void Write_Byte_TO_DS1302(uchar X); // 向DS1302写入一个字节 uchar Read_Byte_FROM_DS1302(); //从DS1302中读取一个字节

uchar Read_Data_FROM_DS1302(uchar addr); //从DS1302指定位臵读取数据 , 读数据

void Write_Data_TO_DS1302(uchar addr,uchar dat); // 向DS1302指定位臵写入数据, 写数据 void SET_DS1302(); // 设臵时间 void GetTime(); // 读取当前时间 void Initialization(); //初始化函数

//------------------------------------------ // 主程序

//---------------------------------------- void main() {

Initialization(); while(1) {

if(Adjust_Index==-1) GetTime(); } }

//*-----------------初始化函数--------------*// void Initialization() {

Initialize_LCD1602(); //调用液晶初始化函数

作者 张小波

IE=0x83; //允许中断 外部0中断和T0中断 IP=0x01; // 设臵中断优先级

IT0=0x01; // 设臵外部中断的脉冲触发方式

TMOD=0x01; // 设臵定时器的工作方式,为方式1 TH0=-50000/256; // 写入初值 TL0=-50000%6; // 写入初值 TR0=1; // 启动定时器 }

//*--------写字节函数,向DS1302写入一个字节--------*//

void Write_Byte_TO_DS1302(uchar X) // 向DS1302写入一个字节 {

uchar i;

for(i=0;i<8;i++) {

SDA=X&1; CLK=1; CLK=0; X>>=1; } }

//*--------读字节函数,从DS1302读取一个字节--------*//

uchar Read_Byte_FROM_DS1302() //从DS1302中读取一个字节 {

uchar i,byte,t; for(i=0;i<8;i++) {

byte>>=1; t=SDA;

byte|=t<<7; CLK=1; CLK=0; }

//BCD码转换

return byte/16*10+byte; }

//------------------------------------------------------------- //从DS1302指定位臵读取数据 , 读数据

//---------------------------------------------------------------

uchar Read_Data_FROM_DS1302(uchar addr) //从DS1302指定位臵读取数据 {

uchar dat; RST=0; CLK=0; RST=1;

, 读数据

作者 张小波

Write_Byte_TO_DS1302(addr); //向DS1302写入一个地址

dat=Read_Byte_FROM_DS1302(); //在上面写入的地址中读取数据 CLK=1; RST=0;

return dat; }

//------------------------------------------------------------- //向DS1302指定位臵写入数据, 写数据

//---------------------------------------------------------------

void Write_Data_TO_DS1302(uchar addr,uchar dat) // 向DS1302指定位臵写入数据, 写数据 {

CLK=0; RST=1;

Write_Byte_TO_DS1302(addr); Write_Byte_TO_DS1302(dat); CLK=1; RST=0; }

//----------------------------------------------------------- // 设臵时间

//--------------------------------------------------------- void SET_DS1302() // 设臵时间 {

uchar i;

Write_Data_TO_DS1302(0x8E,0x00); //写控制字,取消写保护 // 分,时,日,月,年依次写入 for(i=1;i<7;i++) {

// 分的起始地址是10000010(0x82),后面依次是时,日,月,周,年,写入地址每次递增2 Write_Data_TO_DS1302(0x80+2*i,(DateTime[i]/10<<4)|(DateTime[i])); }

Write_Data_TO_DS1302(0x8E,0x80); //写控制字,加写保护 }

//-------------------------------------------------------- // 读取当前时间

//------------------------------------------------------- void GetTime() // 读取当前时间 {

uchar i;

for(i=0;i<7;i++) {

DateTime[i]=Read_Data_FROM_DS1302(0x81+2*i); } }

作者 张小波

//------------------------------------------------------------ //日期与时间值转换为数字字符

//------------------------------------------------------------- void Format_DateTime(uchar d,uchar *a) {

a[0]=d/10+'0'; a[1]=d+'0'; }

//------------------------------------------------------------------- // 判断是否为闰年

//------------------------------------------------------------------- uchar Is_Leapyear(uint year) {

return (year%4==0&&year0!=0)||(year@0==0) ; }

//------------------------------------------------------------------ // 求自2000.1.1开始的任何一天是星期几?

//---------------------------------------------------------------------- void Refresh_Week_Day() {

uint i,d,w=5; //已知1999年12.31是星期五 for(i=2000;i<2000+DateTime[6];i++) {

d=Is_Leapyear(i)?366:365; w=(w+d)%7; } d=0;

for (i=1;i

//保存星期,0-6表示星期日,星期一至星期六,为了与DS1302的星期格式匹配,返回值需要加1 DateTime[5]=(w+d)%7+1; }

//------------------------------------------------- //年,月,日和时,分++/--

//---------------------------------------------------- void Datetime_Adjust(char X) {

switch(Adjust_Index) {

case 6: //年调整,00-99

if(X==1&&DateTime[6]<99) {

DateTime[6]++;

作者 张小波

}

if(X==-1&&DateTime[6]>0) {

DateTime[6]--; }

//获取2月天数

MonthsDays[2]=Is_Leapyear(2000+DateTime[6])?29:28; //如果年份变化后当前月份的天数大于上限则设为上限 if(DateTime[3]>MonthsDays[DateTime[4]]) {

DateTime[3]=MonthsDays[DateTime[4]]; }

Refresh_Week_Day(); //刷新星期 break;

case 4: //月调整 01-12

if(X==1&&DateTime[4]<12) {

DateTime[4]++; }

if(X==-1&&DateTime[4]>1) {

DateTime[4]--; }

//获取2月天数

MonthsDays[2]=Is_Leapyear(2000+DateTime[6])?29:28; //如果年份变化后当前月份的天数大于上限则设为上限 if(DateTime[3]>MonthsDays[DateTime[4]]) {

DateTime[3]=MonthsDays[DateTime[4]]; }

Refresh_Week_Day(); //刷新星期 break;

case 3: // 日调整00-28或00-29或00-30或00-31

//调节之前首先根据当前年份得出该年中2月的天数 MonthsDays[2]=Is_Leapyear(2000+DateTime[6])?29:28; //根据当前月份决定调节日期的上限

if(X==1&&DateTime[3]

DateTime[3]++; }

if(X==-1&&DateTime[3]>0) {

DateTime[3]--; }

作者 张小波

Refresh_Week_Day(); //刷新星期 break;

case 2: // 时调整

if(X==1&&DateTime[2]<23) {

DateTime[2]++; }

if(X==-1&&DateTime[4]>0) {

DateTime[2]--; } break;

case 1: // 分调整

if(X==1&&DateTime[1]<59) {

DateTime[1]++; }

if(X==-1&&DateTime[4]>0) {

DateTime[1]--; } break; } }

//-------------------------------------------------------------- // 定时器0刷新LCD显示函数

//------------------------------------------------------------- void T0_INT()interrupt 1 {

TH0=-50000/256; // 写入初值 TL0=-50000%6; // 写入初值 if(++tcount!=2) return; tcount=0;

//按指定格式生成待显示的日期时钟

Format_DateTime(DateTime[6],LCD_DSY_BUFFER1+5); Format_DateTime(DateTime[4],LCD_DSY_BUFFER1+8); Format_DateTime(DateTime[3],LCD_DSY_BUFFER1+11); //星期

strcpy(LCD_DSY_BUFFER1+13,Week[DateTime[5]-1]); //时,分。秒

Format_DateTime(DateTime[2],LCD_DSY_BUFFER2+5); Format_DateTime(DateTime[1],LCD_DSY_BUFFER2+8); Format_DateTime(DateTime[0],LCD_DSY_BUFFER2+11); // 显示年、月、日、星期、时、分、秒

作者 张小波

LCD_Display(0x00,LCD_DSY_BUFFER1); LCD_Display(0x40,LCD_DSY_BUFFER2); }

//---------------------------------------------------------- // 键盘中断处理函数

//--------------------------------------------------------- void EX_INT0()interrupt 0 {

if(k1==0) {

while (k1==0);

if(Adjust_Index==-1||Adjust_Index==-1) {Adjust_Index=7;} Adjust_Index--; if(Adjust_Index==5)

{Adjust_Index=4;} //跳过对星期的调节 LCD_DSY_BUFFER2[13]='[';

LCD_DSY_BUFFER2[14]=Change_Flag[Adjust_Index]; LCD_DSY_BUFFER2[15]=']'; }

else if(k2==0) // 加 {

while(k2==0);

Datetime_Adjust(1); }

else if(k3==0) // 减 {

while(k3==0);

Datetime_Adjust(-1); }

else if(k4==0) {

while(k4==0); SET_DS1302();

LCD_DSY_BUFFER2[13]=' '; LCD_DSY_BUFFER2[14]=' '; LCD_DSY_BUFFER2[15]=' ';

Adjust_Index=-1; //操作索引重设为-1,时间继续正常显示 } }

作者 张小波

作业: 1、 简述液晶显示的原理? 2、 简述1602字符型LCD的结构及其引脚功能? 3、 详细说明LCD1602的指令功能? 4、 简要说明数组的定义及命名规则? 5、 如何对数组元素进行初始化? 6、 单片机I/O扩展有哪些芯片? 7、 简述8155的结构及其引脚功能?

作者 张小波

作业: 1、 简述液晶显示的原理? 2、 简述1602字符型LCD的结构及其引脚功能? 3、 详细说明LCD1602的指令功能? 4、 简要说明数组的定义及命名规则? 5、 如何对数组元素进行初始化? 6、 单片机I/O扩展有哪些芯片? 7、 简述8155的结构及其引脚功能?

本文来源:https://www.bwwdw.com/article/i887.html

Top