#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <stdio.h>
// --- COSTANTI E MACRO DI SISTEMA ---
#define MEM_MAX 600 // Limite invalicabile per il buffer RAM (evita la collisione con lo Stack)
#define sbi(reg, bit) (reg |= (1 << bit)) // Set Bit: accende un singolo bit tramite maschera OR
#define cbi(reg, bit) (reg &= ~(1 << bit)) // Clear Bit: spegne un singolo bit tramite maschera AND invertita
#define F_CPU 16000000UL // Frequenza del cristallo di quarzo (16 MHz)
#define BAUD 9600 // Velocità di trasmissione seriale standard
#define MYUBRR F_CPU/16/BAUD-1 // Calcolo a tempo di compilazione per il registro di baud rate
// --- FUNZIONI UART (Comunicazione Seriale) ---
// Inizializza l'ufficio telegrafico: imposta velocità e formato del pacchetto dati (8 bit, 2 stop bit)
void UART_Init(unsigned int ubrr)
{
UBRR0H = (unsigned char)(ubrr>>8); // Carica gli 8 bit più significativi
UBRR0L = (unsigned char)ubrr; // Carica gli 8 bit meno significativi
UCSR0B = (1<<TXEN0); // Accende esclusivamente il trasmettitore (TX)
UCSR0C = (1<<USBS0)|(3<<UCSZ00); // Imposta 2 bit di stop e pacchetti da 8 bit di dati
}
// Il "Manovale": attende il via libera dall'hardware e trasmette un singolo byte fisicamente
void UART_Transmit(char data)
{
while (!(UCSR0A & (1<<UDRE0))); // Attesa bloccante finché il registro dati non è vuoto (spia UDRE0)
UDR0 = data; // Inserisce il carattere nel registro per l'invio fisico
}
// Il "Manager": scompone una stringa di testo e la affida lettera per lettera al manovale
void UART_Print(const char* str)
{
while (*str)
{
UART_Transmit(*str++); // Trasmette il carattere puntato e avanza alla cella di memoria successiva
}
}
// --- VARIABILI GLOBALI (La "Lavagna" Condivisa) ---
uint16_t ram_buffer[MEM_MAX]; // Array di accumulo per i tempi catturati
uint16_t indice_ram = 0; // Puntatore alla riga di memoria corrente
uint8_t stato_sistema = 0; // Macchina a stati: 0=Attesa, 1=Campionamento, 2=Stampa, 3=Fine
uint8_t ultimo_stato_in = 0; // Tracciamento logico per il rilevamento dei fronti (variazioni)
uint8_t stato_partenza = 0; // Snapshot del livello logico iniziale per l'intestazione VCD
// Variabili per l'Orologiaio (Interrupt Service Routine)
volatile uint16_t tempo_trascorso = 0; // Deve essere 'volatile' perché modificata da un interrupt
// --- IL DIRETTORE (Programma Principale) ---
int main(void)
{
Configura_Hardware();
// Sincronizzazione al "Tempo Zero": leggiamo lo stato fisico reale del pin prima di avviare il sistema
ultimo_stato_in = (PINB & (1 << 0)) ? 1 : 0;
stato_partenza = ultimo_stato_in; // Cristallizza lo stato iniziale
sei(); // Abilita gli interrupt globali (accende il sistema nervoso)
// Ciclo infinito: smista l'esecuzione in base allo stato logico del sistema
while (1)
{
if (stato_sistema == 0 || stato_sistema == 1)
{
Automa_ME1();
}
else if (stato_sistema == 2)
{
Automa_ME3();
}
// Nello Stato 3 il while continua a girare a vuoto, bloccando di fatto il sistema a fine lavoro
}
return 0;
}
void Automa_ME1(void)
{
// Lettura bare-metal: isoliamo il bit 0 del registro PINB tramite maschera (1<<0)
// Operatore ternario: se il bit è 1, in_sig vale 1, altrimenti 0.
uint8_t in_sig = (PINB & (1 << 0)) ? 1 : 0;
// ----------------------------------------------------
// FASE A: STATO 0 (Attesa del primissimo fronte)
// ----------------------------------------------------
if (stato_sistema == 0)
{
if (in_sig != ultimo_stato_in)
{
// Trigger attivato: il segnale è cambiato.
cli(); // Blocco critico: spegne gli interrupt per azzerare i contatori in sicurezza
tempo_trascorso = 0; // Azzera il contatore software
TCNT0 = 0; // Azzera il contatore hardware per annullare sfasamenti al microsecondo
sei(); // Riattiva gli interrupt
ultimo_stato_in = in_sig;
stato_sistema = 1; // Transizione di stato: avvia il campionamento continuo
}
}
// ----------------------------------------------------
// FASE B: STATO 1 (Campionamento in corso)
// ----------------------------------------------------
else if (stato_sistema == 1)
{
// 1. Condizioni di terminazione di sicurezza (Overflow Temporale o Saturazione RAM)
// 65534 è l'ultimo valore utile prima del collasso (overflow) di un intero a 16 bit
if (tempo_trascorso >= 65534 || indice_ram >= MEM_MAX)
{
stato_sistema = 2; // Forza la transizione alla fase di dump (Stampa)
return; // Interrompe il campionamento per questo ciclo
}
// 2. Rilevamento delle variazioni (Cattura dati)
if (in_sig != ultimo_stato_in)
{
cli(); // Operazione atomica per evitare che l'ISR modifichi il tempo mentre lo leggiamo
uint16_t tempo_catturato = tempo_trascorso;
sei();
ram_buffer[indice_ram] = tempo_catturato; // Memorizza l'evento
indice_ram++; // Avanza il puntatore di memoria
ultimo_stato_in = in_sig; // Aggiorna lo storico del segnale
}
}
}
// --- L'OROLOGIAIO (Interrupt Service Routine) ---
// Funzione chiamata in modo asincrono dall'hardware (Timer0) esattamente ogni 50 microsecondi.
ISR(TIMER0_COMPA_vect)
{
tempo_trascorso++; // Incrementa il contatore dei cicli Tau (Tau = 50us)
}
void Automa_ME3(void)
{
char buffer_testo[40]; // Buffer temporaneo per la formattazione testuale dei pacchetti
// 1. Generazione Header VCD. Le macro __DATE__ e __TIME__ vengono risolte in fase di precompilazione
sprintf(buffer_testo, "$date %s %s $end\n", __DATE__, __TIME__);
UART_Print(buffer_testo);
UART_Print("$timescale 1 us $end\n");
UART_Print("$var wire 1 A Segnale_IR $end\n");
UART_Print("$dumpvars\n");
// Stampa il valore logico di partenza associato all'identificativo 'A'
sprintf(buffer_testo, "%dA\n", stato_partenza);
UART_Print(buffer_testo);
// Stampa il Tempo Zero (momento esatto del trigger iniziale)
UART_Print("#0\n");
uint8_t stato_simulato = !stato_partenza; // Inversione logica forzata al primo fronte
sprintf(buffer_testo, "%dA\n", stato_simulato);
UART_Print(buffer_testo);
// 2. Dump della RAM formattato per VCD
for(uint16_t i = 0; i < indice_ram; i++)
{
// Il nostro Tau è 50us. Moltiplicando il tick x 50 otteniamo i microsecondi reali.
// Casting (uint32_t) obbligatorio per prevenire overflow aritmetici durante la moltiplicazione.
sprintf(buffer_testo, "#%lu\n", (uint32_t)ram_buffer[i] * 50);
UART_Print(buffer_testo);
// Ogni cella della RAM rappresenta un'inversione di polarità (da HIGH a LOW o viceversa)
stato_simulato = !stato_simulato;
sprintf(buffer_testo, "%dA\n", stato_simulato);
UART_Print(buffer_testo);
}
// 3. Arresto di sistema
stato_sistema = 3;
}
void Configura_Hardware(void)
{
UART_Init(MYUBRR);
// ----------------------------------------------------
// 1. CONFIGURAZIONE DEL PIN INGRESSO
// ----------------------------------------------------
// Pin 8 di Arduino = PB0 (Bit 0 del registro PORTB).
// DDRB controlla la direzione: scrivendo 0 sul bit 0, impostiamo fisicamente il pin in lettura.
cbi(DDRB, 0);
// ----------------------------------------------------
// 2. CONFIGURAZIONE TIMER0 (Generatore della base dei tempi)
// ----------------------------------------------------
// Unità temporale desiderata (Tau): 50 microsecondi.
// Modalità CTC (Clear Timer on Compare Match): il timer si auto-azzera al raggiungimento del target.
sbi(TCCR0A, WGM01);
// Prescaler impostato a 8 (demoltiplica la frequenza di base). 16MHz / 8 = 2MHz.
sbi(TCCR0B, CS01);
// Registro Bersaglio. Con un clock effettivo di 2MHz, per ottenere 50us servono 100 colpi (da 0 a 99).
OCR0A = 99;
// Abilitazione Interrupt: la CPU eseguirà la ISR ogni volta che il contatore hardware raggiunge OCR0A.
sbi(TIMSK0, OCIE0A);
}