← Microcontrollers 2

Lab 2: The Safety Garage Door

Course: DE6417 Microcontrollers 2

Assessment Type: Laboratory (Practical)

Weighting: 7%

1. Objective

Design and implement a Safety Garage Door Controller using an Arduino Uno R3. This system simulates a motorized garage door with a critical safety feature: an emergency stop triggered by a limit switch (simulating an obstacle sensor).

The key challenge is ensuring the system responds instantly to the safety sensor, even when the motor is actively running. You will achieve this using Pin Change Interrupts (PCINT)—a hardware feature that overrides normal code execution to handle time-critical events.

Key Concept: Unlike regular polling (checking buttons in loop()), interrupts act like an "emergency brake" that the hardware triggers automatically, ensuring immediate response regardless of what code is currently executing.

2. Learning Outcomes

This lab assesses the following course learning outcomes:


3. The Application Scenario

Imagine you are building a controller for a heavy garage door:

Component Role Safety Concern
Stepper Motor Opens or closes the door High torque can cause injury if not stopped
Start Button User initiates door movement Must only work when safe
Limit Switch (Stop) Safety sensor at bottom of door Must stop door INSTANTLY if triggered
The Problem: If your code is busy commanding the motor to move (which takes time), it might not check the safety sensor fast enough using traditional polling methods.

The Solution: We use a Pin Change Interrupt. This acts like an emergency brake that overrides everything else instantly, regardless of what the main code is doing.

4. Hardware Implementation

4.1 Required Components

Quantity Component Purpose
1 Arduino Uno R3 Microcontroller
1 Stepper Motor (28BYJ-48) Garage door motor simulation
1 ULN2003 Driver Board Motor driver (handles high current)
2 Push Buttons Start button & Limit switch
- Jumper Wires Connections
1 Breadboard Circuit assembly

4.2 Wiring Diagram

Complete Wiring Diagram — Lab 2: Safety Garage Door Start Button One leg → A0 | Other → GND Limit Switch One leg → A1 | Other → GND GND Arduino Uno R3 A0 (Pin 14) A1 (Pin 15) GND D8 D9 D10 D11 5V GND ULN2003 Driver Board IN1 IN2 IN3 IN4 + (5V) – (GND) 28BYJ-48 Stepper Motor 5-pin connector Motor ● Legend: Signal (D8–D11) 5V Power GND Motor plug

4.3 Step-by-Step Connections

Step 1: Connect the Motor (The Muscle)

The ULN2003 is a Darlington transistor array that amplifies the Arduino's weak digital signals to drive the stepper motor's coils.

ULN2003 Driver Arduino Pin Notes
IN1 Digital 8 Motor coil control
IN2 Digital 9 Motor coil control
IN3 Digital 10 Motor coil control
IN4 Digital 11 Motor coil control
+ (5-12V) 5V Power supply positive
- (GND) GND Power supply ground
ULN2003 Driver Board — Motor Connection Detail Arduino Uno Digital Pins D8 (IN1) D9 (IN2) D10 (IN3) D11 (IN4) 5V GND ULN2003 Driver Board IN1 IN2 IN3 IN4 + (5V) – (GND) Motor connector 28BYJ-48 Stepper Motor

Step 2: Connect the Buttons (The Brains)

We use Analog pins A0 and A1 as digital inputs. This keeps button wiring separate from motor wiring and demonstrates that analog pins can function as digital I/O.

Button Arduino Pin Function
Start Button A0 (Pin 14) Initiates door movement
Limit Switch A1 (Pin 15) Emergency stop sensor

Wiring each button:

  1. Connect one leg of the button to the designated pin (A0 or A1)
  2. Connect the other leg to GND
  3. We will use INPUT_PULLUP mode, so no external resistor is needed
Button Wiring — INPUT_PULLUP Configuration Arduino Uno R3 A0 A1 GND Internal pull-up resistor active Start Button (Momentary Tactile) Leg 1 → A0 Leg 2 → Limit Switch (Safety Stop) Leg 1 → A1 GND rail INPUT_PULLUP: Pin reads HIGH (unpressed) → LOW when button pressed (connected to GND)
Why INPUT_PULLUP?

5. Understanding Pin Change Interrupts

5.1 Why Can't We Use attachInterrupt()?

The Arduino Uno has only two external interrupt pins: Digital 2 (INT0) and Digital 3 (INT1). Since we're using A0 and A1 for our buttons, we must use the Pin Change Interrupt (PCINT) system instead.

5.2 How PCINT Works

The ATmega328P organizes its pins into three Port Groups:

PCINT Group Port Arduino Pins Control Bit
PCINT0 (Group 0) Port B D8-D13 PCIE0
PCINT1 (Group 1) Port C A0-A5 PCIE1
PCINT2 (Group 2) Port D D0-D7 PCIE2

Since A0 and A1 are on Port C, we need to enable PCINT1 (Group 1).

5.3 The PCINT Registers

To enable Pin Change Interrupts, we configure two special registers:

Register 1: PCICR (Pin Change Interrupt Control Register)

This is the Master Switch that enables interrupt groups.

PCICR Register:
Bit:   7    6    5    4    3    2     1     0
Name:   -    -    -    -    -   PCIE2   PCIE1   PCIE0

We need to set PCIE1 (bit 1) to enable Group 1 (Port C / Analog pins):

PCICR = PCICR | (1 << PCIE1);  // Enable Group 1

Register 2: PCMSK1 (Pin Change Mask Register 1)

This selects which specific pins in Group 1 trigger interrupts.

PCMSK1 Register:
Bit:    7      6       5       4       3       2       1       0
Name:   -   PCINT14   PCINT13   PCINT12   PCINT11   PCINT10   PCINT9   PCINT8
Pin:    -     A6*      A5       A4       A3       A2       A1       A0

For our buttons on A0 and A1:

PCMSK1 = PCMSK1 | (1 << PCINT8);  // Enable A0
PCMSK1 = PCMSK1 | (1 << PCINT9);  // Enable A1

5.4 The ISR (Interrupt Service Routine)

When any enabled pin in Group 1 changes state (HIGH→LOW or LOW→HIGH), the microcontroller:

  1. Pauses the current code execution
  2. Saves the current position
  3. Runs the ISR function ISR(PCINT1_vect)
  4. Returns to where it left off
Important: All pins in a PCINT group share the same ISR. Inside the ISR, you must check which pin actually changed.

6. The Starter Code

Upload this code to your Arduino and study how each section works:

#include <Stepper.h>

// --- Configuration ---
// The 28BYJ-48 motor has 2048 steps per full rotation
const int stepsPerRev = 2048; 
// Initialize the stepper library on pins 8, 10, 9, 11 (Note the specific order!)
Stepper myGarageMotor(stepsPerRev, 8, 10, 9, 11);

// --- Global Variables ---
// 'volatile' is REQUIRED for variables modified inside an ISR
// It tells the compiler: "This value can change unexpectedly - 
// always read from memory, don't optimize it away"
volatile bool emergencyStop = false; // Is the safety switch pressed?
volatile bool doorMoving = false;    // Is the door currently supposed to be moving?
                                     // Also 'volatile' because the ISR writes this too!

void setup() {
  Serial.begin(9600);
  Serial.println("=== Garage Door System Initialized ===");

  // 1. Setup Motor Speed
  myGarageMotor.setSpeed(10); // Run at 10 RPM (slow and safe)

  // 2. Setup Buttons
  // INPUT_PULLUP: Pin is HIGH (1) when not pressed, LOW (0) when pressed
  pinMode(A0, INPUT_PULLUP); // Start Button
  pinMode(A1, INPUT_PULLUP); // Safety Limit Switch

  // 3. ENABLE PIN CHANGE INTERRUPTS
  
  noInterrupts(); // Disable all interrupts while configuring

  // A. Turn on the "Group 1" Master Switch (controls A0-A5)
  // PCIE1 is the bit name for enabling Port C interrupts
  PCICR = PCICR | (1 << PCIE1); 

  // B. Enable the specific pins A0 and A1
  // PCINT8 corresponds to Pin A0
  // PCINT9 corresponds to Pin A1
  PCMSK1 = PCMSK1 | (1 << PCINT8); // Enable interrupt on A0
  PCMSK1 = PCMSK1 | (1 << PCINT9); // Enable interrupt on A1

  interrupts(); // Re-enable interrupts

  Serial.println("Interrupts configured. System ready.");
}

// --- The Interrupt Service Routine (The Emergency Responder) ---
// This code runs AUTOMATICALLY whenever A0 or A1 changes state.
// The name "PCINT1_vect" is fixed - it's the handler for Port C interrupts.
ISR(PCINT1_vect) {
  // CHECK THE LIMIT SWITCH (A1) - SAFETY PRIORITY
  // LOW = button pressed (because we're using INPUT_PULLUP)
  if (digitalRead(A1) == LOW) {
    emergencyStop = true;  // TRIGGER THE EMERGENCY STOP
    doorMoving = false;    // Cancel any movement command
    // Note: We avoid Serial.print() inside ISR - it's too slow!
  }
  // If the Limit Switch is released (HIGH)
  else {
    emergencyStop = false; // Clear the emergency flag
  }

  // CHECK THE START BUTTON (A0)
  // Only start if A0 is pressed AND we are not in an emergency
  if (digitalRead(A0) == LOW && emergencyStop == false) {
    doorMoving = true; // Signal to start the door
  }
}

// --- The Main Loop (The Daily Routine) ---
void loop() {
  // If the door is commanded to move...
  if (doorMoving == true) {
    
    // ... and the safety switch is clear (not pressed)...
    if (emergencyStop == false) {
      Serial.println("Door Moving...");
      
      // Move the motor just 10 steps at a time
      // This keeps each motor command short, allowing
      // more frequent interrupt checks between steps
      myGarageMotor.step(10); 
    } 
    else {
      // If emergencyStop became true during movement
      Serial.println("!!! SAFETY STOP - Obstacle Detected !!!");
      doorMoving = false;
    }
  }
}

6.1 Key Code Concepts Explained

The volatile Keyword

volatile bool emergencyStop = false;

The volatile keyword tells the compiler:

Without volatile: The compiler might optimize your code by keeping emergencyStop in a CPU register. The ISR would update RAM, but loop() would never see the change!

Why Move Only 10 Steps?

Instead of myGarageMotor.step(2048) (full rotation), we use:

myGarageMotor.step(10);

Reason: Each .step() call blocks until complete. By using small steps:


7. Lab Procedure (Testing Protocol)

Follow these steps to verify your system works correctly:

Test 1: Idle State Verification

  1. Power on the Arduino
  2. Open the Serial Monitor (9600 baud)
  3. Expected Result: Motor is stationary. Message shows "System ready."

Test 2: Start Button Functionality

  1. Press and release the Start Button (A0)
  2. Expected Result:
    • Motor starts rotating continuously
    • Serial Monitor shows "Door Moving..." messages

Test 3: The Critical Safety Test ⚠️

  1. While the motor is spinning, press and hold the Limit Switch (A1)
  2. Expected Result:
    • Motor stops INSTANTLY (within one step cycle)
    • Serial Monitor shows "!!! SAFETY STOP !!!"
  3. What's happening internally:
    • The ISR detected A1 went LOW
    • emergencyStop was set to true
    • doorMoving was set to false
    • The main loop saw these flags and stopped sending steps

Test 4: Recovery and Reset

  1. Release the Limit Switch (A1)
  2. Motor remains stopped (by design - requires manual restart)
  3. Press the Start Button (A0) again
  4. Expected Result: Motor resumes movement

Test 5: Safety Priority Verification

  1. Hold the Limit Switch (A1) pressed
  2. While holding, press the Start Button (A0)
  3. Expected Result: Motor does NOT start (safety takes priority)

8. Why Interrupts Are Better Than Polling

Consider this alternative approach using polling (checking buttons in the loop):

// BAD APPROACH - Do not use!
void loop() {
  if (digitalRead(A0) == LOW) {
    // Full rotation takes several seconds!
    myGarageMotor.step(2048);
  }
  // If someone presses A1 during the rotation above,
  // we won't see it until all 2048 steps complete!
}
The Problem:
The Interrupt Solution:

9. Deliverables and Submission

Create a submission folder containing:

9.1 Arduino Sketch (.ino)

Your fully commented code file that includes:

  1. Header comment block:
    • Your name and student ID
    • Lab title and date
    • Brief description of the system
  2. Inline comments explaining:
    • Each register configuration (PCICR, PCMSK1)
    • Why volatile is used
    • The logic flow of the ISR
    • How the safety flag system works

9.2 Video Demonstration

Submit a cloud link (OneDrive, Google Drive, YouTube Unlisted, etc.) to a video showing:

Demonstration What to Show Duration
1. System Startup Power on, Serial Monitor output ~15 sec
2. Normal Operation Press Start, show motor turning ~20 sec
3. Emergency Stop Press Limit Switch while motor runs, show instant stop ~20 sec
4. Safety Priority Try to start with Limit Switch held ~15 sec

Total video length: Approximately 1-2 minutes

Video Requirements:

10. Grading Rubric

Criteria Marks Details
Hardware Setup 15 Correct wiring of motor driver, buttons, and power connections
Interrupt Configuration 20 Correct PCICR and PCMSK1 register setup with clear comments
ISR Implementation 20 Proper use of volatile, correct button state detection, appropriate ISR logic
Safety Flag System 15 Emergency stop correctly halts motor, prevents restart while active
Code Quality 15 Well-structured code, meaningful variable names, comprehensive comments
Video Demonstration 15 Clear demonstration of all tests, good explanation of functionality
Total 100

11. Troubleshooting Guide

Motor Not Moving

  1. Check ULN2003 power connections (5V and GND)
  2. Verify pin order in Stepper() constructor matches your wiring
  3. Ensure motor connector is fully seated on driver board

Buttons Not Responding

  1. Confirm INPUT_PULLUP mode is set
  2. Check button wiring (one leg to pin, other to GND)
  3. Verify correct pin numbers (A0 = pin 14, A1 = pin 15)

Interrupts Not Triggering

  1. Ensure noInterrupts() is followed by interrupts()
  2. Check PCIE1 bit is set in PCICR
  3. Verify PCINT8 and PCINT9 are set in PCMSK1

Motor Doesn't Stop on Safety Press

  1. Confirm emergencyStop is declared as volatile
  2. Check the pin reading inside ISR uses correct logic (LOW = pressed)
  3. Verify both ISR flag setting and loop() flag checking are correct