第十八课 单片机的中断系统

由于不讲中断无法解释实验的某些结果 所以就等这一课讲完了再一起做实验 我们先来看单片

机的中断系统原理

单片机中断系统的目的是为了让 CPU 对内部或外部的突发事件及时地作出响应 并执行相应的程

序 在单片机的开发中 它有着十分重要的作用 那么单片机的中断是怎么回事 它是如何工作的呢

这一课就来讨论这个问题 在讲解之前让我们先来介绍一下中断的原理

一 中断的基本原理

什么是中断 中断的过程是什么 要搞清楚这个问题 我们同样先从生活中的一个例子开始 你 正在家中看书 突然电话铃响了 你放下书 去接电话 和来电话的人交谈 通完电话 回来继续看你 的书 这就是生活中的 中断 现象 就是正常的工作过程被外部的事件打断了 仔细研究一下生活中 的中断 对我们学习单片机中断会很有帮助

第一 中断源 什么可引起中断 生活中很多事件都可以引起中断 比如 有人按了门铃 电话 铃响了 你的闹钟响了 你烧的水开了 等等诸如此类的事件 我们把可以引起中断的事件称之为中

断源 单片机中也有一些可以引起中断的事件比如 按下键盘 定时/计数器溢出 报警等等89C51

单片机中共有 5 个中断源 两个外部中断 两个定时/计数器中断和一个串行口中断

第二 中断的嵌套与优先级处理 设想一下 我们正在看书 电话铃响了 同时又有人按了门铃

你先做那样呢 如果你正在等一个很重要的电话 一般是不会去理会门铃的 而反之 如果你正在等一 个很重要的客人 则可能就不会去理会电话了 如果两者都不是既不等电话 也不等人上门你可

能会按你通常的习惯去处理 总之这里存在一个优先级的问题 单片机中也是如此 也有优先级的问题

优先级的问题不仅仅发生在两个中断同时产生的情况 也发生在一个中断已产生 又有另一个中断产生 的情况 比如你正在接电话 又有人按门铃的情况 或者你正开门与人交谈 又有电话响了的情况 仔 细想一下 我们一般会怎么处理

不会吧 这样就有点手忙脚乱了 要是再来个 MM 这么办 呵呵

第三 中断的响应与处理 当有事件产生 进入中断之前我们必须先记住现在的书看到第几页了

或拿一个书签放在当前页的位置 然后去处理不同的事情因为处理完了 我们还要回来继续看书

电话铃响我们要到放电话的地方去 门铃响了我们要到门那边去 也就是说不同的中断 我们要在不同 的地点处理 而这个地点通常还是固定的 单片机中采用的也是这种方法 五个中断源 每个中断产生 后都要到一个固定的地址去找处理这个中断的程序 当然在去之前首先要保存下面将执行的指令的地

址 以便处理完中断后回到原来的地方继续往下执行程序 具体地说 单片机中断响应可以分为以下几

个步骤 1 停止主程序运行 当前指令执行完后立即终止现在执行的程序 2 保护断点 把程序计数

器 PC 的当前值压入堆栈 保存终止的地址即断点地址以便从中断服务程序返回时能够继续执行

该程序 3 寻找中断入口 根据 5 个不同的中断源所产生的中断 查找 5 个不同的入口地址 4 执行

中断处理程序 这就不讲了 5 中断返回 执行完中断处理程序后 就从中断处返回到主程序 继续 往下执行 以上工作是由计算机自动完成的 与编程者无关 在这 5 个入口地址处存放有中断处理的程

序这是程序编写时放在那儿的 如果没把中断处理程序放在那儿可就错了 因为中断程序无法被执行 到那么执行中断程序有什么好处呢

二 实现中断的好处

单片机为什么要有中断系统 使用中断有什么好处呢 日常生活中 我们除了看书 肯定还要做 很多其他的事情 比如听电话 接待客人 烧水吃饭等等 单片机实行中断也有很多的好处 具体来说

1 实行分时操作 提高 CPU 的效率 只有当服务对象向 CPU 发出中断申请时 才去为它服务 这样 我们就可以利用中断功能同时为多个对象服务 从而大大提高了 CPU 的工作效率 2 实现实时处理

利用中断技术 各个服务对象可以根据需要随时向 CPU 发出中断申请 及时发现和处理中断请求并为

 

68

----------------

 

之服务 以满足实时控制的要求 比如定时的时间到了 就要 CPU 做相应的处理 3 进行故障处理

对难以预料的情况或故障 比如掉电 事故等 可以向 CPU 发出请求中断 由 CPU 作出相应的处理

那么单片机是如何实现中断处理的呢 要了解这个问题 就让我们先来看看单片机中断系统的内部结

三 单片机中断系统的结构

前面已经提到 89C51 单片机有 5 个中断源 看下面的图 这就是 89C51 单片机的中断系统内部 结构图 让我们一一来进行分析

1 中断源

1外部中断

即外中断 0 和外中断 1 经由外部引脚引入 在单片机的硬件上有两个引脚12 脚和 13 脚名

称为 INT0 和 INT1 第二引脚功能 P3.2 P3.3在单片机的内部有一个特殊功能寄存器 TCON 其中 有四位是与外中断有关的 还记得 TCON 是什么吗 对了 是定时器控制寄存器 请看下面的表

用于定时/计数器

用于中断

TF1

TR1

TF0

TR0

IE1

IT1

IE0

IT0

 

A IT0 中断 0INT0的触发方式控制位

可由软件进行置位和复位 IT0=0 中断 0 为低电平触发方式 IT0=1 中断 0 为负跳变触发方式

这两种方式的差异讲起来有点复杂 这里就不介绍了 作为初学者 只要知道就可以了

B IE0 中断 0 INT0的中断请求标志位

当有外部的中断请求时 该位就会置 1在 CPU 响应中断后 该位就自动清 0 注意

这是由硬件自动完成的

IT1 IE1 的用途和 IT0 IE0 是类似的 看上面的表

2内部中断

即定时器 0T0和定时器 1T1中断 与外中断一样 它也是由 TCON 中的四位控制的

TF0 定时器 T0 的溢出中断标记 当 T0 计数器产生溢出时 由硬件置位 TF0 当 CPU 响应

 

69

----------------

 

中断后 再由硬件将 TF0 自动清 0

TF1 与 TF0 类似 这里就不讲了

3串行口中断

负责串行口的发送 接收中断 具体内容我们到下册学习串行接口时再详细讲解 下面我们再 来讲另一个与中断有关的寄存器

2 中断允许寄存器 IEA8H

中断的允许或禁止是由片内可进行位什么是位 大家可别到现在还说不知道哦寻址的 8 位中 断允许寄存器 IE 来控制的 允许中断我们把它称为中断开放 不允许中断我们把它称为中断屏蔽如

何操作 说穿了其实很简单 就是通过对 IE 的相应位的置 1 或请 0 就可以了 请看下面的表格

中断允许寄存器 IE

EA ES ET1 EX1 ET0 EX0

1EA 总中断允许开关 它是个总开关 凡是要设置中断都得先通过它 EA=1 开放所有的 中断 EA=0 则所有中断都被禁止

2ES 串行口中断控制位 ES=1 允许中断 ES=0 禁止中断

3ET1 定时/计数器 1 中断控制位 ET1=1 允许中断 ET1=0 禁止中断

4EX1 外中断 1 中断控制位 EX1=1 允许中断 EX1=0 禁止中断

5ET0 定时器 0 中断控制位 ET0=1 允许中断 ET0=0 禁止中断

6EX0 外中断 0 中断控制位 EX0=1 允许中断 EX0=0 禁止中断

例如 我们现在要设置 T1 允许 INT1 允许 其它不允许 则 IE 应该是 10001100即 8CH也 可以直接用位操作指令 SETB EA SETB ET1 SETB EX1 来实现它 看一下下面的表

 

这里有一点请大家注意 当复位 CPU 时 IE 将被全部清 0

了解了中断的设置 让我们再来看另一个问题 前面我们提到过 中断有优先级和嵌套的问题

那么中断的优先级和嵌套是如何来控制的呢 接着往下看

3 中断源优先级寄存器 IPD8H

单片机执行中断的过程和生活中的中断有些类似 它也有一个自然优先级与人工优先级的问题

那么单片机是如何来设置它们的呢 这就要用到中断优先级寄存器 IP 它也是一个可位寻址的 8 位寄 存器 现在让我们先来看五个中断源的自然优先级是如何设置的

五个中断源的自然优先级由高到低的排列顺序为外中断 0 定时器 0 外中断 1 定时器 1 串口

中断 如果我们不对其进行设置 单片机就按照此顺序不断的循环检查各个中断标志就像我们生活中 按照习惯处理事物一样但有时我们需要人工设置高 低优先级 也就是说由编程者来设定哪些中断

是高优先级 哪些中断是低优先级当然由于只有两级 所以必然只有一些中断处于优先级别 而其他 的中断则处于同一级别 处于同一级别的中断顺序就由自然优先级来确定 这一点请大家务必搞清楚

既然可以设定人工优先级 那么它又是如何来设置的呢 其实很简单 我们只要把 IP 寄存器的对 应位置 1 就可以了 看下面的表

 


开机时 每个中断都处于低优先级 我们可以用指令来对优先级进行设置 例如 现在有如下要

求 将 T0 INT1 设为高优先级 其它为低优先级 求 IP 的值

IP 的首 3 位没用 可任意取值 设为 000 后面根据要求写 00000110 即 IP=06H 看下面的表


 

这里有个问题 如果 5 个中断请求同时发生时又会出现什么情况呢 比如在上例中 五个中断同 时发生 求中断响应的次序 按照我们学到的内容 响应次序应该为 定时器 0 外中断 1 外中断 0

 

70

----------------

 

实时器 1 串行中断 是不是符合我们刚才说的除了人工设置的高优先级外 其余的均按照自然优先 级来处理 其实这很好理解 如果我在家等待一个很重要的电话 同时又有人来敲门或者烧的水开了

当我放下电话后 还是会按照一般的习惯去处理其他的事情比如先开门让客人进来 再去处理烧开的

4 串行口控制寄存器 SCON98H

用于串行口中断及控制 我们留到下册中再详细解释 下面讨论一下另一个问题

四 中断响应的条件和过程

1 中断响应的原理

讲到这里 我们依然对于单片机响应中断的过程感到神秘 我们人响应外界的事件 是因为我们 有多种 传感器 ――眼 耳等可以接受不同的信息 那么单片机是如何做到这点的呢 其实说穿了

一点也不稀奇 单片机工作时 在每个机器周期中都去查询一下各个中断标记 看它们是否是 1如 果是 1就说明有中断请求了 所以所谓中断 其实也就是查询 只不过是每个周期都查一下而已

这要换成人来说 就相当于你在看书的时候 每一秒钟都抬起头来看是不是有人按门铃了 是否有电话

来了 烧的水是否开了 很蠢 是吗 本来嘛 计算机它根本就没人聪明 了解了响应中断的原理

就不难理解中断响应的条件了

2 中断响应的条件

当有下列三种情况之一发生时 CPU 将封锁对中断的响应 而是到下一个机器周期时再继续查询

1CPU 正在处理一个同级或更高级别的中断请求时

2当前的指令没有执行完时

我们知道 单片机有单周期指令 双周期指令 三周期指令和四周期指令 如果当前执行指令是 单周期指令也许没关系 如果是双周期或四周期指令 那就要等整条指令都执行完了 才能响应中断 因 为中断查询是在每个机器周期都可能查到的

3当前正执行的指令是返回指令RET 或 RETI或访问 IP IE 寄存器的指令 则 CPU 将至少 再执行一条指令才能响应中断

这些都是与中断有关的寄存器 如果正访问 IP IE 则可能会出现开 关中断或改变中断的优先级

而中断返回指令则说明本次中断还没有处理完 所以就要等本指令处理结束 再执行一条指令才可以响 应中断

3 中断响应的过程

CPU 响应中断时 首先把当前指令的下一条指令就是中断返回后将要执行的指令的地址断 点地址送入堆栈 然后根据中断标记 硬件执行跳转指令 转到相应的中断源入口处 执行中断服务 程序 当遇到 RETI中断返回指令时 返回到断点处继续执行程序 这些工作都是由硬件自动来完

成的 那么中断入口的地址是如何来确定的呢 在 51 系列单片机中 五个中断源都有它们各自的中断

入口地址 请看下面

 

 


 

 

 

讲到这里 大家应该明白 为什么我们前面的有些程序一开始是这样写的

ORG 0000H

LJMP START

ORG 0030H

START *****

*****

*****

 

71

----------------

 

END

其实这样写的目的 就是为了让出中断源所占用的地址 当然 在程序中如果没有用到中断时

直接从地址 0000H 开始写理论上不是不可以 但在实际工作中最好不要这样做 这里还有一个问题

大家是否注意到 每个中断向量地址只间隔了 8 个字节 如 0003H 000BH 在如此少的空间中如果完 成不了中断程序 又该这么办呢 其实很简单 您只要在中断处安排一条 LJMP 指令 不就可以把中断 服务程序跳转到任何地方去了吗 所以一个完整的主程序看起来应该是这样的

ORG 0000H

LJMP START

ORG 0003H

LJMP AINT0 转外中断 0 服务程序

ORG 000BH

START ***** 主程序开始

*****

*****

AINT0 ***** 中断服务程序

*****

RETI 从中断服务程序返回

END

中断程序处理完成后 一定要执行一条 RETI 指令 执行这条指令后 CPU 将会把堆栈中保存着 的断点地址取出 送回程序计数器 PC 中 那么程序就会根据 PC 中的值从主程序的中断处继续往下执 行了 从 CPU 终止当前程序 且转向另一程序这点看 中断的过程很象子程序 其实它们之间还是有 区别的 中断发生的时间是随机的 而子程序调用则是按程序进行的 所以它们的返回命令也是不一样

的 RET 是用在返回子程序中的 而 RETI 则用在返回中断处理程序中的 这一点千万不能搞错了

五 本课总结

本课主要讲述中断的工作原理和响应的过程以及如何来设置与中断有关的寄存器 中断是单片机 响应外界事件的重要组成部分 与定时/计数器一样 掌握它的工作原理对我们使用单片机开发产品是 非常必需的

六 第 18 课习题

1 51 单片机有哪几个中断源 它们的名称分别是什么

2 中断请求源是由哪些寄存器控制的

3 中断响应的过程是什么

4 简述 RET 和 RETI 的区别

 

72

----------------

 

第十九课 定时与中断实验 一

前面的两节课程我们连续的介绍了单片机定时/计数器和中断的原理及结构 可能大家不是很理

解 这节课我们就来做几个实验验证一下前面所学的内容

实验一 利用定时器实现灯的闪烁

在开始学单片机时我们所做的第一个实验就是 LED 灯的闪烁 不过那是用延时程序做的 现在回 想起来 这样做不是很恰当 为什么呢 因为我们的主程序做了灯的闪烁 就不能再干其它的事情了

难道单片机只能这样工作吗 当然不是 我们可以用定时器来完成灯的闪烁功能

程序如下

ORG 0000H ; AJMP START ; ORG 30H ;

START:MOV P1,#0FFH ;关所有的灯

MOV TMOD,#00000001B ;定时/计数器 0 工作于方式 1

MOV
TH0,#15H
;


MOV
TL0,#0A0H
;立即数
15AH+0A0H=5536

SETB TR0 ; 定时/计数器 0 开始运行

LOOP:JBC TF0,NEXT ;如果 TF0 等于 1 则清 TF0 并转 NEXT 处 此处可以加入其他的任何指令

AJMP LOOP ;否则跳转到 LOOP 处运行

NEXT:CPL P1.0 ;

MOV
TH0,#15H
;

MOV
TL0,#0A0H
;重置定时/计数器的初值

AJMP LOOP ;

END

把程序下载到实验板 看到了什么 灯开始闪烁了 这可是用定时器做的 不再是主程序的循环

了 简单分析一下程序 为什么用 JBC 呢 TF0 是定时/计数器 0 的溢出标记位 当定时器产生溢出后

该位由 0 变 1所以查询该位就可知道定时时间是否已到 该位为 1 后 要用软件将标记位清

0以便下一次定时时间到了将该位由 0 变为 1所以用了 JBC 指令 该指令前面已经学过--

判 1 转移的同时 将该位清 0

以上程序可以实现灯的闪烁了 可是主程序除了让灯闪烁外 还是不能做其他的事啊 不对 我 们可以在 LOOP JBC TF0 NEXT 和 AJMP LOOP 指令之间插入一些指令来做其他的事情 只要保 证执行这些指令的执行时间少于定时时间就可以了

当然 这样的方法还不是最好 所以我们常用下面的方法实现延时程序

实验二 利用中断方式实现灯的延时 程序如下

ORG 0000H ; AJMP START ;

ORG 000BH ;定时器 0 的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG 30H ; START:MOV P1,#0FFH ;关所有灯

MOV TMOD,#01H ;定时/计数器 0 工作于方式 1

 

73

----------------

 

MOV
TH0,#15H
;


MOV
TL0,#0A0H
;立即数 5536


SETB
EA
;开总中断允许



SETB
ET0
;开定时/计数器 0 允许


SETB
TR0
;定时/计数器 0 开始运行

LOOP:AJMP LOOP ;真正工作时 这里可写任意程序

TIME0:

PUSH
ACC
;将 ACC 推入堆栈保护

PUSH
PSW
;将 PSW 推入堆栈保护

CPL P1.0 ;取反 P1.0

MOV
TH0,#15H
;

MOV
TL0,#0A0H
;重置定时常数

POP PSW ;

POP ACC ; RETI ; END

上面的例子中 定时时间一到 TF0 由 0 变 1就会引发中断 CPU 将自动转至 000BH 处

寻找程序并执行 由于留给定时器中断的地址空间只有 8 个字节 显然不足以写下所有的中断处理程序

所以在 ORG 000BH 后安排了一条长跳转指令 转到实际处理中断的程序处 这样 中断程序可以写 在任意地方 也可以写任意长度了 单片机进入定时中断后 首先要保存当前的一些状态 在这里程序 只演示了保存 ACC 和 PSW 实际工作中应该根据需要将可能会改变的单元的值都推入堆栈进行保护 本

程序中实际不需保护任何值 这里只作个演示

这是一项很重要的工作 因为 CPU 所做的自动保护工作是很有限的 它只保护了一个地址就是 中断返回后将要执行的指令的地址 也叫断点地址而其它的所有东西都不保护 所以如果你在主程

序中用到了如 ACC PSW 等寄存器 而在中断程序中又要用到它们 还要保证回到主程序后这里面的

数据还是没执行中断之前的数据 就得自己把它们保护起来 否则程序执行的结果就不是您想象的要求

上面的两个程序运行后 我们发现灯的闪烁非常快 根本分辨不出来 只是视觉上感到有些晃动 而已 为什么呢 我们可以计算一下 定时器中预置的数是 5536 所以每计 6000065536-5536个脉 冲就是定时时间到 这 60000 个脉冲的时间是多少呢 我们的晶振是 12 兆的 所以就是 60000 微秒

即 60 毫秒 因此速度是非常快的 如果我想实现一个 1 秒的定时器 该怎么办呢 在该晶振频率下

最长的定时也就是 65.536 个毫秒啊 请看第三个实验

实验三 延长定时时间的方法 程序如下

ORG 0000H ; AJMP START ;

ORG 000BH ;定时器 0 的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG 30H ;

START:MOV P1,#0FFH ;关所有的灯

MOV 30H,#00H ;软件计数器预清 0

MOV TMOD,#01H ;定时/计数器 0 工作于方式 1

MOV
TH0,#3CH
;


MOV
TL0,#0B0H
;立即数
3CH+0BH=15536

 

SETB EA ;开总中断允许

74

----------------

 

SETB
ET0
;开定时/计数器 0 允许

SETB
TR0
;定时/计数器 0 开始运行

LOOP:AJMP LOOP ;真正工作时,这里可写任意程序

TIME0:

PUSH
ACC
;将 ACC 推入堆栈保护

PUSH
PSW
;将 PSW 推入堆栈保护

INC 30H ;

MOV
A,30H
;

CJNE
A,#20,TIME1
;30H 单元中的值到了 20 了吗

CPL P1.0 ;到了 取反 P1.0

MOV 30H,#0 ;清软件计数器 TIME1:MOV TH0,#15H ;给 T0 重新赋值 MOV TL0,#9FH ;重置定时常数 POP PSW ;

POP ACC ; RETI ; END

先自己分析一下程序 看看是怎么实现的 这里采用了软件计数器的概念 思路是这样的 先用

T0 做一个 50 毫秒的定时器 定时时间到了之后并不是立即取反 P1.0 而是将软件计数器中的值加 1

如果软件计数器计到了 20 就取反一次 P1.0 并清掉软件计数器中的值 否则直接返回 这样 就变 成了 20 次定时中断才取反一次 P1.0 因此定时时间就延长了成了 20*50mS 即 1000 毫秒了 这个思 路在实际的工程应用中是非常有用的 比如有时候我们需要若干个定时器 可 89C51 中总共才有 2 个

怎么办呢 其实 只要这几个定时器在时间上有一定的公约数 我们就可以用软件定时器加以实现 例 如要实现 P1.0 口所接灯按 1S/次 而 P1.1 口所接灯按 2S/次闪烁 怎么实现呢 我们可以用两个计数器

一个在它计到 20 时 取反一次 P1.0 并清 0如上面所示 而另一个则计到 40 取反一次 P1.1 然 后清 0不就行了吗 看下面的实验

实验四 软件定时器的实现方法

程序如下

ORG 0000H ; AJMP START ;

ORG 000BH ;定时器 0 的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG 0030H ;

START:MOV P1,#0FFH ;关所有的灯

MOV 30H,#00H ;软件计数器预清 0

MOV TMOD,#01H ;定时/计数器 0 工作于方式 1

MOV
TH0,#3CH
;

MOV
TL0,#0B0H
;立即数 15536

SETB
EA
;开总中断允许

SETB
ET0
;开定时/计数器 0 允许

SETB
TR0
;定时/计数器 0 开始运行

LOOP:AJMP LOOP ;真正工作时,这里可写任意程序

TIME0:

PUSH
ACC
;将 ACC 推入堆栈保护

PUSH
PSW
;将 PSW 推入堆栈保护

 

75

----------------

 

INC 30H ;

INC 31H ;两个计数器都加 1

MOV
A,30H
;

CJNE
A,#20,TNEXT
;30H 单元中的值到了 20 了吗

CPL P1.0 ;到了 取反 P1.0

MOV 30H,#0 ;清软件计数器

TNEXT:MOV A,31H ;

CJNE A,#40,TRET ;31H 单元中的值到 40 了吗

CPL P1.1 ;

MOV 31H,#0 ;到了 取反 P1.1 并清计数器 返回

TRET:MOV TH0,#15H ;

MOV TL0,#9FH ;重置定时常数

POP PSW ; POP ACC ; RETI ; END

这就是软件定时器的用法 试试看 用软件定时器做一个交通信号灯的实验 要求红灯亮 2 分钟

黄灯亮 1 分钟 绿灯亮 3 分钟 然后红灯再亮 2 分钟 黄灯再亮 1 分钟 绿灯再亮 3 分钟 不断的循环

通过上面的几个实验 大家对定时器的使用方法是不是有了一个初步认识 接下来让我们共同来做一个 更为有趣的实验 用单片机做一个乐曲编奏器

实验五 可编程乐曲演奏器

单片机用作乐曲编奏器的原理是 通过控制定时器的定时时间来产生不同频率的方波 驱动喇叭 发出不同音阶的声音 再利用延迟来控制发音时间的长短 就能控制音调中的节拍 编程时 把乐谱中 的音符和相应的节拍变换为定时常数和延迟常数 把数据表格存放在存储器中 由查表程序得到相应的 定时常数和延迟常数 分别用定时器控制产生方波的频率和发出该频率方波的持续时间 当延迟时间到 了之后 再查下一个音符的定时常数和延迟常数 依次进行下去 就可自动演奏出悦耳动听的乐曲来

程序如下

ORG 001BH ;定时器 T1 的中断入口

MOV
TH1,R1
;重装定时初值

MOV
TL1,R0
;

CPL P3.7 ;P1.0 输出方波

RETI

;中断返回

ORG
100H
;主程序

START:MOV TMOD,#01H ;定时器 T1 工作方式 1

MOV
IE,#88H
;允许 T1 中断

MOV
DPTR,#TAB
;表格首地址

LOOP:CLR A ;

MOVC A,@A+DPTR ;查表

MOV R1,A ;定时器高 8 为存 R1

INC DPTR ; CLR A ;

MOVC A,@A+DPTR ;查表

MOV R0,A ;定时器低 8 为存 R0

ORL A,R1 ;

JZ NEXT0 ;全 0 为休止符

 

76

----------------

 

MOV A,R0 ; ANL A,R1 ;

CJNE A,#0FFH,NEXT ;全 1 表示乐曲结束

SJMP START ;从头开始 循环演奏

NEXT:MOV TH1,R1 ;装入定时值

MOV TL1,R0 ;

SETB
TR1
;启动定时器


SJMP
NEXT1
;


NEXT0:CLR
TR1
;关闭定时器
停止发音

NEXT1:CLR
A
;


INC DPTR ;

MOVC A,@A+DPTR ;查延迟常数

MOV R2,A ;

LOOP1:LCALL D200 ;调用延时 200mS 子程序

DJNZ R2,LOOP1 ;控制延迟次数

INC DPTR ;

AJMP LOOP ;处理下一个音符 D200:MOV R4,#81H ;延时 20mS 子程序 D200B:MOV A,#0FFH ;

D200A:DEC A ; JNZ D200A ; DEC R4 ; CJNE R4,#00H,D200B ; RET ;

TAB: DB 0FEH,25H,02H,0FEH,25H,02H; DB 0FEH,84H,02H,0FEH,84H,02H;

DB 0FEH,84H,04H,0FEH,25H,04H;
DB
0FEH,25H,02H,0FEH,84H,02H;

DB 0FEH,0C0H,04H,0FEH,0C0H,04H;
DB
0FEH,98H,02H,0FEH,84H,02H;

DB 0FEH,57H,08H,00H,00H,04H;
DB
0FFH,0FFH;

END


上述程序是歌曲 新年好 的一段简谱 1=C 1 11 5 │ 3 33 1 │ 1 3 5 5 │ 4 3 2 —│ 如

何来实现的呢单片机的晶振为 12M 则乐曲 频率及定时常数三者之间的对应关系如下表所示

C 调音符 5 6 7 1 2 3 4 5 6 7

频率 Hz 392 440 494 524 588 660 698 784 880 988

半周期 mS
1.28
1.14
1.01
0.95
0.85
0.76
0.72
0.64
0.57
0.51

定时值
FD80
FDC6
FE07
FE25
FE57
FE84
FE98
FEC0
FEE3
FF01

我们用定时器 T1 工作方式 1 来产生歌谱中各音符对应频率的方波 由 P3.7 输出驱动蜂鸣器 节 拍的控制通过调用延时子程序 D200延时 200mS的次数来实现 以每拍 800mS 的节拍时间为例 一

拍需要调用 D200 子程序 4 次 同样的道理 半拍就需要调用 2 次 由此就演奏出了上面的乐曲

这里给大家讲明一点 上面的程序是我从其他的单片机书籍上照搬过来的 由于我对音乐实在是 一窍不通 所以上面的程序我也不知道是否正确 还请大家自己分析一下 如果有不对的地方请您多多 包涵

六 本课总结

本课主要介绍了定时/计数器的使用方法及编程技巧 大家可以通过反复实验来加强和巩固上两 节课所学的知识 另外请您结合第十六课的内容把这几段程序的流程图画出来 这就算是本课的习题吧

77

----------------

 

 

版权所有:单片机教程网 2007
Email:erd51@163.com qq:58565254