BI-SAP.21 Struktura a architektura počítačů
Jdi na navigaci předmětu

Přerušení

Přerušení je schopnost procesoru zareagovat na událost (interní i externí) během vykonávání programu. Externí přerušení jsou zpravidla hlášení od periferií, že došlo k události, kterou by měl procesor zaznamenat (např. stisk tlačítka) a tzv. obsloužit (tj. vykonat kód, který se po stisku tlačítka vykonat má).

Složitější procesory mohou obsahovat i vnitřní a softwarová přerušení, ale u AVR budeme pracovat s přerušeními vnějšími – konkrétně s přerušením od časovače a případně i tlačítek.

Už v názvu přerušení je schovaný princip funkce. Procesor vykonává hlavní kód a ve chvíli, kdy detekuje přerušení, tak hlavní kód přeruší (pouze dokončí aktuální instrukci) a vykoná kód, který najde na adrese, která je k danému přerušení přiřazena (tzv. vektor přerušení). Vektor přerušení je napevno svázaný s přerušením a programátor ho nemá možnost změnit.

1. Základní pravidla pro práci s přerušením

Obsluha přerušení se provádí zpravidla podprogramem. Na rozdíl od podprogramu ale programátor nemá vliv na to, kdy se přerušení vykoná (to ovlivní až uživatel, nebo externí časovač po spuštění programu), takže je potřeba bezpodmínečně provést "úklid" ukázaný na stránce podprogramy (a na níže uvedených příkladech) a to včetně status registru.

Pokud není úklid proveden, tak hrozí riziko, že se přerušení zavolá ve "správnou" (čti nejhorší možnou a nejnevhodnější) chvíli a dojde k nechtěnému přepisu dat. Takové chyby se pak velmi obtížně ladí (při každém spuštění kódu nastane přerušení v jiný okamžik). Situace je srovnatelná s prací s vlákny na pokročilejších procesorech a časově závislými chybami, které tam mohou vznikat.

Druhé pravidlo pro použití přerušení je takové, že kód přerušení by měl být co nejkratší. Při jeho vykonávání totiž procesor zpravidla (u AVR vždy) ignoruje ostatní přerušení a dlouhý kód obsluhy jednoho přerušení znamená, že se zvyšuje šance, že procesor ignoruje přerušení jiné. Pokud se v kódu používá jen jedno přerušení (např. časovač s periodou 1s), tak není toto pravidlo tak kritické jako pravidlo s úklidem, ale i tak je vhodné jej dodržovat (už jen kvůli tomu, kdyby náhodou někdo chtěl kód o další přerušení doplnit později).

Typický kód využívající přerušení je postaven na následujícím principu (detailní příklad je na konci této stránky):

; inicializace

loop:
    ; if(aktivni_flag_preruseni) {
    ;   deaktivuj flag;
    ;   zareaguj;
    ; }
jmp loop

; konec hlavniho programu

interrupt:
    ; proved uklid
    ; aktivuj flag
    ; proved obnovu
reti                 ; 1
  1. reti je návrat z kódu přerušení. Chová se podobně jako ret, ale ještě povolí globální přerušení (jako by tam byly dvě instrukce sei a ret).

2. Nastavení čítače/časovače 1 na AVR

2.1. Základní princip

V rámci SAPů budeme používat zpravidla pouze toto přerušení, které umožní použít přesné časování bez použití čekacích smyček. Ostatní přerušení jsou popsána v referenčním manuálu.

Blokové schéma přerušovacího obvodu můžete nalézt v referenčním manuálu, ale pro základní představu pro náš případ bude stačit zjednodušené schéma na obrázku níže.

Zjednodušené schéma přerušovacího obvodu

Čítač/časovač 1 nabízí několik různých módu činnosti, z nichž využijeme ten, který funguje na následujícím principu:

  • Signál zdroje nejprve projde programovatelnou předděličkou (de facto čítač se šířkou nastavitelnou pomocí bitů CS12:CS10),
  • Zpomalený signál způsobí inkrementaci čítače TCNT1,
  • Hodnota čítače se porovnává s hodnotou, kterou nahrajeme do paměti na adresu OCR1A (tím budeme řídit frekvenci přerušení - viz vzorec níže),
  • Pomocí bitů WGM12 a OCIE1A nastavíme, aby se ve chvíli, kdy platí TCNT1==OCR1A, čítač TCNT1 resetoval a generovalo se přerušení.

2.2. Nastavení frekvence přerušení

Výpočet frekvence přerušení se řídí nastavením předděličky a hodnotou OCR1A

frekvence_preruseni = frekvence_cipu_328P / nastaveni_preddelicky / (OCR1A+1)

ze kterého lze snadno hodnotu OCR1A dopočítat. V našem případě platí, že maximální povolená hodnota OCR1A je 65535 (16bitová hodnota) a frekvence_cipu_328P je 16 MHz. nastaveni_preddelicky je potřeba zvolit tak, aby OCR1A<65536 a pokud možno nedocházelo k zaokrouhlování během výpočtu.

2.3. Ukázka inicializace přerušení

Následující kód inicializuje čítač/časovač 1 tak, aby mohl generovat přerušení 1x za sekundu:

init_int:
    push r16
    cli ; globalni zakazani preruseni

    ; vycisteni aktualni hodnoty citace TCNT1 (aby prvni sekunda nezacala nekde "od pulky")
    ; Neprehazujte poradi nahravani TCNT1H a TCNT1L - hodnota by se nemusela spravne ulozit!
    clr r16
    sts TCNT1H, r16         ; 1 2
    sts TCNT1L, r16         ; 1 2

    ; povoleni preruseni ve chvili, kdy citac TCNT1 dosahne hodnoty OCR1A
    ldi r16, (1<<OCIE1A)    ; 2 3
    sts TIMSK1, r16         ; 2

    ; nastaveni cisteni citace TCNT1 ve chvili, kdy dosahne hodnoty OCR1A (1<<WGM12)
    ; nastaveni preddelicky na 1024 (0b101<<CS10 - bity CS12, CS11 a CS10 jsou za sebou) 4
    ldi r16, (1<<WGM12) | (0b101<<CS10) ; 2 5
    sts TCCR1B, r16         ; 2

    ; nastaveni OCR1A, tj. vysledne frekvence preruseni
    ; frekvence preruseni = frekvence cipu 328P / preddelicka / (OCR1A+1)
    ; frekvence cipu 328P je 16 MHz, tj. 16000000
    ; preddelicka je nastavena na 1024
    ; frekvenci preruseni chceme na 1 Hz
    ; OCR1A = (frekvence cipu 328P / preddelicka / frekvence preruseni) - 1
    ; OCR1A = (16000000 / 1024 / 1) - 1
    ; OCR1A = 15624
    ; 16bitovou hodnotu je treba nastavit do dvou registru OCR1AH:OCR1AL
    ; 15624 = 61 * 256 + 8 ; 6
    ; Neprehazujte poradi nahravani OCR1AH a OCR1AL - hodnota by se nemusela spravne ulozit!
    ldi r16, 61
    sts OCR1AH, r16         ; 2
    ldi r16, 8
    sts OCR1AL, r16         ; 2

    ; zakazani preruseni od tlacitek
    clr r16
    out EIMSK, r16          ; 2

    sei ; globalni povoleni preruseni
    pop r16
    ret
  1. TCNT1 je 16bitová hodnota, je tedy potřeba nastavit obě její poloviny (TCNT1H a TCNT1L).
  2. TCNT1H, TCNT1L a další takto označené názvy jsou pouze předdefinované návěští/konstanty v překladači. Výhodou použití konstant je jednodušší migrace na jiný typ procesoru, který může mít jiné konkrétní hodnoty, ale stejný princip nastavení čítače/časovače.
  3. Výraz (1<<OCIE1A) také vypočítá překladač a do kódu dosadí výslednou konstantu (v našem případě log. 1 posunutá o 1 místo vlevo, tj. 0b00000010).
  4. Povolené hodnoty a jejich význam naleznete v tabulce níže.
  5. Na adrese TCCR1B je uložen jak bit WGM12 nastavující reset čítače, tak nastavení předděličky (CS12:CS10).
  6. OCR1A je také 16bitová hodnota, je tedy potřeba nastavit obě její poloviny (OCR1AH a OCR1AL).

Povolené hodnoty bitů CS12, CS11 a CS10 a jejich význam:

Hodnota bitů CS12, CS11 a CS10Dělící poměr
0b0000 (stop)
0b0011
0b0108
0b01164
0b100256
0b1011024

3. Příklad správného použití přerušení

Správné použití přerušení využívá datovou paměť. U té je větší pravděpodobnost (při správném použití jistota), že nedojde k přepsání cizích dat, protože překladač automaticky hlídá konflikty v umístění návěštích. Tento kód by měl zpracovat přerušení bez problémů:

.dseg                ; prepnuti do pameti dat 1
.org 0x100           ; od adresy 0x100 (adresy 0 - 0x100 nepouzivejte)

flag: .byte 1        ; rezervovani mista pro 1 bajt

.cseg                ; prepnuti do pameti programu
; podprogramy pro praci s displejem
.org 0x1000
.include "printlib.inc"

; Zacatek programu - po resetu
.org 0
    jmp	start
.org 0x16            ; 2
    jmp interrupt

.org 0x100

start:
    ; Inicializace displeje
    call init_disp
    ; Inicializace preruseni od casovace
    call init_int

    ldi r16, 0       ; 3
    sts flag, r16

    ldi r17, 0
    ldi r16, '0'
    call show_char

main_loop:
    lds r20, flag
    cpi r20, 0       ; nacteni a otestovani hodnoty flag-u
    breq main_loop   ; pokud neni flag -> navrat na zacatek
                     ; je flag
    ldi r20, 0       ; vycisteni flag-u
    sts flag, r20

    ; akce provedena 1x za sekundu 4
    inc r16
    call show_char

    jmp main_loop

end: jmp end

init_int:            ; 5
    push r16
    cli ; globalni zakazani preruseni

    ; vycisteni aktualni hodnoty citace TCNT1 (aby prvni sekunda nezacala nekde "od pulky")
    clr r16
    ; Neprehazujte poradi nahravani TCNT1H a TCNT1L - hodnota by se nemusela spravne ulozit!
    sts TCNT1H, r16
    sts TCNT1L, r16

    ; povoleni preruseni ve chvili, kdy citac TCNT1 dosahne hodnoty OCR1A
    ldi r16, (1<<OCIE1A)
    sts TIMSK1, r16

    ; nastaveni cisteni citace TCNT1 ve chvili, kdy dosahne hodnoty OCR1A (1<<WGM12)
    ; nastaveni preddelicky na 1024 (0b101<<CS10 - bity CS12, CS11 a CS10 jsou za sebou)
    ldi r16, (1<<WGM12) | (0b101<<CS10)
    sts TCCR1B, r16

    ; nastaveni OCR1A, tj. vysledne frekvence preruseni
    ; frekvence preruseni = frekvence cipu 328P / preddelicka / (OCR1A+1)
    ; frekvence cipu 328P je 16 MHz, tj. 16000000
    ; preddelicka je nastavena na 1024
    ; frekvenci preruseni chceme na 1 Hz
    ; OCR1A = (frekvence cipu 328P / preddelicka / frekvence preruseni) - 1
    ; OCR1A = (16000000 / 1024 / 1) - 1
    ; OCR1A = 15624
    ; 16bitovou hodnotu je treba nastavit do dvou registru OCR1AH:OCR1AL
    ; 15624 = 61 * 256 + 8
    ; Neprehazujte poradi nahravani OCR1AH a OCR1AL - hodnota by se nemusela spravne ulozit!
    ldi r16, 61
    sts OCR1AH, r16
    ldi r16, 8
    sts OCR1AL, r16

    ; zakazani preruseni od tlacitek
    clr r16
    out EIMSK, r16

    sei ; globalni povoleni preruseni
    pop r16
    ret

interrupt:           ; 6
    ; uklid registru a SREG
    push r16
    in r16, SREG
    push r16

    ; nastav flag
    ldi r16, 1
    sts flag, r16

    ; obnoveni SREG a registru
    pop r16
    out SREG, r16
    pop r16
    reti             ; 7
  1. Více detailů o práci s datovou pamětí naleznete zde.
  2. Na adrese 0x16 je vektor přerušení pro časovač 1 v námi používaném módu. Na okolních adresách jsou další vektory přerušení, proto je na dané adrese pouze jmp interrupt. Přerušení de facto zakáže ostatní přerušení a zavolá podprogram na 0x16 (jako by se zavolaly dvě instrukce cli a call 0x16), proto na dané adrese 0x16 nesmí být další call.
  3. Inicializace paměťového místa (ani paměť ani registry nejsou na vývojovém kitu inicializovány automaticky).
  4. Zde jen jednoduchý inkrement a vytištění hodnoty na displej.
  5. Více detailů o inicializaci naleznete na začátku této stránky.
  6. Přerušení je co nejkratší, obsahuje jen úklid, nezbytnosti k nastavení flagu a obnovení.
  7. reti je návrat z kódu přerušení. Chová se podobně jako ret, ale ještě povolí globální přerušení (jako by tam byly dvě instrukce sei a ret).