一、蜂鸣器
当电流通过线圈时会产生电磁场,电磁场与永磁体相互作用,从而使金属膜产生震动而发声。为使金属膜持续震动,蜂鸣器需要使用震荡电路进行驱动。有些蜂鸣器元件内部自带震荡驱动电路,这种蜂鸣器叫做有源蜂鸣器(Active Buzzer,自激式蜂鸣器);而有些则不带震荡驱动电路,这种蜂鸣器叫做无源蜂鸣器(Passive Buzzer,它激式蜂鸣器)。
1.原理图
2.软件实现
Int_Buzzer .h
#ifndef __INT_BUZZER_H__
#define __INT_BUZZER_H__
#include <STC89C5xRC.H>
#define BUZZ P46
/**
* @brief 蜂鸣器响0.1s
*
*/
void Int_Buzzer_Buzz();
#endif /* __INT_BUZZER_H__ */
Int_Buzzer.c
#include "Int_Buzzer.h"
#include "Util.h"
void Int_Buzzer_Buzz()
{
unsigned char counter = 100;
while (counter) {
BUZZ = ~BUZZ;
Delay1ms(1);
--counter;
}
}
main.c
#include "Int_DigitalTube.h"
#include "Int_MatrixKey.h"
#include "Int_Buzzer.h"
int main()
{
u8 key_pressed = 0;
Int_DigitalTube_Init();
Int_DigitalTube_DisplayNum(key_pressed);
while (1) {
key_pressed = Int_MatrixKey_CheckKey();
if (key_pressed) {
Int_DigitalTube_DisplayNum(key_pressed);
Int_Buzzer_Buzz(); //按键提示音
}
Int_DigitalTube_Refresh();
}
}
二、中断系统
1.概念
中断系统是单片机用于处理外部紧急事件的一种机制。中断系统工作的大致流程如下图所示:当CPU正在处理某项任务时,外部发生了某个紧急事件,此时CPU会暂停当前的工作,转而去处理这个紧急事件,处理完之后,再回到原来被中断的位置,继续处理原来的工作。
中断系统使单片机能够实时响应外部事件,提高了系统的灵活性和响应能力。
中断源
中断源是指能够引发中断的事件。
中断标志位
中断标志位用于标识某个中断是否发生,每个中断源都有一个与之对应的中断标志位。当某个中断发生时,相应的中断标位就会置为1,当CPU检测到标志位时,就会处理相应的中断。当中断处理完毕后,中断标志位需要复位(置0),以便接收下一次中断,有些中断源的标志位,会在CPU处理完中断后,自动复位,而有些则需要开发者手动复位,在使用中断时,需要注意查看手册说明。
中断服务程序
中断服务程序指处理中断的逻辑,当某个中断标志位置1时,CPU会自动执行相应的中断服务程序。
中断优先级
中断优先级是指在多个中断同时发生时,单片机响应中断的先后顺序,并且高优先级的中断可以打断低优先级的中断。
中断源:
STC89C52RC共有8个中断源,8个中断源可分为3类,3个类别分别是外部中断(4个)、定时器中断(3个)、串口中断(1个)。
2.外部中断
外部中断是指由单片机外部的紧急事件触发的中断,通过向单片机的特定引脚发送特定的信号触发。STC89C52RC共提供了4个外部中断引脚,分别是INT0,INT1,INT2,INT3,如下图所示。
51单片机的外部中断支持两种触发方式,分别是低电平触发 和下降沿触发。
3.定时器中断
定时器中断是指由单片机内部的定时器触发的中断。
定时器是大多数单片机都具备的一个功能模块,用于实现定时任务。其用法是,设置一个定时值,然后开始计时,待计时结束后,触发相应的定时器中断,开发者可以在中断服务程序中编写定时任务的逻辑。
STC89C52RC共有三个定时器,分别是Timer0、Timer1、Timer2,每个定时器都有一个相对应的中断。
4.串口中断
串口中断是由单片机串口触发的中断。
串口是单片机用于收发数据的重要接口之一,当单片机通过串口接收到数据或者发送完数据后都会触发相应的中断。
STC89C52RC的串口引脚为TxD和RxD,其中TxD用于发送数据,RxD用于接收数据,如下图所示。
5.中断服务程序
中断服务程序是指用于处理中断的一段代码,当中断发生时,CPU就会暂停当前程序的执行,转而执行对应的中断服务程序,处理完中断后再恢复到原来的程序。
STC89C52RC共有8个中断源,分为4个外部中断、3个定时器中断和1个串口中断,开发者可以为每个中断源声明相应的中断服务程序,中断服务程序的声明语法如下。
6.中断优先级
STC89C52RC共有四个中断优先级,每个中断源都可以单独设置优先级。若多个中断同时发生,优先级高的会被优先处理;若两个中断的优先级相同,则根据其中断号决定处理顺序,中断号越小越优先。
除此之外,高优先级的中断还可以打断低优先级的中断,也就是说当CPU正在处理一个中断时,又发生了另外一个优先级比它还高的中断,此时CPU会暂停原来中断的服务程序,转而去处理这个高优先级的中断,处理完之后,再回到原来低优先级的中断服务程序。这个机制叫做中断嵌套,STC89C52RC支持两级中断嵌套。
三、外部中断
需求:使用外部中断的方式,一键控制LED灯的亮灭。
1.硬件设计
若想通过外部中断的方式检测按钮,必须将按钮接入4个外部中断引脚之一,如下图所示,可以看到SW3可用于触发外部中断0。
2.使用说明
(1)启用中断
默认情况,CPU会屏蔽所有中断请求,也就是说CPU不会响应任何中断请求,要使用中断,必须先进行启用。
每个中断源是否被启用,是由单片机内部的两个寄存器控制的,这两个寄存器分别是IE (Interrupt Enable,中断允许)寄存器和XICON(Auxiliary Interrupt Control,辅助中断控制)寄存器。
从上图可以看出,STC89C52系列的中断系统具有两级控制,首先是EA总控制位,其次是每个中断源各自的控制位。因此若需要启用某个中断,需要先将总控制位EA置位1,再将该中断自身的控制位置为1。//EA=1
(2)配置外部中断触发方式
STC89C52系列的外部中断支持两种触发方式,分别是低电平触发和下降沿触发,4个外部中断各需要1个控制位用于设置触发方式,这4个控制位分布于如下两个寄存器中,分别是XICON (Auxiliary Interrupt Control,辅助中断控制)寄存器中、TCON( Timer 0 and 1 Control****)****寄存器,如下图所示。
4个控制位的具体作用可参考下图(STC89C52系列中断系统结构图)。
(3)配置中断优先级
STC89C52系列的中断系统支持四个优先级,所以每个中断源的优先级都需要通过2个控制位进行配置,8个中断源共需要16个控制位,这16个控制位分布在如下3个寄存器中,分别是IPH (Interrupt Priority High,中断优先级高位)寄存器,IP (Interrupt Priority Low,中断优先级低位)寄存器,XICON(Auxiliary Interrupt Control,辅助中断控制)寄存器。
(4)定义中断服务程序
外部中断0到外部中断3的中断号分别是:0、2、6、7,以外部中断0为例,其中断服务程序应定义为
cpp
void INT0_Handler() interrupt 0
{
}
3.软件设计
实现思路:
(1)启用外部中断0
cpp
//打开中断总开关
EA=1;
//打开外部中断0开关
EX0=1;
(2)配置外部中断0的触发方式
下降沿触发
cpp
IT0=1;
(3)只有一个中断,优先级不考虑
(4)中断服务程序
cpp
void INT0_Func() interrupt 0
{
LED0=~LED0;
}
完整代码:
cpp
#include <STC89C5xRC.H> //包含STC89C52的头文件
#define LED0 P00
void Init_INT0()
{
// 打开中断总开关
EA = 1;
// 打开外部中断0开关
EX0 = 1;
// 配置外部中断为下降沿触发
IT0 = 1;
}
void main()
{
Init_INT0();
while (1);
}
/**
* @brief 中断函数中打开LED0,代表当触发中断时,LED0会打开。
* 根据我们之前的设置,当按下SW3时,P32为低电平,此时LED0打开。
*
* interrupt 0表示0号中断触发时执行该函数的逻辑
* 如果用C语言编程,中断查询次序就是中断号
*/
void INT0_Func() interrupt 0
{
// 中断中打开LED0
LED0 = ~LED0;
}
四、定时器中断--闪烁LED灯
1.需求:
使用定时器中断的方式实现LED闪烁,具体要求是使用定时器0令LED1每1秒钟闪烁一次。
定时器的基本工作原理,是使用一个n位的脉冲计数器,对时钟信号的脉冲进行计数,每个脉冲加1,当脉冲计数器达到最大值(2n)时,也就是溢出时,触发定时器中断。
2.启用定时器中断
cpp
//中断总开关
EA=1;
//定时器0中断开关
ET0=1;
选择定时器工作模式
|------------|------------|----------------|
| M1 | M0 | 工作模式 |
| 0 | 0 | 工作模式0(13位) |
| 0 | 1 | 工作模式1(16位) |
| 1 | 0 | 工作模式2(8位自动重装载) |
| 1 | 1 | 工作模式3(双8位) |
计算脉冲初始值
假如现在需要定时1ms,那么1ms需要的脉冲个数应为0.001/(12/11059200),因此定时器的初始值应为65536-0.001/(12/11059200),大约等于64614。
计算完毕后,需要将该值赋予TL0(低8位)和TH0(高8位),如下。
TL0 = 64614;
TH0 = 64614 >> 8;
启动定时器
在做完上述配置后,还需最后一步------启动定时器,启动之后定时器才会开始工作。定时器的启动可由单片机内部的寄存器控制,也可由单片机的外部引脚控制。具体控制逻辑如下图所示。
当GATE=0时,外部引脚(INT0,P3.2)无效,此时只能由内部寄存器TR0控制,当TR0=1时,脉冲计数器开始计数,TR0=0时,停止计数。
当GATE=1时,外部引脚(INT0,P3.2)生效,此时只有当内部寄存器TR0和外部引脚INT0都为1时,脉冲计数器才开始计数,否则停止计数。
3.定义中断服务程序
void Timer0_Hander() interrupt 1
{
//编写定时任务逻辑
}
4.软件设计
(1)启用定时器中断
cpp
EA=1;
ET0=1;
(2)选择定时器0的工作模式
GATE=0 C/T=0 M1=0 M0=1
C/T是TMOD寄存器中定时器0的控制位,选择定时器C/T应为0.
故,TMOD寄存器低四位是0001,高四位保持原值。
TMOD&=0xF0;//高四位不变,低四位归零。
TMOD|=0x01;//高四位不变,最低位变1.
(3)启动定时器
GATE已经设置为0,只要TR0=1,即可启动。
(4)定义中断服务程序
cpp
void Timer0_Hander() interrupt 1
{
static unsigned int count = 0;
//重新状态脉冲计数器
TL0 = 64614;
TH0 = 64614 >> 8;
//统计中断次数
if (count++ >= 500) {
LED1 = ~LED1;
count = 0;
}
}
5.完整代码
Dri_Timer0.c
cpp
#include "Dri_Timer0.h"
#include <STC89C5xRC.H>
void Dri_Timer0_Init()
{
EA=1;//总开关
ET0=1;//定时器0开关
//定时器0工作方式
TMOD&=0xF0;
TMOD|=0x01;
//脉冲计数器初始值
TL0=64614;
TH0=64614>>8;
//启动定时器
TR0=1;
}
Dri_Timer0.h
cpp
#ifndef __DRI_TIMER0_H__
#define __DRI_TIMER0_H__
void Dri_Timer0_Init();//定时器0开关,启动函数
#endif /* __DRI_TIMER0_H__ */
main.c
cpp
#include <STC89C5xRC.H>
#include "Dri_Timer0.h"
#include "Com_Util.h"
#define LED1 P00
void main()
{
Dri_Timer0_Init();
while(1);
}
//定时中断服务程序
void Timer0_Hander() interrupt 0
{
LED1=~LED1;
Com_Util_Delay1ms(500);
}
6.定时器封装
为使定时器使用起来更加方便和通用,我们可以将定时器代码进一步封装。
(1)思路
初始化定时器-->中断服务程序,轮询调用函数.