Course: DE6417 Microcontrollers 2
Assessment Type: Laboratory (Practical)
Weighting: 7%
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.
loop()), interrupts
act like an "emergency brake" that the hardware triggers automatically, ensuring immediate response
regardless of what code is currently executing.
This lab assesses the following course learning outcomes:
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 |
| 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 |
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 |
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:
INPUT_PULLUP mode, so no external resistor is neededattachInterrupt()?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.
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).
To enable Pin Change Interrupts, we configure two special registers:
This is the Master Switch that enables interrupt groups.
We need to set PCIE1 (bit 1) to enable Group 1 (Port C / Analog pins):
PCICR = PCICR | (1 << PCIE1); // Enable Group 1
This selects which specific pins in Group 1 trigger interrupts.
For our buttons on A0 and A1:
PCMSK1 = PCMSK1 | (1 << PCINT8); // Enable A0
PCMSK1 = PCMSK1 | (1 << PCINT9); // Enable A1
When any enabled pin in Group 1 changes state (HIGH→LOW or LOW→HIGH), the microcontroller:
ISR(PCINT1_vect)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;
}
}
}
volatile Keywordvolatile bool emergencyStop = false;
The volatile keyword tells the compiler:
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!
Instead of myGarageMotor.step(2048) (full rotation), we use:
myGarageMotor.step(10);
Reason: Each .step() call blocks until complete. By using small steps:
emergencyStop more frequentlyFollow these steps to verify your system works correctly:
emergencyStop was set to truedoorMoving was set to falseConsider 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!
}
myGarageMotor.step(2048) takes ~12 seconds at 10 RPMemergencyStop = trueCreate a submission folder containing:
Your fully commented code file that includes:
volatile is usedSubmit 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
| 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 |
Stepper() constructor matches your wiringINPUT_PULLUP mode is setnoInterrupts() is followed by interrupts()emergencyStop is declared as volatile