您现在的位置是:网站首页> 硬件

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

  • 硬件
  • 2021-09-18
  • 1044人已阅读
摘要

原文

一、休眠模式

??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)

1.png

微控器内部除了中央处理器(CPU),还有內存、模拟数位转换器、序列通信…等模块。越省电的模式,仍在运作中的模块就越少。

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


睡眠中断的触发

中断触发种类:


通过外部中断。只有在发生外部中断时才会唤醒。

通过UART(USB串行接口)。保持睡眠,直到通过串行接口收到数据。

通过一个内部计时器,将定期通过Timer1从睡眠中醒来,执行一个动作并返回到睡眠状态。

通过看门狗定时器。定期通过看门狗定时器从睡眠中醒来,执行一个动作并返回到睡眠状态。请注意使用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.png

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


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)。 

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;

}


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



Top