Ο AVR είναι ένας 8bit μικροελεγκτής που σημαίνει ότι μπορεί να χειρίζεται δεδομένα μεγέθους 8bit κάθε φορά. Όμως μερικοί καταχωρητές του Timer1 όπως οι TCNT1, OCR1A, ICR1 κ.τ.λ. είναι μεγέθους 16bit. Σε αυτή την περίπτωση οι καταχωρητές διαχωρίζονται σε δυο 8bit καταχωρητές και κάθε ένας προσπελαύνεται ανεξάρτητα. Αυτό είναι εντάξει στις περισσότερες περιπτώσεις. Για παράδειγμα όταν θέλουμε να φορτώσουμε τα περιεχόμενα του SP (stack pointer) πρώτα θα φορτώσουμε το μισό και μετά το άλλο μισό, όπως δείχνεται παρακάτω:
LDI R16, 0x12
OUT SPL, R16
LDI R16, 0x34
OUT SPH, R16 ; SP = 0x3412
Στους 16bit timers, θα πρέπει να διαβάσουμε ή να γράψουμε ολόκληρο το περιεχόμενο ενός καταχωρητή μεμιάς, αλλιώς θα έχουμε προβλήματα. Για παράδειγμα φαντάσου το ακόλουθο σενάριο: Ο καταχωρητής TCNT1 περιέχει την τιμή 0x15FF. Διαβάζουμε το χαμηλότερο byte του TCNT1 το οποίο είναι το 0xFF και το αποθηκεύουμε στον R20. Την ίδια στιγμή με ένα κύκλο ρολογιού η τιμή του TCNT1 γίνεται 0x1600. Τώρα διαβάζουμε το υψηλότερο byte του TCNT1 το οποίο είναι τώρα 0x16 και το αποθηκεύουμε στον R21. Έτσι διαβάζουμε την τιμή R21:R20=0x16FF αντί για την πραγματική που είναι η 0x15FF
Αυτό το πρόβλημα υπάρχει σε πολλούς 8bit μικροελεγκτές. Οι σχεδιαστές του μικροελεγκτή AVR έλυσαν αυτό το πρόβλημα με την χρήση ενός 8bit καταχωρητή που ονομάζεται ΤΕΜΡ και χρησιμοποιείται σαν buffer (μεσολαβητής). Όταν γράφουμε ή διαβάζουμε το υψηλότερο byte ενός 16bit καταχωρητή, η τιμή θα γραφεί στον καταχωρητή ΤΕΜΡ. Όταν γράφουμε στο χαμηλότερο byte ενός 16bit καταχωρητή τα περιεχόμενα του ΤΕΜΡ θα γραφούν στο υψηλότερο byte του 16bit καταχωρητή την ίδια στιγμή. Για παράδειγμα θεωρήστε το ακόλουθο πρόγραμμα.
LDI R16, 0x15
STS TCNT1H, R16 ; store 0x15 in TEMP of Timer1
LDI R16, 0xFF
STS TCNT1L, R16 ; TCNT1L = R16, TCNT1H = TEMP
Μετά την εκτέλεση της εντολής STS TCNT1H, R16 το περιεχόμενο του R16, 0x15 θα αποθηκευτεί στον καταχωρητή TEMP. Καθώς η εντολή STS TCNT1L, R16 εκτελείται τα περιεχόμενα του R16, 0xFF φορτώνονται στον TCNT1L και τα περιεχόμενα του καταχωρητή ΤΕΜΡ 0x15 φορτώνονται στον TCNT1H. Έτσι η τιμή 0x15FF φορτώνεται στον TCNT1 μεμιάς.
Σημείωση: Σύμφωνα με το εσωτερικό κύκλωμα του AVR θα πρέπει πρώτα να γράψουμε το υψηλότερο byte των 16bit καταχωρητών και μετά να γράψουμε το χαμηλότερο byte. Διαφορετικά το πρόγραμμα δεν θα δουλέψει σωστά. Για παράδειγμα ο ακόλουθος κώδικας δεν θα δουλέψει σωστά. Αυτό διότι, όταν ο TCNT1L φορτώνεται, τα περιεχόμενα του ΤΕΜΡ θα φορτωθούν στον TCNT1H. Αλλά εδώ όταν ο καταχωρητής TCNT1L φορτώνεται, ο ΤΕΜΡ περιέχει “σκουπίδια” και αυτό είναι κάτι που δεν θέλουμε.
LDI R16, 0xFF
STS TCNT1L, R16 ; TCNT1L = R16, TCNT1H = TEMP
LDI R16, 0x15
STS TCNT1H, R16 ; store 0x15 in TEMP of Timer1
Όταν διαβάζουμε το χαμηλότερο byte ενός 16bit καταχωρητή, τα περιεχόμενα του υψηλότερου byte θα αντιγραφούν στον καταχωρητή TEMP. Έτσι το ακόλουθο πρόγραμμα διαβάζει τα περιεχόμενα του TCNT1
LDS R20, TCNT1L ; R20 = TCNT1L, TEMP = TCNT1H
LDS R21, TCNT1H ; R21 = TEMP of Timer1
Θα πρέπει να προσέξουμε την σειρά που διαβάζουμε το υψηλότερο και το χαμηλότερο byte των 16bit καταχωρητών. Διαφορετικά το αποτέλεσμα θα είναι λανθασμένο.
Σημείωση: το διάβασμα των OCR1A και OCR1B καταχωρητών δεν εμπλέκει ενδιάμεσο καταχωρητή. Ίσως να αναρωτηθείς το γιατί. Αυτό διότι ο AVR μικροελεγκτής δεν ανανεώνει τα περιεχόμενα των OCR1A και OCR1B εκτός αν το κάνουμε εμείς. Για παράδειγμα θεωρήστε τον ακόλουθο κώδικα.
LDS R20, OCR1AL ; R20 = OCR1L
LDS R21, OCR1AH ; R21 = OCR1H
Ο κώδικας αυτός διαβάζει το χαμηλότερο byte του OCR1A και μετά το υψηλότερο byte. Μεταξύ των δυο αναγνώσεων τα περιεχόμενα του καταχωρητή παραμένουν αμετάβλητα. Γι’ αυτό το λόγο ο AVR δεν απασχολεί τον καταχωρητή TEMP όταν διαβάζουμε τους OCR1A και OCR1B καταχωρητές.
Παράδειγμα: θεωρείστε XTAL=16MHz. Γράψτε ένα πρόγραμμα το οποίο να εναλλάσσει το ΡΒ5 μια φορά σε κάθε χιλιοστό του δευτερολέπτου.
Απάντηση: XTAL=16MHz σημαίνει ότι κάθε κύκλος ρολογιού διαρκεί 0,0625μs. Για καθυστέρηση 1ms χρειαζόμαστε 1ms/0,0625μs = 16000 κύκλους ρολογιού = 0x3E80 κύκλους. Αρχικοποιούμε τον timer έτσι ώστε μετά από 16000 κύκλους η σημαία OCF1A να τίθεται σε 1 και μετά να εναλλάσσει το ΡΒ5.
LDI R16, HIGH(RAMEND)
OUT SPH, R16
LDI R16, LOW(RAMEND)
OUT SPL, R16 ; initialize the stack
SBI DDRB, 5 ; PB5 as an output
BEGIN: SBI PORTB, 5 ; PB5 = 1
RCALL DELAY_1ms
CBI PORTB, 5 ; PB5 = 0
RCALL DELAY_1ms
RJMP BEGIN
;----------- Timer1 delay
DELAY_1ms:
LDI R20, 0x00
STS TCNT1H, R20 ; TEMP = 0
STS TCNT1L, R20 ; TCNT1L = 0, TCNT1H = TEMP
LDI R20, HIGH(16000-1)
STS OCR1AH, R20 ; TEMP = 0x1F
LDI R20, LOW(16000-1)
STS OCR1AL, R20 ; OCR1AL = 0x3F, OCR1AH = TEMP
LDI R20, 0x00
STS TCCR1A, R20 ; WGM11:10 = 00
LDI R20, 0x09
STS TCCR1B, R20 ; WGM13:12 = 01, CTC mode, CS = 1
AGAIN:
SBIS TIFR1, OCFR1A ; if OCF1A is set skip next instruction
RJMP AGAIN
LDI R19, 0
STS TCCR1B, R19 ; stop timer
STS TCCR1A, R19
LDI R20, 1<<OCF1A
OUT TIFR1, R20 ; clear OCF1A flag
RET
Παράδειγμα: Γράψε τον προηγούμενο κώδικα του παραδείγματος χρησιμοποιώντας την σημαία TOV1.
Απάντηση: Για καθυστέρηση 1ms θα πρέπει να φορτώσουμε τον καταχωρητή TCNT1 έτσι ώστε να υπερχειλίζει μετά από 16000 = 0x3E80 κύκλους. Στην κατάσταση λειτουργίας Normal η κορυφή είναι η τιμή 0xFFFF=65535. Έχουμε 65535 + 1 – 16000 = 49536 = 0xC180. Έτσι θα πρέπει να φορτώσουμε τον TCNT1 με την τιμή 49536 ή 0xC180 στο δεκαεξαδικό ή απλά μπορούμε να χρησιμοποιήσουμε 65536 – 16000 όπως δείχνεται παρακάτω.
LDI R16, HIGH(RAMEND) ; initialize stack pointer
OUT SPH, R16
LDI R16, LOW(RAMEND)
OUT SPL, R16
SBI DDRB, 5 ; PB5 as an output
BEGIN: SBI PORTB, 5 ; PB5 = 1
RCALL DELAY_1ms
CBI PORTB, 5
RCALL DELAY_1ms
RJMP BEGIN
;---------- Timer delay
DELAY_1ms:
LDI R20, HIGH(65536-16000) ; R20 = high byte of 49536
STS TCNT1H, R20 ; TEMP = 0xE0
LDI R20, LOW(65536-16000) ; R20 = low byte of 49536
STS TCNT1L, R20 ; TCNT1L = 0xC1, TCNT1H = TEMP
LDI R20, 0x00
STS TCCR1A, R20 ; WGM11:10 = 00
LDI R20, 0x01
STS TCCR1B, R20 ; WGM13:12 = 00, Normal mode, CS = 1
AGAIN:
SBIS TIFR1, TOV1 ; if OCF1A is set skip next instruction
RJMP AGAIN
LDI R19, 0
OUT TCCR1B, R19 ; stop timer
OUT TCCR1A, R19
LDI R20, 1<<TOV1
OUT TIFR1, R20 ; clear TOV1 flag
RET
Παραγωγή μεγάλων χρονικών διαστημάτων με prescaler
Όπως έχουμε δει μέχρι στιγμής, το μέγεθος της χρονικής καθυστέρησης εξαρτάται από δυο παράγοντες (α) την συχνότητα του κρυσταλλικού ταλαντωτή και (β) την τιμή του 16bit καταχωρητή του timer. Μπορούμε να χρησιμοποιήσουμε την επιλογή prescaler του καταχωρητή TCCR1B για να αυξήσουμε την καθυστέρηση. Η επιλογή prescaler μας επιτρέπει να διαιρέσουμε το σήμα ρολογιού που τροφοδοτεί τον timer με ένα παράγοντα από 8 έως 1024 όπως φαίνεται στο σχήμα.

Όπως έχουμε δεί μέχρι στιγμής με την επιλογή no prescaler, η συχνότητα του ρολογιού του εσωτερικού ταλαντωτή τροφοδοτεί κατευθείαν τον Timer1. Εάν επιλέξουμε τη δυνατότητα prescaler στον TCCR1B καταχωρητή, μπορούμε να διαιρέσουμε τους κύκλους ρολογιού προτού τροφοδοτηθούν στον Timer1. Τα χαμηλότερα 3bits του καταχωρητή TCCR1B ορίζουν τον αριθμό με τον οποίο θα διαιρεθεί το σήμα ρολογιού προτού τροφοδοτήσει τον timer. Όπως φαίνεται στο σχήμα αυτός ο αριθμός μπορεί να είναι 8, 64, 256 ή 1024.
Παράδειγμα: Ένα LED είναι συνδεμένο στο ΡΒ5. Θεωρώντας XTAL = 16MHz, γράψε ένα πρόγραμμα το οποίο να αναβοσβήνει το LED μια φορά το δευτερόλεπτο.
Απάντηση: Επειδή XTAL = 16MHz οι διάφορει έξοδοι από τον prescaler είναι όπως ακολουθεί
Scaler Timer_Clock Timer_Period Timer_Value
None 16MHz 1/16MHz = 0,0625μs 1s/0,0625μs = 16M
8 16MHz/8 = 2MHz 1/2MHz = 0,5μs 1s/0,5μs = 2M
64 16MHz/64 = 250kHz 1/250kHz = 4μs 1s/4μs = 250.000
256 16MHz/256 = 62,5kHz 1/62,5kHz = 16μs 1s/16μs = 62.500
1024 16MHz/1024 = 15625Hz 1/15625Hz = 64μs 1s/64μs = 15.625
Από τους παραπάνω υπολογισμούς μπορούμε να χρησιμοποιήσουμε την επιλογή 256 ή 1024. Επιλέγουμε 1024 για αυτό το παράδειγμα.
LDI R16, HIGH(RAMEND) ; initialize stack pointer
OUT SPH, R16
LDI R16, LOW(RAMEND)
OUT SPL, R16
SBI DDRB, 5 ; PB5 as an output
BEGIN: SBI PORTB, 5 ; PB5 = 1
RCALL DELAY_1s
CBI PORTB, 5
RCALL DELAY_1s
RJMP BEGIN
;----------Timer1 delay
DELAY_1s:
LDI R20, HIGH(15625-1)
STS OCR1AH, R20 ; TEMP = $3D (15624 = 0x3D08)
LDI R20, LOW(15625-1)
STS OCR1AL, R20 ; OCR1AL = $08 (15624 = 0x3D08)
LDI R20, 0
STS TCNT1H, R20 ; TEMP = 0x00
STS TCNT1L, R20 ; TCNT1L = 0x00, TCNT1H = TEMP
LDI R20, 0x00
STS TCCR1A, R20 ; WGM11:10 = 00
LDI R20, 0x5
STS TCCR1B, R20 ; WGM13:12=00, Normal mode, CS=CLK/1024
AGAIN: SBIS TIFR1, OCF1A ; if OCF1A is set skip next instruction
RJMP AGAIN
LDI R19, 0
STS TCCR1B, R19 ; stop timer
STS TCCR1A, R19
LDI R20, 1<<OCF1A
OUT TIFR1, R20 ; clear OCF1A flag
RET
Παράδειγμα: Θεωρώντας XTAL=16MHz γράψε ένα πρόγραμμα που να παράγει σήμα συχνότητας 1 Hz στο ΡΒ5
Απάντηση: Με 1 Hz έχουμε περίοδο Τ = 1/F = 1/1Ηz = 1 sec από όπου μισό είναι High και μισό Low. Έτσι χρειαζόμαστε καθυστέρηση διάρκειας 0,5 sec.
Για XTAL = 16MHz, οι διάφοροι έξοδοι από τον prescaler είναι οι εξής:
Scaler Timer_Clock Timer_Period Timer_Value
None 16MHz 1/16MHz = 0,0625μs 0,5s/0,0625μs = 4M
8 16MHz/8 = 2MHz 1/2MHz = 0,5μs 0,5s/0,5μs = 1M
64 16MHz/64 = 250kHz 1/250kHz = 4μs 0,5s/4μs = 125.000
256 16MHz/256 = 62,5kHz 1/62,5kHz = 16μs 0,5s/16μs = 31.250
1024 16MHz/1024 = 15625Hz 1/15625Hz = 64μs 0,5s/64μs = 7812,5
Από τους παραπάνω υπολογισμούς μπορούμε να χρησιμοποιήσουμε μόνο τις επιλογές 256 ή 1024. Θα πρέπει να χρησιμοποιήσουμε την επιλογή 256 μιας που δεν μπορούμε να χρησιμοποιήσουμε μια τιμή με δεκαδικά ψηφία.
LDI R16, HIGH(RAMEND) ; initialize stack pointer
OUT SPH, R16
LDI R16, LOW(RAMEND)
OUT SPL, R16
SBI DDRB, 5
BEGIN: SBI PORTB, 5
RCALL DELAY_1s
CBI PORTB, 5
RCALL DELAY_1s
RJMP BEGIN
;-------- Timer delay
DELAY_1s:
LDI R20, HIGH(31250-1)
STS OCR1AH, R20 ; TEMP = $7A (31249=$7A11)
LDI R20, LOW(31250-1)
STS OCR1AL, R20 ; OCR1AL = $11
LDI R20, 0x00
STS TCNT1H, R20 ; TEMP = 0x00
STS TCNT1L, R20 ; TCNT1L = 0x00, TCNT1H = TEMP
LDI R20, 0x00
STS TCCR1A, R20 ; WGM11:10 = 00
LDI R20, 0x4
STS TCCR1A, R20 ; WGM13:12 = 00, Normal mode, CS=CLK/256
AGAIN: SBIS TIFR1, OCF1A ; if OCF1A is set skip next instruction
RJMP AGAIN
LDI R19, 0
OUT TCCR1B, R19 ; stop timer
OUT TCCR1A, R19
LDI R20, 1<<OCF1A
OUT TIFR1, R20
RET