Debounce Algorithm là gì?

Tại sao phím cơ học bounce và cách xử lý bằng counter-based debounce, timestamp debounce, scan buffer và state machine trong firmware.

Cập nhật 5 phút đọc
Keyboard cover

Phím cơ học bounce: khi nhấn, tiếp điểm đóng mở nhiều lần trong vài đến vài chục ms trước khi ổn định. Điều khó chịu của bounce là nó không sai theo kiểu ổn định — có phím bounce 2 ms, có phím 10 ms, và cùng một phím cũng có thể khác nhau theo lực nhấn.

Debounce là bước lọc bắt buộc giữa matrix scan và HID report. Không có debounce, bàn phím gửi ký tự lặp ngẫu nhiên và không dùng được.

1. Bounce trông như thế nào

Nhấn phím tại t=0ms:

GPIO ───┐  ┌─┐ ┌──┐  ┌────────────── HIGH (nhả)
        └──┘ └─┘  └──┘
        0  2  4  6  8  10ms

Không debounce: PRESS, RELEASE, PRESS, RELEASE, PRESS (5 event)
Có debounce:    PRESS (1 event sau khi tín hiệu ổn định)

2. Counter-based debounce

Cách đơn giản và phổ biến nhất: đếm số lần scan liên tiếp cùng trạng thái trước khi chấp nhận.

#define DEBOUNCE_COUNT  5   /* 5 scan × 1ms/scan = 5ms debounce */typedef struct {    uint8_t  stable;    uint8_t  counter;    uint8_t  raw_prev;} DebounceState_t;void Debounce_Update(DebounceState_t *d, uint8_t raw){    if (raw == d->raw_prev) {        if (d->counter < DEBOUNCE_COUNT) d->counter++;        if (d->counter == DEBOUNCE_COUNT) d->stable = raw;    } else {        d->counter  = 0;        d->raw_prev = raw;    }}

DEBOUNCE_COUNT = 5 với scan period 1 ms → debounce window 5 ms. Tăng lên 10–20 nếu switch bounce nhiều hơn.

3. Timestamp debounce

Thay vì đếm scan, dùng timestamp để đo thời gian ổn định. Tham số now_ms được truyền vào từ bên ngoài để giữ hàm portable:

#define DEBOUNCE_MS  10typedef struct {    uint8_t  stable;    uint8_t  raw_prev;    uint32_t change_tick;} DebounceTs_t;void DebounceTs_Update(DebounceTs_t *d, uint8_t raw, uint32_t now_ms){    if (raw != d->raw_prev) {        d->raw_prev    = raw;        d->change_tick = now_ms;    }    if ((now_ms - d->change_tick) >= DEBOUNCE_MS) {        d->stable = raw;    }}

Trên STM32, now_ms có thể lấy từ HAL_GetTick(). Trên RTOS hoặc platform khác, nó có thể đến từ system tick riêng.

Counter vs Timestamp debounce
Tiêu chí Counter Timestamp
Độ phức tạp Đơn giản Phức tạp hơn một chút
Debounce window Phụ thuộc scan period Cố định theo ms, không đổi
Scan period thay đổi Window bị thay đổi theo Không ảnh hưởng
RAM 1 byte counter/phím 4 byte tick/phím
Phù hợp với Scan period cố định Scan period không đều

4. Scan-buffer debounce

Một cách khác là giữ nhiều raw scan gần nhất rồi xác nhận trạng thái ổn định bằng phép AND/OR trên các buffer. Cách này hợp với keyboard matrix vì mỗi lần scan thường tạo ra một bitmap phím.

Ví dụ với scan period 5 ms và 4 buffer:

scan[0] = newest
scan[1] = previous
scan[2] = older
scan[3] = oldest

Normal path có thể dùng 2 buffer mới nhất:

stable_on  = scan[0] & scan[1]   /* phím được xác nhận nhấn */
stable_off = ~(scan[0] | scan[1]) /* phím được xác nhận nhả */

Recovery path hoặc trạng thái nghi ngờ có thể dùng cả 4 buffer để chắc hơn.

5. Tích hợp vào scan loop

uint8_t key_stable[NUM_ROWS][NUM_COLS]   = {0};uint8_t key_prev[NUM_ROWS][NUM_COLS]     = {0};uint8_t debounce_cnt[NUM_ROWS][NUM_COLS] = {0};uint8_t raw_prev[NUM_ROWS][NUM_COLS]     = {0};void Matrix_ScanAndDebounce(void){    Matrix_Scan();  /* cập nhật matrix_raw[r][c] */    for (uint8_t r = 0; r < NUM_ROWS; r++) {        for (uint8_t c = 0; c < NUM_COLS; c++) {            uint8_t raw = matrix_raw[r][c];            if (raw == raw_prev[r][c]) {                if (debounce_cnt[r][c] < DEBOUNCE_COUNT)                    debounce_cnt[r][c]++;                if (debounce_cnt[r][c] == DEBOUNCE_COUNT)                    key_stable[r][c] = raw;            } else {                debounce_cnt[r][c] = 0;                raw_prev[r][c]     = raw;            }            if (!key_prev[r][c] && key_stable[r][c]) OnKeyPress(r, c);            if ( key_prev[r][c] && !key_stable[r][c]) OnKeyRelease(r, c);            key_prev[r][c] = key_stable[r][c];        }    }}

6. Release debounce

Debounce cần áp dụng cho cả press lẫn release. Counter-based ở trên đã xử lý cả hai — counter reset mỗi khi raw state thay đổi dù là 0→1 hay 1→0.

Một số firmware dùng debounce window khác nhau cho press và release để tối ưu cảm giác gõ. Tuy nhiên điều này phụ thuộc loại switch và yêu cầu sản phẩm. Nếu chưa đo bounce thực tế, dùng cùng window cho press và release là lựa chọn an toàn hơn.

7. Key repeat

Sau khi đã lọc được press/release sạch, vẫn còn một câu hỏi khác: nếu người dùng giữ phím thì firmware nên làm gì?

Debounce xử lý bounce nhưng không xử lý key repeat. Key repeat là logic riêng ở tầng trên, dùng counter đếm thời gian giữ phím sau khi đã debounce xong.

Debounce  → lọc bounce vật lý, output: press/release event
Key Repeat → đếm thời gian giữ, output: repeat event mỗi N ms

Bài liên quan

Nguồn tham khảo public

Bài viết này hữu ích với bạn?

Chia sẻ, góp ý, hoặc ủng hộ nếu bạn thấy nội dung này có giá trị.

Góp ý

Nội dung liên quan

Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.

Biến note thành bài viết hoàn chỉnh

Notes là nơi ghi nhanh khái niệm.