The system is structured around a 4-state Finite State Machine (FSM) running on the ATmega328P. All peripherals are driven using direct register manipulation.
Hardware Modules:
Bill of Materials:
| Component | Quantity | Notes |
| —————————- | ———- | ————————————– |
| Arduino Nano ATmega328P | 1 | 16 MHz, 5V |
| Passive buzzer | 1 | Sound |
| Push buttons | 7 | Password input, pull-up resistors |
| Reset button | 1 | Reset purpose |
| Green LED + 220Ω resistor | 1 | Correct password indicator |
| Red LED + 220Ω resistor | 1 | Wrong password indicator |
| 16×2 LCD (HD44780) | 1 | I2C adapter |
| Breadboard + jumper wires | - | Prototyping |
| USB cable | 1 | For flashing the ATmega328P |
| PIR Sensor (HC-SR501) | 1 | Motion detection for automatic arming |
| Servo Motor SG90 | 1 | physical door lock mechanism |
Electrical Notes:
Software Design:
Password Validation FSM:
The system uses a finite state machine with 4 states:
^ State ^ Code ^ Description ^
| IDLE / Active | stare = 0 | System waiting; allows arming/disarming PIR and password change |
| ALARM countdown | stare = 1 | 30s countdown, periodic beep, user enters password |
| ALARM triggered | stare = 2 | Time expired or wrong password; red LED + continuous buzzer |
| Password change | stare = 3 | Two-step sub-FSM: verify old password, then set new one |
FSM Transitions:
0 → 1: Start button pressed (PD2) OR PIR armed + motion detected (PB3)1 → 0 (success): 4 digits entered, password correct → servo opens, system resets1 → 2 (failure): Timer expires (30s) OR wrong password entered2 → 0: Correct password entered even in alarm state → system resets0 → 3: Password-change button pressed (PC1)3 → 0: Old password verified + new password saved to EEPROMThe project uses exclusively bare-metal avr-libc — no Arduino Framework. This choice provides full control over peripheral registers, deterministic timing, smaller code size, and compatibility with any AVR toolchain (avr-gcc + avrdude).
<avr/io.h>
Provides symbolic definitions for all ATmega328P peripheral registers (DDRx, PORTx, PINx, TCCRx, OCRx, TWCR, UBRR0, etc.). Without this header, raw hexadecimal register addresses would be required, making the code unportable and unreadable. It is an absolute necessity for any bare-metal AVR project.
<util/delay.h>
Provides _delay_ms() and _delay_us(), computed at compile time based on F_CPU = 16000000UL. Used in:
A hardware timer alternative would be more CPU-efficient for long waits, but for short and precise initialization delays, _delay_us() is the correct approach.
<avr/eeprom.h>
Provides eeprom_update_byte() and eeprom_read_byte() for safe access to the 1 KB internal EEPROM of the ATmega328P. Critically, eeprom_update_byte() (unlike eeprom_write_byte()) checks whether the existing value equals the new value before performing a write. This extends EEPROM lifetime (rated ~100,000 write cycles per cell) — important when the same password bytes might be rewritten repeatedly. Used to persist the 4-digit password across power cycles.
The implementation is directly based on concepts from the following laboratories:
Laboratory 0 — GPIO
Used for all digital inputs and outputs. Pin direction is set via DDRx, output values via PORTx, and input states read via PINx. Internal pull-ups (PORTx |= (1 « Pxy) with DDRx &= ~(1 « Pxy)) eliminate the need for external resistors on all 6 buttons. LED driving, buzzer toggling, and servo signal generation all rely on basic GPIO operations.
Laboratory 1 — UART
Integrated for real-time debugging. Every significant state transition (alarm activation, key press, password change, arming) generates a descriptive UART message transmitted at 9600 baud. Initialization uses the formula UBRR = F_CPU / 16 / BAUD − 1 = 103 for 9600 baud at 16 MHz. Transmission uses polling on the UDRE0 flag in UCSR0A.
Laboratory 2 — Interrupts
Timer1 uses the hardware CTC overflow flag (OCF1A in TIFR1) checked by polling in the main loop. This decouples the 1-second countdown from the rest of the loop execution, ensuring temporal accuracy independent of code execution time. The flag is cleared by writing a logical 1 to it (TIFR1 |= (1 « OCF1A)) — the AVR write-1-to-clear mechanism. Button edge detection (comparing current and previous state) provides interrupt-like single-trigger behaviour without ISR overhead.
Laboratory 3 — Timers
Timer1 is configured in CTC mode (WGM12 = 1) with prescaler 1024 (CS12 = 1, CS10 = 1) and OCR1A = 15624, generating a precise 1 Hz event: 16,000,000 / 1024 / (15624 + 1) = 1 Hz. TCNT1 is reset to 0 at alarm activation, and the OCF1A flag is polled each main loop iteration to count elapsed seconds for the 30-second countdown.
Laboratory 6 — I2C
The HD44780 LCD with PCF8574 I2C adapter is controlled via a manually implemented I2C driver (no third-party library). Hardware TWI registers used: TWBR = 12 for ~400 kHz fast mode, TWSR = 0 for prescaler 1, TWCR for start/stop/write control. Each LCD character requires 2 I2C transactions (upper nibble + lower nibble), each including an enable pulse. This custom implementation allows full control over I2C address, backlight, and LCD command timing.
The main while(1) loop is organized as a sequential state machine. Each iteration evaluates the current state and performs the appropriate actions:
while(1) { // 1. Update PIR indicator LED (state-independent) // 2. In state 0: check Arming button (PB4) and Password-Change button (PC1) // 3. Check alarm trigger: Start button (PD2) OR PIR + motion (PB3) // 4. In state 1: check Timer1 flag (TIFR1) → decrement countdown, beep // 5. In states 1 and 2: read keypad → validate password // 6. In state 2: toggle buzzer (PD7) for continuous alarm // 7. In state 3: two-step password change sub-FSM }
Key interactions between functionalities:
reset_efectuat = 1) OR PIR armed + motion detected (PB3 HIGH). Resets TCNT1 = 0, secunde = 0, taste_apasate = 0.parola_setata[]. Servo opens, reset_efectuat = 0 (blocks new start until RESET is pressed).reset_efectuat = 0 → confirms reset, sets reset_efectuat = 1.eeprom_citeste_parola()) and written only when explicitly changed (eeprom_salveaza_parola()).Detailed pin mapping with justification for each choice:
^ Arduino Nano Pin ^ AVR Pin ^ Direction ^ Connected Component ^ Justification ^
| A0 | PC0 | Output | Servo SG90 signal | Available digital pin on port C; soft PWM implemented manually with delay loops (1000 µs / 1500 µs pulses). Hardware timer not required. |
| A1 | PC1 | Input, internal pull-up | Password-change button | Free pin on port C, adjacent to servo. Active-low, internal pull-up enabled. |
| D2 | PD2 | Input, internal pull-up | Start / Reset button | PD2 doubles as INT0 external interrupt pin — future upgrade potential. Currently used with polling. |
| D3 | PD3 | Input, internal pull-up | Password button 1 | PD3 doubles as INT1. Available on port D. |
| D4 | PD4 | Input, internal pull-up | Password button 2 | Available digital pin on port D. |
| D5 | PD5 | Output | Green LED | PWM-capable pin (OC0B), used as simple GPIO for the OK status indicator. |
| D6 | PD6 | Output | Red LED | PWM-capable pin (OC0A), used as simple GPIO for the alarm indicator. |
| D7 | PD7 | Output | Passive buzzer | Simple digital pin; tone frequency generated by GPIO toggle with delays (500 µs ≈ 1 kHz). |
| D8 | PB0 | Input, internal pull-up | Password button 3 | Available digital pin on port B. |
| D9 | PB1 | Input, internal pull-up | Password button 4 | Available digital pin on port B. |
| D10 | PB2 | Output | PIR armed indicator LED | Available pin on port B; visually shows when the PIR sensor is armed. |
| D11 | PB3 | Input, internal pull-up | PIR sensor output | HC-SR501 digital output (HIGH = motion). Monitored by polling in state 0. |
| D12 | PB4 | Input, internal pull-up | Arming button | Available pin on port B for the arm/disarm toggle button. |
| A4 | PC4 | I/O (SDA) | LCD via I2C | Hardware I2C SDA pin on ATmega328P. Mandatory for the PCF8574 I2C adapter. |
| A5 | PC5 | I/O (SCL) | LCD via I2C | Hardware I2C SCL pin on ATmega328P. Mandatory for the PCF8574 I2C adapter. |
| D1 | PD1 | Output (TX) | UART debug | Hardware USART0 TX pin. Transmits debug messages at 9600 baud to Serial Monitor. |
DDRx &= ~(1 « Pxy); PORTx |= (1 « Pxy);. Buttons are active-low: logic goes LOW when pressed (connected to GND). No external pull-up resistors needed.