// Autore: Vittori Matteo
// Matricola: 126646
// link analisi:
// https://docs.google.com/document/d/1UHxso-cX7cJiFWlDNlBvoOX_gzhk8qu8950fqoEQPIY/edit?usp=sharing
// Definizioni dei pin che sono usati nel sistema
#define ECHO_PIN 2
#define BUTTON_PIN 3
#define TRIG_PIN 4
#define BUZZER_PIN 5
#define RELAY_PIN 7
#define SEGMENTS_PIN PORTC // A0-A6 -> Segmenti (a-g)
#define DIGITS_PIN PORTB // D8-D11 -> Cifre (Digit 0-3)
// definisce il numero di millisecondi necessari per la gestione del debounce,
// sapendo che 5 * 8.192 ≈ 25 ms
#define DEBOUNCE_TIME 3
// definisce la durata sotto la quale la pressione viene tradotta in '.' e
// sopra la quale in '_', 80 in quanto 49 * 8.192 ≈ 400ms
#define MORSE_DURATION 49
// definisce la lunghezza del correctMorseCode
#define CODE_LENGTH 3
// definisce il numero massimo di tentativi che l'utente può fare,
// NOTA: il display è impostato per visualizzare un numero di tentativi <= di 3,
// perciò inserendo un numero maggiore, finché il numero di tentativi è > di 3,
// il numero di tentativi rimanenti non verrà visualizzato.
#define MAX_ATTEMPTS 3
// Variabili per gestione CODICE MORSE
// inserire nella variabile 'correctMorseCode' 0('.') o 1('_') in base al codice che si vuole generare e modificare
// 'CODE_LENGTH' in base alla lunghezza del codice inserito.
// La lunghezza massima del codice dipende dalla grandezza della variabile nella quale viene memorizzato,
// es. uint8_t (8 simboli max), uint16_t (16 simboli max), etc.
// Perciò se si vuole un codice più lungo basta modificare la grandezza di 'correctMorseCode' e 'morseCode'.
volatile uint8_t correctMorseCode = 0b010; // Codice Morse segreto, **(8 simboli max)**
volatile uint8_t morseCode; // Variabile per salvare il codice Morse inserito
volatile uint8_t codeLength; // Variabile per gestione del codice inserito
volatile uint8_t errors; // numero errori commessi
volatile bool flagStart; // flag che indica che l'inseriento è iniziato
volatile bool pinState; // indica lo stato precedente del pin
volatile bool STOP_flag; // indica lo stop all'inserimento del codice
// variabili che permette di decrementare il numero di tenativi possibili ogni volta che si attiva l'allarme
volatile uint8_t max_attemps; // viene inizializzata con il valore di MAX_ATTEMPTS
// contatore a 8 bit che incrementa ad ogni overflow, ogni 8.192ms,
// con questo si riesce a contare ≈ 2 secondi
volatile uint16_t overflowCount; // utilizzato per la gestione timeout, pressione e fine inserimento del codice
// Variabili per gestione DISPLAY
// MESSAGE_DURATION: Questa variabile indica la durata totale di visualizzazione di un messaggio sul display
// Viene utilizzata per determinare il termine della visualizzazione del messaggio e per la gestione del lampeggio
volatile uint16_t count_cycles, MESSAGE_DURATION;
// digit: Questa variabile tiene traccia dell'indice del "digit"
// segments[]: Questo array contiene i valori di segmenti che devono essere accesi per ciascun digit
volatile uint8_t digit, segments[] = {0,0,0,0};
void setup() {
cli(); // Disabilita gli interrupt globali per evitare interruzioni durante la configurazione iniziale
Serial.begin(1000000);
// Impostazione del pin del pulsante come input con pull-up e del pin ECHO (pin D2) come input
DDRD &= ~_BV(BUTTON_PIN); // Pin D3 come input
EICRA |= _BV(ISC10); // Abilita interrupt su pin change
PORTD |= _BV(BUTTON_PIN); // Abilita pull-up interno
// Configura il pin ECHO (pin D2)
DDRD &= ~_BV(ECHO_PIN); // Pin D2 come input
EICRA |= _BV(ISC00); // Configura INT0 per attivarsi su pin change
EIMSK |= _BV(INT0); // Abilita l'interrupt INT0
// Impostazione del pin TRIG (pin D4) come output
DDRD |= _BV(TRIG_PIN); // Pin D4 come output
// Configurazione resgistri per gestione display 7 segmenti 4 digits
DDRC = 0x7F; // A0-A6 = 1 (output)
DDRB = 0x0F; // D8-D11 = 1 (output)
// Configurazione pin buzzer
DDRD |= _BV(BUZZER_PIN); // D5 come output
// Configurazione pin RELAY
DDRD |= _BV(RELAY_PIN); // Imposta il pin 7 come output
// Configurazione del Timer0 per gestione BUZZER e segnale TRIG
TCCR0A = 0; // Modalità normale
TCCR0B = (_BV(WGM01)|_BV(CS01)|_BV(CS00)); // CTC, prescaler 64
OCR0A = 37; // Frequenza ≈ 750 Hz
OCR0B = 3; // 3 * 4µs ≈ 12µs
TIMSK0 = 0; // Disabilita tutti gli interrupt del Timer 0
// Configurazione del Timer1 per gestione DISPLAY
TCCR1A = 0; // Modalità normale
TCCR1B |= _BV(CS11); // Imposta il prescaler a 8 (1 tick ogni ≈ 0.5µs)
TCCR1B &= ~(_BV(CS12) | _BV(CS10)); // reset dei pin non di interesse
OCR1A = 10000; // 10000 * 0.5µs ≈ 5ms
OCR1B = 10000; // utilizzato per gestione lampeggio del messaggio '8888'
// configurazione di Timer2 per gestione inserimento codice morse
TCCR2B |= _BV(CS22) | _BV(CS21); // Imposta prescaler 256 in modo da avere un overflow ogni 8.192ms
// inizializza le variabili utilizzate
flagStart = overflowCount = errors = morseCode = 0;
// inizialmente il numero massimo di tantativi dipsonibili corrisponde a quello definito
max_attemps = MAX_ATTEMPTS;
// Configura il timer watchdog
setupWatchdogTimer();
sei(); // Riabilita gli interrupt globali dopo la configurazione
// Imposta sleep mode su Power-down
SMCR |= _BV(SM1); // Power-down mode
startSleepMode(); // entra in modalità risparmio energetico
}
void loop() {
}
// Codice da eseguire quando il Watchdog Timer genera un interrupt
ISR(WDT_vect) {
Serial.print("DEBUG: WDT interrupt "); // **DEBUG**
// Invia impulso TRIG
PORTD |= _BV(TRIG_PIN); // Attiva TRIG
TCNT0 = 0;
TIMSK0 |= _BV(OCIE0B); // Abilita l'interrupt
}
// ISR per disattivare il TRIG dopo ≈ 12µs
ISR(TIMER0_COMPB_vect) {
TIMSK0 &= ~_BV(OCIE0B); // Disabilita l'interrupt per OCR0B
PORTD &= ~_BV(TRIG_PIN); // Disattiva TRIG
Serial.println("- TRIG inviato "); // **DEBUG**
}
// ISR per la gestione del segnale ECHO e calcolo distanza rilevata
ISR(INT0_vect) {
if(PIND & _BV(ECHO_PIN)){
// Rising edge: inizia la misurazione
TCNT1 = 0; // Resetta Timer1
}else{
// oggetto rilevato entro 30cm ((1740µs * 0.5(tick ogni 0.5 µs)) / 58) = 30cm
if (TCNT1 < 3480){
Serial.println("DEBUG: oggetto rilevato "); // **DEBUG**
TCCR1B |= _BV(WGM12); // abilita la modalità CTC su timer1
WDTCSR &= ~_BV(WDIE); // disabilita il WDT interrupt
_ciao_MESSAGE();
}
else{
Serial.println("DEBUG: nessun oggetto rilevato "); // **DEBUG**
// oggetto non rilevato entro 30cm, start sleep mode
startSleepMode();
}
}
}
// ISR per gestione messaggi su DISPLAY, che si attiva ogni 5ms
ISR(TIMER1_COMPA_vect) {
// Spegne tutti i digits
DIGITS_PIN = 0; // Per anodo comune, tutti i pin sono impostati a 0 per spegnere i digit
// Lampeggio non attivo
SEGMENTS_PIN = ~segments[digit]; // Inverte i segmenti (anodo comune usa logica opposta)
DIGITS_PIN = _BV(digit); // Seleziona il digit con un segnale alto
digit = (digit + 1) & 0b11; // incremento del contatore digit
// controlla se è stata raggiunta la durata del messaggio voluta
if (count_cycles++ == MESSAGE_DURATION) {
TIMSK1 &= ~_BV(OCIE1A); // Disabilita l'interrupt per OCR1A
DIGITS_PIN = 0; // Spegne i digits
count_cycles = 0; // Reset contatore
// gestisce l'attivazione/disabilitazione degli interrupt in base al messaggio visualizzato
checkStopFlagAndHandle();
}
}
// ISR per gestione messaggio '8888' su DISPLAY, che si attiva ogni 2.5ms
ISR(TIMER1_COMPB_vect) {
// Spegne tutti i digits
DIGITS_PIN = 0; // Per anodo comune, tutti i pin sono impostati a 0 per spegnere i digit
// Lampeggio attivo
if((count_cycles & 0xFF) < 128){ // Maschera bit per ottenere lo stesso effetto di % 128
// Aggiorna i segmenti e attiva il digit corrispondente
SEGMENTS_PIN = ~segments[digit];
DIGITS_PIN = _BV(digit);
digit = (digit + 1) & 0b11;
}
// controlla se è stata raggiunta la durata del messaggio voluta
if (count_cycles++ == MESSAGE_DURATION) {
TIMSK1 &= ~_BV(OCIE1B); // Disabilita l'interrupt per OCR1B
TIMSK0 &= ~_BV(OCIE0A); // Disabilita l'interrupt per OCR0A
DIGITS_PIN = 0; // Spegne i digits
count_cycles = 0; // Reset contatore
WDTCSR |= _BV(WDIE); // Abilita interrupt WDT
TCCR1B &= ~_BV(WGM12); // Disabilita la modalità CTC su timer1
startSleepMode();
}
}
// ISR per gestione BUZZER
ISR(TIMER0_COMPA_vect) {
// Inverte lo stato del pin D5 per generare il segnale PWM
PORTD ^= _BV(PORTD5);
}
// ISR per gestione tempi di inserimento CODICE MORSE
ISR(TIMER2_OVF_vect) {
overflowCount++; // Incrementa il contatore overflow
// controlla che non siano stati superati i 2 s dall'ultima pressione
// nel caso venga superato il codice viene considerato come inserito e si procede con la valutazione del codice
if(flagStart && overflowCount == 0xff) { // 255 * 8.192ms ≈ 2s
Serial.println(); // **DEBUG**
TIMSK2 &= ~_BV(TOIE2); // Disabilita interrupt su overflow (Timer2)
overflowCount = 0; // reset counter
EIMSK &= ~_BV(INT1); // Disabilita interrupt su INT1
checkCode();
}else{
// timeout inserimento codice
if(overflowCount >= 732){ // 732 * 8.192ms ≈ 6s (5,99s)
TIMSK2 &= ~_BV(TOIE2); // Disabilita interrupt su overflow (Timer2)
overflowCount = 0; // reset counter
Serial.println("DEBUG: timeout inserimento codice"); // **DEBUG**
EIMSK &= ~_BV(INT1); // Disabilita interrupt su INT1
TCCR1B &= ~_BV(WGM12); // Disabilita la modalità CTC su timer1
WDTCSR |= _BV(WDIE); // Abilita interrupt WDT
startSleepMode();
}
}
}
// ISR per gestione PULSANTE e inserimento CODICE MORSE
ISR(INT1_vect) {
bool currentState = !(PIND & _BV(BUTTON_PIN)); // Stato attuale del pin (premuto o rilasciato)
// Gestisci solo se lo stato è cambiato
if (currentState != pinState) {
pinState = currentState; // Aggiorna lo stato del pin
if (currentState) { // Pulsante premuto
overflowCount = 0; // Registra il momento di inizio della pressione
flagStart = true; // Attiva il flag di inizio
} else { // Pulsante rilasciato
if (overflowCount > DEBOUNCE_TIME) {
morseCode <<= 1; // Shift a sinistra per fare spazio al nuovo simbolo
codeLength++;
if (overflowCount < MORSE_DURATION) {
// Aggiungi un `0` per il punto
Serial.print("."); // **DEBUG**
} else {
morseCode |= 1; // Aggiunge un `1` per la linea
Serial.print("_"); // **DEBUG**
}
}
}
}
}
void checkStopFlagAndHandle() {
if (STOP_flag) {
PORTD &= ~_BV(RELAY_PIN); // Imposta il pin 7 a LOW per disattivare il relay
STOP_flag = false; // Reset flags
WDTCSR |= _BV(WDIE); // Abilita interrupt WDT
TCCR1B &= ~_BV(WGM12); // Disabilita la modalità CTC su timer1
startSleepMode();
} else {
EIMSK |= _BV(INT1); // Abilita l'interrupt INT1
TIMSK2 |= _BV(TOIE2); // Abilita interrupt su overflow
}
}
// metodo che si occupa della valutazione del codice insirito
void checkCode(){
Serial.print("DEBUG: checkCode() "); // **DEBUG**
if(morseCode == correctMorseCode && codeLength == CODE_LENGTH){
// Il codice inserito è corretto perciò procedo con sblocco serratura e visualizzando 'Ent'
correctCode();
}
else{
// il codice inserito è sbagliato
if(++errors == max_attemps) {
// Il codice inserito è sbagliato per più di 3 tentativi perciò il display lampeggia "8888",
// ed attiva una sirena di allarme.
alarm();
}
else{
// Il codice inserito è sbagliato,
// il display visualizza "Er n" dove n è il numero di tentativi rimasti
attemptsFailed();
}
}
}
void correctCode() {
max_attemps = 3; // una volta che il codice inserito è corretto il numero max di tentativi viene resettato
Serial.println("- correctCode() "); // **DEBUG**
// Reset delle variabili utilizzate
flagStart = morseCode = errors = codeLength = 0;
_Ent_MESSAGE();
}
void alarm() {
if(max_attemps > 1)max_attemps--;
Serial.println("- alarm() "); // **DEBUG**
// Reset delle variabili utilizzate
flagStart = morseCode = errors = codeLength = 0;
_8888_MESSAGE();
}
void attemptsFailed() {
Serial.println("- attemptsFailed() "); // **DEBUG**
// resert delle variabili utilizzate per l'inserimento del codice
flagStart = morseCode = codeLength = 0;
_Er_MESSAGE();
}
void _ciao_MESSAGE() {
Serial.println("DEBUG: _ciao_MESSAGE() "); // **DEBUG**
// 'C', 'I', 'A', 'O'
segments[0] = 0b00111001;
segments[1] = 0b00110000;
segments[2] = 0b01110111;
segments[3] = 0b00111111;
MESSAGE_DURATION = 600; // imposta la durata del messaggio a 400 che equivale a 3s(600 * 5ms)
TIMSK1 |= _BV(OCIE1A); // abilita l'interrupt per OCR1A
}
void _Ent_MESSAGE() {
Serial.print("DEBUG: _Ent_MESSAGE() "); // **DEBUG**
// 'E', 'n', 't'
segments[0] = 0b01111001;
segments[1] = 0b01110100;
segments[2] = 0b01110000;
segments[3] = 0; // Spazio vuoto
MESSAGE_DURATION = 600; // imposta la durata del messaggio a 600 che equivale a 3s
STOP_flag = true;
Serial.println("- attivazione relay "); // **DEBUG**
PORTD |= _BV(RELAY_PIN); // Imposta il pin 7 a HIGH per attivare il relè
TIMSK1 |= _BV(OCIE1A); // abilita l'interrupt per OCR1A
}
void _8888_MESSAGE() {
Serial.println("DEBUG: _8888_MESSAGE() "); // **DEBUG**
// visualizza '8888'
segments[0] = 0b01111111;
segments[1] = 0b01111111;
segments[2] = 0b01111111;
segments[3] = 0b01111111;
TIMSK0 |= _BV(OCIE0A); // Abilita l'interrupt per il match OCR0A
MESSAGE_DURATION = 6000; // imposta la durata del messaggio a 6000 che equivale a 30s
TIMSK1 |= _BV(OCIE1B); // abilita l'interrupt per OCR1A
}
void _Er_MESSAGE() {
Serial.println("DEBUG: _Er_MESSAGE() "); // **DEBUG**
// 'E', 'r', spazio, n errori
segments[0] = 0b01111001;
segments[1] = 0b01110000;
segments[2] = 0; // Spazio vuoto
// Associa il numero binario corretto in base a errors
switch (max_attemps-errors) {
case 0:
segments[3] = 0b00111111; // 'O'
break;
case 1:
segments[3] = 0b00110000; // '1'
break;
case 2:
segments[3] = 0b01011011; // '2'
break;
default:
segments[3] = 0; // Spazio vuoto per valori non validi
break;
}
MESSAGE_DURATION = 400; // imposta la durata del messaggio a 400 che equivale a 2s
TIMSK1 |= _BV(OCIE1A); // abilita l'interrupt per OCR1B
}
// Configura il timer watchdog per generare interrupt ogni 4 secondi
void setupWatchdogTimer() {
MCUSR &= ~_BV(WDRF); // Resetta il flag WDT
WDTCSR |= _BV(WDCE) | _BV(WDE); // Abilita la modifica dei registri del watchdog
// WDP3 = 1000 -> 4 secondi
WDTCSR = _BV(WDP3); // Configura il prescaler per 4 secondi
WDTCSR |= _BV(WDIE); // Abilita interrupt WDT
}
// Configura il microcontrollore per la modalità Power-down (consumo minimo)
void startSleepMode() {
Serial.println("DEBUG: sleep "); // **DEBUG**
SMCR |= _BV(SE); // Abilita la modalità Sleep
asm volatile("sleep"); // Esegue l'istruzione sleep per mettere il microcontrollore in modalità Power-down
SMCR &= ~_BV(SE); // Disabilita la modalità sleep dopo il risveglio
}