; **************************************************************************************** ; ; File: AVR_TIMER2.ASM * ; Data: 12.Mar.2004 * ; Version: 1.0 * ; IDE: AVR-Studio 4.0 * ; * ; Autore: Giuseppe Franco - giuseppe.franco@chilab.polito.it * ; Riferimenti: Politecnico di Torino - Laboratorio Materiali e Microsistemi * ; ; **************************************************************************************** ; Questo programma serve ad imparare come utilizzare la periferica ; integrata del timer con il relativo evento di interrupt, in particolare: ; ; --> come configurare i registri del timer utilizzato. ; --> come gestire alcuni eventi generati dal timer ; --> come utilizzare la funzione di 'compare'. ; --> come generare eventi sincroni direttamente sui pin dedicati OCx. ; --> come generare eventi sincroni con l'utilizzo dell'interrupt sul flag OCIEx. ; --> come gestire la condivisione di variabili comuni alla routine di interrupt ; ed al resto del programma (istruzioni 'push' e 'pop' ). ; ; **************************************************************************************** .NOLIST ;.INCLUDE "C:\programmi\atmel\avr tools\avrassembler\appnotes\8515def.inc" .INCLUDE "C:\usr\develop\atmel\avr tools\avrassembler\appnotes\8535def.inc" .LIST ; Definizione dei registri utilizzati .DEF mp = R16 .DEF n1 = R18 .DEF n2 = R19 .DEF n3 = R20 .DEF loop_cnt = R21 .EQU ResetAddr = 0 ; indirizzo del vettore di reset ; ( non definito nel file precedentemente incluso '8535def.inc' delle definizioni standard dell'architettura AVR ). .EQU MainAddr = 20 ; indirizzo di inizio del programma. .ORG ResetAddr rjmp main .ORG OC1Aaddr rjmp Timer1_COMPA_ISR ; nelle righe precedenti sono stati mappati i vettori di salto alle routine di gestione ; degli interrupt ( ISR -> Interrupt Service Routine); in questo caso l'unica utilizzata ; è quella relativa agli eventi generati dalla funzione di compare del timer1 canale A. ; In questo modo si verifica l'utilità di inserire come prima istruzione eseguita al reset ; il relativo salto alla ISR di reset. ; La direttiva .ORG serve ad indicare al compilatore di generare il codice mappandolo ; a partire dall'indirizzo assoluto indicato dall'argomento della direttiva. .ORG MainAddr ; Il programma decidiamo di farlo partire dall'indirizzo assoluto 0020. main: ; Viene impostato lo stack-pointer ed azzero il registro di stato (tutti gli interrupt sono mascherati) ldi mp,HIGH(RAMEND) out SPH,mp ldi mp,LOW(RAMEND) out SPL,mp ldi mp,0x00 out SREG,mp ldi mp,0b00000011 out DDRC,mp ; Abilito la porta C in uscita sul bit 0 ed 1 per pilotare un led con ; il metodo di scrittura diretta sul pin della porta. ldi mp,0b00100000 out DDRD,mp ; Abilito la porta D sul bit 5 in uscita per avere la commutazione ; automatica del pin quando avviene l'evento di comparazione. ldi mp,0b00000011 out PORTC,mp ; Spengo tutti i led sulla porta C ; Vengono impostati i registri di controllo del timer 1 TCCR1A e TCCR1B ; Questi registri in generale controllano i modi di funzionamento del timer, ; per le diverse modalità ( funzione di output-compare su un pin di uscita ; dedicato, funzione di PWM, funzione di input-capture da un pin, selezione ; della sorgente di clock e di eventuale prescaler, abilitazione o meno degli ; interrupt, etc..). In generale come per tutte le periferiche, quando queste ; sono abilitate, vengono perse le funzionalità di I/O su alcuni pin delle ; porte disponibili, i quali assumono una funzione dedicata: in questo caso ; tali pin possono fungere da uscite per il timer, da ingressi di conteggio, ; o da ingressi di clock esterni. ; ldi mp,(1 << COM1A0) out TCCR1A,mp ldi mp,0 out TCCR1B,mp ; I registri di controllo sono impostati con i valori di 'default': le funzioni di output-compare ; sui pin OC1A e OC1B sono disabilitate, le funzioni PWM sono disabilitate, il timer risulta ; fermo. ldi mp,HIGH(31560) out OCR1AH,mp ldi mp,LOW(31560) out OCR1AL,mp ; I registri di 'compare' sono impostati con il valore su 16 bit (31560) da comparare: ; Affinché tutti i 16 bit vengano trasferiti contemporaneamente nei due registri, ; occorre scrivere prima il byte alto, quindi il byte basso. ldi mp,0 out TCNT1H,mp out TCNT1L,mp ; Viene azzerato il timer. ; anche in questo caso affinché tutti i 16 bit vengano trasferiti contemporaneamente ; nei due registri del timer, occorre scrivere prima il byte alto, quindi il byte basso. ldi mp,(1 << OCIE1A) out TIMSK,mp ; Viene abilitata la gestione dell'interrupt sull'evento di compare del timer1 canale A. ldi mp,(1 << CTC1) | (1 << CS12) out TCCR1B,mp ; Viene avviato il timer con fattore di prescaler posto a 256x e sorgente di clock interna. ; Viene inoltre attivata la modalità di auto azzeramento del timer quando avviene un ; evento di comparazione. ldi mp,0b10000000 out SREG,mp ; Sono abilitati tutti gli interrupt gestiti. ; In alternativa su può utilizzare l'iatruzione assembler 'SEI' per abilitare il solo bit 'I' ; nel registro di stato. main_loop: ldi loop_cnt,3 flash_loop: rcall delay dec loop_cnt brne flash_loop sbis PORTC, PC0 rjmp set_bit cbi PORTC, PC0 rjmp main_loop set_bit: sbi PORTC, PC0 rjmp main_loop ; Viene invertito lo stato di uscita del led sulla porta C ; Occorre notare che il pin 5 della porta D automaticamente commuta quando ; il flag OCF1A risulta impostato, in questo modo si sgrava la CPU del compito ; di commutare l'uscita. ; Notare l'utilizzo delle funzioni con operatore sui bit (cbi e sbi), e di salto ; di istruzione sullo stato dei bit ( sbis ). ; Quindi viene ripetuto il loop. ; Verificate inoltre cha il lampeggio ottenuto in questa parte di programma non ; è costante, a causa del tempo consumato dalla routine di interrupt. ; Di seguito compare la routine di gestione dell'interrupt associata ad un evento di ; compare del timer1 canale A. Timer1_COMPA_ISR: push loop_cnt push n3 push n2 push n1 in n1, SREG push n1 ; le variabili n1,n2 e loop_count sono usate dalla routine di ritardo 'delay'. ; Tale routine è utilizzata contemporaneamente sia nel programma principale che ; in questa parte di codice in interrupt. ; Se l'evento di interrupt avviene in un istante in cui il programma principale ; stà eseguendo un ciclo di ritardo, questa funzione di interrupt 'sporca' ; queste variabili durante l'esecuzione del proprio ritardo, ed al successivo ; ritorno nel punto in cui il programma principale si era interrotto, si provoca un ; malfunzionamento nel calcolo dei tempi nel loop di ritardo che si stava eseguendo. ; E sempre ** I M P O R T A N T E ** nelle funzioni di gestione degli interrupt, ; ** S A L V A R E ** il contesto delle variabili, dei registri ed in generale delle ; risorse utilizzate in maniera condivisa con le parti di codice esterne. ; E' inoltre importante ** S A L V A R E ** il registro di stato, che deve mantenere ; i valori originali dei flags al ritorno dall'interrupt. ldi loop_cnt,5 int_flash_loop: sbi PORTC, PC1 rcall delay cbi PORTC, PC1 rcall delay dec loop_cnt brne int_flash_loop sbi PORTC, PC1 ; Viene eseguito un burst di lampeggi su un led collegato al pin 1 della porta C, ; ad ogni generazione di un evento di 'compare'. ; Questo lampeggio resta sincrono con ciascun evento a differenza di quello gestito ; in polling, il quale assumerà dei ritardi nel tempo del proprio lampeggio ; comparato con gli altri due led. Infatti l'interrupt 'consuma' tempo al programma ; normalmente in esecuzione, per cui i ritardi 'software' generati nel main ; non forniscono tempi costanti. in mp, TIFR sbr mp, (1 << OCF1A) out TIFR, mp ; Viene azzerato il bit di stato della comparazione del canale A, OCF1A. pop n1 out SREG, n1 pop n1 pop n2 pop n3 pop loop_cnt ; Viene recuperato il contesto salvato all'ingresso della ISR, ovviamente in ordine ; inverso rispetto a quello con cui le rispettive variabili sono state salvate sullo stack. reti ; complicatissima routine per generare un ritardo. delay: ldi n3,0x2 delay_loop3: ldi n2,0xff delay_loop2: ldi n1,0xff delay_loop1: dec n1 brne delay_loop1 dec n2 brne delay_loop2 dec n3 brne delay_loop3 ret