The SW-520D is the most practical directional tilt sensor for Arduino projects, offering a specific trigger angle of roughly 20 to 45 degrees at a unit cost of under $0.20. Unlike omnidirectional ball switches, the SW-520D uses a dual-ball internal mechanism that only closes the circuit when tilted in one specific direction, making it ideal for anti-tamper alarms, screen rotation triggers, and rollover detection. This guide provides the exact wiring topology, a hardware-plus-software debounced C++ implementation, and a diagnostic framework for when the sensor misfires.

Project Overview and Difficulty Rating

Target Board: This code and wiring schematic specifically target the Arduino Nano v3 (ATmega328P) running at 16MHz/5V. The logic translates directly to the Uno R3 or Mega 2560, but pin mappings will need adjustment for 3.3V boards like the ESP32 or Arduino Due.
  • Difficulty: 2/5 (Beginner-Intermediate)
  • Estimated Build Time: 20 minutes
  • Core Concept: Mechanical switch debouncing via RC filtering and state-machine logic.

SW-520D vs. SW-200D vs. SW-420: Spec Sheet and Selection Table

Choosing the wrong tilt or vibration switch is the most common reason these projects fail in the field. The SW-520D is directional, the SW-200D is omnidirectional, and the SW-420 is strictly for high-frequency vibration. Review this spec sheet before finalizing your BOM.

Specification SW-520D (Directional) SW-200D (Omni) SW-420 (Vibration)
Internal Mechanism Dual steel ball (conductive) Single steel ball (cage) Spring-mass suspension
Trigger Angle / Sensitivity 20° - 45° (One direction) Any angle > 15° High-frequency shock only
Contact Resistance < 50 mΩ < 50 mΩ < 100 mΩ
Max Switching Voltage 12V DC 12V DC 12V DC
Typical Price (2026) $0.12 - $0.18 / unit $0.08 - $0.12 / unit $0.10 - $0.15 / unit
Best Use Case Rollover detection, screen flip Generic tilt alarms, toys Knock sensors, motor fault

Parts List and Pin Mapping

Do not skip the 100nF ceramic capacitor. Mechanical tilt sensors suffer from severe contact bounce, which can register as dozens of triggers in a single millisecond. Combining a hardware RC (Resistor-Capacitor) filter with software debouncing yields a rock-solid signal.

Required Components

  • 1x Arduino Nano v3 (ATmega328P)
  • 1x SW-520D Directional Tilt Sensor
  • 1x 10kΩ Resistor (Brown-Black-Orange-Gold) for external pull-up
  • 1x 100nF (0.1µF) Ceramic Capacitor for hardware debounce
  • Half-size breadboard and male-to-male jumper wires

Pin Mapping Table

Component Component Pin / Lead Arduino Nano Pin Notes
SW-520D Pin 1 (Arbitrary) GND Connects to common ground rail
SW-520D Pin 2 (Arbitrary) D2 (Digital Pin 2) Signal line (also supports INT0)
10kΩ Resistor Lead A 5V Pulls D2 HIGH when switch is open
10kΩ Resistor Lead B D2 Junction with sensor and capacitor
100nF Capacitor Lead A D2 Parallel to resistor junction
100nF Capacitor Lead B GND Completes RC low-pass filter to ground

Wiring Steps and Circuit Assembly

  1. Seat the Nano: Press the Arduino Nano v3 into the breadboard, ensuring pins span across the center divider.
  2. Establish Power Rails: Connect Nano 5V to the red rail and Nano GND to the blue rail using jumper wires.
  3. Place the Sensor: Insert the two leads of the SW-520D into the same row on the breadboard. The sensor is non-polarized, so orientation of the leads does not matter electrically, though the physical plastic body dictates the tilt direction.
  4. Wire the Pull-Up: Insert the 10kΩ resistor. Connect one leg to the 5V red rail and the other leg to the row shared by the tilt sensor.
  5. Add the RC Filter: Insert the 100nF ceramic capacitor. Connect one leg to the same row as the sensor and resistor junction, and the other leg to the blue GND rail. This creates a low-pass filter with a time constant of $\tau = R \times C = 10,000 \times 0.0000001 = 1ms$, smoothing out micro-bounces.
  6. Route the Signal: Run a jumper wire from the sensor/resistor/capacitor junction row to Digital Pin 2 (D2) on the Nano.
  7. Ground the Sensor: Run a jumper wire from the second tilt sensor lead to the blue GND rail.
Safety & Reliability Note: Never connect the tilt sensor directly across 5V and GND without a load or pull-up resistor. While the sensor itself won't short the supply in its open state, wiring errors during prototyping can dead-short the Nano's 5V regulator, triggering its thermal shutdown or permanently damaging the onboard USB VBUS diode.

Compilable Arduino Code with Debounce Logic

This sketch uses a non-blocking millis() state machine. We avoid delay() to keep the main loop free for other tasks. It also includes an error-handling routine to detect if the sensor is stuck or wired incorrectly.

// Target Board: Arduino Nano v3 (ATmega328P)
// Project: SW-520D Tilt Sensor with Hardware/Software Debounce

#define TILT_PIN        2      // Digital Pin 2 (also INT0)
#define DEBOUNCE_MS     50     // Software debounce window
#define STUCK_THRESHOLD 5000   // 5 seconds stuck LOW triggers error

// State variables
int currentTiltState = HIGH;   // Assume pulled HIGH initially
int lastTiltState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long stuckTimer = 0;
bool tiltTriggered = false;

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; } // Wait for serial port (Native USB boards)
  
  // We use an external 10k pull-up, so standard INPUT is correct.
  // If you omitted the resistor, change this to INPUT_PULLUP.
  pinMode(TILT_PIN, INPUT);
  
  Serial.println(F("[SYS] SW-520D Tilt Sensor Initialized."));
  Serial.println(F("[SYS] Tilt the sensor >20 degrees to trigger."));
  stuckTimer = millis();
}

void loop() {
  // Read the raw state from the hardware-filtered pin
  int reading = digitalRead(TILT_PIN);

  // Software Debounce Logic
  if (reading != lastTiltState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > DEBOUNCE_MS) {
    if (reading != currentTiltState) {
      currentTiltState = reading;
      
      // Sensor closes to GND, so LOW means tilted
      if (currentTiltState == LOW) {
        Serial.println(F("[EVT] TILT DETECTED: Angle exceeded threshold."));
        tiltTriggered = true;
        stuckTimer = millis(); // Reset stuck timer on valid transition
      } else {
        Serial.println(F("[EVT] SENSOR RESET: Returned to upright."));
        tiltTriggered = false;
        stuckTimer = millis();
      }
    }
  }

  // Error Handling: Detect stuck LOW (missing pull-up or short)
  if (currentTiltState == LOW && tiltTriggered) {
    if (millis() - stuckTimer > STUCK_THRESHOLD) {
      Serial.println(F("[ERR] Tilt sensor stuck LOW - check pull-up resistor or wiring"));
      stuckTimer = millis(); // Prevent serial buffer flooding
    }
  }

  lastTiltState = reading;
}

Debugging: First Three Things to Check When It Fails

When the serial monitor spits out errors or fails to register a tilt, do not rewrite the code. Mechanical switches fail at the physical layer. Follow this ranked diagnostic path.

1. Exact Error: [ERR] Tilt sensor stuck LOW - check pull-up resistor or wiring

Cause: The microcontroller is reading a continuous 0V on D2.
Fix:

  • Verify the 10kΩ resistor is actually connected to the 5V rail, not a floating row.
  • Measure the voltage at D2 with a multimeter while the sensor is upright. It must read ~4.9V to 5.0V. If it reads 0V, your pull-up is missing or the sensor is internally shorted.
  • If you forgot the external resistor, change pinMode(TILT_PIN, INPUT); to pinMode(TILT_PIN, INPUT_PULLUP); in the code to use the ATmega328P's internal ~30kΩ pull-up.

2. Symptom: Serial Monitor Shows Rapid [EVT] Spam from a Single Tilt

Cause: Contact bounce is overwhelming the software debounce window. According to All About Circuits, mechanical switch contacts can bounce for up to 20ms depending on the mass of the internal elements.
Fix:

  • Check your breadboard connections for the 100nF capacitor. A missing capacitor forces the software to do all the heavy lifting.
  • Increase #define DEBOUNCE_MS 50 to 100 in the code. Note that going above 150ms will introduce noticeable UI lag if you are using this to trigger an LED or buzzer.

3. Symptom: Sensor Only Triggers When Tapped, Not Tilted Slowly

Cause: You are likely using an SW-420 vibration sensor by mistake, or the SW-520D is mounted at the wrong baseline angle.
Fix:

  • Inspect the component body. The SW-520D typically has a distinct flattened edge or directional marking compared to the symmetrical SW-420.
  • The SW-520D requires gravity to pull the internal conductive balls away from the contact pins. If mounted horizontally on a breadboard, it may not trigger. Mount it vertically on a perpendicular proto-board for proper gravity-assisted switching.

Extending and Simplifying the Build

How to Simplify (Drop the External Hardware)

If you are building a quick prototype and lack a 10kΩ resistor and 100nF capacitor, you can rely entirely on the ATmega328P's internal pull-up resistors. Change the pin mode to INPUT_PULLUP.
The Trade-off: The internal pull-up is roughly 30kΩ to 50kΩ. This higher impedance makes the input pin highly susceptible to EMI (Electromagnetic Interference) from nearby motors, relays, or AC mains wiring. Only use this simplified approach in low-noise, battery-powered environments.

How to Extend (Interrupts and IoT)

For battery-powered IoT nodes (like an ESP32 deep-sleep tilt alarm), polling the pin in the loop() wastes milliamps. Instead, use hardware interrupts to wake the MCU.

  • Interrupt Wiring: Ensure the sensor is on D2 or D3 on the Nano (these map to INT0 and INT1). On an ESP32, any GPIO can be an interrupt.
  • Code Modification: Replace the polling logic with attachInterrupt(digitalPinToInterrupt(TILT_PIN), tiltISR, FALLING);. As noted in the official Arduino attachInterrupt documentation, keep the ISR (Interrupt Service Routine) as short as possible—just set a volatile bool flag and handle the serial printing in the main loop.
  • Network Extension: Pair the interrupt flag with an ESP8266/ESP32 to send an MQTT payload to Home Assistant the millisecond the enclosure is opened or tilted, creating a zero-latency tamper mesh.
Pro-Tip for Enclosure Design: When 3D printing an enclosure for a tilt sensor project, design a small internal bracket that holds the sensor perfectly plumb (0°) when the enclosure is sitting flat on a desk. The SW-520D's 20° trigger threshold means a poorly printed, warped enclosure floor can cause false triggers simply from the sensor being mounted at a 10° baseline slant.