#include <avr/io.h>
#include <avr/interrupt.h>
// ============================================================================
// = DEFINIZIONE MACRO =
// ============================================================================
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // Macro usata per settare a 0 un determinato bit
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Macro usata per settare a 1 un determinato bit
#endif
#define _bitRead(value, bit) (((value) >> (bit)) & 0x01) // Macro per leggere un determinato bit di un byte
#define SMY_FLAG(n,mode) (mode == 1? (GPIOR0 |= _BV(n)): (GPIOR0 &=~_BV(n))) // Macro per settare flag booleane generiche
#define TMY_FLAG(n,mode) (mode == 1? (GPIOR0 & _BV(n)): (GPIOR0 &_BV(n)==0)) // Macro per testare flag booleane generiche
// ============================================================================
// = DEFINIZIONE COSTANTI E VARIABILI GLOBALI =
// ============================================================================
#define TIME_BEFORE_IDLE 20000// Tempo prima del passaggio allo stato IDLE
#define DEBOUNCE_TIME 100// tempo per il debounce del keypad
#define FLAG_IDLE 0 // Posizione (nel registro GPIOR0) della flag di stato: 0 = idle, 1 = operativo
#define FLAG_AROW 1 // Posizione (nel registro GPIOR0) della flag che autorizza la lettura delle righe
// PINs PERIFERICHE
#define DISPLAY_SEGS_AF 0x3F // PINS segmenti A-F del display a 7 segmenti
#define DISPLAY_SEGS_G 3 // Posizione del BIT del segmento G per il display a 7 segmenti
#define KEYPAD_ROWS 0x0F // PINS righe del keypad
#define KEYPAD_COLS 0xF0 // PINS colonne del keypad
// Variabili globali
volatile uint32_t counter = 0; // Contatore di millisecondi dall'avvio del sistema
uint32_t lastKeyPressTime = 0; // tempo dell'ultima pressione di un tasto nel keypad
uint32_t currentTime = 0; // Utilizzata nel loop per memorizzare il tempo attuale
volatile uint8_t keypadStorage; // Variabile utilizzata per memorizzare i nibble di riga e colonna
uint16_t debounce = DEBOUNCE_TIME;
const byte HEX_CODES[16] = { // Mappa dei caratteri da visualizzare nel display
0x0C, // 1
0xB6, // 2
0x9E, // 3
0xEE, // A
0xCC, // 4
0xDA, // 5
0xFA, // 6
0xF8, // b
0x0E, // 7
0xFE, // 8
0xDE, // 9
0x72, // C
0xE2, // F
0x7E, // 0
0xF2, // E
0xBC // d
};
// ============================================================================
// = FUNZIONI PER INIZIALIZZAZIONE COMPONENTI =
// ============================================================================
void keypadInit(){ // Inizializza i pin e gli interrupt per il keypad
// Imposta i pin delle righe come OUTPUT
DDRB |= KEYPAD_ROWS; // R1-R4 (PIN 11-8)
// Imposta i pin delle colonne come input PULL UP
DDRD &= KEYPAD_COLS; // C1-C4 (PIN 7-4)
PORTD |= 0xF0; // INPUT PULL UP
PCICR |= 0x04; // Abilita gli interrupt sul cambio di stato dei pin delle colonne (PCINT20-PCINT23)
PCMSK2 |= KEYPAD_COLS; // Abilita interrupt per C1-C4
}
void sevenSegInit(){ // Inizializzazione dei pin per il display a 7 segmenti
DDRC |= DISPLAY_SEGS_AF; // OUTPUT pin A0-A5
PORTC |= DISPLAY_SEGS_AF; // OUTPUT HIGH pin A0-A5
sbi(DDRD,DISPLAY_SEGS_G); // OUTPUT pin D3 (led G)
sbi(PORTD,DISPLAY_SEGS_G); // pin D3 HIGH (led G)
}
void timerInit() {
// Imposta il prescaler a 64
sbi(TCCR0B, CS01);
sbi(TCCR0B, CS00);
// Imposta il registro di confronto per generare un'interruzione ogni 1 ms
OCR0A = 249;
// Abilita l'interruzione di confronto su A
sbi(TIMSK0, OCIE0A);
// Abilita le interruzioni globali
sei();
}
void updateDisplay(uint8_t character){ // Funzione che mostra un carattere nel display a 7 segmenti
PORTC |= DISPLAY_SEGS_AF; // SPENGO IL DISPLAY A-F
PORTC &= ~character >> 1;
if(character&0x80) // Segmento G
cbi(PORTD,DISPLAY_SEGS_G); // Accende il segmento
else
sbi(PORTD,DISPLAY_SEGS_G); // Spegne il segmento
}
uint8_t encoder4t2(uint8_t bits){ // ENCODER 4 --> 2
uint8_t result = 0x00; // Variabile per output
bits = ~bits; // Inverte i bit (0 diventa 1 - 1 diventa 0)
// Operazioni logiche per l'encoder
result = (result & ~(1<<0)) | ((_bitRead(bits,2) | _bitRead(bits,0))<<0); // BIT 0
result = (result & ~(1<<1)) | ((_bitRead(bits,1) | _bitRead(bits,0))<<1); // BIT 1
return result; // Ritorna il risultato "decimale"
}
void setup() {
timerInit(); // Inizializza il timer (per l'idle)
keypadInit(); // Inizializza il keypad
sevenSegInit(); // Inizializza il display a 7 segmenti
sei(); // Abilita gli interrupt globali
SMY_FLAG(FLAG_AROW,0);
}
void readRows(){ // Legge le righe del keypad
keypadStorage |= (PINB & KEYPAD_ROWS); // Memorizza i bit delle righe (Riga del bottone selezionato)
// Ritorna allo stato iniziale (righe output low e colonne input pullup)
// Righe
DDRB |= KEYPAD_ROWS; // PIN D11-D8 come output
PORTB = 0x00; // PIN D11-D8 LOW
// Colonne
DDRD &= ~(KEYPAD_COLS); // PIN D7-D4 come input
PORTD |= 0xF0; // PIN D7-D4 input PULL-UP
}
void loop() {
cli(); // INIZIO ZONA CRITICA DISABILITA INTERRUPT GLOBALI
currentTime = counter; // Memorizza il tempo attuale
if(TMY_FLAG(FLAG_AROW,1)) { // verifica se c'è stata la pressione di un bottone
readRows(); // Legge le righe
}
if(((keypadStorage & KEYPAD_COLS) != 0xF0) && ((keypadStorage & KEYPAD_ROWS) != 0x00)){ // Verifica se un tasto è stato premuto
uint8_t riga = encoder4t2(keypadStorage & KEYPAD_ROWS); // Determina l'indice della riga (4 bit meno significativi: 0b00001111)
// Determina l'indice della colonna
/*
Dato che l'encoder lavora solo con i 4 bit meno significativi e che le righe sono "monitorate" dai
4 bit più significativi occorre shiftarli quindi ad esempio:
- 0b11110000 deve diventare 0b00001111 per poter essere correttamente processato dall'encoder
*/
uint8_t col = encoder4t2((((keypadStorage&KEYPAD_COLS) >> 4) | ((keypadStorage&KEYPAD_COLS) << 4))); // Determina l'indice della colonna
/*
Il keypad è una "matrice" di bottoni, mentre i valori da mostrare sul display sono archiviati in un vettore
occorre quindi convertire i due indici della matrice (righe e colonne) ad un singolo indice che corrisponda
correttamente con i valori presenti nel vettore
*/
uint8_t indiceVettore = (riga * 4) + col; // Determina l'indice del carattere da mostrare
updateDisplay(HEX_CODES[indiceVettore]); // Mostra nel display il carattere
keypadStorage = 0x00;
lastKeyPressTime = currentTime; // Imposta il tempo dell'ultima pressione di un bottone
SMY_FLAG(FLAG_IDLE,1); // Imposta la flag (il dispositivo mostra un carattere)
}
// Verifica se il dispositivo sta mostrando un carattere da più di TIME_BEFORE_IDLE
// senza che si siano verificate altre pressioni e passa allo stato di IDLE
if(TMY_FLAG(FLAG_IDLE,1) && (currentTime - lastKeyPressTime >= TIME_BEFORE_IDLE)){ // IDLE
PORTC |= DISPLAY_SEGS_AF; // Spegne il display (Segmenti A-F)
sbi(PORTD,DISPLAY_SEGS_G); // Spegne il display (Segmento G)
SMY_FLAG(FLAG_IDLE,0); // Imposta la flag (dispositivo in stato di idle)
}
sei(); // FINE ZONA CRITICA RIABILITA INTERRUPT GLOBALI
}
// ============================================================================
// = ISR =
// ============================================================================
ISR(PCINT2_vect) { // interrupt su pin D0-D7 (COLONNE)
// Lettura delle colonne e maschera per Bit più significativi delle colonne
cbi(PCICR,2); // INTERRUPT DISABILITATI COLONNE
keypadStorage = PIND & KEYPAD_COLS;
// SCAMBIO: RIGHE --> INPUT (PULL-UP) COLONNE --> (OUTPUT LOW)
// RIGHE
DDRB &= ~(KEYPAD_ROWS); // PIN D11-D8 come INPUT (valore invertito di 00001111)
PORTB |= 0x0F; // PIN D11-D8 pull-up
// Colonne
DDRD |= KEYPAD_COLS; // PIN D7-D4 OUTPUT
PORTD &= 0x08; // PIN D7-D4 LOW
SMY_FLAG(FLAG_AROW,1);
}
// ISR per timer
ISR(TIMER0_COMPA_vect){
// Incrementa il contatore ogni volta che viene generata un'interruzione
counter++;
if(debounce>0){
debounce--;
}else if(TMY_FLAG(FLAG_AROW,1)){
debounce = DEBOUNCE_TIME;
sbi(PCICR,2); // INTERRUPT ABILITATI COLONNE
SMY_FLAG(FLAG_AROW,0);
}
}