Your button fires twice. Here is why, and here is the fix.
Ya da tüm dersleri gör.
- 503parça tezgahta
- 44kart şu an çalışıyor
- 1.00xgerçek zamanlı, her kartta
Dokunmatik bir anahtar, bir kontağın üzerine bastırılan küçük bir metal kubbedir ve inip sabit kalmaz. İner, bir milimetrenin bir kısmı kadar geri yaylanır, yeniden iner ve oturmadan önce bunu birkaç kez tekrarlar; masaya düşen bir madeni paranın masaya ilk değdiği anda durmaması gibi. Elektriksel olarak kontak, basışın ilk birkaç milisaniyesinde ve bırakıldığında yeniden, sabit tutmadan önce birkaç kez açılıp kapanır. O pindeki kenarları sayan bir kod yanlış sayar, çünkü tek bir basış iki, üç ya da daha fazla olarak kaydedilebilir.
Mokxi bunu saklamak yerine modeller. Tezgahtaki her buton varsayılan olarak typical adlı bir profille sıçrar: üç ile sekiz geçiş, basış başladıktan 1 ile 5 milisaniye arasında oturur. Bu gerçek bir dokunmatik anahtara yakındır ve yukarıdaki devrenin bir hatanın sahnelenmiş gösterimi olmadığı anlamına gelir; bu hatanın kendisidir ve masada olduğu gibi olur.
Simülatörün gösterdiği
Yukarıda çalışan program, Uno'nun hazır interrupt örneğidir. Pin 2'ye, her düşen kenarda (pinin 5 V'tan 0 V'a düştüğü her anda) tetiklenen ve bir sayaca bir ekleyen bir kesme işleyicisi bağlar; bilerek içinde hiçbir debounce mantığı yoktur. Devreyi düzenleyicide aç, seri monitörü aç ve butona herhangi bir butona basar gibi bir kez, temiz bir şekilde bas. Tek basıştan sonra yazdırılan sayı çoğu zaman bir değil iki ya da üçtür ve sayaç eşliğine göre açılıp kapanan karttaki LED bazen beklemediğin durumda kalır. Hiçbir şey bozuk değil. Anahtar sıçradı, işleyici her sıçramayı ayrı bir basış olarak gördü ve hepsini sadakatle saydı, çünkü kodun istediği tam olarak budur.
Bütün ders tek cümlede şudur: fazladan basışları kod değil anahtar üretti ve çözüm kodun içindedir, çünkü anahtar kusursuz biçimde temiz yapılamaz.
Düzeltmenin üç yolu
İlk ve en ucuz çözüm bir yazılım debounce penceresidir: pindeki herhangi bir değişiklikten sonra, oturma süresini kapsayacak kadar uzun ve gerçek bir ikinci basışın asla sıçrama sanılmayacağı kadar kısa bir süre boyunca sonraki değişiklikleri yok say. 5 ile 20 milisaniye arası tipik bir anahtarı rahatça kapsar.
İkincisi bir donanım çözümüdür: anahtarın uçlarında küçük bir kondansatör. Gerilimin yükselişini ve düşüşünü yeterince yavaşlatır, böylece sıçramalar asla tam bir mantık geçişine ulaşmaz ve pin gerçekten yalnızca tek bir temiz kenar görür. Bu tezgahta çalışır, çünkü gerçek bir kondansatör dolmak ve boşalmak için gerçek zaman alır ve Mokxi'de de aynı nedenle çalışır: buradaki kondansatör zaman içinde anında anahtarlamak yerine gerçek üstel RC yanıtıyla integral alır; bu yüzden bu çözümün küçültülmüş bir sürümü yalnızca teoride değil simülatörde de gerçekten tutar.
Üçüncüsü, kesme işleyicisinin içinde debounce yapmaktır; bu fazladan bir kural ister: işleyici mümkün olduğunca az şey yapmalıdır, çünkü diğer kesmeler tutulu iken çalışır. Zamanı kaydet ve çık; debounce penceresi dahil asıl karar vermeyi loop() içinde yap.
millis() sürümü
Uno'nun hazır toggle programı, sıçrama gösteriminin kullandığı aynı pin 2 üzerinde, yazılım çözümünün tamamıdır. Son okumayı ve en son değiştiği zamanı tutar ve bir değişikliğe ancak öncekinden beri DEBOUNCE_MS, yirmi beş milisaniye geçtiğinde karşılık verir. Bu program çalışırken butona bir kez bas, pin 13'teki lamba yanar; yeniden bas, söner; basış başına tam bir kez, ki bütün amaç budur.
Kodun gerçekte neyi karşılaştırdığına dikkat et. Butonun ne kadar süredir basılı tutulduğunu saymaz; basış ya da bırakış olsun, her iki kenarda, kabul edilen son değişiklikten bu yana ne kadar zaman geçtiğini ölçer. Pencereyi simetrik yapan şey bu ayrımdır: basıştan hemen sonraki bir sıçrama yok sayılır, bırakıştan hemen sonraki de; hangi kenar olduğuna dair özel bir durum yazmadan aynı yirmi beş milisaniyelik kural ikisini de kapsar.
const int BUTTON_PIN = 2;
const int LAMP_PIN = 13;
const unsigned long DEBOUNCE_MS = 25;
int lastReading = HIGH;
unsigned long lastChange = 0;
bool lampOn = false;
void loop() {
int reading = digitalRead(BUTTON_PIN);
unsigned long now = millis();
if (reading != lastReading && now - lastChange >= DEBOUNCE_MS) {
lastChange = now;
lastReading = reading;
if (reading == LOW) {
lampOn = !lampOn;
digitalWrite(LAMP_PIN, lampOn ? HIGH : LOW);
}
}
}Pull-up direnç sorusu
Hem sıçrama gösterimi hem de çözümü butonu harici bir direnç olmadan doğrudan pin 2'ye bağlar, çünkü kod INPUT_PULLUP ile pinin kendi dahili pull-up'ını açar. O pull-up, buton onu toprağa bağlayana kadar pini 5 V'ta tutar; bu yüzden pin buton basılıyken HIGH değil LOW okur, bir butonun yapması gerektiğine benzeyenin tersi olduğu için insanları ilk görüşte şaşırtan bir ayrıntı.
Alternatif, pinden 5 V'a giden 10 kiloohm'luk gerçek bir harici pull-up'tır; buton basıldığında aynı ağı toprağa çeker. Elektriksel olarak ikisi aynı fikirdir; dahili olan yalnızca daha zayıftır (yaklaşık 35 kiloohm) ve fazladan parça gerektirmez. Pull-up direnç sayfası harici sürümü sıfırdan kurar ve dahili olanın bir yerden çıkmış gibi gelmesi hâlinde okumaya değer.
Varsayılan profil neden böyle ayarlandı
1 ile 5 milisaniye içinde oturan üç ile sekiz geçişli typical, bilerek sıradan bir dokunmatik anahtardır; hemen her breadboard kitindeki türden, konuyu olduğundan kötü göstermek için seçilmiş en kötü durum değil. Ayrıca 10 ile 20 milisaniyede yirmiye kadar geçişe giden bir worst profili de vardır; yorulmuş bir anahtara ya da çok kullanılmış uzun kollu bir uç anahtarına daha yakındır ve tipik durum çözüldükten sonra bir debounce penceresini ona karşı denemeye değer: 5 milisaniyelik bir sıçramaya payı olmadan ayarlanmış bir çözüm, 15 milisaniye titreyen bir anahtarı yine de kaçırabilir.
Sorular
Debounce gecikmesi ne kadar olmalı?
5 ile 20 milisaniye arası tipik bir dokunmatik anahtarı karşılar; Mokxi'nin varsayılan sıçrama profilinin içinde oturduğu aralık da budur. Çok kısa olursa bir kısım sıçrama geçer; çok uzun olursa hızlı bir çift basış duyarsız hissettirmeye başlar.
Zaten yazılımda debounce yapıyorsam kondansatöre ihtiyacım var mı?
Hayır. İkisi aynı sorunu farklı yerlerde çözen alternatiflerdir ve ikisini birden kullanmak yanlış değil, yalnızca gereksizdir. Devreye uyanı seç: yazılım fazladan bir şeye mal olmaz, donanım ise pini okuyan kod hiç değişmese de çalışır.
Debounce kontrolü ekledikten sonra kesmem neden hâlâ iki kez tetikleniyor?
Hemen her zaman debounce penceresi kontrolünün kendisi yavaş işlerle birlikte kesme işleyicisinin içinde olduğu için ya da zaman yanlış saklanmış bir değerle karşılaştırıldığı için. İşleyiciyi saati okumak ve bir bayrak ayarlamakla sınırla, karşılaştırmayı loop() içinde yap.
INPUT_PULLUP'ın ayrıca harici bir dirence ihtiyacı var mı?
Hayır. ATmega328P'nin içine yerleşik bir pull-up direnci açar, bu yüzden butonun yalnızca iki kabloya ihtiyacı vardır: biri pine, biri GND'ye.
Tipik bir anahtarı donanımda hangi değerde kondansatör debounce eder?
Anahtarın uçlarında yaklaşık 100 nanofarad, çoğu zaman ilk basışta deşarj akımını sınırlamak için küçük bir seri dirençle eşleştirilmiş, yaygın bir başlangıç noktasıdır; doğru değer anahtara bağlıdır ve her anahtar için tek bir sayıya güvenmek yerine gerçek parçayla tezgahta kontrol etmeye değer.
Debounce yalnızca butonlar için mi sorundur?
Hayır. Röleler, çevirmeli anahtarlar ve bir motordaki ya da kapıdaki uç anahtarlar dahil her mekanik kontak sıçrar. Aynı çözüm geçerlidir: ilk değişiklikten sonra bir zaman penceresi ya da kontağın uçlarında bir kondansatör; okuyan devreye hangisi uyuyorsa.
Devam et
Bunu gerçekten kur
Düzenleyiciyi aç, bir değeri değiştir ve sayının onunla birlikte oynadığını izle. Kurulacak bir şey yok ve hesap gerekmez.