////////////////////////////////////
/// HOME AIR DEFENSE SYSTEM v8.6 ///
////////////////////////////////////
/*
This is a home radar and warning system.
It uses an Ultrasonic sensor and PIR motion sensor to detect movement,
and uses white warning LEDs and a buzzer when manually activated.
*/
#include <Servo.h>
#include <LiquidCrystal.h>
// Pins
const int TOP_SERVO_PIN = 8;
const int SCAN_SERVO_PIN = 9;
const int TRIG_PIN = 24;
const int ECHO_PIN = 25;
const int PIR_PIN = 22;
const int JOY_X_PIN = A0;
const int JOY_BTN_PIN = 23;
const int BUZZER_PIN = 26;
// Monochrome white LEDs
const int WHITE_LED1_PIN = 51;
const int WHITE_LED2_PIN = 2;
// If your white LEDs are wired backwards, change this to true.
// false = LED turns on with HIGH
// true = LED turns on with LOW
const bool WHITE_LED_ACTIVE_LOW = false;
// LCD pins
// LCD RS, E, D4, D5, D6, D7
const int LCD_RS_PIN = 30;
const int LCD_E_PIN = 31;
const int LCD_D4_PIN = 32;
const int LCD_D5_PIN = 33;
const int LCD_D6_PIN = 34;
const int LCD_D7_PIN = 35;
LiquidCrystal lcd(
LCD_RS_PIN,
LCD_E_PIN,
LCD_D4_PIN,
LCD_D5_PIN,
LCD_D6_PIN,
LCD_D7_PIN
);
// LCD detection settings
// If an object is this close or closer, the LCD warning appears.
int lcdDetectDistanceCm = 50;
// How long the LCD warning stays on screen.
unsigned long lcdMessageTimeMs = 3000;
bool lcdMessageActive = false;
unsigned long lcdMessageStartTime = 0;
// Servos
Servo topServo;
Servo scanServo;
// Top continuous servo values
// For a continuous servo:
// 90 usually means stop
// 180 usually means full speed one way
// 0 usually means full speed the other way
const int topStopSpeed = 90;
const int topForwardSpeed = 180;
const int topReverseSpeed = 0;
// If the top servo moves the wrong direction,
// switch this to true.
bool reverseTopServo = false;
// Joystick center zone
// If joystick_x is between these, the top servo stops.
const int JOY_CENTER_LOW = 430;
const int JOY_CENTER_HIGH = 590;
// Scan servo values
int scanAngle = 0;
int scanStep = 1;
int scanServoStepSize = 1;
bool sweepEnabled = true;
// Bigger number = slower scan servo sweep
unsigned long scanServoDelayMs = 50;
// Top state
// -1 = left/reverse
// 0 = stopped
// 1 = right/forward
int topState = 0;
// Last top servo command value
int topServoCommandValue = topStopSpeed;
// Top estimate for Python UI
float topApproxDeg = 90.0;
const float topMinDeg = 0.0;
const float topMaxDeg = 180.0;
float topDegPerSecondLeft = 75.0;
float topDegPerSecondRight = 75.0;
unsigned long lastTopUpdate = 0;
// Sensor and button values
int distanceCm = 400;
bool motionDetected = false;
bool joystickWarning = false;
// Joystick value
int joyXValue = 512;
// Timing
unsigned long lastSweepTime = 0;
unsigned long lastPingTime = 0;
const unsigned long pingDelayMs = 60;
unsigned long lastSerialTime = 0;
const unsigned long serialDelayMs = 120;
unsigned long lastBlinkTime = 0;
const unsigned long blinkDelayMs = 180;
bool blinkState = false;
// Helper functions
int clampInt(int value, int low, int high) {
if (value < low) return low;
if (value > high) return high;
return value;
}
float clampFloat(float value, float low, float high) {
if (value < low) return low;
if (value > high) return high;
return value;
}
// Set both white LEDs on or off
void setWhiteLEDs(bool on) {
if (WHITE_LED_ACTIVE_LOW) {
digitalWrite(WHITE_LED1_PIN, on ? LOW : HIGH);
digitalWrite(WHITE_LED2_PIN, on ? LOW : HIGH);
} else {
digitalWrite(WHITE_LED1_PIN, on ? HIGH : LOW);
digitalWrite(WHITE_LED2_PIN, on ? HIGH : LOW);
}
}
// Top servo movement using normal Servo.write()
void stopTop() {
topServoCommandValue = topStopSpeed;
topServo.write(topServoCommandValue);
topState = 0;
}
void moveTopLeft() {
if (reverseTopServo) {
topServoCommandValue = topForwardSpeed;
} else {
topServoCommandValue = topReverseSpeed;
}
topServo.write(topServoCommandValue);
topState = -1;
}
void moveTopRight() {
if (reverseTopServo) {
topServoCommandValue = topReverseSpeed;
} else {
topServoCommandValue = topForwardSpeed;
}
topServo.write(topServoCommandValue);
topState = 1;
}
// Approximate top angle for Python UI
void updateTopApprox() {
unsigned long now = millis();
float dt = (now - lastTopUpdate) / 1000.0;
lastTopUpdate = now;
if (topState == -1) {
topApproxDeg -= topDegPerSecondLeft * dt;
} else if (topState == 1) {
topApproxDeg += topDegPerSecondRight * dt;
}
topApproxDeg = clampFloat(topApproxDeg, topMinDeg, topMaxDeg);
}
// Ultrasonic distance reading
int readDistanceCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 25000);
if (duration <= 0) {
return 400;
}
int cm = duration / 58;
return clampInt(cm, 2, 400);
}
// Standard scan servo sweep
void updateSweep() {
if (!sweepEnabled) {
return;
}
unsigned long now = millis();
if (now - lastSweepTime < scanServoDelayMs) {
return;
}
lastSweepTime = now;
scanAngle += scanStep;
if (scanAngle >= 180) {
scanAngle = 180;
scanStep = -scanServoStepSize;
} else if (scanAngle <= 0) {
scanAngle = 0;
scanStep = scanServoStepSize;
}
scanServo.write(scanAngle);
}
// Read joystick, PIR, and joystick button
void readInputs() {
joyXValue = analogRead(JOY_X_PIN);
motionDetected = (digitalRead(PIR_PIN) == HIGH);
// Joystick button controls LEDs and buzzer.
joystickWarning = (digitalRead(JOY_BTN_PIN) == LOW);
}
// Joystick X-axis is the only thing that moves the top assembly servo.
void handleJoystickMovement() {
if (joyXValue < JOY_CENTER_LOW) {
moveTopLeft();
} else if (joyXValue > JOY_CENTER_HIGH) {
moveTopRight();
} else {
stopTop();
}
}
// Get simple direction based on scan servo angle
const char* getScanDirection() {
if (scanAngle < 60) {
return "LEFT";
} else if (scanAngle <= 120) {
return "CENTER";
} else {
return "RIGHT";
}
}
// LCD warning system
void updateLCD() {
unsigned long now = millis();
bool objectClose = false;
if (distanceCm >= 2 && distanceCm <= lcdDetectDistanceCm) {
objectClose = true;
}
// If something is close enough, show or refresh the warning message.
if (objectClose) {
lcdMessageActive = true;
lcdMessageStartTime = now;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ENEMY DETECTED");
lcd.setCursor(0, 1);
lcd.print("at ");
lcd.print(getScanDirection());
}
// If message time is over, clear the LCD.
if (lcdMessageActive && (now - lcdMessageStartTime >= lcdMessageTimeMs)) {
lcd.clear();
lcdMessageActive = false;
}
}
// LED and buzzer behavior
// Only joystick button controls white LED flashing and buzzer.
void updateLEDsAndBuzzer() {
unsigned long now = millis();
if (joystickWarning) {
if (now - lastBlinkTime >= blinkDelayMs) {
lastBlinkTime = now;
blinkState = !blinkState;
}
if (blinkState) {
setWhiteLEDs(true);
digitalWrite(BUZZER_PIN, HIGH);
} else {
setWhiteLEDs(false);
digitalWrite(BUZZER_PIN, LOW);
}
} else {
setWhiteLEDs(false);
digitalWrite(BUZZER_PIN, LOW);
blinkState = false;
}
}
// Labeled serial output for Python UI
void sendData() {
unsigned long now = millis();
if (now - lastSerialTime < serialDelayMs) {
return;
}
lastSerialTime = now;
bool warningActive = joystickWarning;
Serial.print("DATA");
Serial.print("|scan_servo_angle_deg=");
Serial.print(scanAngle);
Serial.print("|ultrasonic_distance_cm=");
Serial.print(distanceCm);
Serial.print("|pir_motion_detected=");
Serial.print(motionDetected ? 1 : 0);
Serial.print("|top_servo_state=");
Serial.print(topState);
Serial.print("|top_servo_est_deg=");
Serial.print(topApproxDeg, 1);
Serial.print("|top_servo_command_value=");
Serial.print(topServoCommandValue);
Serial.print("|top_stop_speed=");
Serial.print(topStopSpeed);
Serial.print("|top_forward_speed=");
Serial.print(topForwardSpeed);
Serial.print("|top_reverse_speed=");
Serial.print(topReverseSpeed);
Serial.print("|control_mode=");
Serial.print("JOYSTICK_ONLY_WRITE");
Serial.print("|warning_active=");
Serial.print(warningActive ? 1 : 0);
Serial.print("|joystick_x=");
Serial.print(joyXValue);
Serial.print("|joystick_center_low=");
Serial.print(JOY_CENTER_LOW);
Serial.print("|joystick_center_high=");
Serial.print(JOY_CENTER_HIGH);
Serial.print("|joystick_button_pressed=");
Serial.print(joystickWarning ? 1 : 0);
// This was an old piece of the code that we kept for Python UI compatibility
// Always 0 because PIR no longer moves the top servo.
Serial.print("|pir_spin_active=");
Serial.print(0);
Serial.print("|scan_servo_delay_ms=");
Serial.print(scanServoDelayMs);
Serial.print("|lcd_detect_distance_cm=");
Serial.print(lcdDetectDistanceCm);
Serial.print("|lcd_message_active=");
Serial.print(lcdMessageActive ? 1 : 0);
Serial.print("|lcd_direction=");
Serial.print(getScanDirection());
Serial.println();
}
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(PIR_PIN, INPUT);
pinMode(JOY_BTN_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
pinMode(WHITE_LED1_PIN, OUTPUT);
pinMode(WHITE_LED2_PIN, OUTPUT);
setWhiteLEDs(false);
// Start LCD
lcd.begin(16, 2);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Radar Ready");
delay(1000);
lcd.clear();
// Attach servos
topServo.attach(TOP_SERVO_PIN);
scanServo.attach(SCAN_SERVO_PIN);
// Stop top servo at startup
stopTop();
// Start scan servo
scanServo.write(scanAngle);
lastTopUpdate = millis();
lastBlinkTime = millis();
Serial.println("READY");
}
void loop() {
readInputs();
// This is the only part that controls the top assembly servo
handleJoystickMovement();
updateTopApprox();
updateSweep();
if (millis() - lastPingTime >= pingDelayMs) {
lastPingTime = millis();
distanceCm = readDistanceCm();
}
updateLCD();
updateLEDsAndBuzzer();
sendData();
}
// View the Wokwi Build Here
// Both the Official Arduino Documentation and the Unofficial Arduino Get Started Documentation helped in coding this project, I use them a lot (not just for this project, either). My main sources are listed below:
// Main Joystick Code based on “arduinogetstarted.com/tutorials/arduino-joystick” - The deadzone code was based off of this Stack Exchange thread: “arduino.stackexchange.com/questions/83413/create-a-dead-zone-for-joystick”
// Motion Sensor Code based on “arduinogetstarted.com/tutorials/arduino-motion-sensor”
// PC-to-Arduino Serial Code based on “arduinogetstarted.com/tutorials/arduino-serial-monitor”
// Millis() Function Syntax based on “docs.arduino.cc/language-reference/en/functions/time/millis/”
// Piezo Buzzer Code based on “arduinogetstarted.com/tutorials/arduino-piezo-buzzer”
// LCD Display Code based on "docs.arduino.cc/learn/electronics/lcd-displays/"
// AI USE DISCLAIMER: AI model GPT-5.5 was used as a troubleshooting and source hunting tool for this project, but did not have an essential role in creating the code.