8. Πρόσβαση στους 16bit καταχωρητές

Ο 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