Arduino中断不会忽略下降沿
Arduino Interrupt won't ignore falling edge
我在完全消除连接到中断的按钮时遇到了一些问题。目标是当按钮为 pressed/released.
时,void loop()
运行 中的语句恰好出现一次
通常最终发生的是两件事之一
- ISR 标志在按下按钮时设置一次。释放按钮没有任何作用,如预期的那样。
- 按下按钮时设置一次 ISR 标志,松开按钮时再设置一次。
这是我的确切代码:
#define interruptPin 2
#define DBOUNCE 100
volatile byte state = LOW; //ISR flag, triggers code in main loop
volatile unsigned long difference;
void setup() {
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(115200);
}
void loop() {
if(state){ //If we have a valid interrupt
Serial.println(difference); //Print the time since the last ISR call
state = LOW; //Reset the flag
}
}
void ISR_0() {
static unsigned long last_interrupt = 0;
if(millis()-last_interrupt > DBOUNCE && digitalRead(interruptPin)){
difference=millis()-last_interrupt;
state = HIGH;
}
last_interrupt = millis(); //note the last time the ISR was called
}
这似乎是一种消除中断的流行方式,但无论出于何种原因,它都不适合我。
我希望在按钮释放的第一个下降沿 digitalRead(interruptPin)
会读取 low
,因此不会设置 state
标志。
由于ISR更新了last_interrupt
时间,第一个下降沿之后的连续反弹似乎仍然被成功忽略。这让我相信去抖动不是问题,但 digitalRead(interruptPin)
是。
除一个状态外,去抖动似乎可以处理所有状态。释放按钮时,代码偶尔仍会将 state
标志设置为 HIGH
。
这是一些示例输出:
3643
(开机等待约 3.6 秒后,我按下按钮,约 1 秒后松开)
在与上述相同的场景中,输出偶尔如下所示:
3643
1018
这显示我按下了按钮,但同时也释放了按钮。
我正在使用 UNO R3 和带有 1k 下拉电阻的瞬时触觉按钮。
我不确定此时出了什么问题。我希望这足够简单,任何人都可以轻松地在他们的 arduino 上测试这个,如果他们愿意的话。
你使用了Uno的内部上拉电阻,但也使用了按钮本身的下拉电阻。那是不正确的,您只能使用其中之一。
在 if 里面,你希望输入为高,所以使用下拉电阻并将 INPUT_PULLUP 更改为 INPUT.
(说清楚:电阻连接在输入引脚和地之间,按钮连接在输入引脚和+5V之间)
[编辑]
我想当ISR被调用时,interruptPin的状态可能会再次改变。
由于 digitalRead 的速度较慢,与(可能的)峰值相比。
我不确定这是否是您想要的,但下面的示例有效。
(出于测试目的,我启用了 LED)。
一件事:至少在去抖动时间(100 毫秒)内按住按钮,否则它将无法工作。
不理想,但如果你想在开关上立即响应,这就是你必须付出的代价。
#define interruptPin 2
#define DBOUNCE 100
volatile byte state = LOW; //ISR flag, triggers code in main loop
volatile unsigned long difference;
bool hasPrinted = false;
void setup() {
pinMode(interruptPin, INPUT);
pinMode (13, OUTPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(115200);
}
void loop() {
digitalWrite(13, state);
if (!hasPrinted) {
Serial.println(difference);
hasPrinted = true;
}
}
void ISR_0() {
static unsigned long last_interrupt = 0;
if(millis()-last_interrupt > DBOUNCE){
state = !state;
hasPrinted = false;
difference = millis()-last_interrupt;
}
last_interrupt = millis(); //note the last time the ISR was called
}
您始终可以在硬件中进行去抖动
我曾多次遇到按钮和编码器的弹跳,在软件中进行去抖动可能会变得混乱并导致代码不可读或逻辑错误。
您可以做的最简单的事情是添加一个 0.1 uF 电容器,如图所示:
Arduino 具有迟滞输入,如果您使用 10K 作为上拉,那么这适用于低于 1 毫秒的弹跳。这是我最喜欢的方法。
如果您想更认真一点,Internet 上有一个 很棒的 pdf,其中包含大量示例和解释:A Guide to Debouncing
正如评论中所说,我认为主要问题是因为您没有实际的去抖动。
所以,按键松开后,会继续弹跳,导致输入引脚上的逻辑电平发生变化。如果此时,当为 digitalRead() 锁存引脚状态时,状态被读取为高电平,则将满足整个条件并执行 state = HIGH;
。
我不是美工,但我尽力画了这个时序图:
因此,为了避免这种情况,您可以使用任何简单的方法来去抖动。最简单的方法是在超时后再次读取引脚状态,超时时间大于最大预期反弹时间。
例如,如果您正在等待按下按钮并获得高逻辑电平(如果按钮连接到 GND,则为低逻辑电平),只需等待大约 5 毫秒,然后再次读取电平。仅当电平仍然高(低)时才处理按钮按下。
正如其他答案中所说,硬件去抖也有帮助。您可以使用更高的电阻(实际上您不需要使用外部电阻:将按钮连接到 GND 并启用内部上拉,大约 35kOhm)。并加一个1nF左右的电容,与按键并联。
在中断中去抖 buttons/switches 很麻烦。
我遇到了(某种)类似的限位开关情况。
限位开关被击中的那一刻,必须发生一些事情 - 所以中断。
然而,当限位开关被释放时中断也会触发,这是一个问题。我将中断设置为在下降沿触发。
无论如何,我最终使用标志在中断之外进行了去抖动。
代码对此进行了解释,但是:
按下开关,ISR 运行(做它需要做的事情)并设置一个 ISR 标志。
ISR 标志阻止 ISR 实际上做任何事情,直到它被清除。
在主循环中,如果设置了 ISR 标志,则调用去抖功能。
去抖功能将等待 pin/switch 在预定时间内稳定在所需状态 (HIGH/LOW),然后清除 ISR 标志,允许 ISR 再次执行某些操作。
#define interruptPin 2
#define DEBOUNCE_TIME 100L
volatile bool ISR_ACTIVATED = false;
volatile bool display_int_time = false;
bool debounce_started = false;
unsigned long Isr_debounce_pin_timer;
volatile unsigned long difference;
void setup() {
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(112500);
}
void loop() {
if (display_int_time) {
Serial.println(difference); //Print the time since the last ISR call
// Right, done with interrupt stuff. clear the interrupt flag
display_int_time = false;
}
// Call debounce ISR routine in main loop
if (ISR_ACTIVATED)
ISR_ACTIVATED = !debounce(interruptPin, HIGH, Isr_debounce_pin_timer);
}
bool debounce(int debounce_pin, bool state, unsigned long &state_latch_start) {
// debounce_pin - what pin are we debouncing?
// state - should the stable state be HIGH or LOW
// state_latch_start - when we 'latched' on to a (possibly) stable state
static bool current_state;
// Read the required pin
current_state = digitalRead(debounce_pin);
// Is the pin at the required state, and we have not yet 'latched' it?
if ((!debounce_started) && (current_state == state)) {
// 'latch' this state (ie take note of when the pin went to the required level)
state_latch_start = millis();
debounce_started = true;
}
// Have we 'latched', but the pin has bounced
if (debounce_started && (current_state != state))
// unlatch
debounce_started = false;
// Have we latched, the pin is at the required level and enough time has passed (ie pin is stable)
if (debounce_started && (current_state == state) && (((unsigned long)(millis() - state_latch_start)) >= DEBOUNCE_TIME)) {
// cool. unlatch
debounce_started = false;
// report back that all is goood.
return(true);
}
// Blast. Either the pin is at the wrong level, or is still bouncing. Tell the boss to try again later!
return(false);
}
void ISR_0() {
static unsigned long last_interrupt = 0;
if((millis()-last_interrupt > DEBOUNCE_TIME) && digitalRead(interruptPin) && !ISR_ACTIVATED){
difference=millis()-last_interrupt;
//state = HIGH;
ISR_ACTIVATED = true;
display_int_time = true;
}
last_interrupt = millis(); //note the last time the ISR was called
}
*** 请注意,编辑我的代码以将去抖时间合并到您的原始 ISR 中,以确保中断有效。我没有看到关于你嘈杂环境的评论
我的最终解决方案,感谢@darrob
#define interruptPin 2
#define DEBOUNCE_TIME 50L
//ISR Flags
volatile bool ISR_DEACTIVATED = false;
volatile bool display_int_time = false;
bool debounce_started = false;
unsigned long Isr_debounce_pin_timer;
int x = 0;
void setup() {
pinMode(interruptPin, INPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(112500);
}
void loop() {
//This runs every time we press the button
if (display_int_time) {
Serial.print("X "); //Print the time since the last ISR call
x++;
(x%10==0)?Serial.println(x):Serial.println();
display_int_time = false; //Done with interrupt stuff. Clear the interrupt flag
}
//Debounce for the ISR routine in main loop
if (ISR_DEACTIVATED)
//Wait until the pin settles LOW to reactivate the ISR
ISR_DEACTIVATED = !debounce(interruptPin, LOW, Isr_debounce_pin_timer);
}
//Returns TRUE if pin is stable at desired state, FALSE if bouncing or other state
bool debounce(const int debounce_pin, const bool state, unsigned long &state_latch_start) {
// debounce_pin - what pin are we debouncing?
// state - should the stable state be HIGH or LOW
// state_latch_start - when we 'latched' on to a (possibly) stable state
//If you are calling this routine to debounce multiple pins in the same loop,
// this needs to be defined outside of the function, and passed in as a separate
// parameter for each debounce item (like unsigned long &state_latch_start)
static bool current_state;
// Read the required pin
current_state = digitalRead(debounce_pin);
// Is the pin at the required state, and we have not yet 'latched' it?
if ((!debounce_started) && (current_state == state)) {
// 'latch' this state (ie take note of when the pin went to the required level)
state_latch_start = millis();
debounce_started = true;
}
// Have we 'latched', but the pin has bounced
if (debounce_started && (current_state != state))
// unlatch
debounce_started = false;
// Have we latched, the pin is at the required level and enough time has passed (ie pin is stable)
if (debounce_started && (current_state == state) && (((unsigned long)(millis() - state_latch_start)) >= DEBOUNCE_TIME)) {
// cool. unlatch
debounce_started = false;
// report back that all is goood.
return(true);
}
//Either the pin is at the wrong level, or is still bouncing. Try again later!
return(false);
}
void ISR_0() {
if(!ISR_DEACTIVATED){
ISR_DEACTIVATED = true;
display_int_time = true;
}
}
我在完全消除连接到中断的按钮时遇到了一些问题。目标是当按钮为 pressed/released.
时,void loop()
运行 中的语句恰好出现一次
通常最终发生的是两件事之一
- ISR 标志在按下按钮时设置一次。释放按钮没有任何作用,如预期的那样。
- 按下按钮时设置一次 ISR 标志,松开按钮时再设置一次。
这是我的确切代码:
#define interruptPin 2
#define DBOUNCE 100
volatile byte state = LOW; //ISR flag, triggers code in main loop
volatile unsigned long difference;
void setup() {
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(115200);
}
void loop() {
if(state){ //If we have a valid interrupt
Serial.println(difference); //Print the time since the last ISR call
state = LOW; //Reset the flag
}
}
void ISR_0() {
static unsigned long last_interrupt = 0;
if(millis()-last_interrupt > DBOUNCE && digitalRead(interruptPin)){
difference=millis()-last_interrupt;
state = HIGH;
}
last_interrupt = millis(); //note the last time the ISR was called
}
这似乎是一种消除中断的流行方式,但无论出于何种原因,它都不适合我。
我希望在按钮释放的第一个下降沿 digitalRead(interruptPin)
会读取 low
,因此不会设置 state
标志。
由于ISR更新了last_interrupt
时间,第一个下降沿之后的连续反弹似乎仍然被成功忽略。这让我相信去抖动不是问题,但 digitalRead(interruptPin)
是。
除一个状态外,去抖动似乎可以处理所有状态。释放按钮时,代码偶尔仍会将 state
标志设置为 HIGH
。
这是一些示例输出:
3643
(开机等待约 3.6 秒后,我按下按钮,约 1 秒后松开)
在与上述相同的场景中,输出偶尔如下所示:
3643
1018
这显示我按下了按钮,但同时也释放了按钮。
我正在使用 UNO R3 和带有 1k 下拉电阻的瞬时触觉按钮。
我不确定此时出了什么问题。我希望这足够简单,任何人都可以轻松地在他们的 arduino 上测试这个,如果他们愿意的话。
你使用了Uno的内部上拉电阻,但也使用了按钮本身的下拉电阻。那是不正确的,您只能使用其中之一。 在 if 里面,你希望输入为高,所以使用下拉电阻并将 INPUT_PULLUP 更改为 INPUT.
(说清楚:电阻连接在输入引脚和地之间,按钮连接在输入引脚和+5V之间)
[编辑]
我想当ISR被调用时,interruptPin的状态可能会再次改变。 由于 digitalRead 的速度较慢,与(可能的)峰值相比。
我不确定这是否是您想要的,但下面的示例有效。 (出于测试目的,我启用了 LED)。 一件事:至少在去抖动时间(100 毫秒)内按住按钮,否则它将无法工作。 不理想,但如果你想在开关上立即响应,这就是你必须付出的代价。
#define interruptPin 2
#define DBOUNCE 100
volatile byte state = LOW; //ISR flag, triggers code in main loop
volatile unsigned long difference;
bool hasPrinted = false;
void setup() {
pinMode(interruptPin, INPUT);
pinMode (13, OUTPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(115200);
}
void loop() {
digitalWrite(13, state);
if (!hasPrinted) {
Serial.println(difference);
hasPrinted = true;
}
}
void ISR_0() {
static unsigned long last_interrupt = 0;
if(millis()-last_interrupt > DBOUNCE){
state = !state;
hasPrinted = false;
difference = millis()-last_interrupt;
}
last_interrupt = millis(); //note the last time the ISR was called
}
您始终可以在硬件中进行去抖动
我曾多次遇到按钮和编码器的弹跳,在软件中进行去抖动可能会变得混乱并导致代码不可读或逻辑错误。
您可以做的最简单的事情是添加一个 0.1 uF 电容器,如图所示:
Arduino 具有迟滞输入,如果您使用 10K 作为上拉,那么这适用于低于 1 毫秒的弹跳。这是我最喜欢的方法。
如果您想更认真一点,Internet 上有一个 很棒的 pdf,其中包含大量示例和解释:A Guide to Debouncing
正如评论中所说,我认为主要问题是因为您没有实际的去抖动。
所以,按键松开后,会继续弹跳,导致输入引脚上的逻辑电平发生变化。如果此时,当为 digitalRead() 锁存引脚状态时,状态被读取为高电平,则将满足整个条件并执行 state = HIGH;
。
我不是美工,但我尽力画了这个时序图:
因此,为了避免这种情况,您可以使用任何简单的方法来去抖动。最简单的方法是在超时后再次读取引脚状态,超时时间大于最大预期反弹时间。 例如,如果您正在等待按下按钮并获得高逻辑电平(如果按钮连接到 GND,则为低逻辑电平),只需等待大约 5 毫秒,然后再次读取电平。仅当电平仍然高(低)时才处理按钮按下。
正如其他答案中所说,硬件去抖也有帮助。您可以使用更高的电阻(实际上您不需要使用外部电阻:将按钮连接到 GND 并启用内部上拉,大约 35kOhm)。并加一个1nF左右的电容,与按键并联。
在中断中去抖 buttons/switches 很麻烦。
我遇到了(某种)类似的限位开关情况。 限位开关被击中的那一刻,必须发生一些事情 - 所以中断。
然而,当限位开关被释放时中断也会触发,这是一个问题。我将中断设置为在下降沿触发。
无论如何,我最终使用标志在中断之外进行了去抖动。 代码对此进行了解释,但是: 按下开关,ISR 运行(做它需要做的事情)并设置一个 ISR 标志。 ISR 标志阻止 ISR 实际上做任何事情,直到它被清除。 在主循环中,如果设置了 ISR 标志,则调用去抖功能。 去抖功能将等待 pin/switch 在预定时间内稳定在所需状态 (HIGH/LOW),然后清除 ISR 标志,允许 ISR 再次执行某些操作。
#define interruptPin 2
#define DEBOUNCE_TIME 100L
volatile bool ISR_ACTIVATED = false;
volatile bool display_int_time = false;
bool debounce_started = false;
unsigned long Isr_debounce_pin_timer;
volatile unsigned long difference;
void setup() {
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(112500);
}
void loop() {
if (display_int_time) {
Serial.println(difference); //Print the time since the last ISR call
// Right, done with interrupt stuff. clear the interrupt flag
display_int_time = false;
}
// Call debounce ISR routine in main loop
if (ISR_ACTIVATED)
ISR_ACTIVATED = !debounce(interruptPin, HIGH, Isr_debounce_pin_timer);
}
bool debounce(int debounce_pin, bool state, unsigned long &state_latch_start) {
// debounce_pin - what pin are we debouncing?
// state - should the stable state be HIGH or LOW
// state_latch_start - when we 'latched' on to a (possibly) stable state
static bool current_state;
// Read the required pin
current_state = digitalRead(debounce_pin);
// Is the pin at the required state, and we have not yet 'latched' it?
if ((!debounce_started) && (current_state == state)) {
// 'latch' this state (ie take note of when the pin went to the required level)
state_latch_start = millis();
debounce_started = true;
}
// Have we 'latched', but the pin has bounced
if (debounce_started && (current_state != state))
// unlatch
debounce_started = false;
// Have we latched, the pin is at the required level and enough time has passed (ie pin is stable)
if (debounce_started && (current_state == state) && (((unsigned long)(millis() - state_latch_start)) >= DEBOUNCE_TIME)) {
// cool. unlatch
debounce_started = false;
// report back that all is goood.
return(true);
}
// Blast. Either the pin is at the wrong level, or is still bouncing. Tell the boss to try again later!
return(false);
}
void ISR_0() {
static unsigned long last_interrupt = 0;
if((millis()-last_interrupt > DEBOUNCE_TIME) && digitalRead(interruptPin) && !ISR_ACTIVATED){
difference=millis()-last_interrupt;
//state = HIGH;
ISR_ACTIVATED = true;
display_int_time = true;
}
last_interrupt = millis(); //note the last time the ISR was called
}
*** 请注意,编辑我的代码以将去抖时间合并到您的原始 ISR 中,以确保中断有效。我没有看到关于你嘈杂环境的评论
我的最终解决方案,感谢@darrob
#define interruptPin 2
#define DEBOUNCE_TIME 50L
//ISR Flags
volatile bool ISR_DEACTIVATED = false;
volatile bool display_int_time = false;
bool debounce_started = false;
unsigned long Isr_debounce_pin_timer;
int x = 0;
void setup() {
pinMode(interruptPin, INPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_0, CHANGE);
Serial.begin(112500);
}
void loop() {
//This runs every time we press the button
if (display_int_time) {
Serial.print("X "); //Print the time since the last ISR call
x++;
(x%10==0)?Serial.println(x):Serial.println();
display_int_time = false; //Done with interrupt stuff. Clear the interrupt flag
}
//Debounce for the ISR routine in main loop
if (ISR_DEACTIVATED)
//Wait until the pin settles LOW to reactivate the ISR
ISR_DEACTIVATED = !debounce(interruptPin, LOW, Isr_debounce_pin_timer);
}
//Returns TRUE if pin is stable at desired state, FALSE if bouncing or other state
bool debounce(const int debounce_pin, const bool state, unsigned long &state_latch_start) {
// debounce_pin - what pin are we debouncing?
// state - should the stable state be HIGH or LOW
// state_latch_start - when we 'latched' on to a (possibly) stable state
//If you are calling this routine to debounce multiple pins in the same loop,
// this needs to be defined outside of the function, and passed in as a separate
// parameter for each debounce item (like unsigned long &state_latch_start)
static bool current_state;
// Read the required pin
current_state = digitalRead(debounce_pin);
// Is the pin at the required state, and we have not yet 'latched' it?
if ((!debounce_started) && (current_state == state)) {
// 'latch' this state (ie take note of when the pin went to the required level)
state_latch_start = millis();
debounce_started = true;
}
// Have we 'latched', but the pin has bounced
if (debounce_started && (current_state != state))
// unlatch
debounce_started = false;
// Have we latched, the pin is at the required level and enough time has passed (ie pin is stable)
if (debounce_started && (current_state == state) && (((unsigned long)(millis() - state_latch_start)) >= DEBOUNCE_TIME)) {
// cool. unlatch
debounce_started = false;
// report back that all is goood.
return(true);
}
//Either the pin is at the wrong level, or is still bouncing. Try again later!
return(false);
}
void ISR_0() {
if(!ISR_DEACTIVATED){
ISR_DEACTIVATED = true;
display_int_time = true;
}
}