Arduino成长日记6 - 中断机制

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_36955622/article/details/98083583

中断是在程序运行中经常用到的功能,用于处理一下实时性比较高的事件,首先来了解一下中断的概念。

中断

当出现需要及时处理的事件(中断请求)时,CPU暂停当前工作的执行转而处理应急事件(中断)的过程。即在程序运行过程中,系统出现了一个必须由CPU立即处理的情况,此时,CPU暂停当前程序去处理这个新事件的过程就就是中断。
举个栗子:你正在看着一本Arduino程序设计的书,看到95页时肚子饿了,你记下来页码跑去吃东西,吃完东西后继续从书的95页往下看;这里面看书就类似CPU在正常工作,肚子饿了吃东西就像处理中断,中断事件处理(吃东西)结束后(CPU)又从停下来的地方继续运行(看书)。


Arduino外部中断

外部中断是单片机实时地处理外部事件的一种内部机制。当某种外部事件发生时,单片机的中断系统将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理;中断处理完毕后.又返回被中断的程序处,继续执行下去。
大多数的Arduino板至少拥有两个外部中断引脚:0号中断(引脚2)和1号中断(引脚3)。不同的Arduino控制板的中断引脚还有所区别,如下表所示。

Arduino控制板 int0 int1 int2 int3 int4 int5
Uno,Ethernet 2 3 X X X X
Mega 2 3 21 20 19 18
Leonardo 3 2 0 1 X X
Due IO

表中int表示Arduino板的中断号对应的引脚,X 则表示该板没有对应的中断引脚, 在所有的Arduino控制板中,Arduino Due比较强大的中断功能,允许在所有IO脚触发外部中断。
Arduino编程中的中断均已函数调用的形式来配置及使用,相对来说比较简单,下面是外部中断的常用函数。

1、中断函数
attachInterrupt(interrupt,function,mode)

描述:当发生外部中断时,调用一个指定的函数。在程序中再次调用时可以指定新的中断调用函数;如在setup函数中初始化函数A为中断0的中断调用函数,再次调用 attachInterrupt 函数时,可以指定函数B为中断0的中断调用函数,在程序中有需要可多次调用更新中断函数。
语法:attachInterrupt(interrupt,function,mode)
attachInterrupt(pin,function,mode)(Due专用)
参数:
interrupt,中断编号;pin,引脚编号(Due专用);
function:中断发生时调用的函数,此函数必须不带任何参数,不返回任何值。
mode:定义什么情况下触发中断,以下四个常数为mode的有效值:

  • LOW: 当引脚为低电平时,触发中断;
  • CHANGE:当引脚电平发生变化时,触发中断;
  • RISING:当引脚电平由低变高时,触发中断(上升沿中断);
  • FALLING:当引脚电平由高变低时,触发中断(下降沿中断);
    对于Due而言,多了一个专用参数 – HIGH,即当引脚为高电平时,触发中断;

注意:不要妄想在中断函数中加延时,在Arduino中断函数中,delay()不会生效,millis()不会持续累加;当中断发生时,串口数据可能会出现丢包;在中断函数里面使用到的全局变量应该声明为volatile变量。

示例:

const byte ledPin = 13;
const byte interruptpin = 2;
volatile byte state = LOW;

void setup(void)
{
	pinMode(ledPin,OUTPUT);
	pinMode(interruptPin,INPUT_PULLUP);
	attachInterrupt(digitalPinToInterrupt(interruptPin),blink,CHANGE);
}

void loop()
{
	digitalWrite(ledPin,state);
}

void blink()
{
	state = !state;
}
detachInterrupt(interrupt)

描述:关闭对应的中断。
参数:interrupt,禁用中断的编号(0或1)。

2、中断使能函数
interrupts()

描述:启用中断/重新启用中断(在被禁用中断过后)。

noInterrupts()

描述:禁用中断。在程序中如果有一些函数的运行不希望被中断打断,可以调用noInterrupts函数来禁用中断的发生,再配合interrupts函数恢复中断使能。
示例:

void setup(){
}

void loop(){
	...
	noInterrupts();
	//放置关键函数(不想被打断)
	interrupts();
	//放置其他函数
	...
}

Arduino定时器中断

外部中断是通过检测输入电平的变化,而产生中断信号。除了外部中断方式外,Arduino控制板还可以按时间变化产生中断,这里使用到定时器(Timer),而对应产生的中断被称为定时器中断。
定时器是嵌入式系统中的一个特殊的计数器。它可以对分频后时钟信号的进行计数,当计数值达到设定值,即会产生定时器中断。且通过时钟频率和计数值可以计算出时间,所以可以达到以时间触发中断的效果。
即当需要按一定的时间间隔执行某个操作时,就需要用到定时器中断了。

以UNO为例,一共有3个定时器可以使用:Timer0,Timer1,Timer2。
每个定时器都有自己的函数库,通过使用硬件内部计时器中断来实现中断效果,下面以Timer1库的程序为例说明~
首先需要安装TimeOne库,也就是Timer1的对应库。下面提供两种安装方式,可以点击链接跳到对应的安装教程:
第一种:Arduino IDE安装TimerOne库
第二种:Sublime Text3环境安装TimerOne库
安装完成后,大概了解一下Timer1的库函数,打开.h文件TimerOne.h,除了包含 Arduino.h 外,还有一个宏定义声明了Timer1是一个16位定时器。

#define TIMER1_RESOLUTION 65536UL  // Timer1 is 16 bit

随后在TimerOne类中定义了定时器相关函数,在配置函数中包含了初始化函数initialize()函数,参数默认为1000000毫秒,也就是1秒;setPeriod()函数较长就不发出来了,主要作用是设置定时器的触发周期,在initialize()函数中调用。

	//****************************
    //  Configuration
    //****************************
    void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) {
		TCCR1B = _BV(WGM13);        // set mode as phase and frequency correct pwm, stop the timer
		TCCR1A = 0;                 // clear control register A 
		setPeriod(microseconds);
    }
    void setPeriod(unsigned long microseconds) __attribute__((always_inline)) {
    	...
    }

定时器状态控制函数

	//****************************
    //  Run Control
    //****************************
	void start() __attribute__((always_inline)) {
		TCCR1B = 0;
		TCNT1 = 0;		// TODO: does this cause an undesired interrupt?
		resume();
    }
    void stop() __attribute__((always_inline)) {
		TCCR1B = _BV(WGM13);
    }
    void restart() __attribute__((always_inline)) {
		start();
    }
    void resume() __attribute__((always_inline)) {
		TCCR1B = _BV(WGM13) | clockSelectBits;
    }

PWM控制相关函数(函数体省略)

	//****************************
    //  PWM outputs
    //****************************
    void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) {
		...
    }
    void pwm(char pin, unsigned int duty) __attribute__((always_inline)) {
		...
    }
    void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) {
		...
    }
    void disablePwm(char pin) __attribute__((always_inline)) {
		...
    }

最重要的来了,定时器中断相关函数

 	//****************************
    //  Interrupt Function
    //****************************
    void attachInterrupt(void (*isr)()) __attribute__((always_inline)) {
		isrCallback = isr;
		TIMSK1 = _BV(TOIE1);
    }
    void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) {
		if(microseconds > 0) setPeriod(microseconds);
		attachInterrupt(isr);
    }
    void detachInterrupt() __attribute__((always_inline)) {
		TIMSK1 = 0;
    }

大概了解过定时器相关函数后,可以开始编写定时器中断程序了。
1、添加TimerOne头文件(安装库之后);
2、初始化定时器,开启定时器中断;
3、编写定时器中断操作;
简单程序如下

#include "TimerOne.h"

//定时器中断函数
void Timer1Interrupt()
{
	digitalWrite(13,digitalRead(13)^1);			//控制LED闪烁
}

void setup()
{
	pinMode(13,OUTPUT);							//设置LED引脚为输出
	Timer1.initialize(500000);					//Timer1每500毫秒触发一次
	Timer1.attachInterrupt(Timer1Interrupt);	//配置定时器中断函数,开启中断
}

void loop(){}

使用定时器中断可以很好的解决Arduino的单任务机制,但是UNO的3个定时器资源同样跟引脚PWM会有冲突,比如Timer0定时器负责 millis()函数delay()函数 的计时工作,同时控制着数字引脚 pin5pin6 的PWM功能;Timer1定时器负责数字引脚 pin9pin10 的PWM功能,也影响 Servo.h库 的使用;Timer2定时器负责数字引脚 pin11pin3 的PWM功能,也和蜂鸣器的 tone()函数有关。

综上所述,定时器的使用会影响对应引脚的PWM功能,和某些时序函数的运行,比如Timer0负责delay()函数延时操作的,使用pin5、pin6的PWM输出可能会有些微误差,像analogWrite(5,0)和analogWrite(5,6)可能是一样的效果

除了对应的硬件定时器,还可以用Timer库和SimpleTimer库来实现定时效果,这两个库不用到硬件定时器,也是用到 millis()函数 来实现延时,所以对 delay函数 也是会有点影响。


串口中断(没这回事儿)

关于Arduino串口中断的也是找了一段时间没结果,后来发现了Arduino没有串口中断这回事,但是却机缘巧合的在main函数中找到了串口的端倪。
以下是Arduino中main函数的内容~

int main(void)
{
	init();
	initVariant();
#if defined(USBCON)
	USBDevice.attach();
#endif
	setup(); 
	for (;;) {
		loop();
		if (serialEventRun) serialEventRun();
	}    
	return 0;
}

即是Arduino没有串口中断,但是却用for循环来检测串口事件,与loop函数在同个循环中,相当于loop函数运行一次顺带检测一次串口事件。

猜你喜欢

转载自blog.csdn.net/qq_36955622/article/details/98083583
今日推荐