Zo een debounce wordt (bij mij toch) altijd aangepast aan de omstandigheden. Meestal worden de toetsen gelezen in een timer interupt, zodat je weet hoeveel tijd er tussen de samples zit.
En als je een toetsenbord gebruikt dan heb je een enkele debouncer voor alle toetsen. Heb je losse buttons die onafhankelijk van elkaar werken dan heb je voor elke button een eigen debouncer nodig.
En bij een toets wil je weten wanneer die wordt ingedrukt. Bij een seinsleutel wil je ook weten wanneer die weer losgelaten wordt.
Hierbij voor de liefhebbers een kompleet voorbeeld in Arduino. Extra uitgebreid om mee te spelen.
De 4 toeten worden aangesloten op pin 2 t/m 5, en de debounce status wordt teruggegeven op pinnen 8 t/m 11, zodat je kunt zien wat er gebeurt.
Toetsen schakelen naar massa, met pullup weerstand naar +5.
Hierbij de complete sketch:
code:
//=================================================================================================
// Debouncing a button
// Using a DEBOUNCE_HANDLER class
class DEBOUNCE_HANDLER
{
public:
int m_State;
int m_Counter;
int m_Value;
int m_ButtonPin;
int m_StatusPin;
unsigned long m_Time;
// Constructor
DEBOUNCE_HANDLER(int ButtonPin, int StatusPin)
{ m_State = LOW;
m_Value = LOW;
m_Counter = 0;
m_ButtonPin = ButtonPin;
m_StatusPin = StatusPin;
pinMode(m_ButtonPin, INPUT_PULLUP);
pinMode(m_StatusPin, OUTPUT);
digitalWrite(m_StatusPin, LOW);
}
// TestPin().
// At most once per millisecond
// -- digitalRead(Pin)
// -- Update m_State if Pin is stable for a minimum number of reads
// -- Using limit as thresshold
void TestPin()
{
unsigned long Now = millis();
if( (Now - m_Time) >= 1 )
{ m_Time = Now;
int Threshold = 500; // Rediculously large, just to show the effect
if(m_State != digitalRead(m_ButtonPin) )
{ // Pin changed. Count.
if(m_Counter < Threshold)
{ m_Counter += 1;
}
if(m_Counter == Threshold)
{ m_State = m_State ? LOW : HIGH;
m_Counter = 0;
digitalWrite(m_StatusPin, m_State);
}
}
else
{ // Pin Same as State : Restart counter.
m_Counter = 0;
}
}
else
{ // Too soon. Try again next time
}
}
// Button : Only true when m_State changes from 1 to 0
bool Button()
{
TestPin();
if( (m_Value == 0) && (m_State == 1) )
{ m_Value = 1;
return false;
}
else if( (m_Value == 1) && (m_State == 0) )
{ m_Value = 0;
return true;
}
else
{ // No change
return false;
}
}
// MorseKey : Return true when key is down, false when key is up
bool MorseKey()
{
TestPin();
return m_State ? false : true;
}
};
//=================================================================================================
// This application has 4 buttons. Each has its own handler
// - Using pins 2, 3, 4 and 5
// - Pin Status is available on Pins 8, 9, 10 and 11
DEBOUNCE_HANDLER Button1( 2, 8);
DEBOUNCE_HANDLER Button2( 3, 9);
DEBOUNCE_HANDLER Button3( 4, 10);
DEBOUNCE_HANDLER Button4( 5, 11);
void setup()
{
Serial.begin(38400);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
// Button() returns true as long as the button s pressed.
if( Button1.Button() )
{ Serial.println( F("Button 1 pressed") );
}
if( Button2.Button() )
{ Serial.println( F("Button 2 pressed") );
}
if( Button3.Button() )
{ Serial.println( F("Button 3 pressed") );
}
// MorseKey() returns true as long as the button is pressed.
// - Use it to switch a light
digitalWrite( LED_BUILTIN, Button4.MorseKey() );
}
Om te debouncen heb je een aantal variabelen nodig om de history te bewaren. Als je elke pin een eigen set variabelen wilt geven moet je die samen kunnen voegen. Dat is hier gedaan door een class DEBOUNCE_HANDLER te definieren.
code:
class DEBOUNCE_HANDLER
{
public:
int m_State;
int m_Counter;
int m_Value;
int m_ButtonPin;
int m_StatusPin;
unsigned long m_Time;
Die variabelen kun je initialiseren als je een button object aanmaakt zoals we verderop zien. Daarvoor heb je een constructor functie nodig. Een constructor funktie heeft dezelfde naam als de class, en geen return value. De funcktie wordt automatisch uitgevoerd als je een nieuw object aanmaakt.
code:
DEBOUNCE_HANDLER(int ButtonPin, int StatusPin)
{ m_State = LOW;
m_Value = LOW;
m_Counter = 0;
m_ButtonPin = ButtonPin;
m_StatusPin = StatusPin;
pinMode(m_ButtonPin, INPUT_PULLUP);
pinMode(m_StatusPin, OUTPUT);
digitalWrite(m_StatusPin, LOW);
}
De feitelijke debounce gebeurt in de TestPin() funktie. Dit is een member funktie binnen de DEBOUNCE_HANDLER dus die werkt voor elke pin met een eigen set variabelen.
code:
void TestPin()
{
unsigned long Now = millis();
if( (Now - m_Time) >= 1 )
{ m_Time = Now;
int Threshold = 500; // Rediculously large, just to show the effect
if(m_State != digitalRead(m_ButtonPin) )
{ // Pin changed. Count.
if(m_Counter < Threshold)
{ m_Counter += 1;
}
if(m_Counter == Threshold)
{ m_State = m_State ? LOW : HIGH;
m_Counter = 0;
digitalWrite(m_StatusPin, m_State);
}
}
else
{ // Pin Same as State : Restart counter.
m_Counter = 0;
}
}
else
{ // Too soon. Try again next time
}
}
Allereerst test de funktie of er al genoeg tijd verstreken is sinds de vorige keer. Zoniet dan gebeurt er niks.
Vervolgens wordt m_State vergeleken met de digitalRead(). m_State is de pin status na debounce en digitalRead() geeft de aktuele pinstatus. Als die verschillend zijn, dan wordt m_Counter opgehoogd, zijn ze hetzelfde dan wordt m_Counter terug op nul gezet.
Als m_Counter de Thresshold waarde bereikt, dan heeft digitalRead() dus meerdere keren dezelfde waarde opgeleverd, en kunnen we m_State aanpassen. Als bonus wordt de m_State na debounce ook weggeschreven naar de m_StatusPin.
De thresshold staat hier op 500. Speciaal om het effect te laten zien. Het systeem wordt daarmee wel erg traag natuurlijk ( een halve seconde reaktie - tijd). Normaal zal een waarde tussen 10 en 50 wel volstaan.
Dan krijgen we de Button() funktie. De applikatie moet die funktie regelmatig uitvoeren. Elke keer dat die funktie wordt gebruikt wordt de Pin Status gelezen en gedebounced in TestPin(). De funktie zelf bevat een edge detector die alleen true geeft als de pin status na debounce (m_State) verandert van 1 naar 0. De extra variabele m_Value hebben we nodig om te zien of m_State anders is dan de vorige keer.
code:
bool Button()
{
TestPin();
if( (m_Value == 0) && (m_State == 1) )
{ m_Value = 1;
return false;
}
else if( (m_Value == 1) && (m_State == 0) )
{ m_Value = 0;
return true;
}
else
{ // No change
return false;
}
}
Speciaal voor seinsleutels is er een aparte funktie MorseKey()
Die gebruikt ook de TestPin() funktie om te debouncen, en daarna geeft die m_State terug, zodat je weet of de sleutel na debonce aan is of uit.
code:
bool MorseKey()
{
TestPin();
return m_State ? false : true;
}
En dan zijn we aan het einde van de class definitie.
Nu kunnen we die gebruiken om een 4-tal button objecten aan te maken. Elke button variabele krijgt een eigen set data en eigen pin nummers.
code:
DEBOUNCE_HANDLER Button1( 2, 8);
DEBOUNCE_HANDLER Button2( 3, 9);
DEBOUNCE_HANDLER Button3( 4, 10);
DEBOUNCE_HANDLER Button4( 5, 11);
En dan kunnen we die button objecten gaan gebruiken in onze applikatie.
code:
void loop()
{
// Button() returns true as long as the button s pressed.
if( Button1.Button() )
{ Serial.println( F("Button 1 pressed") );
}
if( Button2.Button() )
{ Serial.println( F("Button 2 pressed") );
}
if( Button3.Button() )
{ Serial.println( F("Button 3 pressed") );
}
// MorseKey() returns true as long as the button is pressed.
// - Use it to switch a light
digitalWrite( LED_BUILTIN, Button4.MorseKey() );
}
code:
if( Button1.Button() )
{ Serial.println( F("Button 1 pressed") );
}
Hier gebruiken we de Button() funktie van het Button1 object.
Die funktie test de status van de button op Pin 2. Je moet die funktie meerdere keren aanroepen want de toets wordt pas geaccepteerd als die meerdere keren ziet dat de toets is ingedrukt. Dus meestal geeft de funktie false. Maar uiteindelijk toch true. Dat kun je dan zien op de serial port monitor, en op pin 8.
Hetzelfde gebeurt met Button2 en Button3, wel op andere pinnen.
De seinsleutel wordt hier (na debounce) doorgegeven naar een lampje.
code:
digitalWrite( LED_BUILTIN, Button4.MorseKey() );