51单片机有了中断,在程序设计中就可以做到,在做某件事的过程中,停下来先去响应中断,做别的事情,做好别的事情再继续原来的事情。中断优先级是可以给要做的事情排序。

单片机的学习不难,只要掌握学习方法,学起来并不难。什么是好的学习方法呢,一定要掌握二个要点:

1. 要知道寄存器的英文全拼,比如IE = interrupt中断

  不知道全拼,要去猜,去查。这样就可以理解为什么是这个名称,理解了以后就不用记忆了。

2. 每个知识点要有形像的出处

比如看到TF0,脑子里马上要形像地定位到TCON寄存器的某位

看到ET0, 马上要形像地定位到IE寄存器的第2位

形像是记忆的最大技巧。当人眼看到某个图时,是把视觉信号转化成电信号,再转化成人能理解的形像。当我们回忆形像时,就是在重新检索原先那个视觉信号,并放大。在学习过程中,不断练习检索、放大信号,我们的学习能力就会越来越强。

写程序代码时,也要把尽量把每行代码形像化。

51单片机内中断源

8051有五个中断源,有两个优先级。与中断系统有关的特殊功能寄存器有IE(中断允许寄存器)、IP(中断优先级控制寄存器)、中断源控制寄存器(如TCON、SCON的有关位)。51单片机的中断系统结构如下图(注意,IF0应为TF0):

8052有6个中断源,它比8051多一个定时器/计数器T2中断源。

8051五个中断源分别是:

##(1)51单片机外部中断源

8051有两个外部中断源,分别是INT0和INT1,分别从P3.2和P3.3两个引脚引入中断请求信号,两个中断源的中断触发允许由TCON的低4位控制,TCON的高4位控制运行和溢出标志。

INT0 也就是Interrupt 0。在这里应该看一下你的51单片机开发板的电路原理图。离开形像的记忆是没有意义的。读到上面这句,你应该回忆起原理图上的连接。任何记忆都转化为形 像,这是学习的根本原理,我们通过学习单片机要学会这种学习方法,会让你一辈子受益无穷。

TCON的结构如下图:

###(a)定时器T0的运行控制位TR0

TR0由软件置位或者清0。当门控位GATE=0时,TO计数器仅由TR0控制,TR0=1启动计数,TR0=0时停止。当门控位GATE=1时,T0计数器由INT0和TR0共同控制,当INT0=1且TR0=1时启动T0计数器。

###(b)定时器T0溢出标志位TF0

当T0溢出时TF0=1,并向CPU申请中断,CPU响应中断后由硬件将TF0清0,也可以由软件查询方式将TF0清0。

c)定时器T1的运行控制位TR1

功能同TR0。

###(d)定时器T1溢出标志为TF1

功能同TF1。

###(e)外部中断源1(INT1、P3.3)中断请求标志IE1

IE1=1时外部中断源1正在向CPU请求中断,当CPU响应该中断时由硬件将IE1清0(下降沿触发方式)。

###(f)外部中断源1触发方式选择位IT1

IT1=0时外部中断源1选择电平触发方式,当输入低电平时置位IE1;IT1=1时外部中断源1选择下降沿触发方式,当中断源由高电平变低电平时置位 IE1,向CPU请求中断。

###(g)外部中断源0(INT0、P3.2)中断请求标志IE0

功能类同IE1。

###(h)外部中断源0触发方式选择位IT0

功能类同IT1。

CPU在每个机器周期采样INT0和INT1引脚的输入电平。

i、电平触发方式

当CPU采样到低电平时,置位IE0和IE1,采样到高电平时,将IE0和IE1清零。在电平触发方式下,外部中断源必须一直保持低电平(至少保持1个以 上的机器周期)直到CPU响应中断请求,否则中断请求将丢失,同时在中断处理程序结束之前必须,外部中断源必须变为高电平,否则将产生另一次中断。

ii、下降沿触发方式

CPU 每个机器周期采样中断输入引脚,如果相续的两次采样,第一次是高电平,第二次是低电平,则置位相应的IE,响应中断后,硬件自动将IE清0。采样下降沿触 发方式,中断源的高、低电平都必须保持12个振荡周期(即1个机器周期)以上,这样CPU才能有效检测到下降沿,并引发CPU中断。

##(2)51单片机内部中断源

8051有3个内部中断源,分别是定时器T0、T1和串行口中断。8052增加了一个T2定时器中断。

2、51单片机中断使能控制

中断的允许和禁止由中断使能控制寄存器IE控制,其字节地址为0A8H,可以位寻址,其结构如下图所示:

EX0:外部中断0中断允许位;

ET0:定时器/计数器T0中断允许位;

EX1:外部中断1中断允许位;

ET1:定时器/计数器T1中断允许位;

ES:串行口中断允许位;

ET2:定时器/计数器T2中断允许位;(只要8052具有)

EA:CPU中断总允许位,EA=1时所有的中断开放,EA=0时禁止所有的中断。

3、51单片机中断优先级

51有两个优先级:高、低。通过IP(中断优先级寄存器)来设置优先级,其字节地址为0B8H,可位寻址,其结构如下图:

IP中各位值为0时表示低优先级中断,为1时表示高优先级中断。CPU复位后IP=0。

高优先级中断可以中断低优先级中断,同优先级中断不能相互中断。当CPU同时接到同优先级的几个中断请求时,CPU按照如下硬件顺序进行中断响应:

4、51单片机中断请求的撤除

CPU响应中断请求,执行中断服务程序,但在中断返回指令(RETI)之前必须撤除中断信号,否则将可能再次引起中断而发生错误。

中断请求撤销的方法有三种:

a、单片机内部硬件自动复位:对于定时器/计数器T0、T1及采用边沿触发方式的外部中断请求,CPU在响应中断后,由内部硬件自动撤销中断请求;

b、应用软件清除响应标志:对串口发送/接收中断请求及定时器T2的溢出和捕获中断请求,CPU响应中断后,内部无硬件自动复位RI、TI、TF2及EXF2,必须在中断服务程序中清除这些标志,才能撤除中断;

c、既无软件清除也无硬件撤除:对于采用电平方式的外部中断请求,CPU对引脚上的中断请求信号既无控制能力,也无应答信号,为保障CPU响应中断请求中断后,执行返回指令前撤除中断请求,必须考虑另外的措施。

5、51单片机中断响应过程

51单片机在每个机器周期的S5P2状态顺序检查每个中断源的中断请求标志,若有中断源发送中断请求,CPU在下个机器周期的S5P2状态按优先级顺序查询各 中断标志,并且取高优先级的中断进行响应。响应中断后置位相应的中断优先级状态触发器,标明当前中断服务的优先级别,执行硬件调用程序,将程序计数器PC 的内容压入堆栈进行保护。对于中断源的中断入口地址装入程序计数器PC,使程序转入该中断入口处执行中断服务程序,直到遇到RETI指令。执行RETI指 令,撤销中断优先级触发器,弹出断点地址至程序计数器PC,继续源程序的执行过程。

在接收中断申请时,如遇到下列情况之一,硬件调用子程序将被封锁:

a、正在执行同级或高一级的中断服务程序;

b、当前指令周期不是该指令的最后一个周期(或一条指令未执行完);

c、当前正在执行的指令是RETI或对IE、IP的读写操作。

6、中断入口地址

各中断源的中断入口地址为:

STC86C52RC 51单片机中断示例程序

  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
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363


#include <reg52.h>


typedef unsigned char uint8;


typedef unsigned int uint16;


typedef unsigned long uint32;


sbit enableG1 = P1 ^ 3;


sbit enableG2 = P1 ^ 4;


sbit selectC = P1 ^ 2;


sbit selectB = P1 ^ 1;


sbit selectA = P1 ^ 0;


code uint16 num16[16] = {


    0xC0, 0xF9, 0xA4, 0xB0,  0x99, 0x92, 0x82, 0xF8,


    0x80, 0x90, 0x88, 0x83,  0xC6, 0xA1, 0x86, 0x8E


};


//共阳数码管真极表


uint8 num6[6] = {


    0


};


//储存秒,0-5对应于个位...10万位上各位上的值


void enable138(void);


//启用138译码器切换IO口


void refresh_led(void);


void thtl_init(void);


void timer1_init(void);


void et1_init(void);


void main(void) 


{


	enable138();


	timer1_init();


	et1_init();


	while (1);


}


void interrupt_timer1(void) interrupt 3 


{


	static uint16 counter = 0;


	static uint32 sec = 0;


	counter++;


	thtl_init();


	if (counter == 1000) 


	{


		counter = 0;


		sec++;


		num6[0] = sec % 10;


		num6[1] = sec / 10 % 10;


		num6[2] = sec / 100 % 10;


		num6[3] = sec / 1000 % 10;


		num6[4] = sec / 10000 % 10;


		num6[5] = sec / 100000 % 10;


	}


	refresh_led();


	//更新num6数组后再刷新数码管


}


void enable138(void) 


{


	enableG1 = 1;


	enableG2 = 0;


}


//刷新数码管,只显示有效值


void refresh_led(void) 


{


	static uint8 i = 0;


	switch (i) 


	{


		case 0:


		            selectC = 0;


		selectB = 0;


		selectA = 0;


		P0 = num16[num6[0]];


		break;


		case 1:


		            selectC = 0;


		selectB = 0;


		selectA = 1;


		P0 = num6[5] == 0 && num6[4] == 0 && num6[3] == 0 && num6[2] == 0 && num6[1] == 0 ? 0xFF : num16[num6[1]];


		break;


		case 2:


		            selectC = 0;


		selectB = 1;


		selectA = 0;


		P0 = num6[5] == 0 && num6[4] == 0 && num6[3] == 0 && num6[2] == 0 ? 0xFF : num16[num6[2]];


		break;


		case 3:


		            selectC = 0;


		selectB = 1;


		selectA = 1;


		P0 = num6[5] == 0 && num6[4] == 0 && num6[3] == 0 ? 0xFF : num16[num6[3]];


		break;


		case 4:


		            selectC = 1;


		selectB = 0;


		selectA = 0;


		P0 = num6[5] == 0 && num6[4] == 0 ? 0xFF : num16[num6[4]];


		break;


		case 5:


		            selectC = 1;


		selectB = 0;


		selectA = 1;


		P0 = num6[5] == 0 ? 0xFF : num16[num6[5]];


		break;


		default:


		            break;


	}


	i = ++i % 6;


}


//设置计数器初数值,重用的内容都应该写成独立函数出来方便维护


void thtl_init(void) 


{


	TH1 = (65536 - 922) / 256;


	TL1 = (65536 - 922) % 256;


}


void timer1_init(void) 


{


	TMOD |= 0X10;


	TMOD &= 0xDF;


	thtl_init();


	TR1 = 1;


}


void et1_init(void) 


{


	ET1 = 1;


	EA = 1;


}