IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series 37 Module Cảm Biến – Nguyên Lý Knock Sensor KY-031: Piezoelectric Thuận, Va Đập → Điện Áp & Nhận Dạng Pattern

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 động3.3V – 5V
Dòng tiêu thụ<1mA
Output thôXung analog vài mV đến vài V
Output sau comparatorDigital HIGH khi vượt ngưỡng
Độ nhạyĐiều chỉnh qua trimmer
Tần số đáp ứngDC đế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ânKý hiệuMô tả
GNDMass
VCC+Nguồn 3.3V-5V
S / DOUTD0Digital — HIGH khi phát hiện gõ
AOUTA0Analog — 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ụngChi tiết
Khóa cửa gõ bí mậtPattern gõ đặc biệt → mở khóa
Nhận diện gõ bànPhâ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-theftGõ 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.

Bài tiếp theo: