单片机学习小组
直播中

郭中

10年用户 1003经验值
擅长:存储技术
私信 关注

定时器中断的作用是什么?怎样去使用定时器中断呢

中断是什么意思?
定时器中断的作用是什么?怎样去使用定时器中断呢?

回帖(1)

潘福乔

2022-1-24 14:06:27
1 中断的基本概念

        中断装置和中断处理中断处理程序统称为中断系统。中断(Interrupt)是计算机的一个重要概念,现代计算机普遍采用中断技术。
        当计算机执行正常程序时,系统中会出现某些急需处理的异常情况和特殊请求,此时 CPU 会暂时中止现行程序,转去对随机发生的更为紧迫的事件进行处理,处理完毕后,CPU 自动返回原来的程序继续执行,此过程就叫做中断。实现中断功能的硬件和软件统称中断系统。一个完整的中断处理过程包括中断请求、中断响应、中断处理和中断返回。


  • 中断请求 中断过程是从中断源向 CPU 发出中断请求而开始的,其中断请求信号应该至少保持到 CPU 做出响应为止。
  • 中断响应 CPU检测到中断请求后,在一定的条件和情况下进行响应。
  • 中断处理 CPU响应中断结束后,返回原先被中断的程序并继续执行。
  • 中断返回 中断返回是指把运行程序从中断服务程序转回到被中断的主程序中。

中断结构如下图所示:

        我们从一个生活中的例子引入。你正在家中看书,突然电话铃响了,你放下书本,去接电话,和来电话的人交谈,然后放下电话,回来继续看你的书。这就是生活中的“中断”的现象,就是正常的工作过程被外部的事件打断了。
        常见的中断有外部中断和时钟中断:


  • 时钟中断,是指设定CPU内部定时器后,当到达指定时间,将产生中断请求。常用于定时。
  • 外部中断,就是当CPU的外部中断管脚电平变动时,将产生中断请求。 常用于键盘输入、串口通信等。

  如果没有中断的话,arduino 是一直运行 loop 内的代码,一遍一遍重复运行。当有中断产生时候,单片机会停止 loop 的代码,开始运行中断服务函数的代码,运行一遍中断服务函数后,继续回到 loop 内接着刚才运行的代码运行。
  2 外部中断

        外部中断是由外部设备发起请求的中断。要想使用外部中断,就需了解中断引脚的位置,根据外部设备选择中断模式,以及编写一个中断被触发后需执行的中断函数。
2.1 中断引脚与中断编号

        在不同型号的 Arduino 控制器上,中断引脚的位置也不相同,只有中断信号发生在带有外部中断功能的引脚上,Arduino才能捕获到该中断信号并做出响应,下表列举了 Arduino 常见型号控制器的中断引脚所对应的外部中断编号。
[tr]Arduino 型号int0 int1 int2 int3 int4 int5 [/tr]
         UNO             2               3               —               —               —               —     
         MEGA             2               3               21               20               19               18     
         Lernardo               3             2               0               1               —               —     
         DUE             所有引脚均可使用外部中断     




        注:表格中int0、int1等都为外部中断编号。
2.2 中断模式

        为了设置中断模式,还需要了解设备触发外部中断的输入信号类型。中断模式也就是中断触发的方式,在大多数 Arduino 上支持下表中的四种中断触发方式。
[tr]模式名称说明[/tr]
LOW低电平出发
CHANGE电平变化出发,即由高变低、由低变高
RISING上升沿触发,即低电平变高电平
FALLING下降沿触发,即高电平变低电平
        在 Arduino Due 中,还可以使用高电平(HIGH)来触发中断,另外 Arduino Due 上的每一个 I/O 口都可以触发中断,其中断编号便是引脚编号。
2.3 中断函数

        除了设置中断模式外,还需要编写一个响应中断的处理程序——中断函数,当中断被触发后,便可以让Arduino运行该中断函数。中断函数就是当中断被触发后要去执行的函数,该函数不能带有任何参数,且返回类型为空,如:

void counter()
{
        count++;
}
        

当中断被触发后,Arduino 便会执行该函数中的语句。
        这些准备工作完成后,还需要在 setup() 中使用 attachInterrupt() 函数对中断引脚进行初始化配置,以开启 Arduino 的外部中断功能,其用法如下:
(1)attachInterrupt( interrupt,function,mode)
        功能:对中断引脚进行初始化配置。
        参数:
        interrupt,中断编号,注意,这里的中断编号并不是引脚编号。
        function,中断函数名,当中断被触发后即会运行此函数名称所代表的中断函数。
        mode,中断模式。
attachInterrupt(0,counter,RISING);         
如果使用的是 Arduino UNO 或者 MEGA 控制器,则该语句即会开启2号引脚(中断编号0)上的外部中断功能,并指定上升沿时触发该中断。当2号引脚上的电平由低变高后,该中断会被触发,而 Arduino 即会运行 counter()函数中的语句。
        如果不需要使用外部中断了,则可以使用中断分离函数 detachInterrupt() 来关闭中断功能。
在使用 attachInterrupt 函数时要注意以下几点:


  • 在中断函数中 delay 函数不能使用。
  • 使用 millis 函数始终返回进入中断前的值。
  • 读取串口数据的话,可能会丢失。
  • 中断函数中使用的变量需要定义为 volatile 型。
    attachInterrupt 函数的函数原型可在文件 WInterrupts.c 中找到,如下所示:

(2)detachInterrupt( interrupt)
        功能:禁用外部中断。
        参数:
        interrupt,需要禁用的中断编号。
(3)interrupts()和nolnterrupts()
        interrupts 和 noInterrupts 函数在 Arduino 中负责打开和关闭总中断,函数无返回值,无参数,可以在文件 wiring.h 中查看函数原型,如下:

#define interrupts() sei()
#define noInterrupts() cli()


3 定时器中断

3.1 定时器的作用

        定时器对于单片机来说就类似我们现实生活中的时钟,记录很多和时间相关的事件。在我们平时经常使用的 delay() millis() ,micros() ,delayMicroseconds() ,PWM 波生成的 analogWrite() 和 tone() 函数都是通过定时器实现的,不过这些都被 Arduino 的封装库隐藏起来了,为了让使用者更快更便捷地开发项目。
        我们平常使用的 Arduino 单片机为 UNO,NANO和MEGA 2560。UNO 和 NANO 都使用的是 ATmega328 芯片,这款芯片有3个定时器,Timer0,Timer1,Timer2,其中Timer0和Timer2都是8位寄存器(256),Timer1是16位寄存器(65536),意味着更高的分辨率。mege2560 使用的是 ATmege2560 芯片,这款芯片有 6 个定时器,在328 的基础上,增加了 Timer3,Timer4,Timer5。这三个定时器都是16位的寄存器。
[tr]Arduino 型号 参数Timer0 Timer1 Timer2 Timer3 Timer4 Timer5 [/tr]
         UNO             位数               8bit               16bit               8bit               —               —               —   
          对应引脚               5,6               9,10             3,11               —             —             —   
          封装函数               delay(),millis()和micors()等               servo库               tone()等             —               —               —     
         MEGA             位数               8bit               16bit               8bit               16bit               16bit               16bit     
          对应引脚               4,13               11,12             9,10               2,3,5             6,7,8             46,45,44     
          封装函数               delay(),millis()和micors()等               —               tone()等             —               —               servo库     
        注意:对于UNO开发板,引脚5和6的 PWM 功能输出时将产生高于预期的占空比,这是因为 millis() 和 delay() 函数共用一个内部定时器,使得内部计时器在处理PWM 时候分心,这种情况一般出现在低占空比时。MEGA 2560以此类比。
3.2 定时器的基本概念

1、寄存器
        寄存器列表如下,x代表0,1,2,3,4,5这6种定时器。
[tr]寄存器作用[/tr]
TCCRx定时器/计数器控制寄存器;预分频器可以在这里配置
TCNTx定时器/计数器寄存器
OCRx输出比较寄存器
ICRx输入捕捉寄存器(仅适用于16位定时器)
TIMSKx定时器/计数器中断屏蔽寄存器;启用/禁用定时器中断
TIFRx定时器/计数器中断标志寄存器;表示挂起的定时器中断
这里给个 Timer0 的寄存器,更多请参考技术文档

2、预分频系数与比较匹配器
        Arduino UNO 时钟以16MHz运行。计数器的一个刻度值表示 1 / 16,000,000秒(~63ns),跑完1s需要计数值16,000,000。
(1)Timer0 和 Timer2 是8位定时器,可以存储最大计数器值255。
(2)Timer1 是一个16位定时器,可以存储最大计数器值65535。
        一旦计数器达到其最大值,它将回到零(这称为溢出)。因此,需要对时钟频率进行分频处理,即预分频器。通过预分频器控制定时计数器的增量速度。预分频器与定时器的计数速度如下:

  定时器速度(HZ) = Arduino UNO时钟速度(16MHz) / 预分频器系数
        因此,1预分频器将以16MHz递增计数器,8预分频器将在2MHz递增,64预分频器= 250kHz,依此类推。
        定时器 Timer0 的预分频系数配置如表:

        现在您可以用以下公式计算中断频率:
  中断频率(Hz)=(Arduino时钟速度16MHz)/(预分频器*(比较匹配寄存器+ 1))
        重新排列上面的等式,给出你想要的中断频率,你可以求解比较匹配寄存器值:

  比较匹配寄存器= [16,000,000Hz /(预分频器*所需的中断频率)] - 1
        当你使用定时器0和2时,这个数字必须小于256,对于timer1小于65536。
        所以如果你想每秒一次中断(频率为1Hz):

  比较匹配寄存器= [16,000,000 /(预分频器 * 1)] -1
        预分频器为1024,你得到:

  比较匹配寄存器= [16,000,000 /(1024 * 1)] -1 = 15,624
        因为256 <15,624 <65,536,你必须使用timer1来实现这个中断。
3、定时器模式
        定时器可以配置为不同的模式。
(1)PWM模式。 纸浆宽度调制模式。 OCxy输出用于生成PWM信号
(2)CTC模式。 比较匹配时清除计时器。 当定时器计数器到达比较匹配寄存器时,定时器将被清除。
        定时器 Timer0 的模式选择配置如表:
3.3 定时器的配置


int toggle0,toggle1,toggle2;
  void setup(){


  cli();关闭全局中断


  //设置定时器0为10kHz(100us)
  TCCR0A = 0;//将整个TCCR0A寄存器设置为0
  TCCR0B = 0;//将整个TCCR0B寄存器设置为0
  TCNT0  = 0;//将计数器值初始化为0
  //设置计数器为10kHZ,即100us
  OCR0A = 24;//比较匹配寄存器= [16,000,000Hz /(预分频器*所需中断频率)] - 1
             //比较匹配寄存器=24,中断间隔=100us即中断频率10khz
  TCCR0A |= (1 << WGM01);//打开CTC模式
  TCCR0B |= (1 << CS01) | (1 << CS00); //设置CS01位为1,CS00位为1(64倍预分频)   
  TIMSK0 |= (1 << OCIE0A);//启用定时器比较中断
  


  //设置定时器1为1kHz
  TCCR1A = 0;//将整个TCCR1A寄存器设置为0
  TCCR1B = 0;//将整个TCCR1B寄存器设置为0
  TCNT1  = 0;//将计数器值初始化为0
  //设置计数器为1kHZ,即1ms
  OCR1A = 1999;// = (16*10^6)/(1000*8) - 1 (must be <65536)
  TCCR1B |= (1 << WGM12);//打开CTC模式
  TCCR1B |= (1 << CS11);//设置CS11位为1(8倍预分频)
  TIMSK1 |= (1 << OCIE1A);


  //设置定时器2为8kHz
  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
  // set compare match register for 8khz increments
  OCR2A = 249;// = (16*10^6) / (8000*8) - 1 (must be <256)
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);//打开CTC模式
  // Set CS21 bit for 8 prescaler
  TCCR2B |= (1 << CS21);   
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);


  sei();//打开全局中断


}


//中断0服务函数
ISR(TIMER0_COMPA_vect){// timer0中断2Hz切换引脚13(LED)
//产生频率为10kHz / 2 = 5kHz的脉冲波
  if(toggle0){
    digitalWrite(8,HIGH);
    toggle0 = 0;
  }
  else{
    digitalWrite(8,LOW);
    toggle0 = 1;
  }
}


ISR(TIMER1_COMPA_vect){// timer1中断2Hz切换引脚13(LED)
//产生频率为2Hz / 2 = 1Hz的脉冲波
  if(toggle1>=500)
    digitalWrite(13,HIGH);
  if(toggle1<=500)
    digitalWrite(13,LOW);
  toggle1 += 1;
  if(toggle1 >= 1000)
    toggle1 = 0;
}
  
ISR(TIMER2_COMPA_vect){// timer2中断8kHz切换引脚9
//产生频率为8kHz / 2 = 4kHz的脉冲波
  if(toggle2){
    digitalWrite(9,HIGH);
    toggle2 = 0;
  }
  else{
    digitalWrite(9,LOW);
    toggle2 = 1;
  }
}
//loop function
void loop(){
  
}
3.4 定时器中断的使用

        使用定时器中断前,必须先安装对应的 Timer 库,然后导入到 Arduino 的库文件里,并在程序中引用头文件 Timer.h 。
        实例:

// 秒切换一次引脚13的电平
// 包含定时器库的头文件
#include


// 中断服务程序
void flash() {
  static boolean output = HIGH;


  digitalWrite(13, output);
  output = !output;
}


void setup() {
  pinMode(13, OUTPUT);


  FlexiTimer2::set(500, flash); // 中断设置函数,每 500ms 进入一次中断
  FlexiTimer2::start();                    // 开始计时
}


void loop() {
}
其他 Timer 的使用基本一样,只要找到合适的库函数就能够使用,注意使用的定时器不要和你已使用的封装函数冲突,比如对于 UNO 来说,你在使用 Servo.h 的时候,就不能再使用 timer1 了,此时IDE会给你编译报错。
  
举报

更多回帖

发帖
×
20
完善资料,
赚取积分