我正在尝试使用按钮来了解和打印使用计数器按下按钮的次数。但是每次我按下按钮时,计数器都会增加到大约3和5,有时计数器确实开始> 100并继续。

我更喜欢使用此链接来连接arduino的PUSH按钮。

这是我的代码

const int buttonPin = 2;     // the number of the pushbutton pin
const int ledPin =  13;      // the number of the LED pin

// variables will change:
int buttonState = 0;         // variable for reading the pushbutton status
int count = 0;
void setup() {
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);      
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);   
  pinMode(buttonPin, INPUT); 
  Serial.begin(9600);
    buttonState = digitalRead(buttonPin);
  digitalWrite(ledPin,LOW);  
}

void loop(){
    // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the pushbutton is pressed.
  // if it is, the buttonState is HIGH:
  if (buttonState == HIGH) {

    // turn LED on:    
    digitalWrite(ledPin, HIGH); 
    count = count + 1;  
    Serial.print(count);
  } 
  else {
    // turn LED off:
    digitalWrite(ledPin, LOW); 
  }
}


我不知道为什么计数荒谬而又不均匀地出现。

#1 楼

克里斯说得对,问题在于开关的机械触点跳动了。但是,我不同意他的说法,即最优雅的解决方案是轮询。对于计数按钮按下次数的任务而言,轮询非常低效,因此我决定发表自己的答案以进行澄清:

您想要的是中断。当您要不断捕获信息(例如从范围传感器)时,可以使用轮询。中断为您提供时间敏感的控制,并且仅在您需要的确切时间执行处理。

为获得更彻底的答案...

此设计问题通常称为如“开关反跳”。克里斯正确地提到,您可以使用硬件或软件来完成此操作。关于计数精度和CPU负载的最佳实现方法是使用硬件使信号平滑并将按钮连接到Arduino的中断引脚。然后,您可以使用内置的attachInterrupt()函数来消除不断轮询的需要。轮询是浪费CPU能力。 attachInterrupt()函数将分配一个中断处理程序,只要中断引脚看到RISING或FALLING沿(这取决于您如何电连接开关),或更通常地说是CHANGE,就会调用该中断处理程序。这看起来类似于以下内容:

attachInterrupt(0, buttonHandler, RISING);

void buttonHandler() {
   // turn LED on:    
  digitalWrite(ledPin, HIGH); 
  count = count + 1;  
  Serial.print(count);
}


此页面说明了开关反跳的问题,并显示了一些可以轻松实现的示例硬件电路:http:// www。 elexp.com/t_bounc.htm

注意:您可能看不到led变高,因为它将被如此迅速地处理。每次按下或释放按钮时,此代码都会增加计数,具体取决于您是将中断附加到信号的上升沿还是下降沿。

编辑OP的注释:
如果要让serial.print说一直按下按钮,则在中断中添加一个while循环:

void buttonHandler() {
   // turn LED on:    
  digitalWrite(ledPin, HIGH); 
  count = count + 1;
  Serial.print(count);
  while(digitalRead(buttonPin)) //This will loop until the button is released
    Serial.print("ON");
}


请注意,这将导致您的程序停留在中断处理程序中,并且您的主要功能将无法执行。然后,在主要功能中,您可以拥有Seri​​al.print(“ OFF”)。

还可以使用在中断处理程序中设置为true的布尔值:

void buttonHandler() {
   // turn LED on:    
  digitalWrite(ledPin, HIGH); 
  count = count + 1;  
  Serial.print(count);
  buttonWasPressed = true;
}


这样,在您的主机中可以有一个if语句,以防止代码不得不在中断处理程序中循环:

void loop() {
  if(buttonWasPressed) {
    while(digitalRead(buttonPin)) {
      Serial.print("ON");
    }
    buttonWasPressed = false;
  }
  else
    Serial.print("OFF");
}


评论


$ \ begingroup $
我还应该提到,根据您的应用程序,将print语句包含在中断处理程序中通常是一种不好的形式。打印功能非常慢,而且由于中断处理程序会暂时导致主线程暂停,因此冗长的打印语句会大大降低算法速度。对您来说幸运的是,人类按下按钮的速度并不是那么快,并且打印一个int应该没问题。如果您有执行器以100Hz的频率按下此按钮,则可能要开始考虑打印语句占用了多少时间。
$ \ endgroup $
–安德鲁·卡波迪奇(Andrew Capodieci)
13年15月15日在16:41

$ \ begingroup $
我正在寻找一个中断,该中断可以测量40毫秒之间的任何事件,并通过串行打印发送变量ON或OFF...。
$ \ endgroup $
–hailendra
13年20月20日在7:01

$ \ begingroup $
我相信我最新的答案应该可以解决您的注释中提出的问题...我建议使用使用buttonWasPressed布尔值的最后一个示例,因为通常在中断处理程序中进行大量处理是一种不好的形式。
$ \ endgroup $
–安德鲁·卡波迪奇(Andrew Capodieci)
13年20月20日在16:49

#2 楼

您的体验被称为“按钮弹跳”。当您按下标准按钮时,它实际上会在几微秒内非常快速地多次建立和断开接触,通常足以使处理器检测到10到100次或更多次按下。有许多种“反跳”按钮的方法。最简单的方法是在检查是否按下按钮后立即在代码中添加一个小的延迟:

if(buttonState == HIGH){
    delay(10);
    //turn the light on, etc. 
}


还有其他方法,包括硬件和软件。不会中断其他代码运行的最优雅的方法是让计时器定期扫描按钮(即每5毫秒左右),并在检测到按下时将布尔变量设置为TRUE。您也可以在按钮上安装一个小电容器。该原理图可从互联网上获得。

评论


$ \ begingroup $
加上400毫秒的延迟对我来说很好
$ \ endgroup $
–hailendra
13年11月15日在6:25

#3 楼

开关没有从低到​​高或从高到低的快速过渡,而是类似这样的。这称为开关反弹。

图像来源:piconix.com

只需在此处添加答案即可。
有两种常用的避免开关弹跳的方法:


在开关上使用电​​容器,称为RC弹跳。电容器试图使过渡平滑,因此避免了电压尖峰。
此方法可以正常工作,但仍有机会将一些
大的电压峰值发送给Arduino,而Arduino
则将其感知为按钮按下(错误按钮按下) )。因此,避免弹跳不是
'sure'方法。此外,此方法还需要其他硬件。
软件谴责:这是一种更好的方法,几乎​​可以完全消除开关跳动的问题。在
软件中,也有两种主要使用的方法,一种称为
轮询,另一种是中断方法。正如Chris所写,以10到50毫秒的延迟(取决于您使用的开关的质量)轮询
可以避免开关停顿。轮询用于简单的
项目
中,时间不是项目功能的关键。
投票很容易实现。另一方面,对于初学者来说,使用中断
可能会有些麻烦,但这是更好的方法
,因为在执行反跳操作时代码的正常功能不会受到干扰(与轮询位置不同)在此处使用
延迟,并在该时间段内停止处理器执行任何操作)。可以有许多方法。

这是我经常用来避免开关弹跳的伪代码:

//If button is pressed an interrupt, say, INTERRUPT1 is triggered
//Following is the interrupt service routine for INTERRUPT1:
ISR(INTERRUPT1) {                                                                        
//do something you wanted to do on button press                  
disable INTERRUPT1 //so that other button spike don't trigger it
enable, say, INTERRUPT2 Timer
INTERRUPT2 Timer is set to interrupt after, say 10ms                   
}

//Following is the interrupt service routine for INTERRUPT2:
ISR(INTERRUPT2) {
disable INTERRUPT2 Timer  
enable INTERRUPT1 //button press can be recognized now after 10ms have been elapsed
}