Start Elkretssimulator


Kontaktstuds

Lite vanliga knappar



Knapp kopplad till digital ingång

Ganska ofta vill man koppla en liten knapp till processorn. Det är ju inget märkvärdigt kan man tycka. En spontan skiss ser troligen ut som den nedan. Eller så gör man tvärtom, en pullup till 5V och knappen ner mot jord. Det kanske är vanligare.



Om man inte känner till problematiken kring kontaktstuds kommer eventuellt otroligt mystiska fel inträffa. Det kan vara så att man trycker på knappen en gång, men processorn reagerar som om man tryckte 2 gånger eller 5 gånger.

Så, vad är problemet? Kontaktstuds.

Problemet är att den mekaniska kontruktionen inuti kontakten har vissa begränsningar och att det lätt händer att kontakten "studsar" mot metallblecken inuti kontakten. Detta ger en kaskad av till och frånslag, vilket syns tydligt om man kopplar in ett oscilloskop och studerar hur själva tillslaget ser ut. Speciellt kan detta problem inträffa ofta med billigare knappar.

Är knappen kopplad till en lampa eller elmotor, så märker man inte av detta kontaktstuds. Men är knappen kopplad till en snabb mikroprocessor kan konstiga saker hända. Så här kan det se ut när du trycker på knappen om vi betraktar det i ett oscilloskop.



Dvs, om vi gör bilden lite mer pedagogiskt tillrättalagd, så kan det se ut som nedan.



Vad vi tror är ett (singular alltså) tillslag när vi trycker på knappen är i själva verket eventuellt ganska många till och från och tillslag.

Exempel på kontaktstuds

Säg att man är lite av en tvivlare och kanske inte tror på detta ändå, eftersom det låter lite konstigt. Okej, låt oss koppla in en knapp till arduino och se hur det fungerar. Jag kopplar in en knapp på pinne 2, vilket är INT0. Anledningen till att jag väljer denna är att jag vill att ett interrupt skall triggas när jag trycker på knappen. På arduinon kan man låta signalförändringar generera interrupt på pinne 2 (INT0) och pinne 3 (INT1). Nedanstående program skriver ut hur många knapptryck jag gjort 1 gång per sekund via seriemonitorn.

volatile int antal_knapptryck = 0;

void setup()
{
  Serial.begin(9600);
  pinMode(2, INPUT);
  digitalWrite(2, HIGH); // aktivera intern pullup
  attachInterrupt(0, knapptryck, FALLING);
}

void loop()
{
  Serial.println(antal_knapptryck);
  delay(1000);
}

void knapptryck()
{
  antal_knapptryck++;
}


Efter att du tankat över ovanstående program så måste du öppna seriemonitorn i arduinos IDE för att ta emot siffrorna från programmet.



Hårdvarumässig lösning på kontaktstuds

...för knappar (2 anslutningar)

Den spontana lösningen är att fixa en viss fördröjning med hjälp av en kondensator. Utsignalen skickas sedan in i en schmitt-trigger. Denna schmitt-trigger är central för att denna lösning skall fungera. Schmitt-triggern slår till vid en viss nivå och slår av vid en viss nivå. Att koppla en kondensator direkt på processorns in-pinne och förvänta sig att den ska bete sig som en schmitt-trigger, det ska man inte göra. Underliga saker kan hända om man kopplar en konding på en ingång som inte är avsedd för detta. Risken är att man byter kontaktstuds mot någon annan typ av studs.



Uppkopplat på en labb-bräda med arduino ser det ut såhär.



...för switchar (3 anslutningar)

Ifall man har en switch med 2 ändlägen (dvs en switch med 3 ben), då är nog den optimalaste lösningen att använda en SR-latch. SR-latchen kommer göra en "set" vid första tillslag i "set" -läge. SR-latchen kommer göra en "reset" vid första tillslag i "reset" -läge.



SR-latchen utplånar helt problematiken med kontaktstuds i denna typ av switch. En SR -latch med enbart NAND -grindar ser ut som nedan.



Mjukvarumässig lösning

...pinne kopplat till interrupt

Säg att din knapp är kopplad så att ett interrupt reagerar på knappen. Detta interrupt kanske startar någonting. Om man inte tar hänsyn till detta med kontaktstuds hårdvarumässigt, så måste man isåfall ta hänsyn till det mjukvarumässigt (det bör man kanske eventuellt göra ändå). Man måste vara medveten om att det kan komma en "störtskur" med anrop till denna interruptfunktion (p.g.a. kontaktstuds). Ett sätt att lösa problemet kan vara att med en räknare hålla koll på om knappen nyss har tryckts ner och därefter vänta en stund innan ett nytt tryck accepteras.

Studera t.ex. följande exempel.

volatile int nrOfPushButton = 0;
volatile int quarterSecCount = 0;

void setup()
{
  Serial.begin(9600);
  
  // setup trigger interrupt signal change pin 2
  pinMode(2, INPUT);
  digitalWrite(2, HIGH);
  attachInterrupt(0, keyPress, FALLING);
  
  cli();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A =  3905;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS12) | (1 << CS10); // 1024 prescaler
  TIMSK1 |= (1 << OCIE1A);
  sei();
}

void keyPress()
{
  if(quarterSecCount>2) // åtminståne 3x0.25 sec sedan klick
  {
     // gör vad du vill göra när knappen är tryckt
     // i detta fall räknar vi bara knapptryck
    nrOfPushButton++;
  }
  quarterSecCount=0;
}

ISR(TIMER1_COMPA_vect)  // 4 ggr/sekund
{
  quarterSecCount++;
}

void loop()
{
  Serial.println(nrOfPushButton);
  delay(1000);
}


Först sätter vi upp ett interrupt att reagera på knapptryck. Det innebär att funktionen keyPress() anropas varje gång knappen trycks ner, vilket som sagt kan bli ganska många gånger för varje enstaka knapptryck. Sedan sätter vi upp ett interrupt som anropar funktionen ISR 4 gånger per sekund. Vid varje anrops räknar vi upp variablen quarterSecCount.

När knappen trycks ner så kontrollerar vi först att det passerat en stund sedan knappen trycks senaste gången. Genom att testa om quarterSecCount är 3 eller högre så vet vi att det passerat mellan 0.5-0.75 sekunder sedan senaste gången knappen tryckts in.

Detta utplånar all kontaktstuds och vi får en väl fungerande knapp.

...pollad pinne

Ponera att man har en mjukvarumässig lösning där man pollar en pinne (kopplad till en knapp) och när denna pinne är hög (eller låg) så gör man någonting. Det här är ju inget exempel på lösning men konceptet som sådant behöver eventuellt inte ta hänsyn till kontaktstuds. Det värsta som kan hända är att programmet inte reagerar på ett knapptryck och att man måste trycka lite hårdare på knappen. Observera dock att programmet läser av knappen varje sekund och reagerar på detta varje sekund. Denna lösning passar inte alla ändamål.

#define RELEASE HIGH
#define PUSH LOW

volatile int btn = 0;

void setup()
{
  Serial.begin(9600);
  pinMode(2, INPUT);
  digitalWrite(2, HIGH);
}

void loop()
{
  btn=digitalRead(2); 
  Serial.println(btn);
  if(btn==PUSH)
  {
    // gör något
  }
  delay(1000);
}


...timer-interrupt

En annan lösning är att från en periodiskt anropad funktion polla en pinne kopplad till knappen.

#define RELEASE HIGH
#define PUSH LOW

volatile int nrOfPushButton = 0;
volatile int btnStatus = RELEASE;
volatile int quarterSecCount = 0;
volatile int btn;

void setup()
{
  Serial.begin(9600);
 
  pinMode(2, INPUT);
  digitalWrite(2, HIGH);
  
  cli();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A =  3905;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS12) | (1 << CS10); // 1024 prescaler
  TIMSK1 |= (1 << OCIE1A);
  sei();
}

ISR(TIMER1_COMPA_vect) // 4 ggr/sekund
{
  btn=digitalRead(2); 
 
// minst 3x0.25 sec sedan senaste tryck
  if(quarterSecCount>2) 
  {
    if(btnStatus==PUSH && btn==RELEASE)
    {
      btnStatus=RELEASE;
      quarterSecCount=0;
    }
  }
  
  if(btn==PUSH) // if button pressed
  {
    quarterSecCount=0;  
    if(btnStatus==RELEASE)
    {
        btnStatus=PUSH;
        // gör vad du vill göra när knappen är tryckt
        // i detta fall räknar vi bara knapptryck
        nrOfPushButton++;
    }
 }
 
 quarterSecCount++;
}

void loop()
{
  Serial.println(nrOfPushButton);
  delay(1000);
}


Konceptet här är att en funktion anropas när man trycker ner knappen, varpå en tillståndsvariabel btnStatus sätts till PUSH. För att kunna trycka på knappen en gång till måste den vara släppt i 0.5-0.75 sekunder.

Stäng av interrupt och slå på igen

Ett annat sätt att hantera kontaktstuds kan vara att koppla knapptrycket till ett interrupt. När detta interrupt anropas så stänger man ner hela interrupt -funktionaliteten tills det man vill göra, är gjort. För vissa program kan detta vara en enkel och snygg lösning. Stänga ner interrupten kan man göra med funktionerna noInterrupts() och interrupts() på arduino. Den första stänger ner möjligheten till interrupt och den andra drar igång dem igen.

void knappTryck()
{
  noInterrupts();
  // gör det du vill göra
  interrupts();
}


Det viktiga är inte exakt vilken metod du använder för att jobba dig runt problemet. Det viktiga här är att du förstår att problemet med kontaktstuds är högst verkligt.