Eccoci alla seconda puntata. Adesso voglio parlare un po' del programma che fa funzionare il timer che ho progettato, ma prima è meglio se introduco l'hardware finale, parlo un po' dell'allocazione dei PIN così da capire meglio anche il programma.
Scheda finale
Ecco la scheda finale; a essere sinceri è anche il primo PCB che sbroglio e produco, quindi ne vado fiero.
Ecco l'allocazione dei PIN di I/O
Nome Pin | Funzione |
---|---|
RA0 | Segm. B |
RA1 | Segm. A |
RA2 | Segm. F |
RA3 | Segm. G |
RA4 | Punto |
RA5 | Segm. C |
RA6 | Segm. E |
RA7 | Segm. D |
RC2 | Led Rosso ore |
RC3 | Led Rosso min |
RC4 | Led Giallo |
RC5 | CC Disp. SX |
RC6 | CC Disp. DX |
RC7 | Relè |
RB0 | Tasto Start |
RB1 | Tasto + |
RB2 | Tasto - |
RB3 | Tasto Mode |
Oltre ai PIN usati per la programmazione e per il quarzo del TIMER, rimangono due pin liberi (RC0 e RC1) per usi futuri. In questa versione il relè è alimentato con la tensione prelevata prima dello stabilizzatore, quindi non a 5V, ma nel mio caso a 12V.
Il Programma
Ecco il listato del programma, l'ho commentato molto e suddiviso in molte funzioni così da comprenderlo meglio, penso che si spieghi da solo.
/* Ver 1.0 Author: Davide Bagnoli, 2013 MCU: Microchip PIC18F2550 Clock: 2 MHz (internal clock) Compiler: XC8 - Version 1.12 Progetto di un timer ore-min-sec con display. */ /**************************************************************************** Librerie ****************************************************************************/ #include <xc.h> /**************************************************************************** Dichiarazioni Funzioni ****************************************************************************/ void setup_micro(void); //impostazioni del microcontrollore void setup_prog(void); //inizializzazione programma void interrupt tmr_int(void); //gestore degli interrupt void Disp_Num(char n); //visualizza il numero n sul display attivo void Disp_Multiplex(char index); //attiva il display indicato da n (0:dx 1:sx) void Disp_Clear(void); //pulisce il display attivo void Rele_On(void); //accende il relè e il led giallo void Rele_Off(void); //spenge il relè e il led giallo void DecrementaTempo(void); //decrementa di un secondo il tempo impostato char CountDown(void); //torna 0 se il countdown è terminato void Led_Mode(char mode); //cambia i led accesi a seconda della modalità scelta void Count_Start(void); //avvia il countdown void Count_End(void); //termina il countdown /**************************************************************************** Definizione MACRO ****************************************************************************/ #define RELE LATCbits.LATC7 //Pin del Relè #define LED_GIALLO LATCbits.LATC4 //Pin Led giallo segnale timer attivo #define LED_ALTO LATCbits.LATC2 //Pin Led in alto per segnale mode #define LED_BASSO LATCbits.LATC3 //Pin Led in basso per segnale mode #define CAT_DISP_SX LATCbits.LATC5 //Pin Transistor Catodo Comune display SX #define CAT_DISP_DX LATCbits.LATC6 //Pin Transistor Catodo Comune display DX #define BTN_MODE PORTBbits.RB3 //Pin Tasto Mode #define BTN_UP PORTBbits.RB1 //Pin Tasto + #define BTN_DOWN PORTBbits.RB2 //Pin Tasto - #define BTN_START PORTBbits.RB0 //Pin Tasto Start/Stop #define NUM_0 0b11100111 //Configurazioni LATA per numeri display #define NUM_1 0b00100001 #define NUM_2 0b11001011 #define NUM_3 0b10101011 #define NUM_4 0b00101101 #define NUM_5 0b10101110 #define NUM_6 0b11101110 #define NUM_7 0b00100011 #define NUM_8 0b11101111 #define NUM_9 0b10101111 #define NUM_PIN_PUNTO 4 //Numero Pin punto display [0-7] #define PUNTO LATAbits.LATA4 //Pin Punto Display /**************************************************************************** Dichiarazioni Variabili ****************************************************************************/ unsigned char i; unsigned char cont; unsigned char ore; unsigned char min; unsigned char sec; unsigned char start; unsigned char mode;// 0=secondi 1=minuti 2=ore unsigned char end; // 0= bottone rilasciato 1=comando eseguito unsigned char rilascio; /**************************************************************************** Direttive Programmazione ****************************************************************************/ #pragma config OSC = INTIO67 //oscillatore interno, RA6 e RA7 pin di I/O #pragma config FCMEN = OFF #pragma config IESO = OFF #pragma config WDT = OFF //watchdog disabilitato #pragma config PBADEN = OFF //AD disabilitato #pragma config LVP = OFF //programmazione a bassa tensione disabilitata /**************************************************************************** MAIN ****************************************************************************/ void main(void) { setup_micro(); setup_prog(); while(1) { if(i==0)//aggiorno display sx { Disp_Clear(); Disp_Multiplex(0);//display sx switch(mode) { case 0: Disp_Num((char)sec/10); break; case 1: Disp_Num((char)min/10); break; case 2: Disp_Num((char)ore/10); break; } } if(i==5)//aggiorno display dx { Disp_Clear(); Disp_Multiplex(1);//display dx switch(mode) { case 0: Disp_Num((char)sec%10); break; case 1: Disp_Num((char)min%10); break; case 2: Disp_Num((char)ore%10); break; } } if(BTN_MODE && BTN_UP && BTN_DOWN && BTN_START)//se tutti i bottoni NON sono premuti { cont = 0; rilascio = 1; } else cont++;//se un tasto è premuto, faccio il debouncing. if(cont > 80)//almeno un tasto premuto { cont = 0; if(!BTN_MODE && rilascio)//premuto bottone delle modalità, non commuta se tenuto premuto { mode = (mode+1)%3; Led_Mode(mode); rilascio = 0; } if(!BTN_UP && !start)//premuto bottone + { switch(mode) { case 0: sec = (sec+1)%60; break; case 1: min = (min+1)%60; break; case 2: ore = (ore+1)%100; break; } } if(!BTN_DOWN && !start)//premuto bottone - { switch(mode) { case 0: if(sec==0) sec = 59; else sec --; break; case 1: if(min==0) min = 59; else min --; break; case 2: if(ore==0) ore = 99; else ore --; break; } } if(!BTN_START && rilascio)//avvia/ferma timer, non commuta se tenuto premuto { if(!CountDown()) { if(!start)//se il tempo non è zero e non è avviato Count_Start(); else Count_End(); } rilascio = 0; } } i = (i+1)%10;//incremento contatore } } /**************************************************************************** Gestione Interruzioni (Interrupt Handler) ****************************************************************************/ void interrupt tmr_int(void) { if(PIR1bits.TMR1IF)//se l'interruzione è data dal TIMER1 { TMR1H = 0x80;//precarico il timer TMR1L = 0x00; PIR1bits.TMR1IF = 0;//azzero il flag di overflow del TIMER1 PUNTO = ~PUNTO;//lampeggio punto DecrementaTempo();//Diminuisco di 1sec il tempo if(CountDown())//se il tempo è finito Count_End();//spegne il timer e il relè conteggio } } /**************************************************************************** Setup Micro ****************************************************************************/ void setup_micro(void) { OSCCONbits.IRCF = 0b101;//Clock a 2Mhz OSCCONbits.IOFS = 0;//Clock Stabile OSCCONbits.SCS = 0b10;//Clock di sistema da oscillatore interno LATA = 0xFF; TRISA = 0x00;//tutti output LATB = 0x00; TRISB = 0xFF;//tutti input LATC = 0x00; TRISC = 0b00000011;//tutti input trane RC0 e RC1 INTCON2bits.RBPU = 0;//abilito resistori di pullup su PORTB T1CONbits.RD16 = 1;//modalità a 16 bit T1CONbits.T1RUN = 0;//uso clock diverso dalla cpu T1CONbits.T1CKPS0 = 0;//prescaler 1:1 T1CONbits.T1CKPS1 = 0;//prescaler 1:1 T1CONbits.TMR1CS = 1;//clock esterno T1CONbits.T1SYNC = 1;//non sincronizza clock esterno T1CONbits.T1OSCEN = 1;//abilito oscillatore TMR1H = 0x80; TMR1L = 0x00;//precarico il timer per avere un interrupt al secondo PIE1bits.TMR1IE = 1;//abilito interrupt su overflow timer1 IPR1bits.TMR1IP = 1;//interrupt alta priorità su timer1 T1CONbits.TMR1ON = 0;//spengo timer1 INTCONbits.GIEH = 1;//abilito interrupt globali INTCONbits.GIEL = 1;//abilito interrupt sulle periferiche RCONbits.IPEN = 1;//sabilito interrupt a due livelli } /**************************************************************************** Setup Programma ****************************************************************************/ void setup_prog(void) { i = 0; cont = 0; rilascio = 1; mode = 0; end = 0; start = 0; sec = 0; min = 0; ore = 0; Led_Mode(mode); Rele_Off(); Disp_Clear(); PUNTO = 0; } /**************************************************************************** Scrittura su display ****************************************************************************/ void Disp_Num(char n)//modifico solo i 7 bit dei segmenti { char tmp = 0b00000001 << NUM_PIN_PUNTO; //Creo la maschera per il punto tmp = tmp & LATA;//azzero tutti i bit tranne quello dello punto switch(n) { case 0: LATA = tmp | NUM_0; break; case 1: LATA = tmp | NUM_1; break; case 2: LATA = tmp | NUM_2; break; case 3: LATA = tmp | NUM_3; break; case 4: LATA = tmp | NUM_4; break; case 5: LATA = tmp | NUM_5; break; case 6: LATA = tmp | NUM_6; break; case 7: LATA = tmp | NUM_7; break; case 8: LATA = tmp | NUM_8; break; case 9: LATA = tmp | NUM_9; break; default:LATA = tmp | 0x00;//se non conosco il numero spengo il display } } void Disp_Multiplex(char index)//se index == 0 allora display sx altrimenti dx { if(index == 0) { CAT_DISP_SX = 1; CAT_DISP_DX = 0; } else { CAT_DISP_SX = 0; CAT_DISP_DX = 1; } } void Disp_Clear(void)//cancello il display { LATA = LATA & (0b00000001 << NUM_PIN_PUNTO); } /**************************************************************************** On/Off Relè ****************************************************************************/ void Rele_Off(void) { RELE = 0; LED_GIALLO = 0; } void Rele_On(void) { RELE = 1; LED_GIALLO = 1; } /**************************************************************************** Gestione Tempo ****************************************************************************/ char CountDown(void)//ritorna 1 se finito il conto alla rovescia { if(sec == 0 && min == 0 && ore == 0) return 1; return 0; } void DecrementaTempo(void) { if(!CountDown()) { if(sec == 0) { sec = 59; if(min == 0) { min = 59; ore--; } else min--; } else sec--; } } /**************************************************************************** Gestione Led Modalità ****************************************************************************/ void Led_Mode(char mode) { switch(mode) { case 0 : LED_ALTO = 1; LED_BASSO = 1; break; case 1 : LED_ALTO = 0; LED_BASSO = 1; break; case 2 : LED_ALTO = 1; LED_BASSO = 0; break; default: LED_ALTO = 0; LED_BASSO = 0; } } /**************************************************************************** Partenza/Fine Conteggio ****************************************************************************/ void Count_Start(void) { TMR1H = 0x80; TMR1L = 0x00;//precarico il timer per avere un interrupt al secondo T1CONbits.TMR1ON = 1;//accendo timer1 PUNTO = 1; start = 1; Rele_On(); } void Count_End(void) { T1CONbits.TMR1ON = 0;//spengo timer1 PUNTO = 0; start = 0; Rele_Off(); }
La parte più interessante è forse il main, dove è presente un ciclo infinito che si occupa di due cose, il multiplexing dei display e la gestione della pressione dei pulsanti con conseguente debouncing software.
Per il debouncing uso un contatore che viene incrementato se un pulsante dei quattro è premuto, altrimenti viene azzerato. Se questo contatore supera un certo valore, allora c'è almeno un tasto premuto da un certo tempo e quindi ho effettuato un semplice debouncing. Questo tipo di gestione mi permette anche di avere un incremento o decremento rapido se il pulsante viene tenuto premuto. Nel caso dei pulsanti MODE e START, c'è un'ulteriore variabile rilascio, che serve ad attivare una certa reazione al pulsante solo ad ogni pressione inibendo il funzionamento continuo se il tasto viene tenuto premuto.