Digital Button Debouncing Implementation for Embedded Systems

Directly reading button states to control devices like LEDs can lead to misinterpretation due to mechanical bouncing when buttons are pressed or released. This bouncing effect causes rapid state changes that may be incorrectly interpreted as multiple button presses. This section demonstrates how to implement debouncing algorithms using a button-controlled buzzer example.

Buzzer Fundamentals

Buzzer devices fall into two categories based on their construction: piezoelectric and electromagnetic types. Piezoelectric buzzers consist of oscillator circuits, piezoelectric plates, impedance matching components, resonance chambers, and housing. Electromagnetic buzzers contain oscillator units, electromagnetic coils, permanent magnets, vibrating diaphragms, and enclosures. Piezoelectric versions operate through piezoelectric effects, generating sound when alternating voltage is applied. Electromagnetic types use audio frequency currents through coils to create magnetic fields that cause periodic vibration between the coil and magnet.

Buzzers also differ in driving methods: active and passive. Active buzzers contain internal oscillators and produce sound immediately when powered. Passive buzzers require external oscillating signals to generate sound since they lack internal oscillator circuits.

Debouncing Mechanism

Mechanical spring switches exhibit physical bouncing when actuated due to their elastic properties. During pressing or releasing actions, the contacts don't immediately settle into stable positions but rather oscillate before stabilizing. Debouncing eliminates these unwanted oscillations.

Two approaches exist: hardware and software debouncing. Hardware solutions typically employ RS flip-flops or capacitors in circuit design, suitable for systems with few buttons. Software debouncing delays 5-20ms after detecting state changes before sampling and validating button states.

Software Implementation Strategy

Both press and release actions generate bouncing. In digital systems, this appears as rapidly fluctuating high and low levels. The bouncing signal shows low levels briefly before returning high due to mechanical spring behavior. Detecting each low level would register multiple button presses instead of single operations. Therefore, filtering removes signals lasting less than 20ms. Only when button values remain stable for 20ms or longer do we consider them valid. High levels indciate release, low levels indicate press.

  • Filtering short-duration values: Start a 20ms countdown. If the button state changes before completion, restart the countdown from the beginning, treating previous changes as invalid. Complete 20ms without change confirms valid press or release.
  • Detecting state changes: Capture button values in two consecutive cycles (first for metastability elimination, second for comparison). Matching values indicate no change, differing values indicate transitions.

Hardware Configuration

The experiment uses an active buzzer with the following schematic:

Button schematics reference the LED control implementation from previous sections.

Functional Requirements

Use KEY0 to control buzzer operation. Initially, the buzzer sounds continuously. Pressing the button stops the sound. Another press reactivates the buzzer.

System Architecture

The system consists of two modules: the button debouncing module and the buzzer control module.

Implementation Code

Debouncing Module

`timescale 1ns / 1ns

module button_filter #(
    parameter COUNTER_BITS = 20,
    parameter DEBOUNCE_TIME = 20'd100_0000,
    parameter IDLE_STATE = 1'b1
)(
    input clk,
    input reset_n,
    input raw_button,
    output reg filtered_button
);

reg [COUNTER_BITS-1:0] debounce_counter;
reg delayed_input;

always @(posedge clk) begin
    if (!reset_n)
        delayed_input <= IDLE_STATE;
    else
        delayed_input <= raw_button;
end

always @(posedge clk) begin
    if (!reset_n)
        debounce_counter <= 0;
    else if (delayed_input != raw_button)
        debounce_counter <= DEBOUNCE_TIME;
    else if (debounce_counter > 0)
        debounce_counter <= debounce_counter - 1;
    else
        debounce_counter <= 0;
end

always @(posedge clk) begin
    if (!reset_n)
        filtered_button <= IDLE_STATE;
    else if (debounce_counter == 1)
        filtered_button <= raw_button;
end

endmodule

Simulation Testbench for Debouncing

`timescale 1ns / 1ns

module tb_button_filter();

reg clk;
reg reset_n;
reg button_signal;
wire processed_output;

initial begin
    clk = 1'b0;
    reset_n = 1'b0;
    button_signal <= 1;
    #180
    reset_n = 1'b1;
    #2000

    // Press sequence with bouncing
    button_signal <= 0;
    #8
    button_signal <= 1;
    #8
    button_signal <= 0;
    #8
    button_signal <= 1;
    #2
    button_signal <= 0;
    #2
    button_signal <= 1;
    #20
    button_signal <= 0;

    #2000

    // Release sequence with bouncing
    button_signal <= 1;
    #8
    button_signal <= 0;
    #4
    button_signal <= 1;
    #3
    button_signal <= 0;
    #3
    button_signal <= 1;
end

always #10 clk = ~clk;

button_filter #(
    .COUNTER_BITS(20),
    .DEBOUNCE_TIME(25),
    .IDLE_STATE(1'b1)
) uut (
    .clk(clk),
    .reset_n(reset_n),
    .raw_button(button_signal),
    .filtered_button(processed_output)
);

endmodule

Buzzer Control Module

`timescale 1ns / 1ns

module buzzer_controller #(
    parameter BUZZER_INIT_STATE = 1'b1,
    parameter BUTTON_IDLE = 1'b1
)(
    input clk,
    input reset_n,
    input clean_button,
    output reg buzzer_out
);

reg prev_button_state;
wire button_fall_edge;

assign button_fall_edge = prev_button_state & (~clean_button);

always @(posedge clk) begin
    if (!reset_n)
        prev_button_state <= BUTTON_IDLE;
    else
        prev_button_state <= clean_button;
end

always @(posedge clk) begin
    if (!reset_n)
        buzzer_out <= BUZZER_INIT_STATE;
    else if (button_fall_edge)
        buzzer_out <= ~buzzer_out;
end

endmodule

Buzzer Simulation Testbench

`timescale 1ns / 1ns

module tb_buzzer_controller();

reg clk;
reg reset_n;
reg processed_button;
wire buzzer_signal;

initial begin
    clk = 1'b0;
    reset_n = 1'b0;
    processed_button <= 1;
    #200
    reset_n = 1'b1;
    #200

    processed_button <= 0;
    #2000

    processed_button <= 1;
    #2000

    processed_button <= 0;
end

always #10 clk = ~clk;

buzzer_controller #(
    .BUZZER_INIT_STATE(1'b1),
    .BUTTON_IDLE(1'b1)
) uut (
    .clk(clk),
    .reset_n(reset_n),
    .clean_button(processed_button),
    .buzzer_out(buzzer_signal)
);

endmodule

Top-Level Integration

`timescale 1ns / 1ns

module main_controller #(
    parameter BUZZER_INIT_STATE = 1'b1,
    parameter BUTTON_IDLE = 1'b1,
    parameter COUNTER_BITS = 20,
    parameter DEBOUNCE_TIME = 20'd100_0000
)(
    input clk,
    input reset_n,
    input external_button,
    output buzzer_out
);

wire processed_button;
reg input_delay_1, input_delay_2;

always @(posedge clk) begin
    if (!reset_n) begin
        input_delay_1 <= BUTTON_IDLE;
        input_delay_2 <= BUTTON_IDLE;
    end
    else begin
        input_delay_1 <= external_button;
        input_delay_2 <= input_delay_1;
    end
end

button_filter #(
    .COUNTER_BITS(COUNTER_BITS),
    .DEBOUNCE_TIME(DEBOUNCE_TIME),
    .IDLE_STATE(BUTTON_IDLE)
) debounce_inst (
    .clk(clk),
    .reset_n(reset_n),
    .raw_button(input_delay_2),
    .filtered_button(processed_button)
);

buzzer_controller #(
    .BUZZER_INIT_STATE(BUZZER_INIT_STATE),
    .BUTTON_IDLE(BUTTON_IDLE)
) buzzer_inst (
    .clk(clk),
    .reset_n(reset_n),
    .clean_button(processed_button),
    .buzzer_out(buzzer_out)
);

endmodule

Complete System Testbench

`timescale 1ns / 1ns

module tb_system();

reg clk;
reg reset_n;
reg input_btn;
wire buzzer_result;

initial begin
    clk = 1'b0;
    reset_n = 1'b0;
    input_btn <= 1;
    #180
    reset_n = 1'b1;
    
    #2000
    // Press with bouncing
    input_btn <= 0;
    #8
    input_btn <= 1;
    #8
    input_btn <= 0;
    #8
    input_btn <= 1;
    #2
    input_btn <= 0;
    #2
    input_btn <= 1;
    #20
    input_btn <= 0;
    #2000
    // Release with bouncing
    input_btn <= 1;
    #8
    input_btn <= 0;
    #4
    input_btn <= 1;
    #3
    input_btn <= 0;
    #3
    input_btn <= 1;

    #2000
    // Second press with bouncing
    input_btn <= 0;
    #18
    input_btn <= 1;
    #5
    input_btn <= 0;
    #5
    input_btn <= 1;
    #2
    input_btn <= 0;
    #2
    input_btn <= 1;
    #10
    input_btn <= 0;
    #3000
    // Final release with bouncing
    input_btn <= 1;
    #12
    input_btn <= 0;
    #4
    input_btn <= 1;
    #5
    input_btn <= 0;
    #15
    input_btn <= 1;
end

always #10 clk = ~clk;

main_controller #(
    .BUZZER_INIT_STATE(1'b1),
    .BUTTON_IDLE(1'b1),
    .COUNTER_BITS(20),
    .DEBOUNCE_TIME(25)
) system_inst (
    .clk(clk),
    .reset_n(reset_n),
    .external_button(input_btn),
    .buzzer_out(buzzer_result)
);

endmodule

Pin Constraints

Pin assignments:

XDC constraint file:

# Clock constraints
create_clock -period 20.000 -name clk [get_ports clk]
# IO pin constraints
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports reset_n]
set_property -dict {PACKAGE_PIN T4 IOSTANDARD LVCMOS15} [get_ports external_button]
set_property -dict {PACKAGE_PIN V7 IOSTANDARD LVCMOS15} [get_ports buzzer_out]

Tags: digital-design FPGA embedded-systems button-debouncing Verilog

Posted on Wed, 08 Jul 2026 17:36:30 +0000 by Stressed