//Visualizzatore di segnale su ATmega328P e Nokia 5110
//Aleandro Eustacchi
// --- 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
// --- Variabili di Stato Globali ---
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 ---
void Automaton_ProcessInput(void);
void Automaton_ReadControls(void);
void Automaton_UpdateDisplay(void);
// --- Routine di Interrupt ---
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_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 e Logica Applicativa ---
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;
}
}
// --- Funzioni setup() e loop() ---
void setup() {
DDRB |= (1 << PB5);
PORTB |= (1 << PB5);
cli(); // Disabilita interrupt per il setup
// Setup UART (Seriale)
UBRR0H = (uint8_t)(UBRR_VALUE >> 8);
UBRR0L = (uint8_t)UBRR_VALUE;
UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
// Setup SPI
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);
// Setup ADC
ADMUX = (1 << REFS0);
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
// Setup Timer2 per un interrupt ogni ~1ms
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
TCCR2A |= (1 << WGM21);
TCCR2B |= (1 << CS22);
OCR2A = 249;
TIMSK2 |= (1 << OCIE2A);
// Setup LCD
PORTB &= ~(1 << LCD_PIN_RST);
PORTB |= (1 << LCD_PIN_RST);
lcd_command(0x21);
lcd_command(0xB8);
lcd_command(0x04);
lcd_command(0x13);
lcd_command(0x20);
lcd_command(0x0C);
azzera_buffer((void*)displayMem, sizeof(displayMem));
lcd_refresh();
y_offset = SCREEN_HEIGHT - WINDOW_HEIGHT;
sei(); // Riabilita globalmente gli interrupt
uart_send_string("Sistema Inizializzato.\r\n");
uart_send_string("Inserire sequenza dati (es: 100 200 3FF 80A):\r\n");
}
void loop() {
Automaton_ProcessInput();
Automaton_ReadControls();
Automaton_UpdateDisplay();
}
// --- Logica degli Automi ---
void Automaton_ProcessInput(void) {
if (!flag_newDataReceived) return;
if (flag_inputTruncated) {
uart_send_string("Attenzione: Riga di input troppo lunga, comando troncato.\r\n");
flag_inputTruncated = false;
}
char* p = rx_buffer;
uint16_t last_sample;
if (signalCount > 0) {
last_sample = signalSamples[(signalHead - 1 + SIGNAL_BUFFER_SIZE) % SIGNAL_BUFFER_SIZE];
} else {
last_sample = 0;
}
bool data_added = false;
while (*p) {
while (*p == ' ' || *p == ',') p++;
if (*p == '\0') break;
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) {
uint16_t repeat_count = val & 0x07FF;
for (uint16_t i = 0; i < repeat_count; i++) {
push_sample(last_sample);
}
} else {
uint16_t new_sample = val & 0x03FF;
push_sample(new_sample);
last_sample = new_sample;
}
data_added = true;
p += 3;
} else {
uart_send_string("Errore: token non valido. Ignorato.\r\n");
while (*p != ' ' && *p != ',' && *p != '\0') p++;
}
}
if (data_added) flag_viewChanged = true;
if (flag_bufferWasFull) {
uart_send_string("Attenzione: Buffer segnale pieno. Dati vecchi sovrascritti.\r\n");
flag_bufferWasFull = false;
}
flag_newDataReceived = false;
}
void Automaton_ReadControls(void) {
if (!flag_readControls) return;
uint8_t old_x = x_offset, old_y = y_offset;
// Lettura potenziometro Y
ADMUX = (ADMUX & 0xF0) | (POT_Y_CHANNEL & 0x0F);
ADCSRA |= (1 << ADSC);
while (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;
// Lettura potenziometro X
ADMUX = (ADMUX & 0xF0) | (POT_X_CHANNEL & 0x0F);
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC));
uint16_t pot_x_val = ADC;
uint16_t max_x_offset;
if (signalCount > SCREEN_WIDTH) {
max_x_offset = signalCount - SCREEN_WIDTH;
} else {
max_x_offset = 0;
}
if (max_x_offset > 0) {
x_offset = (uint32_t)pot_x_val * max_x_offset / 1023;
} else {
x_offset = 0;
}
if (x_offset != old_x || y_offset != old_y) flag_viewChanged = true;
flag_readControls = false;
}
void Automaton_UpdateDisplay(void) {
if (!flag_viewChanged && !flag_refreshDisplay) return;
azzera_buffer((void*)displayMem, sizeof(displayMem));
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];
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);
}
}
}
}
lcd_refresh();
flag_viewChanged = false;
flag_refreshDisplay = false;
}Loading
nokia-5110
nokia-5110