CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生),那么CPU就会暂停当前的工作(A事件),去执行B事件(中断响应和中断服务),然后B事件做完之后,再回到原来的事件(A事件)中继续工作。(中断的返回)。
随着计算机技术的应用,人们发现中断技术不仅解决了快速主机与I/O设备的数据传送问题,而且还有具有如下的优点:
1. 分时操作:CPU可以分时为多个I/O设备服务,提高了计算机的利用率。
2. 实时操作:CPU能够及时处理应用系统的随机事件,系统的实时性大大增强。
3. 可靠性高:CPU具有处理设备故障及掉电等突发性事件能力,从而使系统可靠性更高。
中断源符号 | 名称 | 中断标志 | 中断引起原因 | 中断号 | 优先级 |
---|---|---|---|---|---|
/INT0 | 外部中断0 | IE0 | 低电平或下降沿信号 | 0 | 最高 |
T0 | 定时器中断0 | TF0 | 定时/计数器0 计数回0溢出 | 1 | ↓ |
/INT1 | 外部中断1 | IE1 | 定电平或下降沿信号 | 2 | ↓ |
T1 | 定时器中断1 | TF1 | 定时/计数器1 计数回0溢出 | 3 | ↓ |
TX/RX | 串行口中断 | TI/RI | 串行通信完成一帧数据发送或接收 | 4 | 最低 |
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
TF0、 TF1: 是定时器中断标志(定时器0溢出标志位、定时器1溢出标志位)
TR0 、TR1: 打开相应的定时器(定时器0运行控制位,=1时启动定时器0、定时器1运行控制位,=1时启动定时器1)
由软件清0关闭定时器0/1。当GATE=1,且INIT为高电平时,TR1置1启动定时器1;当GATE=0时,TR1置1启动定时器0/1。
IT0、IT1: 是外部中断的触发方式。 =0时 低电平触发,=1时负跳变触发。
IE0、IE1: 是外部中断的标志位
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | EA | —— | —— | ES | ET1 | EX1 | ET0 | EX0 |
EA: 总中断允许。 EA=0;CPU屏蔽所有中断的请求 EA=1;开放所有中断。
ES:串行口中断允许位。ES=0; 禁止串行中断。ES=1; 允许串口中断。
ET0、ET1: 定时器/计数器0 和 定时器/计数器 1 中断允许位 =0时 禁止相应的定时器中断。 =1 允许相应的定时器中断。
EX0、EX1: 外部中断0 和 外部中断 1 中断允许位。=0时 禁止相应的外部中断。 =1时 允许相应的外部中断。
——:无效位
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | —— | —— | —— | PS | PT1 | PX1 | PT0 | PX0 |
PS: 串行口中断优先级 =0时,串行口中断定义为高优先级 =1时,低优先级
PT0、PT1: 定时器0 和 定时器 1 的中断优先级 =0时 高 = 1时为低
PX0、PX1: 外部中断0 和 外部中断1 的中断优先级 =0时 高 =1时 低
默认是为低优先级。
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
GATE:门控制
=0:仅有运行控制位TRx来控制定时/计数器的开启。
=1:由TRx和外部中断脉冲计数。(用于计算外部中断 负跳变 的次数)
C/T:计数器模式和定时器模式选择
=0:选择定时器模式
=1:选择计数器模式
M1、M0:选择定时/计数器的工作方式
M1 | M0 | 工作方式 |
---|---|---|
0 | 0 | 方式0:为13位定时/计数器 |
0 | 1 | 方式1:为16位定时/计数器 |
1 | 0 | 方式2:为8位初值自动重装定时/计数器 |
1 | 1 | 方式3:仅适用于T0,分成两个8位计数器,T1停止计数。 |
方式0
方式0为13位计数,由TL0的低5位(高3位未用)和TH0的8位组成。TL0的低5位溢出时向TH0进位,TH0溢出时,置位TCON中的TF0标志,向CPU发出中断请求。
方式1
方式1的计数位数是16位,由TL0作为低8位,TH0作为高8位,组成了16位加1计数器 。
方式2
方式2为自动重装初值的8位计数方式。
方式3
方式3只适用于定时/计数器T0,定时器T1处于方式3时相当于TR1=0,停止计数。
首先先了解一下CPU时序有关知识:
振荡周期: 为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期)
状态周期:2个振荡周期为1个状态周期,用S表示。振荡周期又称S周期或时钟周期。
机器周期:1个机器周期含6个状态周期,12个振荡周期。
指令周期:完成1条指令所占用的全部时间,它以机器周期为单位。
例如:外接晶振为12MHz时,51单片机相关周期的具体值为:
振荡周期=1/12us;
状态周期=1/6us;
机器周期=1us;
指令周期=1~4us;
机器周期就是CPU完成一个基本操作所需要得时间。
机器周期 = 1 /单片机的时钟频率
51单片机内部时钟频率是外部时钟的12分频,也就是当外部晶振的频率输入到单片机里面
的时候要进行12分频。
比如:你用的是12MHZ的晶振,当你使用12MHZ的外部晶振的时候,
机器周期 = 1 / 1M = 1us。(选择定时器工作方式1 16位)
我们2的16次方等于65536,也就是最大值为65536(溢出)
如果定时1ms
初值就为:1ms / 1us = 1000。也就是要计数1000个数, 初值 = 65535-1000+1 = 64536,65536才会溢出。 所以初值即FC18H(十进制为64536)
如果定时50ms
50ms/1us=50000;
初值 = 65535-50000+1=15536;
定时为50ms 初值为15536 即3CB0(十六进制)
对于每个不同的方式计算初值数有公式去计算的,但是我不会哈哈,我是用一个软件来计算的,软件名字 mcuelf这样也比较快
1 设置 外部中断中断源号及触发方式。设置IT0或IT1(TCON寄存器)
2 打开相应的外部中断允许。设置EX0或EX1(IE寄存器)
3 打开总中断。设置EA(IE寄存器)
这里就用外部中断0开示例吧
//配置外部中断0
void initInterrupt0()
{
IT0 = 1; //触发方式为负跳变触发
EX0 = 1; //打开外部中断0允许
EA = 1; //总中断打开
}
中断服务函数(发生中断你想做什么?)
无返回值 函数名(随意起) interrupt 中断号
void interrupt0ServiceFun() interrupt 0
{
//编写你要做的事情
}
外部中断1也是差不多的,这里就不写了。
实现按键按下开灯/关灯
#include "reg52.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long int u32;
sbit KEY = P3^2; //定义中断按键引脚
sbit LED = P2^0; //定义LED1引脚
//配置中断0
void initInterrupt0()
{
EX0 = 1;
IT0 = 1;
EA = 1;
}
//延时函数
void delay(u8 i)
{
while(i--);
}
void main()
{
initInterrupt0(); //调用中断
while(1);
}
//发生中断执行函数
void interruptHandler() interrupt 0
{
delay(12000); //延迟 因为当进入外部中断函数的时候按键时已经按下了这里是消抖作用
if(KEY == 0) //再次确认是否真的按键被按下
{
LED = ~LED; //开灯/关灯
}
}
51单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时器/计数器。
定时器/计数器和单片机的CPU是相互独立的。定时器/计数器工作的过程是自动完成的,不需要CPU的参与。
51单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加1。
有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加1的工作可以交给定时器/计数器处理。CPU转而处理一些复杂的事情。同时可以实现精确定时作用。
实质上是加1计数器,随着输入脉冲,计数器自动加1,
溢出的时候会回0.,且计数器的溢出使相应的中断标志位 置1.
向CPU发出中断请求。如果定时/计数器工作于定时模式,则表示定时时间已到;如果工作于计数模式,则表示计数值已满。
可见,由溢出时计数器的值减去计数初值才是加1计数器的计数值。
定时/计数器的实质是加1计数器(16位),由高8位和低8位两个寄存器THx和TLx组成。TMOD是定时/计数器的工作方式寄存器,确定工作方式和功能;TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。
1.选择工作方式。设置M1、M0 (TMOD寄存器)
2.选择控制方式。设置GATE(TMOD寄存器)
3.选择定时器还是计数器模式。设置C/T(TMOD寄存器)
4.给定时/计数器赋初值。设置THx 和 TLx(定时器初值寄存器)
5.开启总中断。设置EA(IE寄存器)
6.打开相应定时器中断允许。 设置ET0或ET1(IE寄存器)
7.启动定时器。设置TR1或TR0(TCON寄存器)
这里就选择定时器0吧 选择方式1(16位)进行定时示例吧
1.选择工作方式1(16位)M1=0;M0=1;
2.控制方式 :仅有运行控制位TRx来控制定时/计数器的开启。GATE=0;
3.选择定时器模式 C/T=0;
TMOD=0x01;
4.赋初值 这里选择定时为50ms 我的板子晶振是11.0592 具体怎么算我不会,推荐跟我一样不会的用软件mcuelf计算出以下结果
TH0 = 0x4C;
TL0 = 0x00;
5.打开总中断(总开关)
EA=1;
6.打开T0中断开关
ET0=1;
7.启动定时器0
TR0=1;
//配置定时器函数
void time0Config()
{
TMOD=0x01; //设定T0定时器,选择工作方式1(16位),定时器模式 仅有运行控制位TRx来控制定时/计数器的开启。
TH0 = 0x4C; //初值设定
TL0 = 0x00; //初值设定
EA=1; //打开总中断开关
ET0=1; //打开T0中断开关
TR0=1; //启动定时器0
}
//发生中断执行函数(这样就是50ms)
void time0() interrupt 1 //T0中断号为1
{
/*要注意这里不会自动重装,所以要再次设置回初值(除工作方式2)*/
TH0 = 0x4C; //初值重新设定
TL0 = 0x00; //初值重新设定
//编写你要做的事
}
这里就写一个用定时器来做一个简单的时钟显示在LCD1602上
LCD1602我昨天发布了一个LCD1602的使用和显示hello word。这里就是详细讲LCD1602具体的操作了。
引脚定义
//引脚定义
#define LCD P0
sbit E = P2^7; //使能
sbit RS = P2^6; //数据/命令(H/L)
sbit RW = P2^5; //读写(H/L)
lcd1602.h
void write_com(unsigned char command); //写命令函数
void write_data(unsigned char dat); //写数据函数
void init_lcd(); //初始化LCD1602函数
void delay5ms(); //延时5ms函数
lcd1602.c
#include
#include "lcd1602.h"
#define LCD P0
sbit E = P2^7;
sbit RS = P2^6;
sbit RW = P2^5;
/******延迟5毫秒函数********/
void delay5ms() //误差 -0.000000000001us
{
unsigned char a,b;
for(b=15;b>0;b--)
for(a=152;a>0;a--);
}
/******LCD1602写命令函数********/
void write_com(unsigned char command)
{
RS = 0;
RW = 0; //高读低写
LCD = command;
delay5ms(); //这里延时最低要30纳秒 我们直接给5ms
E = 1; //使能拉高
delay5ms(); //最低要求延迟150纳秒 我们直接给5ms
E = 0;
}
/******LCD1602写数据函数********/
void write_data(unsigned char dat)
{
RS = 1;
RW = 0;
LCD = dat;
delay5ms(); //这里延时最低要30纳秒 我们直接给5ms
E = 1; //使能拉高
delay5ms(); //最低要求延迟150纳秒 我们直接给5ms
E = 0;
}
/******初始化LCD1602********/
void init_lcd()
{
write_com(0x06); //写入数据后光标自动右移 整屏不移动。
write_com(0x0c); //开显示功能 无光标 不闪烁
write_com(0x38); //数据总线8位 16X2显示 5*7点阵
write_com(0x01); //清屏 0000 0001
}
main.c
#include
#include "lcd1602.h"
unsigned char t = 0; //用来计数时间
unsigned char i=0;
unsigned char hours = 23; //小时
unsigned char minutes = 59; //分钟
unsigned char seconds = 0; //秒
unsigned char date[16] = {
"2021-01-27 WED"};
unsigned char time[5] = {
"time:"};
//配置定时器函数
void time0Config()
{
TMOD=0x01; //设定T0定时器,选择工作方式1(16位),定时器模式 仅有运行控制位TRx来控制定时/计数器的开启。
TH0 = 0x4C; //初值设定
TL0 = 0x00; //初值设定
EA=1; //打开总中断开关
ET0=1; //打开T0中断开关
TR0=1; //启动定时器0
}
void main()
{
init_lcd(); //1.初始化lcd1602
write_com(0x80); //设置显示日期位置 (第一行第一个开始)
for(i=0;i<16;i++)
{
write_data(date[i]);
}
write_com(0xc0); //设置显示time:位置(第二行第一个开始)
for(i=0;i<5;i++)
{
write_data(time[i]);
}
time0Config();//调用定时器中断
while(1)
{
write_com(0xc6); //设置显示小时的十位 位置
write_data(hours/10+'0'); //小时十位
write_com(0xc7); //设置显示小时的个位 位置
write_data(hours%10+'0'); //小时个位
write_com(0xc8); //设置显示: 位置
write_data(':'); //显示 :
write_com(0xc9); //设置显示分钟的十位 位置
write_data(minutes/10+'0'); //分钟十位
write_com(0xca); //设置显示分钟的个位 位置
write_data(minutes%10+'0'); //分值个位
write_com(0xcb); //设置显示: 位置
write_data(':'); //显示 :
write_com(0xcc); //设置显示秒的十位 位置
write_data(seconds/10+'0'); //秒十位
write_com(0xcd); //设置显示秒的 个位 位置
write_data(seconds%10+'0'); //秒个位
}
}
//发生中断执行函数(一次就是50ms)
void time0() interrupt 1 //T0中断号为1
{
/*要注意这里不会自动重装,所以要再次设置回初值(除工作方式2)*/
TH0 = 0x4C; //初值重新设定
TL0 = 0x00; //初值重新设定
t++;
if(t == 20) //证明够1秒了 20 X 50ms = 1000ms = 1s
{
seconds++; //秒+1
t=0; //够了1秒设置为0 重新计数
}
if(seconds == 60) //如果秒到60
{
minutes++; //分钟+1
seconds = 0; //秒回0
}
if(minutes == 60) //如果分钟到60
{
hours++; //小时+1
minutes = 0; //分钟回0
}
if(hours == 24 ) //如果小时到24
{
hours = 0; //小时回0
}
}