/********************************************************************************
* Motion-Activated Alarm System for Arduino Uno R3
*
* Author: Mycho Famadico
* Date: April 26, 2025
* Hardware: - Arduino Uno R3
* - 16×2 I2C LCD (PCF8574) @ 0x27
* - PIR Motion Sensor @ D8
* - Red LED @ D4
* - Piezo Buzzer @ A3
*
* States: 1) WAITING: Displays “Waiting for Movements…” with animating dots
* 2) ALARM: Displays “Movements Detected!”, sounds buzzer,
* blinks LED (2 s solid, then 100 ms on/off) for 10 s
*
* Timing: - DOT_INTERVAL = 500 ms per animation frame
* - ALARM_DURATION = 10 000 ms total alarm time
* - LED_SOLID_TIME = 2 000 ms solid LED before blinking
* - LED_BLINK_PERIOD = 200 ms blink cycle (100 ms on, 100 ms off)
* - BUZZER_SWITCH = 250 ms between alternating tones
*
* Notes: Uses non-blocking millis() for responsiveness.
********************************************************************************/
#include <Wire.h> // I2C communication library
#include <LiquidCrystal_I2C.h> // LCD driver over I2C
// ======== Pin Definitions ========
constexpr uint8_t PIR_PIN = 8; // PIR sensor digital input pin
constexpr uint8_t LED_PIN = 4; // Red LED digital output pin
constexpr uint8_t BUZZER_PIN = A3; // Piezo buzzer PWM output pin
// ======== LCD Setup ========
constexpr uint8_t LCD_ADDR = 0x27; // I2C address of PCF8574 backpack
constexpr uint8_t LCD_COLS = 16; // Characters per line
constexpr uint8_t LCD_ROWS = 2; // Number of lines
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
// ======== Timing Constants ========
constexpr unsigned long DOT_INTERVAL = 500UL; // ms between dot frames
constexpr unsigned long ALARM_DURATION = 10000UL; // total alarm time in ms
constexpr unsigned long LED_SOLID_TIME = 2000UL; // ms LED stays solid
constexpr unsigned long LED_BLINK_PERIOD = 200UL; // ms per blink cycle
constexpr unsigned long BUZZER_TONE_SWITCH = 250UL; // ms between tone changes
// ======== State Machine ========
enum class SystemState { WAITING, ALARM };
SystemState currentState = SystemState::WAITING;
// ======== Runtime Variables ========
unsigned long lastDotTime = 0; // Timestamp of last dot animation update
uint8_t dotFrame = 0; // Current dot frame index (0–3)
unsigned long alarmStartTime = 0; // Timestamp when alarm began
bool pirPrevState = LOW; // For detecting PIR rising edge
// ======== Function Prototypes ========
void setupHardware();
void setupLCD();
void waitingState(unsigned long now);
void alarmState(unsigned long now);
void animateDots(unsigned long now);
void checkPIR(unsigned long now);
void startAlarm(unsigned long now);
void stopAlarm();
void updateLed(unsigned long elapsed);
void updateBuzzer(unsigned long elapsed);
void setup() {
setupHardware(); // Configure pin modes
setupLCD(); // Initialize and clear LCD
// Initial “waiting” display
lcd.setCursor(0, 0);
lcd.print("Waiting for"); // Line 1 text
lcd.setCursor(0, 1);
lcd.print("Movements..."); // Line 2 text (dots animate)
}
void loop() {
unsigned long now = millis(); // Current time for non-blocking logic
// Dispatch according to current state
if (currentState == SystemState::WAITING) {
waitingState(now);
} else {
alarmState(now);
}
}
/*******************************************************************************
// FUNCTION: setupHardware()
// Purpose: Initialize all I/O pins (inputs, outputs).
********************************************************************************/
void setupHardware() {
pinMode(PIR_PIN, INPUT); // PIR motion sensor
pinMode(LED_PIN, OUTPUT); // Red indicator LED
pinMode(BUZZER_PIN, OUTPUT); // Piezo buzzer
}
/*******************************************************************************
// FUNCTION: setupLCD()
// Purpose: Initialize I2C LCD and turn on backlight.
********************************************************************************/
void setupLCD() {
lcd.init(); // Initialize LCD registers
lcd.backlight(); // Turn on LCD backlight
}
/*******************************************************************************
// FUNCTION: waitingState(now)
// Purpose: Animate dots and watch for PIR trigger.
********************************************************************************/
void waitingState(unsigned long now) {
animateDots(now); // Update “…” animation every 500 ms
checkPIR(now); // Detect motion → switch to ALARM
}
/*******************************************************************************
// FUNCTION: animateDots(now)
// Purpose: Cycle through 4 frames of dot animation.
// Frames: “ ”, “. ”, “.. ”, “…”
********************************************************************************/
void animateDots(unsigned long now) {
if (now - lastDotTime < DOT_INTERVAL) return;
lastDotTime = now;
dotFrame = (dotFrame + 1) % 4;
lcd.setCursor(9, 1); // Position to start dots
switch (dotFrame) {
case 0: lcd.print(" "); break;
case 1: lcd.print(". "); break;
case 2: lcd.print(".. "); break;
case 3: lcd.print("..."); break;
}
}
/*******************************************************************************
// FUNCTION: checkPIR(now)
// Purpose: Detect rising edge from PIR sensor.
// If motion begins, start the alarm.
********************************************************************************/
void checkPIR(unsigned long now) {
bool pirState = digitalRead(PIR_PIN);
if (pirState && !pirPrevState) {
// Rising edge → enter ALARM state
startAlarm(now);
currentState = SystemState::ALARM;
}
pirPrevState = pirState;
}
/*******************************************************************************
// FUNCTION: alarmState(now)
// Purpose: Manage the 10 s alarm:
// - LED: solid 2 s, then blink 100 ms on/off
// - Buzzer: alternate 1000/1500 Hz every 250 ms
// - LCD: “Movements Detected!”
// - Return to WAITING after 10 s
********************************************************************************/
void alarmState(unsigned long now) {
unsigned long elapsed = now - alarmStartTime;
// If alarm duration expired, reset system
if (elapsed >= ALARM_DURATION) {
stopAlarm();
// Restore “waiting” screen
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Waiting for");
lcd.setCursor(0, 1);
lcd.print("Movements...");
// Reset animation timer
dotFrame = 0;
lastDotTime = now;
currentState = SystemState::WAITING;
return;
}
updateLed(elapsed); // LED pattern
updateBuzzer(elapsed); // Buzzer tones
}
/*******************************************************************************
// FUNCTION: startAlarm(now)
// Purpose: Initialize alarm state:
// clear LCD, show “Detected!”, record start time, turn LED on.
********************************************************************************/
void startAlarm(unsigned long now) {
alarmStartTime = now;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Movements");
lcd.setCursor(0, 1);
lcd.print("Detected!");
digitalWrite(LED_PIN, HIGH); // Solid LED at alarm start
}
/*******************************************************************************
// FUNCTION: stopAlarm()
// Purpose: Silence buzzer and turn off LED.
********************************************************************************/
void stopAlarm() {
noTone(BUZZER_PIN); // Stop any buzzer tone
digitalWrite(LED_PIN, LOW); // LED off
}
/*******************************************************************************
// FUNCTION: updateLed(elapsed)
// Purpose: LED behavior during alarm period.
// - First 2 s: solid ON
// - After 2 s: blink ON/OFF every 100 ms
********************************************************************************/
void updateLed(unsigned long elapsed) {
if (elapsed < LED_SOLID_TIME) {
digitalWrite(LED_PIN, HIGH);
} else {
unsigned long phase = (elapsed - LED_SOLID_TIME) % LED_BLINK_PERIOD;
digitalWrite(LED_PIN, (phase < (LED_BLINK_PERIOD / 2)) ? HIGH : LOW);
}
}
/*******************************************************************************
// FUNCTION: updateBuzzer(elapsed)
// Purpose: Alternate buzzer frequency between 1000 Hz and 1500 Hz
// every 250 ms to create an attention-grabbing alarm.
********************************************************************************/
void updateBuzzer(unsigned long elapsed) {
unsigned long phase = elapsed % (2 * BUZZER_TONE_SWITCH);
uint16_t freq = (phase < BUZZER_TONE_SWITCH) ? 1000 : 1500;
tone(BUZZER_PIN, freq);
}
/********************************************************************************
* END OF SKETCH
*
* Upload this to your Arduino Uno R3 with the LiquidCrystal_I2C library installed.
* The system will now:
* • Animate “Waiting for Movements…” dots continuously
* • Instantly trigger a 10 s multi-tone buzzer + LED alarm upon motion
* • Automatically reset when finished
********************************************************************************/