Kampis Elektroecke

Taster entprellen

Xilinx Spartan3

In diesem Artikel zeige ich wie ein Taster an einem FPGA oder CPLD angeschlossen und entprellt werden kann. Die Funktionsweise der Entprellschaltung ist ähnlich der Schaltung eines IC welches speziell zum Entprellen von Tastern entwickelt wurde, dem MAX6817.

Was ist überhaupt “Prellen”?:

Bedingt durch den Aufbau von Tastern kommt es vor, dass ein Taster beim Betätigen nicht direkt komplett betätigt wird, sondern im Inneren noch kurz hin- und her federt. Für schnellen Schaltungen (wie in einem FPGA) sieht dieses Federn, was durchaus mehrere Millisekunden dauern kann, aus wie ein mehrmaliges Betätigen des Tasters. So nimmt das FPGA dann mehrere Tastendrücke wahr, obwohl der Benutzer den Taster nur einmal betätigt hat. Dies sieht dann z. B. so aus:

Ein einfacher Weg dieses Prellen weg zu bekommen ist die Verwendung eines RC-Gliedes. Das RC-Glied benötigt eine gewisse Zeit um den internen Kondensator zu laden. Der Ladevorgang findet aber nur statt, wenn der Taster gerade geschlossen ist. Je nach Kapazität des Kondensators dauert der Ladevorgang dann unterschiedlich lange und nachdem der Kondensator geladen ist (und der Taster noch gedrückt ist) führt der Kondensator den selben Pegel wie der Taster wenn er betätigt wurde. Die Schaltung kann z. B. so aussehen:

Bei dieser Schaltung werden zusätzliche Bauelemente benötigt, was bei kleinen Leiterkarten eventuell Problematisch ist. Aus diesem Grund wird das Entprellen in das FPGA verlagert. Da das FPGA keinen Kondensator bereit stellen kann, muss das Entprellen auf andere Weise geschehen…

Entprellen im FPGA:

Um den Taster zu Entprellen wird ein Taktsignal benötigt, welches bei den meisten FPGA-Schaltungen ohnehin schon vorhanden sein sollte. Mit Hilfe dieses Taktsignals zähle ich dann einfach wie viele Taktzyklen der I/O einen High- bzw. Lowpegel führt. Sobald eine bestimmte Anzahl an Zyklen überschritten wurde kann ich annehmen, dass der Taster sicher geschaltet hat. Den ganzen Vorgang habe ich mal in einer Skizze deutlich gemacht…

Diese Grundidee muss nun nur noch in eine Schaltung umgewandelt werden…
 

Schritt 1. Die Taktquelle:

Als erstes habe ich mir Gedanken um die Taktquelle gemacht. Mein Board ist mit einer 8 MHz Taktquelle ausgestattet. Wenn man von einer typischen Prellzeit von 1-10 ms ausgeht wird man schnell feststellen, dass 8 MHz für diese Zeit doch immens viel sind, da der Counter dann bis 80000 zählen muss um die 10ms Prellen zu überbrücken. Aus diesem Grund habe ich ein VHDL-Modul entworfen, welches mir einen Takt auf einen beliebigen anderen Takt runterteilt:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Clock_Divider is
    Generic ( DIVIDER : INTEGER 
              );
    Port ( Clock_In  : in STD_LOGIC;
           Reset     : in STD_LOGIC;
           Clock_Out : out STD_LOGIC
           );
end Clock_Divider;

architecture Clock_Divider_Arch of Clock_Divider is

    signal Clock_Counter : integer := 0;
    signal Clocksignal : STD_LOGIC := '0';

begin

    process(Clock_In, Reset)
    begin
        if(rising_edge(Clock_In)) then
            if(Reset = '0') then
                Clock_Counter <= 0;
            else
                Clock_Counter <= Clock_Counter + 2;
                
                if(Clock_Counter > DIVIDER) then
                    Clocksignal <= not Clocksignal;
                    Clock_Counter  <= 0;
                end if;
            end if;
        end if;
    end process;
	
    Clock_Out <= Clocksignal;

end Clock_Divider_Arch;

Das Modul besitzt einen Clock-Eingang, einen Clock-Ausgang und einen Eingang wo das Teilungsverhältnis angegeben wird. Das Modul macht im Grunde nichts anderes als bei jeder positiven Taktflanke einen Zähler hoch zu zählen und bei erreichen des Zählerstandes, der in dem Signal Div gespeichert ist, wird der Clock-Ausgang umgeschaltet. Durch eine entsprechende Instantiierung des Moduls und einen geeigneten Teiler für den Takt erzeuge ich mir jetzt einen 1 kHz Takt, welchen ich mir an einem Pin ausgeben lassen kann:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Debounce is
    Port ( Clk : in  STD_LOGIC;
           Output : out STD_LOGIC
           );
end Debounce;

architecture Debounce_Arch of Debounce is

   signal Clock_1khz : STD_LOGIC := '0';

   component ClockDiv
        Generic ( DIVIDER : INTEGER 
                  );
        Port (  Clock_In : in STD_LOGIC;
                Clock_Out : out STD_LOGIC
                );
   end component;

begin

    Div_1kHz : ClockDiv generic map (DIVIDER => 8000)
                        port map (  Clock_In => Clk, 
                                    Clock_Out => Clock_1khz
                                    );
    Output <= Clock_1khz;

end Debounce_Arch ;

Ein Test mit dem Oszilloskop zeigt das der Takt auch richtig erzeugt wird:

Damit wäre die Taktquelle für meine Debounce-Schaltung schon mal erzeugt.

Schritt 2. Die Debounce-Schaltung:

Zeit um sich der Schaltung zum Entprellen eines Tasters zu widmen. Auch diese Schaltung habe ich in ein VHDL-Modul gepackt, damit ich diese Schaltung bei mehrmaliger Benutzung nur noch instantiieren muss. Werfen wir mal einen Blick auf das Modul…

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Debounce is
    Generic (BOUNCETIME : INTEGER
             );
    Port ( Clock    : in  STD_LOGIC;
           Reset    : in STD_LOGIC;
           Input    : in  STD_LOGIC;
           Output   : out STD_LOGIC
	   );
end Debounce;

architecture Debounce_Arch of Debounce is

   signal Debounce_Counter : INTEGER := 0;
   signal Taster_Debounce : STD_LOGIC := '0';

begin

    process(Clock, Reset, Debounce_Counter, Input)
    begin
        if(rising_edge(Clock)) then
            if(Reset = '0') then
                Debounce_Counter <= 0;
            else
               if(Input = Taster_Debounce) then
	               Debounce_Counter <= 0;
	           else
	               Debounce_Counter <= Debounce_Counter + 1;
	           end if;
            end if;	

            if(Debounce_Counter = BOUNCETIME) then
                Debounce_Counter <= 0;
                Taster_Debounce <= Input;
            end if;
        end if;
    end process;
	
    Output <= Taster_Debounce;

end Debounce_Arch;

Das Modul besitzt zwei Eingänge, einen für das nicht entprellte Taster-Signal und einen für den Takt, sowie einen Output für das entprellte Taster-Signal. Zudem wird noch ein Integerwert in das Modul übergeben, der für die Prellzeit steht.

Gehen wir die Funktion mal mit dem Einschaltzustand durch. Zu diesem Zeitpunkt sind die Signale Debounce_Counter und Taster_Debounce mit den oben angegebenen Werten vorhanden (0 und ‘0‘). Sobald jetzt eine steigende Flanke von dem Taktsignal anliegt schaut das FPGA nach ob der Eingangspin für den Taster den selben Zustand hat wie das Signal Taster_Debounce:

  • Eingangspin hat den selben Zustand:
    Taster wurde nicht betätigt. Debounce_Counter kann 0 bleiben, bzw. wird auf 0 gesetzt.
  • Eingangspin hat einen anderen Zustand – in diesem Fall 1, also High
    Taster wurde betätigt, Debounce_Counter wird um 1 erhöht.

Der oben beschriebene Vorgang wiederholt sich nun solange bis das Signal Debounce_Counter den selben Wert hat wie die Konstante BOUNCETIME. Sobald dies eintritt ist die Zeit, wonach man annehmen kann, dass der Taster wirklich gedrückt wurde, vorüber und der Debounce_Counter wird gelöscht und das Signal Taster_Debounce, welches den aktuellen Zustand des Tasters speichert, wird auf den Zustand des Eingangs gesetzt (in diesem Fall 1). Der Taster ist jetzt entprellt und sobald er los gelassen wird, passiert das selbe nochmal, nur dass dieses mal der Eingang des Moduls eine 1 führt statt einer 0.

Die beiden fertigen Module müssen jetzt nur noch zusammengeführt werden. Für die Zusammenführung erstelle ich mir ein neues VHDL Modul mit dem Namen Top, welches einen Clock-Eingang, einen Eingang für den Taster und einen Ausgang für das entprellte Tastersignal (z. B. für einen Zähler oder eine LED) besitzt:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Taster_Entprellen is
    Port (  Taster_In    : in STD_LOGIC;
            Reset        : in STD_LOGIC;
            Clock        : in STD_LOGIC;
            Taster_Out   : out STD_LOGIC
            );
end Taster_Entprellen;

architecture Taster_Entprellen_Arch of Taster_Entprellen is

    signal Clock_1khz : STD_LOGIC := '0';

    component ClockDiv
         Generic ( DIVIDER: INTEGER 
                   );
         Port ( Clock_In    : in STD_LOGIC;
                Reset       : in STD_LOGIC;
                Clock_Out   : out STD_LOGIC
                );
    end component;
	
   component Debounce 
        Generic (   BOUNCETIME : INTEGER 
                    );
        Port (  Clock    : in  STD_LOGIC;
                Reset    : in STD_LOGIC;
                Input    : in  STD_LOGIC;
                Output   : out STD_LOGIC
	        );
   end component;

begin

    Clock_1000Hz : ClockDiv generic map (   DIVIDER => 125000)
                                port map (  Clock_In => Clock,
                                            Reset => Reset,
                                            Clock_Out => Clock_1khz
                                            );
														  
    Button_1 : Debounce generic map (BOUNCETIME => 20)
                        port map (  Input => Taster_In,
                                    Reset => Reset,
                                    Output => Taster_Out, 
                                    Clock => Clock_1khz
                                    );

end Taster_Entprellen_Arch;

Die Schaltung zum Entprellen von Tastern ist damit fertig und Einsatzbereit.

Der Quellcode kann in meinem Git-Repository heruntergeladen werden.

Last Updated on

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.