Knock sensor và passive buzzer dùng cùng loại vật liệu piezoelectric — nhưng theo chiều ngược nhau. Passive buzzer: điện áp → rung âm thanh. Knock sensor: rung cơ học → điện áp. Một tấm ceramic mỏng, hai hướng sử dụng hoàn toàn khác nhau.
Nguyên Lý Hoạt Động
1. Hiệu Ứng Piezoelectric Thuận
Hiệu ứng piezoelectric thuận (direct piezoelectric effect) — khác với chiều ngược trong passive buzzer:
Chiều ngược (Passive Buzzer):
Điện áp AC → Tấm PZT uốn cong → Âm thanh (khuếch tán không khí)
Chiều thuận (Knock Sensor):
Lực va đập → Tấm PZT bị ứng suất → Phân ly điện tích → Điện áp đầu ra
Lý thuyết:
Lực (F) → Ứng suất (σ = F/A) → Điện tích (Q = d × σ × A)
Điện áp: V = Q/C = d × σ × A / C
Trong đó: d = hệ số piezo (PZT-5A: d₃₃ ≈ 380 pC/N)
Thực tế: Gõ nhẹ tay vào bàn → xung điện áp ngắn vài mV đến vài volt tùy cường độ gõ và loại piezo.
2. Cấu Tạo Piezo Knock Sensor
Mặt cắt tấm piezo:
┌──────────────────┐ ← Điện cực trên (bạc)
│ PZT Ceramic │ ← Vật liệu áp điện (1-5mm)
└──────────────────┘ ← Điện cực dưới (đồng/bạc)
│ │
(+) (-) khi gõ lên mặt trên
Đặc điểm điện:
- Trở kháng rất cao: vài MΩ (giống tụ điện)
- Cần pull-down resistor (thường 1MΩ) để giữ tín hiệu ở 0V khi không có va đập
- Xung điện áp rất ngắn (~1-10ms) và không đối xứng
- Biên độ xung tỉ lệ với cường độ va đập
3. Module KY-031 — Với Comparator
Module KY-031 thêm mạch xử lý tín hiệu:
KY-031 Block Diagram:
[Piezo Disc]
│ (xung vài mV-V khi gõ)
│
[1MΩ pull-down] ← Giữ baseline = 0V
│
AOUT ─────────────────────────→ Tín hiệu analog thô
│
[LM393 Comparator]
(+) ← Tín hiệu piezo
(-) ← Trimmer ngưỡng
│
DOUT ─────────────────────────→ HIGH khi vượt ngưỡng
Thông Số Kỹ Thuật
| Thông số | Giá trị |
|---|---|
| Điện áp hoạt động | 3.3V – 5V |
| Dòng tiêu thụ | <1mA |
| Output thô | Xung analog vài mV đến vài V |
| Output sau comparator | Digital HIGH khi vượt ngưỡng |
| Độ nhạy | Điều chỉnh qua trimmer |
| Tần số đáp ứng | DC đến vài kHz |
Sơ Đồ Chân (Pinout)
KY-031 / Knock Sensor Module:
┌────────────────────────────────┐
│ [Piezo Disc] [Trimmer] │
│ [LM393] [LED] │
└────────────────────────────────┘
GND VCC AOUT (tùy module) DOUT/S
| Chân | Ký hiệu | Mô tả |
|---|---|---|
| GND | – | Mass |
| VCC | + | Nguồn 3.3V-5V |
| S / DOUT | D0 | Digital — HIGH khi phát hiện gõ |
| AOUT | A0 | Analog — tín hiệu raw (nếu có) |
Lưu ý module khác nhau: Một số module chỉ có 3 chân (GND, VCC, S). Một số có thêm AOUT. S = DOUT từ comparator.
Kết Nối Phần Cứng
Module với ESP32 DevKit V1
ESP32 DevKit V1 Knock Sensor Module
───────────────────── ─────────────────
3V3 ─────────────────→ VCC
GND ─────────────────→ GND
GPIO4 (Input) ─────────→ S / DOUT ← Digital output
GPIO34 (ADC) ──────────→ AOUT ← Nếu module có AOUT
Module với Arduino Uno
Arduino Uno Knock Sensor Module
───────────────────── ─────────────────
5V ─────────────────→ VCC
GND ─────────────────→ GND
Pin 2 (INT0) ─────────→ S / DOUT ← Interrupt pin
A0 (Analog) ─────────→ AOUT ← Nếu module có AOUT
Code Arduino IDE
Code Phát Hiện Gõ Cơ Bản — Arduino Uno
/*
* Knock Sensor KY-031 — Phát hiện gõ với interrupt
* Board: Arduino Uno
* Kết nối: VCC→5V, GND→GND, S→Pin2 (INT0)
*
* DOUT: HIGH khi có gõ (active HIGH — khác với nhiều sensor khác!)
* Interrupt trên RISING edge: LOW→HIGH khi bắt đầu gõ
*
* Điều chỉnh trimmer cho đến khi LED trên module sáng khi gõ,
* tắt khi không gõ.
*/
const int KNOCK_PIN = 2;
const int LED_PIN = 13;
volatile int knockCount = 0;
volatile bool knockDetected = false;
// ISR — gọi khi phát hiện gõ (RISING edge)
void onKnock() {
knockCount++;
knockDetected = true;
}
void setup() {
Serial.begin(9600);
pinMode(KNOCK_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
// RISING: LOW→HIGH khi có gõ
attachInterrupt(digitalPinToInterrupt(KNOCK_PIN), onKnock, RISING);
Serial.println("=== Knock Sensor KY-031 ===");
Serial.println("Gõ vào bàn để test...");
}
unsigned long lastKnockTime = 0;
void loop() {
if (knockDetected) {
knockDetected = false;
unsigned long now = millis();
// Debounce 150ms — bỏ qua rung phụ
if (now - lastKnockTime > 150) {
lastKnockTime = now;
Serial.print("GÕ! Lần thứ: ");
Serial.println(knockCount);
// Blink LED khi phát hiện gõ thật
digitalWrite(LED_PIN, HIGH); delay(50);
digitalWrite(LED_PIN, LOW);
}
}
}
Code Đếm Số Lần Gõ Trong Cửa Sổ — Arduino Uno
/*
* Knock Sensor — Đếm số lần gõ trong 2 giây
* Ứng dụng: gõ 2 lần mở cửa, gõ 3 lần tắt đèn
* Board: Arduino Uno
* Kết nối: VCC→5V, GND→GND, S→Pin2
*/
const int KNOCK_PIN = 2;
const int LED_OK = 9; // LED xanh — đúng pattern
const int LED_FAIL = 10; // LED đỏ — sai pattern
// Cấu hình
const int TARGET_KNOCKS = 3; // Cần gõ đúng 3 lần
const unsigned long KNOCK_WINDOW_MS = 2000; // Cửa sổ 2 giây
volatile int knockCount = 0;
bool listening = false;
unsigned long firstKnockTime = 0;
void onKnock() {
unsigned long now = millis();
// Debounce 100ms
static unsigned long lastISRTime = 0;
if (now - lastISRTime < 100) return;
lastISRTime = now;
if (!listening) {
// Lần gõ đầu tiên → bắt đầu đếm
listening = true;
firstKnockTime = now;
knockCount = 1;
} else {
knockCount++;
}
}
void setup() {
Serial.begin(9600);
pinMode(KNOCK_PIN, INPUT);
pinMode(LED_OK, OUTPUT);
pinMode(LED_FAIL, OUTPUT);
attachInterrupt(digitalPinToInterrupt(KNOCK_PIN), onKnock, RISING);
Serial.println("Gõ cửa: 3 lần trong 2 giây");
}
void loop() {
if (listening) {
unsigned long elapsed = millis() - firstKnockTime;
if (elapsed > KNOCK_WINDOW_MS) {
// Hết cửa sổ → kiểm tra số lần gõ
listening = false;
Serial.print("Đã gõ: ");
Serial.print(knockCount);
Serial.print(" lần. ");
if (knockCount == TARGET_KNOCKS) {
Serial.println("ĐÚNG! Mở khóa.");
digitalWrite(LED_OK, HIGH); delay(1000); digitalWrite(LED_OK, LOW);
} else {
Serial.print("SAI! Cần ");
Serial.print(TARGET_KNOCKS); Serial.println(" lần.");
digitalWrite(LED_FAIL, HIGH); delay(500); digitalWrite(LED_FAIL, LOW);
}
knockCount = 0;
}
}
}
Code Đọc AOUT Analog — Arduino Uno (Nếu Module Có AOUT)
/*
* Knock Sensor — Đọc tín hiệu analog thô từ piezo
* Thấy xung điện áp khi gõ
* Board: Arduino Uno
* Kết nối: VCC→5V, GND→GND, AOUT→A0
*/
const int AOUT_PIN = A0;
const int THRESHOLD = 100; // Ngưỡng phát hiện (0-1023)
int peakValue = 0;
unsigned long lastPeakTime = 0;
void setup() {
Serial.begin(9600);
Serial.println("=== Knock Sensor Analog ===");
Serial.println("Gõ mạnh / nhẹ để so sánh biên độ");
}
void loop() {
int val = analogRead(AOUT_PIN);
// Theo dõi peak trong 50ms
if (val > peakValue) peakValue = val;
if (millis() - lastPeakTime > 50) {
lastPeakTime = millis();
if (peakValue > THRESHOLD) {
Serial.print("GÕ! Biên độ: ");
Serial.print(peakValue);
// Phân loại độ mạnh
if (peakValue < 200) Serial.println(" → Nhẹ");
else if (peakValue < 500) Serial.println(" → Vừa");
else Serial.println(" → MẠNH");
}
peakValue = 0; // Reset peak
}
}
Code ESP32 — Secret Knock (Pattern Gõ Bí Mật)
/*
* Knock Sensor — ESP32, nhận dạng pattern gõ bí mật
* Pattern: NGẮN - DÀI - NGẮN - NGẮN (như "knock-knock")
* Board: ESP32 DevKit V1
* Kết nối: VCC→3V3, GND→GND, S→GPIO4
*/
const int KNOCK_PIN = 4;
const int LED_PIN = 5;
// Pattern mục tiêu: khoảng cách giữa các lần gõ (ms)
// Gõ1 → wait 200ms → Gõ2 → wait 600ms → Gõ3 → wait 200ms → Gõ4
const int PATTERN_SIZE = 3; // 4 lần gõ = 3 khoảng cách
const int PATTERN_GAPS[3] = {250, 600, 250}; // ms
const int TOLERANCE = 200; // ±200ms chấp nhận
unsigned long knockTimes[10];
int knockCount = 0;
bool collecting = false;
unsigned long lastKnockTime = 0;
volatile bool newKnock = false;
void IRAM_ATTR onKnockISR() {
static unsigned long lastISR = 0;
unsigned long now = millis();
if (now - lastISR > 100) { // Debounce
lastISR = now;
newKnock = true;
}
}
bool checkPattern() {
if (knockCount != PATTERN_SIZE + 1) return false; // Cần đúng số lần gõ
for (int i = 0; i < PATTERN_SIZE; i++) {
int gap = knockTimes[i + 1] - knockTimes[i];
if (abs(gap - PATTERN_GAPS[i]) > TOLERANCE) return false;
}
return true;
}
void setup() {
Serial.begin(115200);
pinMode(KNOCK_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(KNOCK_PIN), onKnockISR, RISING);
Serial.println("=== Secret Knock ESP32 ===");
Serial.println("Pattern: NGẮN - DÀI - NGẮN - NGẮN (gõ 4 lần)");
}
void loop() {
if (newKnock) {
newKnock = false;
unsigned long now = millis();
if (!collecting) {
collecting = true;
knockCount = 0;
}
if (knockCount < 10) {
knockTimes[knockCount++] = now;
Serial.printf("Gõ #%d tại %lu ms\n", knockCount, now);
}
lastKnockTime = now;
}
// Timeout: 1.5 giây sau lần gõ cuối → kiểm tra
if (collecting && (millis() - lastKnockTime > 1500)) {
collecting = false;
Serial.printf("Nhận được %d lần gõ\n", knockCount);
if (checkPattern()) {
Serial.println("✓ ĐÚNG PATTERN! Mở khóa...");
// Blink 3 lần xanh
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, HIGH); delay(200);
digitalWrite(LED_PIN, LOW); delay(100);
}
} else {
Serial.println("✗ Sai pattern. Thử lại.");
// Blink 1 lần đỏ (dùng LED_PIN đơn)
for (int i = 0; i < 5; i++) {
digitalWrite(LED_PIN, HIGH); delay(50);
digitalWrite(LED_PIN, LOW); delay(50);
}
}
knockCount = 0;
}
}
Kết Quả Mong Đợi
=== Knock Sensor KY-031 ===
Gõ vào bàn để test...
GÕ! Lần thứ: 1
GÕ! Lần thứ: 2
GÕ! Lần thứ: 3
Ứng Dụng Thực Tế
| Ứng dụng | Chi tiết |
|---|---|
| Khóa cửa gõ bí mật | Pattern gõ đặc biệt → mở khóa |
| Nhận diện gõ bàn | Phân biệt gõ nhẹ / mạnh / số lần |
| Nhạc cụ điện tử | Gõ tạo tiếng trống/nhạc theo nhịp |
| Anti-theft | Gõ vào đồ vật bảo vệ → báo động |
| Điều khiển cử chỉ | Gõ 2 lần → next song |
Lưu Ý Khi Sử Dụng
1. Xung ngắn — bắt buộc interrupt
Xung piezo kéo dài 5-50ms. loop() có thể bỏ sót nếu có delay hoặc code nặng. Dùng interrupt để đảm bảo không mất gõ.
2. Rung phụ sau va đập — debounce quan trọng
Một lần gõ cứng tạo ra nhiều xung phụ (ringing) kéo dài. Debounce 100-200ms là cần thiết. Chỉnh trimmer để giảm độ nhạy nếu vẫn còn nhiều false trigger.
3. Chỉnh trimmer trước khi code
Trimmer quá nhạy → môi trường rung (xe cộ, người đi) gây trigger liên tục. Quá kém → không phát hiện được. Chỉnh đến khi: gõ nhẹ 1 lần = 1 trigger, môi trường bình thường = không trigger.
4. Phân biệt với vibration switch SW-18010P (Bài 25)
SW-18010P: spring contact cơ học, kích hoạt bằng rung liên tục nhỏ. Knock sensor: piezo, kích hoạt bằng va đập mạnh tức thời. Chọn đúng loại cho ứng dụng.


