Arduino休眠模式和看门狗以及中断详解

一、休眠模式

  Arduino睡眠模式也称为Arduino省电模式(Power Save mode)或Arduino待机模式(Standby Mode)。Arduino睡眠模式允许用户停止或关闭微控制器中未使用的模块,从而显着降低功耗。 Arduino UNO、Arduino Nano和Pro-mini配备了ATmega328P,它有一个欠压检测器(BOD),用于监控睡眠模式时的电源电压。
   ATmega328P有六种睡眠模式,要进入任何睡眠模式,我们需要在睡眠模式控制寄存器(SMCR.SE)中启用睡眠位。然后,睡眠模式选择位选择Idle、ADC noise reduction、Power-Down、Power-Save、Standby和External Standby的睡眠模式。在现实世界中,实际上只有一种模式很有用;掉电模式(SLEEP_MODE_PWR_DOWN)。
  内部或外部Arduino中断或复位可以将Arduino从睡眠模式唤醒。
  Arduino像电脑和手机一样,也具备睡眠/休眠/待机功能。在睡眠状态下,系统几乎完全停止运作,只保留基本的侦测功能,因此只消耗少许电力。以电脑为例,在睡眠状态下,可被键盘按键或者网络信息唤醒。

主要功能函数如下:

  #include <avr/sleep.h>//引用库文件  
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);// 设置休眠模式  
  sleep_mode (); // 进入休眠状态

注意: sleep_mode 为宏指令,它会自动自动开启休眠功能、进入睡眠状态、禁用休眠功能。 按照官方解释,在某些条件下, sleep_mode 宏会导致个别操作步骤开启休眠功能并发出sleep指令进入休眠,所以,另外提供了以下三个指令来分步完成sleep_mode ()工作:

  sleep_enable();  // 开启休眠功能  
  sleep_cpu (); // 进入休眠状态  
  sleep_disable();//关闭休眠功能 


也就是说,貌似使用sleep_mode会出现意外情况,所以,根据情况自己选择吧。

#include <avr/sleep.h>
void setup () 
{  
  set_sleep_mode (SLEEP_MODE_PWR_DOWN); // 采用“Power-down”睡眠模式 
  sleep_enable();// 启动睡眠模式 
  sleep_cpu ();  // 进入睡眠模式
} 
void loop () 
{
}

  这段程序在UNO R3控制板上,约消耗32.9 mA电流;但是在精简的「准系统」Arduino板,仅仅消耗0.36mA(360μA)

  微控器内部除了中央处理器(CPU),还有內存、模拟数位转换器、序列通信…等模块。越省电的模式,仍在运作中的模块就越少。
  例如,在”Power-Down”(电源关闭)睡眠模式之下,微控器仅剩下外部中断和看门狗定时器(Watchdog Timer)仍持续运作。而在Idle睡眠模式底下,SPI,UART(也就是序列端口)、定时器、模拟数位转换器等,仍持续运作,只有中央处理器和闪存(Flash)时脉信号被停止。时脉信号就像心跳一样,一旦停止时脉信号,相关的元件也随之暂停。

睡眠中断的触发

中断触发种类:

  1. 通过外部中断。只有在发生外部中断时才会唤醒。
  2. 通过UART(USB串行接口)。保持睡眠,直到通过串行接口收到数据。
  3. 通过一个内部计时器,将定期通过Timer1从睡眠中醒来,执行一个动作并返回到睡眠状态。
  4. 通过看门狗定时器。定期通过看门狗定时器从睡眠中醒来,执行一个动作并返回到睡眠状态。请注意使用Watchdog可以提供最长的睡眠时间和最低的功耗。

二、看门狗

  看门狗,又叫 watchdog timer,是一个定时器电路, 一般有一个输入,叫喂狗(kicking the dog or service the dog),一个输出到MCU的RST端,MCU正常工作的时候,每隔一端时间输出一个信号到喂狗端,给 WDT清零,如果超过规定的时间不喂狗(一般在程序跑飞时),WDT 定时超过,就会给出一个复位信号到MCU,是MCU复位,防止MCU死机。看门狗的作用就是防止程序发生死循环,或者说程序跑飞。出于对单片机运行状态进行实时监测的考虑,产生了一种专门用于监测单片机程序运行状态的芯片,俗称"看门狗"(watchdog)。
  看门狗定时器(WDT:Watch Dog Timer)实际上是一个计数器。一般给看门狗一个大数,程序开始运行后看门狗开始倒计数。如果程序运行正常,过一段时间CPU应该发出指令让看门狗复位,令其重新开始倒计数。如果看门狗计数减到0,就认为程序没有正常工作(因为没有及时复位),就强制整个系统复位(单片机重启)。
  所以,当你开启看门狗后,需要在看门狗超时(计数减到0)前,对其进行 喂狗(复位)操作,否则看门狗会强制你的单片机重启,从头运行程序。如果看门狗在休眠或空闲模式下超时,器件将唤醒并从PWRSAV指令执行处继续执行代码,同时“休眠”状态位(RCON< 3>)或“空闲”状态位(RCON< 2>)会置1,表示器件之前处于省电模式。
  功能作用:看门狗可以在你的程序陷入死循环的时候,让单片机复位而不用整个系统断电,从而保护你的硬件电路。

使用看门狗需要引用头文件 【 avr/wdt.h 】,在wdt.h中,提供了3个看门狗API:

wdt_enable(timeout) //看门狗启动,并设置超时时间
wdt_disable() //看门狗停止
wdt_reset() //看门狗复位(喂狗)

wdt_enable(timeout) 中timeout为超时时间,当超过这个时间后没有喂狗,则单片机重启。
这个时间可使用如下常量:

0=15(16)ms, 1=30(32)ms,2=60(64)ms,3=120(128)ms,4=250ms,5=500ms
6=1 sec,7=2 sec, 8=4 sec, 9= 8sec

使用看门狗很简单,只需要做下面三步即可:

1、引用头文件 #include avr/wdt.h
2、Setup函数中启动看门狗,并设置超时时间为两秒:wdt_enable(WDTO_2S);
3、Loop函数中喂狗,防止饿死(重启): wdt_reset();

代码如下:

#include <avr/wdt.h>  
int ledPin = 13;
void setup() 
{  
  pinMode(ledPin, OUTPUT);       
  wdt_enable(WDTO_2S);//启动看门狗,设置喂狗时间不能超过2秒      
}  
void loop()  
{  
  digitalWrite(ledPin, HIGH);    
  delay(500);     
  digitalWrite(ledPin, LOW);    
  //喂狗。如果超过2S没有喂狗,则单片机重启。 也就是说,如果本循环执行时间超过2S的话,单片机就会自动重启。
  wdt_reset();    
} 

其它应用:
【利用看门狗进行休眠唤醒】
用下面的代码,代替wdt_enable(),并且不要喂狗。
这样就实现了看门狗超时后,执行唤醒函数,而不是重启单片机。

void wdt_setup(int ii) 
{
	// ii为看门狗超时时间,支持以下数值:
	// 0=16毫秒, 1=32毫秒,2=64毫秒,3=128毫秒,4=250毫秒,5=500毫秒
	// 6=1秒 ,7=2秒, 8=4秒, 9=8秒
	byte bb;
	if (ii > 9 ) ii = 9;
	bb = ii & 7;
	if (ii > 7) bb |= (1 << 5);
	bb |= (1 << WDCE);
	//开始设置看门狗中断   
	MCUSR &= ~(1<<WDRF);  //清除复位标志
	WDTCSR |= (1<<WDCE) | (1<<WDE);
	//设置新的看门狗超时时间
	WDTCSR = bb;
	//设置为定时中断而不是复位
	WDTCSR |= _BV(WDIE); 
	//别忘了设置【看门狗唤醒执行函数】
}

看门狗唤醒执行函数:

ISR(WDT_vect)
{
    //唤醒后执行的代码
}

实例一

测试代码如下:

#include <avr/wdt.h>  
#include <avr/sleep.h>
int ledPin = 13;
int data=0;

ISR(WDT_vect)
{
  //看门狗唤醒执行函数
  data++;
}

void setup() 
{  
	pinMode(ledPin, OUTPUT);   
	set_sleep_mode(SLEEP_MODE_PWR_DOWN); //设置休眠模式。
	sleep_enable(); //开启休眠功能。
	//ACSR |=_BV(ACD);//关掉ACD,据说很省电。不知道唤醒以后要不要重新开,怎么开?
	//ADCSRA=0;//关掉ADC,据说很省电。不知道唤醒以后要不要重新开,怎么开?
	//按照官方解释,sleep_enable()最好写在中断(attachInterrupt())前,防止中断在开始休眠前就提前释放而造成休眠后无法唤醒。
	//开始设置看门狗中断,用来唤醒。   
	MCUSR &= ~(1<<WDRF);
	WDTCSR |= (1<<WDCE) | (1<<WDE);
	WDTCSR = 1<<WDP1 | 1<<WDP2;
	WDTCSR |= _BV(WDIE); 
}  

void loop()  
{  
  if (data>=5)
  {
	  digitalWrite(ledPin, HIGH);    
	  delay(500);     
	  digitalWrite(ledPin, LOW);    
	  data=0;
  }
  sleep_cpu();//进入休眠状态,从此处开始进入休眠。这里不需要喂狗。目的就是等狗超时后执行唤醒函数。
} 

本实验程序的行为如下:
1.启动时,每隔0.5秒点、灭三次位于第13脚的LED。
2.LED闪烁完毕后,进入“Power-down(断电)”睡眠模式,5秒之后又开始闪烁一次。

实例二

测试代码如下:

#include <avr/wdt.h>  
#include <avr/sleep.h>
int ledPin = 13;
int data=0;

ISR(WDT_vect)
{
  //看门狗唤醒执行函数
  data++;
}
void setup() 
{  
	pinMode(ledPin, OUTPUT);   
	set_sleep_mode(SLEEP_MODE_PWR_DOWN); //设置休眠模式。
	sleep_enable(); //开启休眠功能。
	//ACSR |=_BV(ACD);//关掉ACD,据说很省电。不知道唤醒以后要不要重新开,怎么开?
	//ADCSRA=0;//关掉ADC,据说很省电。不知道唤醒以后要不要重新开,怎么开?
	//按照官方解释,sleep_enable()最好写在中断(attachInterrupt())前,防止中断在开始休眠前就提前释放而造成休眠后无法唤醒。
	//开始设置看门狗中断,用来唤醒。   
	MCUSR &= ~(1<<WDRF);
	WDTCSR |= (1<<WDCE) | (1<<WDE);
	WDTCSR = 1<<WDP1 | 1<<WDP2;
	WDTCSR |= _BV(WDIE); 
}  
void loop()  
{  
  if (data>=5)
  {
	  digitalWrite(ledPin, HIGH);    
	  delay(500);     
	  digitalWrite(ledPin, LOW);    
	  data=0;
  }
  sleep_cpu();//进入休眠状态,从此处开始进入休眠。这里不需要喂狗。目的就是等狗超时后执行唤醒函数。 
} 

或者代码可以如下:

#include <avr/wdt.h>  
#include <avr/sleep.h>
int ledPin = 13;
int data=0;

ISR(WDT_vect)
{
  //看门狗唤醒执行函数
  data++;
}
void setup() 
{  
	pinMode(ledPin, OUTPUT);   
	set_sleep_mode(SLEEP_MODE_PWR_DOWN); //设置休眠模式。
	//开始设置看门狗中断,用来唤醒。   
	MCUSR &= ~(1<<WDRF);
	WDTCSR |= (1<<WDCE) | (1<<WDE);
	WDTCSR = 1<<WDP1 | 1<<WDP2;
	WDTCSR |= _BV(WDIE); 
}  

void loop()  
{  
  if (data>=5)
  {
	  digitalWrite(ledPin, HIGH);    
	  delay(500);     
	  digitalWrite(ledPin, LOW);    
	  data=0;
  }
  sleep_mode(); //进入休眠状态,从此处开始进入休眠。这里不需要喂狗。目的就是等狗超时后执行唤醒函数。 
} 

三、外部中断

1.为什么需要中断?

  因为没有中断,你不能让你的Arduino进入睡眠状态,并期望它再次唤醒(一般来说,在有限的情况下,有办法在没有中断的情况下从睡眠中唤醒)。如果你不能入睡,一直工作你的能量很快就会消耗殆尽。睡眠模式消耗的功率非常小,但需要特别设置。你必须知道的第一件事是如何编写代码来利用中断,然后你可以使用更强大的技术。

2.不关心功耗还需要中断吗?

  是! 即使你不打算让处理器进入睡眠状态,也可能需要中断!如果您有一个与时间相关的应用,如需要每间隔几毫秒发生一次操作,或者需要在发生外部事件后立即发生操作,这是中断就会至关重要。如果你熟悉Arduino编程的基础知识,你可能会想知道为什么你不能只用一个简单的while循环来检查什么时候执行你的动作。简单的while循环是可以按顺序读取每个引脚的状态,但这种方法并不可靠。如果只简单的应用while循环来检查按钮的轮询的话,你的while循环再次检查按钮状态之前,该按钮是否已经被按下并释放?你会错过按钮按下。如果您正在寻找一个非常快速的事件,例如来自传感器的信号,因为害怕错过关键事件,你必须经常进行轮训,从而导致你的程序无法做任何其他事情。但是有了中断,你100%保证能够赶上事件。使用中断还可以使您不必经常检查状态,从而节省计算能力,并让while循环更快地完成其他任务。
  试想一下,你正在家里吃饭,这时传来了敲门声,虽然你巨饿,虽然面前全是山珍海味,但此时你不得不去开门,同时不得不放停下生命中最重要的事情——吃饭。打开门后,你发现只是一个查水表的,你检查了水表读数并告诉了查水表的人。关上门,你马不停蹄的又投入了于食物的作战中。
  我们来分析一下这个颇具传奇性的故事,在这里人生的主旋律——吃饭,就是你的主程序,而敲门声,就是一个中断信号,它让你不得不去执行你的人生插曲——开门接客这个中断函数。完成这个小插曲后,你又要投入到主线剧情 吃饭这个主程序上。
  现在我想告诉你一个惊天秘密,其实你妈欺骗了你,你根本不是他们亲生的,你是人造人,而你的大脑里装备了一个arduino控制器!你的型号是 Arduino 吃货,之所以叫这么2的名字,是因为你的loop的写法问题。我们来看看你的loop函数。

void loop() 
{();
}
//吃,是的,你没有看错,你的人生是如此的幸福,就是不断的 吃();循环。
//但实际上,你还有附加功能 开门();
void 开门()
{
打开门;
if(门口的人==女神)
    跪舔();
if(门口的人==查水表的)
    报告水表读数();
}

  为了让你能顺利执行 开门();动作,你的亲生父母还得在Setup函数中设置 开门();这个动作何时启动。具体的方法是attachInterrupt(中断通道, 中断函数, 触发方式); 在这里中断通道就是你的耳朵(不要问为什么不是屁股),触发 开门();这个函数的方式是敲门声。

void setup()
{
      attachInterrupt(耳朵, 开门, 敲门声);
}

  这样设定后,你每次听到敲门声,就不得不去打开门,并执行相应的动作了。也许你对这样的人物设定不太满意,但这就是你的宿命,少年。忘记你蛋碎的屌丝设定吧,我们要开始严肃的讨论问题了!

3.各种板子的中断

UNO、NANO、ProMINI这仨板子都是INT0 (D2针脚:中断编号为0)、INT1(D3针脚:中断编号为1)。 

4.中断函数、中断触发模式与设置中断

【中断函数】:就是你要去执行的函数,这个函数不能带任何参数,且没有返回类型。如:

void hello()
{
  Serial.println("hello");
} 

【中断模式】:就是你的中断触发方式。在大多数arduino上有以下四种触发方式

LOW                  低电平触发
CHANGE            电平变化,高电平变低电平、低电平变高电平
RISING              上升沿触发
FALLING           下降沿触发
HIGH                高电平触发(该中断模式仅适用于Arduino due) 

【设置中断】:在定义中断函数后,要使用外部中断,你只需要在程序的Setup部分配置好中断函数即可,配置函数如下:

attachInterrupt(interrupt, function, mode); 
//interrupt为你中断通道编号,function为中断函数,mode为中断触发模式 
需要注意的是在Arduino Due中,中断设置有点不同: 
attachInterrupt(pin, function, mode);
//due 的每个IO均可以进行外部中断,所以这里第一个参数为pin,即你使用的引脚编号。 
//如果在程序中途,你不需要使用外部中断了,你可以用中断分离函数来取消这一中断设置:detachInterrupt(interrupt); 
同样在Arduino Due上,该函数为detachInterrupt(Pin);

5.例程

外部中断的使用也是非常简单的,下面我们来看一个官方提供的例程

volatile int state = 0; 
void setup()
{
  pinMode(13, OUTPUT);
  attachInterrupt(0, blink, CHANGE);//当int.0电平改变时,触发中断函数led
} 
void loop()
{
  digitalWrite(13, state);
} 
void led()//中断函数
{
  state = !state;
}

应用:利用外部中断,可以在很多地方提高你程序的运行效率。你可以运用以上知识,做一个简单的监控装置。

猜你喜欢

转载自blog.csdn.net/qq_39400113/article/details/107536805
今日推荐