51单片机(二)

51单片机简明笔记(二)

7中断与定时器

7-1 定时与中断基本概念

问:按照顺序执行结构,如果main函数一直在循环,那么程序的读取应该一直在main函数处,下面的中断函数永远读取不到,难道说单片机c语言不满足顺序执行结构?

答:是的,单片机的C语言程序并不完全满足顺序执行结构。在单片机编程中,除了顺序执行结构,还广泛使用了中断结构。

在顺序执行结构中,程序会按照代码的顺序,从上到下依次执行。但是,当一个中断事件发生时,单片机会暂停当前正在执行的程序,保存当前的程序状态,然后跳转到对应的中断服务程序执行。

在代码中,main函数是程序的主体,它在单片机上电或复位后开始执行,并且会一直循环执行。然而,Timer0_Routine函数是定时器0的中断服务程序,当定时器0产生中断时,这个函数会被自动调用。

因此,即使main函数一直在循环,只要定时器0产生中断,Timer0_Routine函数就会被执行。这就是为什么main函数和Timer0_Routine函数可以交替执行的原因。

问:常见的中断函数与中断结构?

答:在单片机编程中,中断函数是一种特殊的函数,当满足一定的条件时,它会暂停主函数的执行内容,转而去执行中断函数1。以下是一些关于中断函数的基本概念和用法:

中断函数的书写:中断函数与我们封装过的子函数的写法不同,有个区分标志“interrupt”。函数名我们可以随意取,只要不跟以前封装定义好的函数名冲突就行1。

EX0和EA:我们说过要想进入中断函数,必须满足它的一些前提条件。这里的EX0(IE0)和EA(IE7)要初始化赋值给1才能进入的了中断函数去执行任务。EA叫做总中断开关,EX0是针对P3.2的外部中断的开关。也就是要想进入中断函数必须满足两个开关都要闭合(都为1)1。

中断函数注意事项:中断函数应该尽可能地短小精悍,以避免在执行中断时影响程序的性能。在中断函数中使用的变量应该是 volatile 类型,以确保其值会在中断时被正确更新。为了确保程序的可靠性,应该避免在中断函数中使用复杂的逻辑控制结构,例如循环和递归2。

关于常见的中断结构,以下是一些基本的分类:

外部中断:利用检测GPIO引脚变化来执行一些操作3。

定时器中断:定时时间到了。

串口中断:串口接收到了数据。

中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源3。

链接:https://www.dotcpp.com/course/367
问:单片机中寄存器有哪些种类?所谓的RAM又和寄存器是什么关系?

单片机中寄存器有很多种类,根据功能和用途,可以分为以下几类:

CPU寄存器:用于存储和检查CPU的状态,如程序计数器、累加器、栈指针等。

特殊功能寄存器:用于控制和设置单片机的外围功能,如定时器/计数器、串口、中断、看门狗等。

通用寄存器:用于存储临时数据和变量,如寄存器组R0-R7等。

可位寻址寄存器:用于存储和操作单个位,如P0-P3等。
RAM用于存储程序运行中的数据和变量,断电即丢失。51单片机 ### 7-2 _crol_函数

_crol_函数功能:将 c 进行b位左位移,并将值以unsigned char类型返回

1
2
3
4
unsigned char _crol_ (
unsigned char c, /* character to rotate left */
unsigned char b); /* bit positions to rotate */

函数返回类型为unsigned char,函数的两个形参也是unsigned char类型。

c 为要被进行 位左移 的形式参数

b 为要进行的 位移数

7-3 中断相关寄存器

中断请求TCON

51单片机Tcon寄存器是定时器/计数器控制寄存器,它可以控制定时器/计数器的启动、停止、溢出和中断。它的每一位都有特定的功能,如下:(7->0)

TF1:定时器T1溢出标志位。当定时器T1溢出时,由硬件自动置1,并向CPU申请中断。CPU响应中断后,自动对TF1清零。TF1也可以用软件清零。
TR1:定时器T1运行控制位。可由软件置1或清零来启动或关闭定时器T1,使定时器T1开始计数。用指令SETB TR1或CLR TR1使TR1置1或清零。
TF0:定时器T0溢出标志位。当定时器T0溢出时,由硬件自动置1,并向CPU申请中断。CPU响应中断后,自动对TF0清零。TF0也可以用软件清零。
TR0:定时器T0运行控制位。可由软件置1或清零来启动或关闭定时器T0,使定时器T0开始计数。用指令SETB TR0或CLR TR0使TR0置1或清零。
IE1:外部中断INT1请求中断标志位。当外部中断INT1引脚出现有效的请求信号时,由硬件自动置1,并向CPU申请中断。CPU响应中断后,自动对IE1清零。IE1也可以用软件清零。
IT1:外部中断INT1触发方式控制位。当IT1=1时,为跳变沿触发方式,INT1上的电平从高到低的负跳变有效;当IT1=0时,为电平触发方式,INT1上低电平有效。
IE0:外部中断INT0请求中断标志位。当外部中断INT0引脚出现有效的请求信号时,由硬件自动置1,并向CPU申请中断。CPU响应中断后,自动对IE0清零。IE0也可以用软件清零。
IT0:外部中断INT0触发方式控制位。当IT0=1时,为跳变沿触发方式,INT0上的电平从高到低的负跳变有效;当IT0=0时,为电平触发方式,INT0上低电平有效。
#### 中断允许IE

EA:中断总允许位。EA=1,CPU开放中断;EA=0,CPU禁止所有的中断请求。EA相当于一个总开关,只有当EA=1时,其他位才有效。

ES:串行口中断允许位。ES=1,允许串行口中断;ES=0,禁止串行口中断。当ES=1时,如果串行口接收或发送缓冲区满或空时,会产生中断请求。

ET1:定时器/计数器1溢出中断允许位。ET1=1,允许T1中断;ET1=0,禁止T1中断。当ET1=1时,如果定时器/计数器1溢出时,会产生中断请求。

EX1:外部中断1允许位。EX1=1,允许外部中断1中断;EX1=0,禁止外部中断1中断。当EX1=1时,如果外部中断引脚INT1出现有效的请求信号时,会产生中断请求。

ET0:定时器/计数器0溢出中断允许位。ET0=1,允许T0中断;ET0=0,禁止T0中断。当ET0=1时,如果定时器/计数器0溢出时,会产生中断请求。

EX0:外部中断0允许位。EX0=1,允许外部中断0中断;EX0=0,禁止外部中断0中断。当EX0=1时,如果外部中断引脚INT0出现有效的请求信号时,会产生中断请求。
#### 如何中断? 中断响应条件
①中断源有中断请求;
②此中断源的中断允许位为 1;
③CPU 开中断(即 EA=1)。
以上三条同时满足时,CPU 才有可能响应中断。在使用中断时我们需要做什么呢?
①你想使用的中断是哪个?选择相应的中断号;
②你所希望的触发条件是什么?
③你希望在中断之后干什么?
我们以外部中断 0 为例,如下:
主程序中需要有以下代码

1
2
3
4
5
6
7
8
EA=1//打开总中断开关
EX0=1//开外部中断 0
IT0=0/1//设置外部中断的触发方式
中断服务函数:
void int0() interrupt 0 using 1
{
//编写用户所需的功能代码
}
void int0() interrupt 0 using 1是一种定义中断服务函数的语法,它的含义是:

void表示这个函数没有返回值,也没有参数。
int0表示这个函数的名字是int0,你可以自己取其他的名字,但要避免和系统函数重名。
interrupt 0表示这个函数是外部中断0的中断服务函数,当外部中断0发生时,CPU会自动调用这个函数。
using 1表示这个函数使用第1组寄存器,也就是R0-R7的地址是08H-0FH。using后面的数字可以是0-3,分别对应第0-3组寄存器。
例如,以下是一个简单的中断服务函数,它的功能是每当外部中断0发生时,就让P0口的值取反:

1
2
3
4
5
void int0() interrupt 0 using 1
{
P0 = ~P0; // P0口取反
}

关于using 1是否可以省略的问题
using 1是否可以省略,取决于你的具体需求和编译器的设置。一般来说,using 1是用来指定中断服务函数使用的寄存器组的,如果不写using 1,编译器会自动选择一组寄存器作为绝对寄存器访问的。1 using 1对代码是有一定的影响的,比如:

使用using 1可以节省一些代码空间,因为不需要在进入和退出中断时保存和恢复寄存器的值。 使用using 1可以提高一些执行速度,因为不需要进行寄存器的切换操作。 使用using 1可以避免一些潜在的错误,比如如果在中断中调用了其他函数,而这些函数也使用了相同的寄存器组,就可能导致数据的混乱和覆盖。 所以,如果你的中断服务函数比较简单,不需要调用其他函数,也不需要使用多个寄存器,那么你可以省略using 1,让编译器自动选择寄存器组。但是,如果你的中断服务函数比较复杂,需要调用其他函数,或者需要使用多个寄存器,那么你最好使用using 1,来指定不同的寄存器组,以提高效率和安全性。 ### 7-4 外部中断-按键控制LED灯 (外部中断用法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <REGX52.H>
#include "Delay.h"
void Init_INT0()
{
EA=1; //cpu中断允许
EX0=1; //INT0中断允许
IT0=1; //触发模式为下降沿触发
}
void main()
{
Init_INT0();
while(1)
{

}
}
void exti0() interrupt 0 //其实就是一个if的逻辑
{
Delay_1ms(1);
if(P3_2==0)
P2_0=!P2_0;
}
从这个图可以看到INT0默认的外部中断触发方式是IO口P3_2,也就是和独立按键共用了一个I/O口。 ### 7-5按键控制流水灯(定时器用法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>

unsigned char KeyNum,LEDMode;

void main()
{
P2=0xFE;
Timer0Init();
while(1)
{
KeyNum=Key(); //获取独立按键键码
if(KeyNum) //如果按键按下
{
if(KeyNum==1) //如果K1按键按下
{
LEDMode++; //模式切换
if(LEDMode>=2)LEDMode=0;
}
}
}
}

void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++; //T0Count计次,对中断频率进行分频
if(T0Count>=500)//分频500次,500ms
{
T0Count=0;
if(LEDMode==0) //模式判断
P2=_crol_(P2,1); //LED输出
if(LEDMode==1)
P2=_cror_(P2,1);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <REGX52.H>
void Timer0Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}

/*定时器中断函数模板(1000ms)
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;

}
}
*/




51单片机(二)
http://example.com/2024/02/02/EE/51单片机(二)/
作者
bradin
发布于
2024年2月2日
许可协议