其他分享
首页 > 其他分享> > 51单片机(STC89C52)的中断和定时器

51单片机(STC89C52)的中断和定时器

作者:互联网

关于STC89C51/C52:

中断

中断允许控制寄存器 IE
字节地址A8H, CPU对中断系统所有中断以及某个中断源的开放和屏蔽是由中断允许寄存器IE控制的

D7 D6 D5 D4 D3 D2 D1 D0
EA ET2 ES ET1 EX1 ET0 EX0

52单片机一共有6个中断源, 它们的符号, 名称以及各产生的条件分别如下

  1. INT0 - 外部中断0, 由P3.2端口线引入, 低电平或下降沿引起
  2. INT1 - 外部中断1, 由P3.3端口线引入, 低电平或下降沿引起
  3. T0 - 定时器/计数器0中断, 由T0计数器计满回零引起
  4. T1 - 定时器/计数器1中断, 由T1计数器计满回零引起
  5. T2 - 定时器/计数器2中断, 由T2计数器计满回零引起 <--这个是52特有的
  6. TI/RI - 串行口中断, 串行端口完成一帧字符发送/接收后引起

定时器中断
51单片机内部共有两个16位可编程的定时器,即定时器T0和定时器T1, 52单片机内部多一个T2定时器. 它们既有定时功能,也有计数功能。可通过设置与它们相关的特殊功能寄存器选择启用定时功能还是计数功能. 这个定时器系统是单片机内部一个独立的硬件部分,它与CPU和晶振通过内部某些控制线连接并相互作用,CPU一旦设置开启定时功能后,定时器便在晶振的作用下自动开始计时,但定时器的计数器计满后,会产生中断。
定时器/计数器的实质是加1计数器(16位), 由高8位和低8位两个寄存器组成.

代码例子

#include<reg52.h>
 
sbit led=P3^0;

/** 中断的设置,首先设置中断的触发方式,再设置开启终端,最后开启总中断。*/
void main() {
  IT0=1; //设置外部中断0的触发方式为下降沿
  EX0=1; //开启外部中断0
  IT1=1; //设置外部中断1的触发方式为下降沿
  EX1=1; //开启外部中断1
  EA =1; //总中断开关
  while(1) {
    P0=0xaa;
    P0=0xff;
  }
}

void EX0_ISR(void) interrupt 0 {
  led=~led;
}
 
void EX1_ISR(void) interrupt 2 { //外部中断1的中断在此为2!
  led=~led;
}

代码例子二

#include <reg52.h>

// 定义I/0引脚名称
sbit led1=P1^1;
sbit led2=P1^2;
sbit led3=P1^3;
sbit led4=P1^4;
sbit P32=P3^2;

//全局变量及位标志定义
bit FINT0;
bit FINT1;
bit FT0;
bit FT1;
bit FT2;
unsigned char T0_10ms;
unsigned char T0_50ms;
unsigned char T0_100ms;

//函数声明
void int_0(); //外部中断0
void int_1(); //外部中断1
void timer_0(); //定时器中断1
void timer_1(); //定时器中断2
void serial_1(); //串行中断1
void serial_2(); //串行中断2

//用户函数声明
void initial(); //初始化

void main(){
  initial();
  while(1){
    P32=0; //为了使按'取消'、'确定'键能够产生INT0及INT1中断
    //led4=0; //上一句等价于此句
    if(FINT0){ //中断0来到要做什么事情
      FINT0=0;
      led1=0; //INT0中断时点亮
      led2=0;
      led3=0;
      led3=0; //可以在此设一个断点
    }
    if(FINT1){ //中断1来到要做什么事情
      FINT1=0;
      led1=1; //INT1中断时熄灭
      led2=1;
      led3=1;
    }
    if(FT0){
      FT0=0;
      if(++T0_10ms > 30){
        T0_10ms=0;
        //定时多少做什么事,未初始化里定时器尚未设置
      }
    }
  }
}

void initial(){
  EA=1; // CPU所有中断开(IE最高位MSB)
  EX0=1; // INT0中断开
  IT0=0; // INT0 0:低电平触发, 1:下降沿触发
  EX1=1; // INT1中断开
  IT1=0; // INT1 0:低电平触发, 1:下降沿触发
  return;
}

//INT0中断 由P3.2引脚产生
void int_0() interrupt 0 using 0 {
  FINT0=1;
}

//INT1中断 由P3.3引脚产生
void int_1() interrupt 2 using 1 {
  FINT1=1;
}

//定时器0中断
void timer_0() interrupt 1 using 2 {
  FT0=1;
}

//定时器1中断
void timer_1() interrupt 3 using 3 {
  FT1=1;
}

//串行中断1
void serial_1() interrupt 4 { }

//定时器2中断
void timer_2() interrupt 5 {
  FT2=1;
}

定时器

89C51有两个计数器T0和T1, 89C52还有一个定时器T2

定时器T0和T1

控制寄存器TCON
字节地址88H, 位寻址8FH - 88H

位地址 8F 8E 8D 8C 8B 8A 89 88
位符号 TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0

模式控制寄存器 TMOD
逐位定义的8位寄存器, 只能使用字节寻址, 字节地址为89H

D7 D6 D5 D4 D3 D2 D1 D0
GATE C/T M1 M0 GATE C/T M1 M0
M1 M0 工作方式 计数器模式
0 0 TMOD=0x00 13位计数器 (8192) 13-bit timer/counter, 8-bit of THx & 5-bit of TLx
0 1 TMOD=0x01 16位计数器 (65536) 16-bit timer/counter, THx cascaded with TLx
1 0 TMOD=0x02 自动重载8位计数器 8-bit timer/counter (auto-reload mode), TLx reload with the value held by THx each time TLx overflow
1 1 TMOD=0x03 T0分为2个8位计数器,T1为波特率发生器. Split the 16-bit timer into two 8-bit timers i.e. THx and TLx like two 8-bit timer

用11.0592MHz晶振的C52产生较精确的1秒定时中断, 下面的代码是基于SDCC的8052.h. 下面说明一下定时器初始值的计算

  1. 由晶振11.0592 MHz, 得到定时器时钟为 11.0592 / 12 = 0.9216 MHz,
  2. 因此1ms对应 921.6 个时钟周期,
  3. 因此50ms对应 46080 个时钟周期,
  4. 将其设为一次中断后, 20次中断就对应1s

代码

#include <8052.h>

const unsigned char th = (65536 - 46080) / 256;
const unsigned char tl = (65536 - 46080) % 256;
volatile unsigned char i = 0;

void main() {
  TMOD= 0x01; //工作方式为16位定时器
  TH0 = th;   //计数寄存器高8位
  TL0 = tl;   //计数寄存器低8位
  EA  = 1;    //允许中断
  ET0 = 0x01; //允许T0中断
  TR0 = 1;    //启动T0
  while(1);
}

void Timer0IRQ(void) __interrupt (1) // 中断处理函数 T0 -> 中断1
{
  i++;
  if(i > 20) {
    P0_7 = (P0_7 == 1)? 0 : 1; //触发P0.7 LED闪烁
    i = 1; // 注意这边不能初始化为0, 否则每次会多跑一个中断
  }
  TH0 = th; //计数寄存器高8 位重新载入
  TL0 = tl; //计数寄存器低8 位重新载入
}

定时器T2

控制寄存器TCON2
字节地址0C8H, 可位寻址

CF CE CD CC CB CA C9 C8
TF2 EXF2 RCLK TCLK EXEN2 TR2 C/T2 CP/RT2
溢出标志位 定时器外部标志 接收时钟标志 发送时钟标志 外部使能 启动、停止控制位 选择位 捕获重装标志

模式控制寄存器T2MOD
字节地址0C9H, 不可位寻址

D7 D6 D5 D4 D3 D2 D1 D0
-- -- -- -- -- -- T2OE DCEN

使用 __nop(); 精确定时

假如使用者想要产生精确的延迟时间,建议使用__nop()函数来组合达成。__nop()函数能够产生 1 个精确的 CPU 频率周期延迟时间。然而,由于 flash 的速度低于 CPU 的频率速度,在 CPU 内部有缓存优化的技术,编译程序也会自动针对程序做优化,造成__nop() 函数组合出来的时间会与预期的时间不同。因此,建议将程序放置于 SRAM 中执行,以避免优化造成的非预期延迟时间问题. 以产生 2 us 的延迟时间为例:

  1. CPU 频率= 32MHz => 1 CPU 频率周期花费 1/32000000 sec = 31.25 ns
  2. 2us 延迟时间 = 2000ns / 31.25 ns = 64 次 CPU 频率周期

由于执行一次 for 循环需要花费 5 个 CPU 频率周期的时间,因此可以使用以下的方式达到 2 us 的时间延迟

  1. 执行一次 for 循环需要 5 个 CPU 频率周期
  2. 执行一次 __NOP()指令需要 1 个 CPU 频率周期
  3. 64 个 CPU 频率周期 = 8 * ( 5 ( for 循环 ) + 3 * ( __NOP() ) )
void Delay_Test_Function(void) {
  for(i = 0; i < 8 ; i++) {    /* Delay for 2 us. */
    __NOP();
    __NOP();
    __NOP();
  }
}

例子2, 执行一次 PA = 0 需花费 11 CPU 指令周期,这意味着 I/O 会持续 (64+11) * 31.25 ns = 2343.75 ns 的时间才进行转态。

void Delay_Test_Function(void) {
  uint32_t i, DelayCNTofCPUClock = 8;
  PA0 = 1;
  for(i = 0; i < DelayCNTofCPUClock ; i++) {    /* Delay for 2 micro seconds. */
    __NOP();
    __NOP();
    __NOP();
  }
  PA0 = 0;
}

参考

标签:定时器,中断,51,void,T2,T0,单片机,STC89C52,CPU
来源: https://www.cnblogs.com/milton/p/14994525.html