IoTLabs

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

Series 37 Module Cảm Biến – Nguyên Lý Vibration Switch SW-18010P: Lò Xo Dẫn Điện, Phát Hiện Rung & Pattern Gõ

Vibration switch SW-18010P phát hiện rung chấn, không phải nghiêng. Bên trong là lò xo kim loại bao quanh trụ dẫn điện — rung làm lò xo dao động, chạm vào trụ tạo ra các xung điện cực ngắn (micro giây đến mili giây). Phải dùng interrupt — polling sẽ bỏ sót toàn bộ sự kiện.

Nguyên Lý Hoạt Động

1. Cơ Chế Lò Xo Dẫn Điện

SW-18010P chứa một lò xo xoắn kim loại bao quanh trụ dẫn điện trung tâm:

SW-18010P — Cắt dọc:

                    Vỏ kim loại
    ┌───────────────────────────────┐
    │                               │
    │   ╔═══╗  Lò xo (spring)      │
    │   ║   ║                       │ ← Lò xo = terminal 1
    │   ║ ● ║  Trụ trung tâm       │ ← Trụ = terminal 2
    │   ║   ║                       │
    │   ╚═══╝                       │
    │                               │
    └───────────────────────────────┘
     Pin 1              Pin 2
   (lò xo)         (trụ trung tâm)

Khi TĨNH: lò xo không chạm trụ → HỞ mạch
Khi RUNG: lò xo dao động, chạm trụ trong tích tắc → ĐÓNG mạch → xung ngắn

Thời gian xung: Mỗi lần chạm thường chỉ kéo dài 50μs – 5ms — cực ngắn. digitalRead() trong loop() gần như chắc chắn bỏ sót. Bắt buộc dùng interrupt.

2. Độ Nhạy Rất Cao

SW-18010P nhạy hơn nhiều so với tilt switch:

  • Tilt switch (SW-520D): chỉ phát hiện khi nghiêng đủ để bi lăn
  • Vibration switch: chỉ cần chạm nhẹ vào bàn cũng đủ để lò xo dao động

Ứng dụng phù hợp: Phát hiện gõ cửa, chấn động, va đập nhẹ, rung từ máy móc, thay vì phát hiện hướng.

3. Module KY-002

Một số kit dùng KY-002 thay SW-18010P:

  • KY-002: có IC khuếch đại + tụ lọc → ít bounce hơn
  • SW-18010P bare: bounce nhiều hơn nhưng độ nhạy cao hơn

Cả hai dùng code giống nhau (interrupt + debounce).

Thông Số Kỹ Thuật

Thông sốGiá trị
Điện áp tối đa12V
Dòng tối đa30mA
LoạiCơ học (spring contact)
Thời gian xung50μs – 5ms
Độ nhạyRất cao (nhạy với rung nhỏ)
OutputDigital (ON/OFF), active LOW với pull-up

Sơ Đồ Chân (Pinout)

Module cảm biến rung — Nhìn từ mặt trước:

┌──────────────────────────────────┐
│   [SW-18010P hoặc KY-002]        │
│   [Pull-up 10kΩ]                 │
│   [LED trạng thái]               │
└──────────────────────────────────┘
   GND   VCC   S
   (-)  (3.3-5V) (Signal OUT)
ChânKý hiệuMô tả
GNDMass
VCC+Nguồn 3.3V-5V
SSignalOutput — LOW khi phát hiện rung (active LOW)

Logic output: Module có pull-up → khi yên tĩnh S = HIGH, khi rung tạo xung LOW ngắn.

Kết Nối Phần Cứng

Module với ESP32 DevKit V1

ESP32 DevKit V1           Vibration Module
─────────────────────     ─────────────────
3V3  ─────────────────→  VCC
GND  ─────────────────→  GND
GPIO4 (Input) ─────────→  S

GPIO4: interrupt-capable, không phải strapping pin.

Module với Arduino Uno

Arduino Uno               Vibration Module
─────────────────────     ─────────────────
5V   ─────────────────→  VCC
GND  ─────────────────→  GND
Pin 2 (INT0) ─────────→  S    ← Bắt buộc dùng interrupt pin

Không dùng Pin khác — xung quá ngắn, chỉ Pin 2 (INT0) và Pin 3 (INT1) có hardware interrupt trên Uno.

Code Arduino IDE

Code Phát Hiện Rung Cơ Bản — Arduino Uno

/*
 * Vibration Switch SW-18010P — Interrupt phát hiện rung
 * Board: Arduino Uno
 * Kết nối: VCC→5V, GND→GND, S→Pin2 (INT0)
 *
 * QUAN TRỌNG: Xung rung rất ngắn (~50μs-5ms)
 * Bắt buộc dùng interrupt — không dùng digitalRead() trong loop()
 *
 * active LOW: xung xuống LOW khi phát hiện rung
 */

const int VIB_PIN = 2;
const int LED_PIN = 13;

volatile int vibrationCount = 0;  // Đếm số xung rung
volatile bool vibDetected   = false;

// ISR — gọi khi có xung LOW (rung)
void onVibration() {
  vibrationCount++;
  vibDetected = true;
}

void setup() {
  Serial.begin(9600);
  pinMode(VIB_PIN, INPUT);  // Module đã có pull-up
  pinMode(LED_PIN, OUTPUT);

  // Trigger trên FALLING edge: HIGH→LOW khi rung
  attachInterrupt(digitalPinToInterrupt(VIB_PIN), onVibration, FALLING);

  Serial.println("=== Vibration Switch SW-18010P ===");
  Serial.println("Gõ nhẹ vào bàn để test...");
}

void loop() {
  if (vibDetected) {
    vibDetected = false;
    digitalWrite(LED_PIN, HIGH);  // Sáng khi phát hiện rung

    Serial.print("RUN! Tổng xung: ");
    Serial.println(vibrationCount);

    delay(200); // Giữ LED sáng 200ms để nhìn thấy
    digitalWrite(LED_PIN, LOW);
  }
}

Code Đếm Rung Trong Cửa Sổ Thời Gian — Arduino Uno

/*
 * Vibration Switch — Đếm xung rung trong 1 giây
 * Ứng dụng: phân biệt rung nhẹ vs rung mạnh
 * Board: Arduino Uno
 * Kết nối: VCC→5V, GND→GND, S→Pin2
 */

const int VIB_PIN = 2;
const int LED_PIN = 13;
const unsigned long WINDOW_MS = 1000; // Cửa sổ 1 giây

volatile int pulseCount = 0;
unsigned long windowStart = 0;

void onVibration() {
  pulseCount++;
}

void setup() {
  Serial.begin(9600);
  pinMode(VIB_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(VIB_PIN), onVibration, FALLING);
  windowStart = millis();
  Serial.println("Vibration Rate Monitor (xung/giây)");
  Serial.println("Gõ nhẹ / mạnh / rung để xem sự khác biệt");
}

void loop() {
  if (millis() - windowStart >= WINDOW_MS) {
    // Tắt interrupt tạm để đọc pulseCount an toàn
    noInterrupts();
    int count = pulseCount;
    pulseCount = 0;
    interrupts();

    windowStart = millis();

    // Phân loại mức rung
    String level;
    if (count == 0)        level = "Yên tĩnh";
    else if (count < 5)    level = "Rung nhẹ";
    else if (count < 20)   level = "Rung vừa";
    else                   level = "Rung MẠNH";

    // Hiển thị bar chart ngắn
    Serial.print(count);
    Serial.print(" xung/s → ");
    Serial.print(level);
    Serial.print(" [");
    int barLen = min(count, 30);
    for (int i = 0; i < barLen; i++) Serial.print("█");
    Serial.println("]");

    // LED sáng tỉ lệ với độ rung
    digitalWrite(LED_PIN, count > 2 ? HIGH : LOW);
  }
}

Code Secret Knock — Nhận Dạng Pattern Gõ Bí Mật (Arduino Uno)

/*
 * Vibration Switch — Secret Knock: nhận dạng pattern gõ
 * Gõ đúng 3 tiếng (ngắn - dài - ngắn) trong 2 giây → unlock
 * Board: Arduino Uno
 * Kết nối: VCC→5V, GND→GND, S→Pin2, LED xanh→Pin9, LED đỏ→Pin10
 */

const int VIB_PIN    = 2;
const int LED_OK     = 9;   // Xanh — unlock thành công
const int LED_FAIL   = 10;  // Đỏ — sai pattern

// Pattern mục tiêu: 3 gõ với khoảng cách
// Gõ 1, chờ ~200ms, Gõ 2, chờ ~600ms, Gõ 3
const int PATTERN_KNOCKS = 3;
const int PATTERN_TIMING[] = {0, 250, 650}; // ms sau lần gõ đầu tiên
const int TIMING_TOLERANCE = 150; // ±150ms chấp nhận được

volatile bool knockDetected = false;
unsigned long knockTimes[10]; // Lưu thời điểm mỗi tiếng gõ
int knockCount = 0;
unsigned long lastKnockTime = 0;
bool listening = false;

void onKnock() {
  if (!listening) return;

  unsigned long now = millis();
  // Debounce 80ms
  if (now - lastKnockTime > 80) {
    lastKnockTime = now;
    knockDetected = true;
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(VIB_PIN, INPUT);
  pinMode(LED_OK, OUTPUT);
  pinMode(LED_FAIL, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(VIB_PIN), onKnock, FALLING);
  Serial.println("=== Secret Knock System ===");
  Serial.println("Pattern: GÕ - chờ 250ms - GÕ - chờ 650ms - GÕ");
  startListening();
}

void startListening() {
  knockCount = 0;
  listening = true;
  Serial.println("\nĐang chờ gõ cửa...");
}

bool checkPattern() {
  if (knockCount != PATTERN_KNOCKS) return false;

  for (int i = 1; i < knockCount; i++) {
    int actualGap = knockTimes[i] - knockTimes[0];
    int targetGap = PATTERN_TIMING[i];
    if (abs(actualGap - targetGap) > TIMING_TOLERANCE) return false;
  }
  return true;
}

void loop() {
  if (knockDetected && listening) {
    knockDetected = false;

    if (knockCount < 10) {
      knockTimes[knockCount++] = millis();
      Serial.print("Gõ #"); Serial.println(knockCount);
    }
  }

  // Timeout: 2.5 giây sau lần gõ cuối → kiểm tra pattern
  if (knockCount > 0 && millis() - knockTimes[knockCount-1] > 2500) {
    listening = false;

    if (checkPattern()) {
      Serial.println("✓ ĐÚNG! Mở khóa...");
      for (int i = 0; i < 3; i++) {
        digitalWrite(LED_OK, HIGH); delay(200);
        digitalWrite(LED_OK, LOW);  delay(100);
      }
    } else {
      Serial.println("✗ SAI pattern. Thử lại.");
      digitalWrite(LED_FAIL, HIGH); delay(1000);
      digitalWrite(LED_FAIL, LOW);
    }

    delay(500);
    startListening();
  }
}

Code ESP32 — Vibration Alarm Non-Blocking

/*
 * Vibration Switch — ESP32, báo động rung non-blocking
 * Board: ESP32 DevKit V1
 * Kết nối: VCC→3V3, GND→GND, S→GPIO4
 * Buzzer→GPIO5 (active buzzer)
 */

const int VIB_PIN    = 4;
const int BUZZER_PIN = 5;

// Ngưỡng: cần ≥ N xung trong X giây mới coi là "rung thật"
const int MIN_PULSES = 3;
const unsigned long DETECT_WINDOW_MS = 500;

volatile int pulseCount = 0;
unsigned long windowStart = 0;
bool alarmOn = false;
unsigned long alarmStartTime = 0;
const unsigned long ALARM_DURATION_MS = 5000; // Báo 5 giây

void IRAM_ATTR onVibISR() {
  pulseCount++;
}

void setup() {
  Serial.begin(115200);
  pinMode(VIB_PIN,    INPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);
  attachInterrupt(digitalPinToInterrupt(VIB_PIN), onVibISR, FALLING);
  windowStart = millis();
  Serial.println("=== Vibration Alarm ESP32 ===");
}

void loop() {
  unsigned long now = millis();

  // Kiểm tra cửa sổ thời gian
  if (now - windowStart >= DETECT_WINDOW_MS) {
    noInterrupts();
    int count = pulseCount;
    pulseCount = 0;
    interrupts();
    windowStart = now;

    // Nếu đủ xung → kích hoạt alarm
    if (count >= MIN_PULSES && !alarmOn) {
      alarmOn = true;
      alarmStartTime = now;
      Serial.printf("PHÁT HIỆN RUNG! %d xung/0.5s → Báo động\n", count);
    }
  }

  // Quản lý buzzer alarm
  if (alarmOn) {
    if (now - alarmStartTime < ALARM_DURATION_MS) {
      // Beep nhanh khi đang báo
      bool buzzerState = ((now / 100) % 2 == 0);
      digitalWrite(BUZZER_PIN, buzzerState ? HIGH : LOW);
    } else {
      // Hết thời gian → tắt alarm
      alarmOn = false;
      digitalWrite(BUZZER_PIN, LOW);
      Serial.println("Alarm tắt.");
    }
  }
}

Kết Quả Mong Đợi

=== Vibration Switch SW-18010P ===
Gõ nhẹ vào bàn để test...
RUN! Tổng xung: 1
RUN! Tổng xung: 5
RUN! Tổng xung: 12

Ứng Dụng Thực Tế

Ứng dụngChi tiết
Báo động rung xeCảnh báo khi xe bị động vào
Gõ cửa thông minhGõ N lần → gửi thông báo
Mẫu gõ bí mậtUnlock bằng pattern gõ
Theo dõi rung máyCảnh báo máy móc rung bất thường
Đếm bước chânGắn ở giày, đếm mỗi bước

Lưu Ý Khi Sử Dụng

1. Phải dùng interrupt — không có cách khác

Xung kéo dài 50μs-5ms. loop() Arduino chạy ~100μs/vòng → hoàn toàn có thể bỏ sót. Không có interrupt → code không đáng tin cậy.

2. Bounce rất nhiều từ 1 lần rung

Một lần gõ cửa có thể tạo 5-20 xung do lò xo dao động. Xử lý bằng: đếm xung trong cửa sổ thời gian thay vì đếm “sự kiện”, hoặc debounce 80-100ms.

3. Nhiễu từ môi trường

Trong nhà có nhiều nguồn rung: xe tải đi qua, HVAC, người đi bộ. Tăng MIN_PULSES ngưỡng để giảm false positive.

4. noInterrupts() / interrupts() khi đọc biến volatile

pulseCount là biến 16-bit (int) — đọc không atomic trên 8-bit AVR. Luôn tắt interrupt khi đọc+reset để tránh race condition.

Bài tiếp theo: