Prellmentesítés

Gyakran használunk amatőr kapcsolásainkban nyomógombokat. Egy átlagos nyomógomb két egymáshoz nyomható fémdarabból áll. Ezek a fém érintkezők a nyomógomb lenyomásakor pattognak egymáson, és ez a rendkívül gyors programunk számára többszörös kapcsolás érzetét kelti. Ezt a jelenséget „prell”-nek nevezik. Megpróbáltam három különböző programmal bemutatni a problémát, és azt is, hogyan lehet kiküszöbölni a prell jelenségét a programban. A cél az, hogy a program ne érzékeljen többszörös lenyomást. Fontos tudni, hogy a nyomógomb elengedése is okoz prell jelenséget, tehát ebben az állapotban is meg kell szüntetnünk a többszörös érintkezést.

A prell megszüntetésére van egy nagyon egyszerű megoldás. Figyeljük a bemenetet, és ha azt érzékeljük, hogy a bemenetet lehúzta 0-ra a nyomógomb, akkor megvárjuk amíg lenyugszik a kontaktusok egymáson pattogása, és akkor jelezzük a programnak egy változóban, hogy megnyomták a nyomógombot. A várakozás szokásos értéke az énprogramjaimban 50msec. Lehetséges, hogy kevesebb is elég. Kezdetben 20msec értékkel próbálkoztam, de találtam egy nyomógombot, ami még így is kettőt számolt, ezért lett immár 50. Ez egyben azt is jelenti, hogy 50msec-el később jelezzük a nyomógomb megnyomását, de ha ez nagyon fontos, pl. stoppert készítünk az olimpiai gátfutás döntőjének időméréséhez, akkor lehet az 50sec-el korrigálni.

Három bemutató programot készítettem:

  1. Az első program bemutatja, hogyan is okoz problémát a prell. Egy számlálót növelek minden gombnyomáskor, és nem lepődünk meg, hogy néha akár 4-5-öt is számol.
  2. A második program időzítőket használ, 50msec-et várakozik az első kontaktus után. Jól működik, de nagyon lelassítja a program működését.
  3. A harmadik program prellmentesít és nem is lassítja le olyan nagyon a program működését.

Lássuk a prell káros hatásait:

/*****************************************************************************************************************
 * Bemutató program arra, milyen problémát okoz egy nyomógomb prell-je                                           *
 * A program növeli egy szamláló értékét minden gombnyomáskor, és a lenyomás pillanatában kiírja a számláló      *
 * értékét s oros portra. Megfigyelhető, hogy egy lenyomással akár 3-4 kiírás és számláló növelés is történik,   *
 * sőt ez még a nyomógomb elengedésekor is előfordul.                                                            * 
 * Futás közben a program megméri az egy másodperc alatt végrehajtott loop ciklusok számát is, és ezt az értéket *
 * másodpercenként kiírja a soros portra. A forráskód végén kommenteben egy futási eredmény.                     *
 *****************************************************************************************************************/

bool allapot;          //ebbe a változóba beolvassuk a digitális bemenet állapotát 
bool elozoallapot;     //minden loop ciklus végén ebbe áttöltjük a ciklus elején beolvasott bemeneti állapotot, ez segít észrevenni, ha a következő ciklusban változott a bemenet állapota
int szamlalo;          //minden nyomógomb megnyomáskor növeljük az értékét
long ciklus_szamlalo;      //minden loop() ciklusban növeljük az értékét, mert kíváncsai vagyunk, hány ciklus zajlik le egy másodperc alatt
long ido;              //ebben tároljuk az utolsó idoszamlalo kiírás időpontját, hogy 1000msec (1 sec) múlva újra kiírhassuk annak értékét

void setup() 
{
  pinMode(3,INPUT);      //nyomógomb bemenet
  digitalWrite(3,HIGH);  //felhúzó ellenállás bekapcsolva
  Serial.begin(9600);    //elindítjuk a soros portot
}

void loop() 
{
  ciklus_szamlalo=ciklus_szamlalo+1;            //minden ciklusban növeljük az értékét, így ha 1 másodpercenként kiírjuk 
                                        //az értékét, megtudhatjuk hány ciklus zajlott le
  allapot=digitalRead(3);               //beolvassuk az allapot változóba a bemenet pillanatnyi állapotát 
                                        //(0-lenyomtuk a nyomógombot, 1-a nyomógomb nincs lenyomva)
  if (allapot==0 and elozoallapot==1)   //azt érzékeljük, amikor az előző ciklusban még nem volt megnyomva 
                                        //a nyomógomb, most pedig igen. Ez a bemeneten egy "lefutó él" érzékelést jelent
  {
    szamlalo=szamlalo+1;                //növeljük a számláló értékét, mert a nyomógomb le lett nyomva
    Serial.println(szamlalo);           //kiírjuk a soros portra a számláló állapotát
  }
  elozoallapot=allapot;                 //tároljuk a ciklus elején beolvasott bemeneti állapotot

  if (ido+1000<millis())                //ha 1 sec telt el az elozo 
  {
    Serial.println(ciklus_szamlalo);    //kiírjuk ciklus_szamlalo értékét a soros portra
    ciklus_szamlalo=0;                  //töröljük ciklus_szamlalo értékét, újra kezdjük a számlálást
    ido=millis();                       //tároljuk a millis() értékét, így az if-ben vizsgálhatjuk, hogy eltelt-e 1 másodperc
  }
}

Futási eredmény a sorosporton:
78750
78831
1
2
78401
78831
3
78573
78769
4
78520
5
78422
78831
6
7
8
9
78541
10
11
12
13

A 78ezres szám az egy másodperc alatt végrehajtott loop() ciklusok száma. Közte a növekvő sorszámok a megszámolt billentyű lenyomások. Akkor van prell, amikor két vagy több sorszám is megjelenik egymás mögött!

A most következő a programban időzítéseket használtam a prellmentesítésre. Prellmentesít, de rossz megoldás:

/*************************************************************************************************************
 * Bemutató program arra, hogyan lehet úgy prellmentesíteni a nyomógomb lenyomást, hogy időzítéseket         *
 * használunk az első kontaktus észrevételét követően. Ekkor 50msec múlva újra megnézzük a nyomógomb         *
 * állapotát, é ha még akkor is nyomva volt, akkor már biztosan lenyugodott a kontaktus, és le van nyomva.   *
 * A program növeli egy szamláló értékét minden gombnyomáskor, és a lenyomás pillanatában kiírja a számláló  *
 * értékét a soros portra. Közben a program megméri az egy másodperc alatt végrehajtott loop ciklusok számát *  
 * is, és ezt az értéket másodpercenként kiírja a soros portra. A forráskód végén kommenteben egy futási     *
 * eredmény.                                                                                                 *
 *************************************************************************************************************/
bool allapot;              //ebbe a változóba beolvassuk a digitális bemenet állapotát 
bool elozoallapot;         //minden loop ciklus végén ebbe áttöltjük a ciklus elején beolvasott bemeneti állapotot, ez segít észrevenni, ha a következő ciklusban változott a bemenet állapota
int szamlalo;              //minden nyomógomb megnyomáskor növeljük az értékét
long ciklus_szamlalo;      //minden loop() ciklusban növeljük az értékét, mert kíváncsai vagyunk, hány ciklus zajlik le egy másodperc alatt
long ido;                  //ebben tároljuk az utolsó idoszamlalo kiírás időpontját, hogy 1000msec (1 sec) múlva újra kiírhassuk annak értékét

void setup() 
{
  pinMode(3,INPUT);      //nyomógomb bemenet
  digitalWrite(3,HIGH);  //felhúzó ellenállás bekapcsolva
  Serial.begin(9600);    //elindítjuk a soros portot
}

void loop() 
{
  ciklus_szamlalo=ciklus_szamlalo+1;    //minden ciklusban növeljük az értékét, így ha 1 másodpercenként kiírjuk 
                                        //az értékét, megtudhatjuk hány ciklus zajlott le
  if (digitalRead(3)==LOW)              //első lenyomás érzékelése
    {
      delay(50);                        //várum 50msec-et, azonban itt elveszik 50msec ido, és nem történik addig semmi
      if (digitalRead(3)==LOW)          //még mindíg nyomva, tehát biztosan le van nyomva és nincs már prell, 
        {allapot=1;}                    //allapot változót bebillentjük, ez már egy prellmentes jelzése
    }                                   //a nyomógomb lenyomásának
                                     
  if (digitalRead(3)==HIGH)             //első elengedés érzékelése
    {
      delay(50);                        //várum 50msec-et, azonban itt elveszik 50msec ido, és nem történik addig semmi
      if (digitalRead(3)==LOW)          //még mindíg elengedve, tehát biztosan elengedtük és nincs már prell
        {allapot=0;}                    //allapot változót bebillentjük, ez már egy prellmentes jelzése
    }                                   //a nyomógomb elengedésének

  if (allapot==0 and elozoallapot==1)   //azt érzékeljük, amikor az előző ciklusban még nem volt megnyomva 
                                        //a nyomógomb, most pedig igen. Ez a bemeneten egy "lefutó él" érzékelést jelent
  {
    szamlalo=szamlalo+1;                //növeljük a számláló értékét, mert a nyomógomb le lett nyomva
    Serial.println(szamlalo);           //kiírjuk a soros portra a számláló állapotát
  }
  elozoallapot=allapot;                 //tároljuk a prellmentes nyomógomb pillanatnyi állapotát, hogy 
                                        //a változást észrevegyük egy következő ciklusban
  if (ido+1000<millis())                //ha 1 sec telt el az elozo ido változóban tárolt időpont óta, akkor 
  {
    Serial.println(ciklus_szamlalo);    //kiírjuk ciklus_szamlalo értékét a soros portra
    ciklus_szamlalo=0;                  //töröljük ciklus_szamlalo értékét, újra kezdjük a számlálást
    ido=millis();                       //tároljuk a millis() értékét, így az if-ben vizsgálhatjuk, hogy eltelt-e 1 másodperc
  }
}

Hogy miért is rossz megoldás, az a futási eredményből látszik:
21
20
21
19
20
1
19
2
21
19
21
3
19
4
21
19
5
20

A 20 körüli szám a loop() ciklus végrehajtási ideje. Ezen nem csodálkozunk, hiszen kb. 50 millisec időt azzal töltünk, hogy várjuk a nyomógomb biztos kontaktusát. Biztosan lehetne optimalizálni a működését. Pl. ha csak a bemenet változásakor időzítünk, akkor nem lesz minden ciklus ilyen lassú, csak az, amikor megnyomjuk vagy elengedjük a nyomógombot. Azonban a lényeg nem változott, mert ilyenkor is kihagyhatunk valamit az eltelő 50msec alatt. Persze lehet megszakítást használni, és akkor nem veszik el semmi, de csak két megszakítás figyelésre használható bemenetünk van. Mi történik, ha pl. 10 jelet is figyelni kell. Megy az is, de akkor meg kell egy plusz hardver (logikai “or” kapuk). Szóval lehet róla konferenciát tartani, ami szintén nagyon jó, mert konferenciákon ingyen van a kaja!

Az viszont vitán felül áll, hogy a prell teljesen eltűnt (elmarad a konferencia, mégse lesz ingyen kaja), hiszen soha nem jelenik meg egymás után kétszer sorszám, amit a prell okozna.

És íme a majdnem tökéletes megoldás. Prellmentesít, és nem is lassítja le drasztikusan a program futási sebességét. Marad erő egyébre is, a nyomógomb figyelésen kívül!

/************************************************************************************************************************************
 * Bemutató program arra, hogyan lehet úgy prellmentesíteni a nyomógomb lenyomást, hogy ne lassítsuk le a program futási sebességét *
 * időzítők használatával. A program növeli egy szamláló értékét minden gombnyomáskor, és a lenyomás pillanatában kiírja a számláló *
 * értékét s oros portra. Közben a program megméri az egy másodperc alatt végrehajtott loop ciklusok számát is, és ezt az értéket   *
 * másodpercenként kiírja a soros portra. A forráskód végén kommenteben egy futási eredmény.                                        *
 ************************************************************************************************************************************/
bool allapot;              //ebbe a változóba beolvassuk a digitális bemenet állapotát 
bool elozoallapot;         //minden loop ciklus végén ebbe áttöltjük a ciklus elején beolvasott bemeneti állapotot, ez segít észrevenni, ha a következő ciklusban változott a bemenet állapota
int szamlalo;              //minden nyomógomb megnyomáskor növeljük az értékét
long ciklus_szamlalo;      //minden loop() ciklusban növeljük az értékét, mert kíváncsai vagyunk, hány ciklus zajlik le egy másodperc alatt
long ido;                  //ebben tároljuk az utolsó idoszamlalo kiírás időpontját, hogy 1000msec (1 sec) múlva újra kiírhassuk annak értékét
bool prell_tmp;            //segéd változó az első kontaktus megtörténtének jelzésére
long prell_time;           //segéd változó az első kontaktus időpontjának rögzítésére

void setup() 
{
  pinMode(3,INPUT);      //nyomógomb bemenet
  digitalWrite(3,HIGH);  //felhúzó ellenállás bekapcsolva
  Serial.begin(9600);    //elindítjuk a soros portot
}

void loop() 
{
  ciklus_szamlalo=ciklus_szamlalo+1;    //minden ciklusban növeljük az értékét, így ha 1 másodpercenként kiírjuk 
                                        //az értékét, megtudhatjuk hány ciklus zajlott le
  
  if (digitalRead(3)==LOW and prell_tmp==0)                             //első lenyomás érzékelése
    {prell_tmp=1;prell_time=millis();}                                  //prell_tmp=1 jelzi, hogy már volt egy kontaktus
  if (digitalRead(3)==LOW and prell_tmp==1 and millis()>prell_time+50)  // már 50msecv óta nyomva van, most már biztos, hogy lenyomták és nem prellezik
    {allapot=1;digitalWrite(11,HIGH);prell_tmp=0;}                      //allapot=1 jelzi a nyomógomb lenyomást és ez már nem prell-es, 
                                                                        //prell_tmp=0-val várjuk a következő eseményt
  if (digitalRead(3)==HIGH and prell_tmp==0)                            //első elengedés érzékelése
    {prell_tmp=1;prell_time=millis();}                                  //prell_tmp=1 jelzi, hogy megszakadt a kontaktus      
  if (digitalRead(3)==HIGH and prell_tmp==1 and millis()>prell_time+50) //már 50msecv óta elengedve, most már biztos, hogy elengedték és nem prellezik
    {allapot=0;prell_tmp=0;}                                            //allapot=0 jelzi a nyomógomb elengedést s ez már nem prell-es, 
                                                                        //prell_tmp=0-val várjuk a következő eseményt

  if (allapot==0 and elozoallapot==1)   //azt érzékeljük, amikor az előző ciklusban még nem volt megnyomva 
                                        //a nyomógomb, most pedig igen. Ez a bemeneten egy "lefutó él" érzékelést jelent
  {
    szamlalo=szamlalo+1;                //növeljük a számláló értékét, mert a nyomógomb le lett nyomva
    Serial.println(szamlalo);           //kiírjuk a soros portra a számláló állapotát
  }
  elozoallapot=allapot;                 //tároljuk a ciklus elején beolvasott bemeneti állapotot

  if (ido+1000<millis())                //ha 1 sec telt el az elozo 
  {
    Serial.println(ciklus_szamlalo);    //kiírjuk ciklus_szamlalo értékét a soros portra
    ciklus_szamlalo=0;                  //töröljük ciklus_szamlalo értékét, újra kezdjük a számlálást
    ido=millis();                       //tároljuk a millis() értékét, így az if-ben vizsgálhatjuk, hogy eltelt-e 1 másodperc
  }
}

A soros porton megjelenő futási eredmény:
31230
31222
31190
31222
31190
31226
1
31233
2
31256
31191
3
31256
4
31212
31230
5
31249
31222

Látható, hogy nincs prell és még talán a loop() ciklus ideje is elég gyors maradt!

Mennyire volt hasznos amit olvastál?

Kattints egy csillagra az értékeléshez!

Sajnálom, hogy amit olvastál nem volt hasznos számodra!

Szeretném ha elégedett lennél!

Írd le kérlek, hogy mi nem tetszett!?