A tilt switch detects orientation changes using a rolling conductive ball that bridges two internal contacts. To wire a standard KY-017 tilt switch module to an Arduino Uno R3, connect the module VCC to 5V, GND to GND, and the Digital Out (DO) pin to Arduino Pin 2. Because the internal rolling ball creates severe mechanical contact bounce, you must use either a hardware capacitor or software debouncing to get clean state transitions.
This guide walks through the exact hardware specs, wiring procedures, and a complete, compilable Arduino sketch with built-in fault detection. We are targeting the Arduino Uno R3 (ATmega328P) variant, though the code and wiring are 100% compatible with the Uno R4 Minima and Nano Every.
Tilt Switch Sensor Specs and Module Comparison
Not all tilt switches are created equal. The raw sensor inside the popular KY-017 module is the SW-520D, a dual-ball, directional tilt sensor. It is fundamentally different from the older, omnidirectional SW-200D or the mercury-based switches that are now largely banned due to RoHS compliance. Below is a data-dense comparison of the most common tilt sensors you will encounter on the bench in 2026.
| Sensor / Module | Internal Mechanism | Activation Angle | Max Contact Current | Contact Resistance | Typical Price (2026) |
|---|---|---|---|---|---|
| SW-520D (in KY-017) | Dual steel ball (Directional) | ~45° from vertical | 10 mA | < 50 mΩ | $1.50 (module) |
| SW-200D (in KY-020) | Single steel ball (Omnidirectional) | ~30° any direction | 10 mA | < 100 mΩ | $1.20 (module) |
| SW-460D | Dual ball (High sensitivity) | ~15° from vertical | 10 mA | < 50 mΩ | $2.00 (raw) |
| MPU-6050 (Alternative) | MEMS 3-Axis Accelerometer | Programmable (0.1° res) | N/A (I2C Digital) | N/A | $3.50 (module) |
Parts List and Pin Mapping
The KY-017 module is highly recommended over a raw SW-520D switch for beginners because it includes an LM393 dual comparator. The LM393 converts the messy analog resistance of the rolling ball into a crisp, clean digital HIGH/LOW signal, saving you from writing complex analog threshold code.
Required Materials
- Microcontroller: Arduino Uno R3 (ATmega328P) - ~$22.00
- Sensor: KY-017 Tilt Sensor Module (SW-520D + LM393) - ~$1.50
- Wiring: 4x Male-to-Female or Male-to-Male jumper wires
- Prototyping: Standard 830-point solderless breadboard
Pin Mapping Table
| KY-017 Module Pin | Arduino Uno R3 Pin | Function & Notes |
|---|---|---|
| VCC | 5V | Powers the LM393 and the internal LED. Do not use 3.3V. |
| GND | GND | Common ground reference. |
| DO (Digital Out) | Pin 2 | Outputs LOW when tilted, HIGH when upright (depends on trimpot). |
| AO (Analog Out) | Not Connected | Raw analog voltage from the switch divider. Rarely used. |
Wiring Steps and Debounced Arduino Code
Mechanical tilt switches suffer from severe contact bounce. When the steel ball rolls across the internal contacts, it physically bounces, causing the electrical connection to make and break dozens of times in a few milliseconds. If you read the pin directly in a fast loop(), a single tilt event will register as 50 separate triggers. We handle this using a non-blocking millis() debounce routine, following the official Arduino debounce architecture.
Step-by-Step Wiring
- De-energize the board: Ensure the Arduino is unplugged from USB before wiring.
- Connect Power: Route the KY-017 VCC pin to the Arduino 5V rail, and GND to the Arduino GND rail.
- Connect Signal: Connect the DO (Digital Out) pin on the module to Arduino Digital Pin 2.
- Enable Internal Pull-up: While the KY-017 has its own pull-up resistors, we will also enable the ATmega328P internal pull-up in software as a failsafe against floating inputs.
- Verify Polarity: Double-check that VCC and GND are not reversed. Reversing them will instantly destroy the LM393 comparator chip.
Complete Compilable Code
This sketch includes a startup calibration phase. If the sensor is wired incorrectly or shorted, it will halt and output a specific error string to the Serial Monitor.
/*
* KY-017 Tilt Switch Debounced Reader
* Target Board: Arduino Uno R3 (ATmega328P)
* Author: ElectricalFlux
*/
// --- Pin Definitions ---
#define TILT_PIN 2 // Digital Out from KY-017
#define STATUS_LED 13 // Onboard LED
// --- Debounce Variables ---
bool lastStableState = HIGH;
bool currentReading = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // 50ms debounce window
void setup() {
Serial.begin(115200);
// Configure pins with internal pull-up as a failsafe
pinMode(TILT_PIN, INPUT_PULLUP);
pinMode(STATUS_LED, OUTPUT);
// --- Startup Calibration & Fault Detection ---
Serial.println("Initializing Tilt Sensor...");
delay(100); // Allow LM393 to stabilize
// Assume the device is placed on a flat surface at boot.
// The SW-520D should read HIGH (upright) when flat.
int bootState = digitalRead(TILT_PIN);
if (bootState == LOW) {
// If it reads LOW on a flat surface, it's either tilted,
// or the DO line is shorted to ground/VCC depending on module logic.
Serial.println("ERROR: TILT_SENSOR_SHORT_TO_VCC");
Serial.println("Fault: Sensor reads LOW on flat surface.");
Serial.println("Check: 1. Is the board actually tilted?");
Serial.println("Check: 2. Is DO pin shorted to GND?");
Serial.println("Check: 3. Is the LM393 trimpot fully counter-clockwise?");
// Blink LED rapidly to indicate hardware fault
while(true) {
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
delay(100);
}
}
Serial.println("Sensor OK. Upright state confirmed.");
digitalWrite(STATUS_LED, HIGH);
}
void loop() {
// Read the current state of the tilt switch
int reading = digitalRead(TILT_PIN);
// If the switch changed, due to bounce or actual tilt
if (reading != lastStableState) {
// Reset the debouncing timer
lastDebounceTime = millis();
}
// If the state has been stable for longer than the debounce delay
if ((millis() - lastDebounceTime) > debounceDelay) {
// If the reading has actually changed from the last stable state
if (reading != currentReading) {
currentReading = reading;
// Execute state change logic
if (currentReading == LOW) {
Serial.println("[EVENT] Tilted! (Ball broke contact)");
digitalWrite(STATUS_LED, LOW);
} else {
Serial.println("[EVENT] Upright! (Ball bridged contact)");
digitalWrite(STATUS_LED, HIGH);
}
}
}
// Save the raw reading for the next loop iteration
lastStableState = reading;
}
Debugging: First Three Things to Check When It Fails
When your serial monitor is spamming false triggers, or the sensor refuses to change state, do not immediately rewrite your code. 95% of tilt switch failures are hardware or threshold issues. Here are the first three things to check, ranked by likelihood.
1. The Serial Monitor Prints ERROR: TILT_SENSOR_SHORT_TO_VCC
The Cause: The code's startup calibration expects a HIGH signal when the module is resting flat on your desk. If it reads LOW, the microcontroller assumes a wiring fault or a severely misadjusted comparator.
The Fix:
- Ensure the breadboard is perfectly level. The SW-520D is highly sensitive to 45° angles.
- Check your jumper wire from the DO pin to Pin 2. If it is internally broken or shorted to the GND rail, the ATmega328P will read a permanent LOW.
- Use a multimeter to measure the voltage at the DO pin. It should read ~5V when flat, and ~0V when tilted. If it reads 0V constantly, the LM393 chip on the module may be dead.
2. Erratic Triggering (Serial Monitor Spamming Events)
The Cause: Mechanical contact bounce or environmental vibration. If your project is mounted to a motor, a speaker, or a desk that gets bumped, the micro-vibrations will cause the steel ball to chatter against the contacts.
The Fix:
- Software: Increase the
debounceDelayconstant in the code from50to150or200milliseconds. - Hardware: Solder a 100nF (0.1µF) ceramic capacitor directly across the DO and GND pins on the KY-017 module. This creates a hardware low-pass filter that absorbs micro-bounces before they reach the Arduino.
3. The Sensor Never Triggers (Stuck HIGH)
The Cause: The LM393 comparator's threshold voltage is set too high via the blue trimpot on the module. The comparator compares the voltage divider of the tilt switch against a reference voltage set by the potentiometer. If the reference is too high, the rolling ball can never pull the voltage low enough to trip the comparator.
The Fix:
- Power the module and connect a multimeter to the AO (Analog Out) pin.
- Tilt the module until the ball rolls.
- Using a small Phillips or flathead screwdriver, slowly turn the blue trimpot counter-clockwise until the onboard LED on the module toggles off, then clockwise until it toggles on. Set it exactly at the transition point for your desired tilt angle.
Extending and Simplifying the Build
Once you have the basic binary tilt detection working, you will likely want to optimize the build for your specific application. Here is how to scale the project up or strip it down.
INPUT_PULLUP in your pinMode(). The ATmega328P's internal 20kΩ-50kΩ pull-up resistor is sufficient to pull the pin HIGH when the ball rolls away, eliminating the need for the LM393 comparator and external resistors.
Extending: Moving to Hardware Interrupts
If your Arduino is busy doing other tasks (like driving a display or reading a heavy sensor array), polling Pin 2 in the loop() might cause you to miss a quick tilt event. Because the Uno R3's Pin 2 supports hardware interrupts, you can refactor the code to use an Interrupt Service Routine (ISR).
To do this, replace the polling logic with attachInterrupt(digitalPinToInterrupt(TILT_PIN), tiltISR, CHANGE);. Inside the ISR, simply record the millis() timestamp of the event, and handle the debounce logic and Serial printing back in the main loop(). Never use Serial.println() or delay() inside an ISR, as this will crash the microcontroller.
Extending: Adding I2C Multiplexing
If you need to monitor the tilt status of multiple enclosures (e.g., a security system monitoring three different server rack doors), daisy-chaining digital pins wastes I/O. Instead, use a raw SW-520D switch paired with an MCP23017 I2C I/O Expander. This allows you to monitor up to 16 tilt switches using only the Arduino's A4 and A5 SDA/SCL pins, keeping your wiring clean and your code highly scalable.






