1. 犀牛前端部落首页
  2. Arduino和RaspberryPi

Arduino定时器和中断的使用

本教程介绍了Arduino板的定时器和中断的使用。作为Arduino程序员,你将在没有知识的情况下使用定时器和中断,因为Arduino API隐藏了所有低级硬件。许多Arduino函数使用定时器,例如时间函数:delay(),millis()和micros()和delayMicroseconds().PWM函数analogWrite()使用定时器,作为tone()和noTone()函数。甚至伺服电机库492也使用定时器和中断。

什么是定时器?

定时器或更准确的定时器/计数器是Arduino控制器内置的硬件(其他控制器也有定时器硬件)。它就像一个时钟,可用于测量时间事件。
定时器可以通过一些特殊寄存器进行编程。您可以为计时器,操作模式和许多其他事项配置预分频器。
Arduino的控制器是Atmel AVR ATmega168或ATmega328。这些芯片是引脚兼容的,只是内部存储器的大小不同。两者都有3个定时器,分别叫timer0,timer1和timer2。 Timer0和timer2为8bit定时器,timer1为16bit定时器。 8位和16位定时器之间最重要的区别是定时器分辨率。 8位表示256个值,其中16位表示65536值,用于更高分辨率。
Arduino Mega系列的控制器是Atmel AVR ATmega1280或ATmega2560。同样只有内存大小不同。这些控制器有6个定时器。定时器0,定时器1和定时器2与ATmega168 / 328相同。 timer3,timer4和timer5都是16bit定时器,类似于timer1。
所有定时器都取决于Arduino系统的系统时钟。通常系统时钟为16MHz,但对于Arduino Pro 3,3V则为8Mhz。所以在编写自己的定时器功能时要小心。
定时器硬件可以配置一些特殊的定时器寄存器。在Arduino固件中,所有定时器都配置为1kHz频率,并且中断是通用的。

定时器0:
Timer0是一个8位定时器。
在Arduino世界中,timer0用于定时器功能,如delay(),millis()和micros()。如果更改timer0寄存器,这可能会影响Arduino定时器功能。 所以你应该知道你在做什么。

定时器1:
Timer1是一个16位定时器。
在Arduino世界中,Servo库492在Arduino Uno上使用timer1(Arduino Mega上的timer5)。

定时器2:
Timer2是一个8bit定时器,如timer0。
在Arduino工作中,tone()函数使用timer2。

Timer3,Timer4,Timer5:
定时器3,4,5仅适用于Arduino Mega主板。 这些定时器都是16位定时器。

定时器寄存器
您可以通过定时器寄存器更改定时器行为。 最重要的定时器寄存器是:
TCCRx – 定时器/计数器控制寄存器。 预分频器可以在这里配置。

Arduino定时器和中断的使用

TCNTx – 定时器/计数器寄存器。 实际的计时器值存储在此处。

OCRx – 输出比较寄存器

ICRx – 输入捕捉寄存器(仅适用于16位定时器)

TIMSKx – 定时器/计数器中断屏蔽寄存器。 启用/禁用定时器中断。

TIFRx – 定时器/计数器中断标志寄存器。 表示挂起的定时器中断。

时钟选择和定时器频率

可以为每个定时器独立选择不同的时钟源。 要计算定时器频率(例如使用timer1的2Hz),您将需要:

Arduino的CPU频率为16Mhz
最大定时器计数器值(8位为256位,16位定时器为65536位)
通过选择的预分频器划分CPU频率(16000000/256 = 62500)
将结果除以所需频率(62500 / 2Hz = 31250)
如果失败,请根据最大定时器计数器值(31250 <65536成功)验证结果,选择更大的预分频器。

Arduino定时器和中断的使用

定时器模式

定时器可以配置为不同的模式。

PWM模式。 纸浆宽度调制模式。 OCxy输出用于生成PWM信号

CTC模式。 比较匹配时清除计时器。 当定时器计数器到达比较匹配寄存器时,定时器将被清除。

Arduino定时器和中断的使用

什么是中断?

在控制器上运行的程序通常按指令顺序运行。中断是一个外部事件,它会中断正在运行的程序并运行一个特殊的中断服务程序(ISR)。 ISR完成后,继续执行下一条指令。指令意味着单个机器指令,而不是一行C或C ++代码。
在挂起的中断能够调用ISR之前,必须满足以下条件:

通常必须启用中断
必须启用相应的中断掩码

通常可以使用函数中断()/ noInterrupts()启用/禁用中断.。默认情况下,Arduino固件启用中断。通过设置/清除中断屏蔽寄存器(TIMSKx)中的位来使能/禁止中断屏蔽。
发生中断时,中断标志寄存器(TIFRx)中的标志位置1。进入ISR或手动清除中断标志寄存器中的位时,该中断将自动清零。

Arduino函数attachInterrupt()和detachInterrupt()只能用于外部中断引脚。这些是不同的中断源,这里不讨论。

定时器中断

定时器可以生成不同类型的中断。寄存器和位定义可以在处理器数据手册(Atmega328 147或Atmega2560 135)和I / O定义头文件中找到(idux8.h代表Arduino,iomxx0_1.h代表Arduino Mega代表硬件/ tools / avr / include / avr文件夹)。后缀x代表定时器号(0..5),后缀y代表输出号(A,B,C),例如TIMSK1(定时器1中断屏蔽寄存器)或OCR2A(定时器2输出比较寄存器A)。

定时器溢出:
定时器溢出意味着定时器已达到限制值。发生定时器溢出中断时,定时器溢出位TOVx将在中断标志寄存器TIFRx中置1。当中断屏蔽寄存器TIMSKx中的定时器溢出中断使能位TOIEx置1时,将调用定时器溢出中断服务程序ISR(TIMERx_OVF_vect)。

输出比较匹配:
发生输出比较匹配中断时,OCFxy标志将在中断标志寄存器TIFRx中置1。当中断屏蔽寄存器TIMSKx中的输出比较中断使能位OCIExy置1时,将调用输出比较匹配中断服务ISR(TIMERx_COMPy_vect)例程。

定时器输入捕获:
当发生定时器输入捕捉中断时,输入捕捉标志位ICFx将被设置在中断标志寄存器TIFRx中。当中断屏蔽寄存器TIMSKx中的输入捕捉中断使能位ICIEx置1时,将调用定时器输入捕捉中断服务程序ISR(TIMERx_CAPT_vect)。

PWM和定时器
定时器和PWM输出之间存在固定的关系。当您查看数据手册或处理器的引脚分配时,这些具有PWM功能的引脚具有OCRxA,OCRxB或OCRxC等名称(其中x表示定时器编号0..5)。 PWM功能通常与其他引脚功能共享。
Arduino有3个定时器和6个PWM输出引脚。定时器和PWM输出之间的关系是:
引脚5和6:由timer0控制
引脚9和10:由timer1控制
引脚11和3:由timer2控制

在Arduino Mega上我们有6个定时器和15个PWM输出:
引脚4和13:由timer0控制
引脚11和12:由timer1控制
引脚9和10:由timer2控制
引脚2,3和5:由定时器3控制
引脚6,7和8:由定时器4控制
引脚46,45和44 ::由定时器5控制

有用的第三方库

存在一些使用计时器的第三方库:

Ken Shirrifs IR库126.使用timer2。 发送和接收任何类型的红外遥控信号,如RC5,RC6,索尼
Timer1和Timer3库732.使用timer1或tiner3。 编写自己的计时器中断服务例程的简便方法。

我已将这些库移植到Arduino Mega的不同计时器上。 所有移植的库都可以在附件中找到。

陷阱

在编写Arduino并使用使用定时器的函数或库时,您可能会遇到一些陷阱。

伺服库使用Timer1。在Arduino上使用伺服库时,不能在引脚9,10上使用PWM。对于Arduino Mega来说,它有点困难。所需的计时器取决于伺服器的数量。每个计时器可以处理12个伺服系统。对于前12个伺服系统,将使用定时器5(在引脚44,45,46上松开PWM)。对于24伺服定时器,将使用定时器5和1(在引脚11,12,44,45,46上松开PWM)。对于36个伺服定时器5,将使用1和3(在引脚2,3,5上丢失PWM, 11,12,44,45,46)..对于48个伺服系统,将使用所有16​​位定时器5,1,3和4(丢失所有PWM引脚)。
引脚11具有共用功能PWM和MOSI。 SPI接口需要MOSI,Arduino上不能同时使用引脚11上的PWM和SPI接口。在Arduino Mega上,SPI引脚位于不同的引脚上。
tone()函数至少使用timer2。当您在Arduino Mega上使用Arduino和Pin 9,10的tone()函数时,不能在引脚3,11上使用PWM。

例子

一些例子的时间。

带比较匹配中断的LED闪烁

第一个示例使用CTC模式下的timer1和比较匹配中断来切换LED。定时器配置为2Hz的频率。 LED在中断服务程序中切换。

Arduino定时器和中断的使用

/* Arduino 101: timer and interrupts
   1: Timer1 compare match interrupt example 
*/
#define ledPin 13


void setup()

{

  pinMode(ledPin, OUTPUT);

  

  // initialize timer1 

  noInterrupts();           // disable all interrupts

  TCCR1A = 0;

  TCCR1B = 0;

  TCNT1  = 0;


  OCR1A = 31250;            // compare match register 16MHz/256/2Hz

  TCCR1B |= (1 << WGM12);   // CTC mode

  TCCR1B |= (1 << CS12);    // 256 prescaler 

  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt

  interrupts();             // enable all interrupts

}


ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine

{

  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin

}


void loop()

{

  // your program here…

}


 Blinking LED with timer overflow interrupt

same example like before but now we use the timer overflow interrupt. In this case timer1 is running in normal mode. 

The timer must be preloaded every time in the interrupt service routine.



/* 
 * Arduino 101: timer and interrupts
 * 2: Timer1 overflow interrupt example 
 * more infos: http://www.letmakerobots.com/node/28278
 * created by RobotFreak 
 */

#define ledPin 13

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

  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;

  TCNT1 = 34286;            // preload timer 65536-16MHz/256/2Hz
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_OVF_vect)        // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  TCNT1 = 34286;            // preload timer
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}

void loop()
{
  // your program here...
}

使用定时器读取正交编码器

下一个例子是我的Ardubot项目的一部分。它使用timer2和比较匹配中断来读取编码器输入。 Timer2默认初始化为1kHz(1ms周期)的频率。 在中断服务程序中,读取所有编码器引脚的状态,并使用状态机来消除错误读数。 使用定时器中断比使用4个输入更改中断更容易处理。

正交编码器的信号是2位格雷码.只有1位从一个状态变为另一个状态。 状态机非常适合检查信号并计算编码器滴答。 计时器必须足够快,以识别每个状态变化。 对于这里使用的Pololu车轮编码器,1ms定时器足够快。

Arduino定时器和中断的使用

以下示例已经过修改,可以与Arduino V1.x一起使用:

/*
/* 
 * Arduino 101: timer and interrupts
 * 3: Timer2 compare interrupt example.
 */
#if ARDUINO >= 100

#include “Arduino.h”

#else

#include “WConstants.h”

#endif


// Encoder Pins

#define encLtA 2

#define encLtB 3

#define encRtA 11 

#define encRtB 12

#define ledPin 13


#define LT_PHASE_A		digitalRead(encLtA)

#define LT_PHASE_B		digitalRead(encLtB)

#define RT_PHASE_A		digitalRead(encRtA)

#define RT_PHASE_B		digitalRead(encRtB)


static volatile int8_t encDeltaLt, encDeltaRt;

static int8_t lastLt, lastRt;

int encLt, encRt;


ISR( TIMER2_COMPA_vect )

{

  int8_t val, diff;


  digitalWrite(ledPin, HIGH);   // toggle LED pin

  val = 0;

  if( LT_PHASE_A )

    val = 3;

  if( LT_PHASE_B )

    val ^= 1;					// convert gray to binary

  diff = lastLt - val;				// difference last - new

  if( diff & 1 ){				// bit 0 = value (1)

    lastLt = val;				// store new as next last

    encDeltaLt += (diff & 2) - 1;		// bit 1 = direction (+/-)

  }


  val = 0;

  if( RT_PHASE_A )

    val = 3;

  if( RT_PHASE_B )

    val ^= 1;					// convert gray to binary

  diff = lastRt - val;				// difference last - new

  if( diff & 1 ){				// bit 0 = value (1)

    lastRt = val;				// store new as next last

    encDeltaRt += (diff & 2) - 1;		// bit 1 = direction (+/-)

  }

  digitalWrite(ledPin, LOW);   // toggle LED pin

}


void QuadratureEncoderInit(void)

{

  int8_t val;


  cli();

  TIMSK2 |= (1<<OCIE2A);

  sei();

  pinMode(encLtA, INPUT);

  pinMode(encRtA, INPUT);

  pinMode(encLtB, INPUT);

  pinMode(encRtB, INPUT);


  val=0;

  if (LT_PHASE_A)

    val = 3;

  if (LT_PHASE_B)

    val ^= 1;

  lastLt = val;

  encDeltaLt = 0;


  val=0;

  if (RT_PHASE_A)

    val = 3;

  if (RT_PHASE_B)

    val ^= 1;

  lastRt = val;

  encDeltaRt = 0;


  encLt = 0;

  encRt = 0;

}


int8_t QuadratureEncoderReadLt( void )			// read single step encoders

{

  int8_t val;


  cli();

  val = encDeltaLt;

  encDeltaLt = 0;

  sei();

  return val;					// counts since last call

}


int8_t QuadratureEncoderReadRt( void )			// read single step encoders

{

  int8_t val;


  cli();

  val = encDeltaRt;

  encDeltaRt = 0;

  sei();

  return val;					// counts since last call

}


void setup()

{

  Serial.begin(38400);

  pinMode(ledPin, OUTPUT);

  QuadratureEncoderInit();

}


void loop()

{

  encLt += QuadratureEncoderReadLt();

  encRt += QuadratureEncoderReadRt();

  Serial.print("Lt: “);

  Serial.print(encLt, DEC);

  Serial.print(” Rt: ");

  Serial.println(encRt, DEC);

  delay(1000);

}

原创文章,作者:犀牛前端部落,如若转载,请注明出处:https://www.pipipi.net/1030.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注