3.11. Khử rung (Debouncing)¶
Một công tắc được vẽ như một tiếp điểm mở hoặc đóng hoàn hảo, nhưng tiếp điểm của công tắc thực tế không chuyển đổi sạch sẽ giữa hai trạng thái. Chúng rung -- đóng ngắt tiếp xúc điện nhiều lần trong vài mili giây trước khi ổn định. Một GPIO input đọc chân (pin) nhìn thấy đó như một loạt cạnh; vòng lặp kiểm tra không cẩn thận đếm nhiều lần "nhấn" cho một lần nhấn thực, và trình xử lý ngắt chạy nhiều lần cho mỗi lần nhấn thực.
Một công tắc đang rung tạo ra một loạt chuyển đổi nhanh trước khi ổn định.¶
Khử rung là thực hành lọc tiếng rung để mỗi lần nhấn vật lý chỉ được ghi nhận là một sự kiện duy nhất. Hai phương pháp giải quyết vấn đề này -- phần mềm (một quy tắc thời gian trong firmware) hoặc phần cứng (một bộ lọc nhỏ trên dây). Chúng không loại trừ lẫn nhau.
3.11.1. Khử rung bằng phần mềm¶
Ý tưởng là ghi nhớ khi ngõ vào lần cuối thay đổi và từ chối các thay đổi tiếp theo trong một khoảng thời gian ngắn kể từ dấu thời gian đó. Rung tiếp điểm thường kéo dài dưới 10 ms; một lần nhấn thực mất 50 -- 100 ms; một cửa sổ 30 -- 50 ms bắt được tất cả các rung mà không chặn các lần nhấn thực.
Trong vòng lặp kiểm tra, đọc chân (pin), so sánh với giá trị ổn định cuối cùng, và chỉ chấp nhận một thay đổi sau khi cửa sổ khử rung đã trôi qua:
import time
from machine import Pin
button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_state = 1
last_change = 0
DEBOUNCE_MS = 50
while True:
now = time.ticks_ms()
state = button.value()
if state != last_state and time.ticks_diff(now, last_change) > DEBOUNCE_MS:
last_change = now
last_state = state
if state == 0:
do_action()
time.sleep_ms(10)
Đối với các lần đọc theo ngắt, áp dụng cùng quy tắc thời gian bên trong trình xử lý, sau đó chuyển lần nhấn thực sang ngữ cảnh chính thông qua micropython.schedule() (xem GPIO input):
import time
import micropython
from machine import Pin
button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_irq = 0
DEBOUNCE_MS = 50
def handle_press(pin):
do_action()
def on_press(pin):
global last_irq
now = time.ticks_ms()
if time.ticks_diff(now, last_irq) < DEBOUNCE_MS:
return
last_irq = now
micropython.schedule(handle_press, pin)
button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)
ISR lọc các rung theo dấu thời gian và xếp hàng hàm gọi lại; handle_press chạy lại trong ngữ cảnh chính, nơi việc cấp phát và I/O chậm đều an toàn.
3.11.2. Khử rung bằng phần cứng¶
Khử rung bằng phần cứng lọc tiếng rung về mặt điện, trước khi nó đến được chân (pin). Công cụ tiêu chuẩn là tụ điện.
Tụ điện là linh kiện hai cực lưu trữ điện tích. Về mặt vật lý, nó gồm hai tấm dẫn điện được giữ cách nhau một khoảng ngắn, ngăn cách bởi một chất cách điện (điện môi).
Tụ điện tấm phẳng song song: hai dây dẫn ngăn cách bởi một lớp cách điện.¶
Đặt một điện áp vào hai đầu cực của nó sẽ đẩy các điện tích bằng nhau và ngược chiều lên hai tấm; mối quan hệ là
Q = C × V
trong đó Q là điện tích lưu trữ (coulomb), V là điện áp trên tụ điện, và C là điện dung của nó (farad). Điện dung được cố định bởi cấu trúc của thiết bị; điện dung lớn hơn có nghĩa là lưu trữ nhiều điện tích hơn ở cùng điện áp.
Hệ quả: tụ điện không thể thay đổi điện áp của nó ngay lập tức. Điện tích chảy vào hay ra phải đi qua bất kỳ điện trở nào trên đường đi, và điện trở đó quyết định tốc độ thay đổi điện áp.
3.11.2.1. Hằng số thời gian RC¶
Sạc tụ điện qua một điện trở tạo ra sự tăng theo hàm mũ mượt mà về phía điện áp nguồn, chứ không phải một bước nhảy. Thời gian đặc trưng của sự tăng đó là hằng số thời gian RC:
τ = R × C
Sau một τ, tụ điện đã đạt khoảng 63 % điện áp nguồn. Sau 5 τ, nó đạt hơn 99 % -- "sạc đầy" cho các mục đích thực tế.
Tụ điện sạc theo đường cong hàm mũ. τ = RC là thời gian để đạt 63 % điện áp cuối cùng.¶
Phóng điện qua một điện trở đi theo hình ảnh gương: điện áp giảm theo hàm mũ từ giá trị ban đầu về không, giảm xuống 37 % điện áp ban đầu sau một τ, và xuống dưới 1 % sau 5 τ.
Tụ điện phóng điện theo sự giảm theo hàm mũ. τ = RC là thời gian để giảm xuống 37 % điện áp ban đầu.¶
3.11.2.2. Mạch khử rung¶
Một tụ điện giữa chân (pin) ngõ vào và đất, được cấp qua một điện trở nối tiếp, tạo thành một bộ lọc thông thấp. Các đột biến nhanh không có thời gian sạc hoặc phóng điện tụ điện qua điện trở đó; chân (pin) ở gần với bất kỳ điện áp nào trước đột biến. Các thay đổi chậm -- một lần nhấn có chủ ý -- sạc hoặc phóng điện tụ điện và giá trị đọc theo sau.
R1 kéo phía cao của công tắc lên Vcc, tạo ra tín hiệu công tắc thô bị rung. R2 và C sau đó lọc thông thấp tín hiệu đó vào chân (pin):
Khử rung phần cứng: R2 và C lọc thông thấp tín hiệu công tắc thô trước khi nó đến chân (pin).¶
Giá trị điển hình: R1 = 10 kΩ (kéo lên), R2 = 10 kΩ (nối tiếp), C = 100 nF.
Khi công tắc mở, dòng điện chạy Vcc → R1 → R2 → tụ điện (nối tiếp), sạc tụ điện lên Vcc với τ_charge = (R1 + R2) × C = 2 ms.
Khi công tắc đóng, nút công tắc kẹp xuống đất và tụ điện xả qua R2 đơn lẻ về đất đó với τ_discharge = R2 × C = 1 ms.
Cả hai cạnh đều được lọc RC. Vì tụ điện nằm trên nút riêng của nó, hạ nguồn từ R2 so với công tắc, nó dao động sạch sẽ giữa Vcc (mở) và 0 V (đóng) -- không có dòng điện phải chạy qua R1 ở trạng thái ổn định trong cả hai trường hợp.
3.11.3. Lựa chọn giữa hai phương pháp¶
Phần mềm là lựa chọn mặc định. Nó không tốn gì về linh kiện, ngưỡng dễ điều chỉnh, và nó hoạt động trên bất kỳ chân (pin) nào mà CPU đọc.
Phần cứng đáng đầu tư linh kiện khi rung ảnh hưởng đến thứ gì đó không phải là code kiểm tra của CPU -- một ngắt không được kích hoạt hai lần, một bộ đếm phần cứng, một ngoại vi không có bộ lọc riêng.
Khử rung phần mềm và phần cứng cũng cùng tồn tại hòa bình: bộ lọc RC nhỏ triệt tiêu các đột biến tệ nhất, và cửa sổ khử rung phần mềm bao phủ những gì còn lại.