// ============================================================================
// Progetto: Visualizzatore di segnale su ATmega328P e Nokia 5110
// Autore: Aleandro Eustacchi
// ============================================================================
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdbool.h>
// --- Definizioni Globali e Costanti ---
#define UART_BAUD_RATE 9600
#define UBRR_VALUE ((F_CPU / 16 / UART_BAUD_RATE) - 1)
// --- Mappatura Pin e Costanti Hardware ---
#define LCD_PORT PORTB
#define LCD_DDR DDRB
#define LCD_PIN_RST PB1
#define LCD_PIN_CE PB2
#define LCD_PIN_DC PB0
#define SPI_PIN_MOSI PB3
#define SPI_PIN_SCK PB5
#define POT_Y_CHANNEL 0
#define POT_X_CHANNEL 1
#define SCREEN_WIDTH 84
#define SCREEN_HEIGHT 48
#define WINDOW_HEIGHT 10
#define SIGNAL_BUFFER_SIZE 256
#define RX_BUFFER_SIZE 64
#define CONTROLS_READ_INTERVAL_MS 50
#define DISPLAY_REFRESH_INTERVAL_MS 100
// ============================================================================
// DEFINIZIONE DEGLI STATI FORMALI DEGLI AUTOMI (FSM)
// ============================================================================
// Stati per l'automa di Gestione e Decodifica Input
typedef enum {
INPUT_STATE_IDLE,
INPUT_STATE_PROCESSING,
INPUT_STATE_ERROR
} InputState_t;
// Stati per l'automa di Lettura Asincrona dei Controlli Analogici
typedef enum {
CONTROLS_STATE_IDLE,
CONTROLS_STATE_READ_Y,
CONTROLS_STATE_READ_X,
CONTROLS_STATE_EVALUATE
} ControlsState_t;
// Stati per l'automa di Rendering e Refresh dell'LCD
typedef enum {
DISPLAY_STATE_IDLE,
DISPLAY_STATE_CLEAR,
DISPLAY_STATE_RENDER,
DISPLAY_STATE_REFRESH
} DisplayState_t;
// --- Registri di Stato correnti per gli Automi ---
InputState_t currentInputState = INPUT_STATE_IDLE;
ControlsState_t currentControlsState = CONTROLS_STATE_IDLE;
DisplayState_t currentDisplayState = DISPLAY_STATE_IDLE;
// --- Variabili di Stato Globali e Buffer ---
volatile uint8_t displayMem[SCREEN_WIDTH * (SCREEN_HEIGHT / 8)];
uint16_t signalSamples[SIGNAL_BUFFER_SIZE];
volatile uint16_t signalHead = 0, signalTail = 0, signalCount = 0;
volatile bool flag_readControls = false;
volatile bool flag_refreshDisplay = false;
volatile bool flag_viewChanged = true;
volatile bool flag_newDataReceived = false;
volatile bool flag_inputTruncated = false;
volatile bool flag_bufferWasFull = false;
volatile uint8_t x_offset = 0, y_offset = 0;
char rx_buffer[RX_BUFFER_SIZE];
volatile uint8_t rx_idx = 0;
// --- Dichiarazioni Anticipate dei Driver ---
void azzera_buffer(void* ptr, uint16_t num);
void spi_write(uint8_t data);
void lcd_command(uint8_t cmd);
void lcd_data(uint8_t data);
void uart_send_string(const char* s);
void lcd_refresh(void);
uint8_t hex_char_to_int(char c);
bool my_isxdigit(char c);
void push_sample(uint16_t sample);
// --- Dichiarazioni dei Motori degli Automi ---
void Automaton_ProcessInput(void);
void Automaton_ReadControls(void);
void Automaton_UpdateDisplay(void);
// ============================================================================
// ROUTINE DI INTERRUPT (ISR)
// ============================================================================
// ISR Timer2: Generatore di base dei tempi per gli eventi asincroni (Genera i trigger)
ISR(TIMER2_COMPA_vect) {
static uint16_t ms_counter = 0;
ms_counter++;
if (ms_counter % CONTROLS_READ_INTERVAL_MS == 0) {
flag_readControls = true;
}
if (ms_counter % DISPLAY_REFRESH_INTERVAL_MS == 0) {
flag_refreshDisplay = true;
}
}
// ISR USART RX: Cattura in background i singoli caratteri esadecimali inviati
ISR(USART_RX_vect) {
char c = UDR0;
if ((c == '\n' || c == '\r') && (rx_idx > 0)) {
rx_buffer[rx_idx] = '\0';
rx_idx = 0;
flag_newDataReceived = true;
} else if (c >= ' ' && c <= '~') {
if (rx_idx < (RX_BUFFER_SIZE - 1)) {
rx_buffer[rx_idx++] = c;
} else {
flag_inputTruncated = true;
}
}
}
// ============================================================================
// FUNZIONI DRIVER HARDWARE (HAL)
// ============================================================================
void azzera_buffer(void* ptr, uint16_t num) {
uint8_t* byte_ptr = (uint8_t*)ptr;
for (uint16_t i = 0; i < num; i++) {
byte_ptr[i] = 0;
}
}
void spi_write(uint8_t data) {
SPDR = data;
while (!(SPSR & (1 << SPIF)));
}
void lcd_command(uint8_t cmd) {
LCD_PORT &= ~(1 << LCD_PIN_DC);
LCD_PORT &= ~(1 << LCD_PIN_CE);
spi_write(cmd);
LCD_PORT |= (1 << LCD_PIN_CE);
}
void lcd_data(uint8_t data) {
LCD_PORT |= (1 << LCD_PIN_DC);
LCD_PORT &= ~(1 << LCD_PIN_CE);
spi_write(data);
LCD_PORT |= (1 << LCD_PIN_CE);
}
void uart_send_string(const char* s) {
while (*s) {
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = *s++;
}
}
void lcd_refresh(void) {
lcd_command(0x40 | 0);
lcd_command(0x80 | 0);
for (uint16_t i = 0; i < sizeof(displayMem); i++) {
lcd_data(displayMem[i]);
}
}
uint8_t hex_char_to_int(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return 255;
}
bool my_isxdigit(char c) {
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
return true;
}
return false;
}
void push_sample(uint16_t sample) {
signalSamples[signalHead] = sample;
signalHead = (signalHead + 1) % SIGNAL_BUFFER_SIZE;
if (signalCount < SIGNAL_BUFFER_SIZE) {
signalCount++;
} else {
signalTail = (signalTail + 1) % SIGNAL_BUFFER_SIZE;
flag_bufferWasFull = true;
}
}
// ============================================================================
// CONFIGURAZIONE INIZIALE E FUNZIONE SETUP
// ============================================================================
void setup() {
DDRB |= (1 << PB5);
PORTB |= (1 << PB5);
cli(); // Inibisce gli interrupt globali durante la configurazione dei registri
// Inizializzazione USART0
UBRR0H = (uint8_t)(UBRR_VALUE >> 8);
UBRR0L = (uint8_t)UBRR_VALUE;
UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
// Inizializzazione Interfaccia SPI Master
DDRB |= (1 << SPI_PIN_MOSI) | (1 << SPI_PIN_SCK) | (1 << LCD_PIN_CE) | (1 << LCD_PIN_RST) | (1 << LCD_PIN_DC);
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
PORTB |= (1 << LCD_PIN_CE);
// Inizializzazione ADC
ADMUX = (1 << REFS0);
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
// Inizializzazione Timer2 per interrupt a ~1ms (CTC Mode)
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
TCCR2A |= (1 << WGM21);
TCCR2B |= (1 << CS22);
OCR2A = 249;
TIMSK2 |= (1 << OCIE2A);
// Sequenza di Reset e Inizializzazione del Display PCD8544 (Nokia 5110)
PORTB &= ~(1 << LCD_PIN_RST);
PORTB |= (1 << LCD_PIN_RST);
lcd_command(0x21); // Extended instruction set
lcd_command(0xB8); // Imposta contrasto VOP
lcd_command(0x04); // Imposta coefficiente di temperatura
lcd_command(0x13); // Bias del display 1:48
lcd_command(0x20); // Istruzioni standard, indirizzamento orizzontale
lcd_command(0x0C); // Display in modalità normale (pixel attivi su sfondo chiaro)
azzera_buffer((void*)displayMem, sizeof(displayMem));
lcd_refresh();
y_offset = SCREEN_HEIGHT - WINDOW_HEIGHT;
sei(); // Riabilita globalmente la ricezione delle ISR
uart_send_string("Sistema Inizializzato (FSM Mode).\r\n");
uart_send_string("Inserire sequenza dati (es: 100 250 3FF 80A 1A0):\r\n");
}
// ============================================================================
// LOOP PRINCIPALE: SCHEDULER COOPERATIVO NON-BLOCCANTE
// ============================================================================
void loop() {
// Invocazione ciclica degli automi. Ciascun metodo controlla i propri stati
// interni ed esegue un rilascio istantaneo della CPU senza funzioni bloccanti.
Automaton_ProcessInput();
Automaton_ReadControls();
Automaton_UpdateDisplay();
}
// ============================================================================
// MOTORI DI LOGICA DEGLI AUTOMI A STATI FINITI
// ============================================================================
// Automa 1: Gestione e Decodifica del Flusso Seriale RLE Rilevato
void Automaton_ProcessInput(void) {
switch (currentInputState) {
case INPUT_STATE_IDLE:
// Transizione attiva solo all'arrivo di un frame di stringa completo
if (flag_newDataReceived) {
if (flag_inputTruncated) {
uart_send_string("Attenzione: Riga di input troppo lunga, comando troncato.\r\n");
flag_inputTruncated = false;
}
currentInputState = INPUT_STATE_PROCESSING;
}
break;
case INPUT_STATE_PROCESSING: {
char* p = rx_buffer;
uint16_t last_sample = (signalCount > 0) ? signalSamples[(signalHead - 1 + SIGNAL_BUFFER_SIZE) % SIGNAL_BUFFER_SIZE] : 0;
bool data_added = false;
bool syntax_error = false;
while (*p) {
while (*p == ' ' || *p == ',') p++;
if (*p == '\0') break;
// Parsing del token a 3 cifre esadecimali
if (my_isxdigit(p[0]) && my_isxdigit(p[1]) && my_isxdigit(p[2])) {
uint16_t val = (hex_char_to_int(p[0]) << 8) | (hex_char_to_int(p[1]) << 4) | hex_char_to_int(p[2]);
if (val & 0x0800) { // MSB alto -> Istruzione di REPLICA
uint16_t repeat_count = val & 0x07FF;
for (uint16_t i = 0; i < repeat_count; i++) {
push_sample(last_sample);
}
} else { // MSB basso -> CAMPIONE REALE
uint16_t new_sample = val & 0x03FF;
push_sample(new_sample);
last_sample = new_sample;
}
data_added = true;
p += 3;
} else {
syntax_error = true;
while (*p != ' ' && *p != ',' && *p != '\0') p++;
}
}
if (data_added) flag_viewChanged = true;
if (flag_bufferWasFull) {
uart_send_string("Attenzione: Buffer pieno. Dati sovrascritti.\r\n");
flag_bufferWasFull = false;
}
// Selezione dinamica dello stato successivo
if (syntax_error) {
currentInputState = INPUT_STATE_ERROR;
} else {
flag_newDataReceived = false;
currentInputState = INPUT_STATE_IDLE;
}
break;
}
case INPUT_STATE_ERROR:
uart_send_string("Errore: uno o piu token non validi rilevati nell'input.\r\n");
flag_newDataReceived = false;
currentInputState = INPUT_STATE_IDLE;
break;
}
}
// Automa 2: Interrogazione Sequenziale Asincrona dei Potenziometri ADC (X/Y)
void Automaton_ReadControls(void) {
static uint8_t old_x = 0, old_y = 0;
switch (currentControlsState) {
case CONTROLS_STATE_IDLE:
// Avviato periodicamente dal flag temporale generato dal Timer2
if (flag_readControls) {
flag_readControls = false;
old_x = x_offset;
old_y = y_offset;
// Configura il multiplexer per il Canale Y ed avvia la conversione
ADMUX = (ADMUX & 0xF0) | (POT_Y_CHANNEL & 0x0F);
ADCSRA |= (1 << ADSC);
currentControlsState = CONTROLS_STATE_READ_Y;
}
break;
case CONTROLS_STATE_READ_Y:
// Controllo asincrono: se l'ADC sta ancora elaborando, restituisce subito il controllo al loop
if (!(ADCSRA & (1 << ADSC))) {
uint16_t pot_y_val = ADC;
uint8_t max_y_offset = SCREEN_HEIGHT - WINDOW_HEIGHT;
y_offset = (uint32_t)pot_y_val * max_y_offset / 1023;
// Switch istantaneo sul Canale X ed inoltro nuova richiesta di conversione
ADMUX = (ADMUX & 0xF0) | (POT_X_CHANNEL & 0x0F);
ADCSRA |= (1 << ADSC);
currentControlsState = CONTROLS_STATE_READ_X;
}
break;
case CONTROLS_STATE_READ_X:
// Verifica fine conversione canale X senza bloccare l'esecuzione globale
if (!(ADCSRA & (1 << ADSC))) {
uint16_t pot_x_val = ADC;
uint16_t max_x_offset = (signalCount > SCREEN_WIDTH) ? (signalCount - SCREEN_WIDTH) : 0;
if (max_x_offset > 0) {
x_offset = (uint32_t)pot_x_val * max_x_offset / 1023;
} else {
x_offset = 0;
}
currentControlsState = CONTROLS_STATE_EVALUATE;
}
break;
case CONTROLS_STATE_EVALUATE:
// Confronto di stato per forzare il refresh visivo in caso di variazione dei controlli
if (x_offset != old_x || y_offset != old_y) {
flag_viewChanged = true;
}
currentControlsState = CONTROLS_STATE_IDLE;
break;
}
}
// Automa 3: Elaborazione Geometrica della Memoria Display e Flusso Seriale SPI
void Automaton_UpdateDisplay(void) {
switch (currentDisplayState) {
case DISPLAY_STATE_IDLE:
if (flag_viewChanged || flag_refreshDisplay) {
currentDisplayState = DISPLAY_STATE_CLEAR;
}
break;
case DISPLAY_STATE_CLEAR:
azzera_buffer((void*)displayMem, sizeof(displayMem));
currentDisplayState = DISPLAY_STATE_RENDER;
break;
case DISPLAY_STATE_RENDER:
// Pipeline di mappatura matematica dei vettori nel buffer grafico locale
for (uint8_t screen_x = 0; screen_x < SCREEN_WIDTH; screen_x++) {
uint16_t sample_index = x_offset + screen_x;
if (sample_index < signalCount) {
uint16_t buffer_idx = (signalTail + sample_index) % SIGNAL_BUFFER_SIZE;
uint16_t sample_value = signalSamples[buffer_idx];
// Conversione in altezza proporzionale (Livello 0-9)
uint8_t h = (uint32_t)sample_value * (WINDOW_HEIGHT - 1) / 1023;
uint8_t y_pixel = y_offset + (WINDOW_HEIGHT - 1) - h;
if (y_pixel < SCREEN_HEIGHT) {
uint8_t bank = y_pixel / 8;
uint8_t bit_pos = y_pixel % 8;
uint16_t address = bank * SCREEN_WIDTH + screen_x;
if (address < sizeof(displayMem)) {
displayMem[address] |= (1 << bit_pos); // Accensione logica del singolo pixel
}
}
}
}
currentDisplayState = DISPLAY_STATE_REFRESH;
break;
case DISPLAY_STATE_REFRESH:
lcd_refresh(); // Spinta fisica del buffer tramite porta SPI all'hardware esterno
flag_viewChanged = false;
flag_refreshDisplay = false;
currentDisplayState = DISPLAY_STATE_IDLE;
break;
}
}Loading
nokia-5110
nokia-5110