Proces přijetí paketu můžeme rozložit na dílčí úlohy:
- upoutání pozornosti procesoru při příchodu synchronizační sekvence
- detekce libovolné hrany
- zachycení konce synchronizační sekvence
- real-time: příjem obsahu, kanálové dekódování, odstranění bit stuffingu, uložení do SRAM, detekce EOP, vše v jedné smyčce
- dodatečné dekódování (v případě, že nebylo provedeno při příjmu paketu)
- výpočet CRC, kontrola správnosti
- rozbor paketu (parsování)
- příprava handshakové odpovědi (ACK/NAK/STALL) případně dat k odeslání
Časově kritické jsou pouze operace příjmu obsahu a výpočet CRC. Vzorkování vstupního sériového signálu je nutné provádět ekvidistantně, nejlépe uprostřed bitu. Detekce hrany musí proběhnout přesně, aby bylo zajištěno vzorkování v polovině bitu. Veškeré operace musejí trvat stejnou dobu, aby nevznikal časový skluz při vzorkování vstupu. Příjem obsahu zajišťuje programová smyčka, která musí za 8-12 taktů zvládnout dekódovat data a uložit je do SRAM. Základem úspěšné implementace USB do AVR mikrokontroléru je právě optimalizace této smyčky. Jedná se nám o konstantní objem výpočtů, které musíme s paketem provést, a rozvržení těchto výpočtů v čase. Buď se většina provede v přijímací smyčce (a data se do SRAM budou ukládat čistá) nebo se v přijímací smyčce data pouze zaznamenají a výpočet se provede až po skončení přenosu, kdy už není potřeba pracovat synchronně se vstupem. Časová prodleva, která může nastat mezi příjmem paketu a odesláním potvrzení (handshake) nemůže být libovolně dlouhá, jinak by byl paket považován za nedoručený a přenos by byl opakován.
Neoptimalizovaný příjem dat
Soustřeďme se nyní pouze na real-time operaci příjmu paketu. Předpokládejme, že synchronizační sekvence je již přijata a že načtení vstupu (vzorkování) je třeba provést každý první takt v celkovém cyklu. Navrhneme smyčku, jejíž trvání bude vždy trvat konstantní počet hodinových taktů. Teprve poté budeme optimalizovat posloupnosti instrukcí tak, abychom vyhověli požadavkům na přesné trvání cyklu v délce 8 (pro 12MHz), 10 (pro 15MHz), 12 (pro 18MHz) a 13 (pro 19,5MHz) hodinových taktů.
Smyčka musí zvládnout:
- načíst bit z portu B
- provést dekódování NRZI
- dekódovat bit stuffing (po šesti jedničkách vynechat cyklus)
- deserializovat bity (vrotovat je do registru)
- po 8 bitech zapsat data do SRAM
- detekovat přítomnost SE0 (DP i DM v nule) a opustit smyčku
Obecná smyčka splňující všechny předchozí body (čísla značí počet taktů):
10 ldi Xl,0x00 ;inicializace X ukazatele (někam do paměti)
11 ldi Xh,0x01
12 ldi bit,0 ;inicializace počtu bitů
13 ldi stuff,6 ;hledáme 6 jedniček za sebou
14,15 rjmp prijmi_bit ;můžeme přijímat
kontroluj_se0:
13 andi bit,7 ;počet bitů nesmí vybočit z rozsahu 0-7 (z čísla 255 se udělá 7)
14 andi line,(1<<DP)|(1<<DM) ;vymaskujeme pouze bity DP a DM
15 breq konec ;pokud jsou oba nulové, je to konec paketu
prijmi_bit:
1 in line,PINB ;načtení vstupů do registru line
2 eor minule, line ;výpočet inverzní NRZI, výstup je ale negován (AVR nemá instrukci XNOR)
3 bst minule, DP ;uložíme dekódovaný bit do vlajky T
4 mov minule, line ;stav linky musíme uchovat pro příští iteraci
5(6) brts prisla_nula ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
6 dec stuff ;přijali jsme jedničku, spočítáme ji
7 breq cekej ;jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
8 sec ;nastavit carry=1, kvůli rotaci
9 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
10 dec bit ;počítáme bity, z nuly se stane nula vždy po osmi taktech (viz andi bit,7)
11(12) brne kontroluj_se0 ;bitů ještě nebylo 8, přijmeme další
12,13 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
14,15 rjmp prijmi_bit ;pokračujeme dalšími bity
prisla_nula:
7 ldi stuff,6 ;nula nám vymaže předchozí přijatou řadu jedniček
8 clc ;carry=0
9 ror data ;pravá rotace, nula z carry se dostane na 7. bit v datovém registru
10 dec bit ;počítáme bity, z nuly se stane nula vždy po osmi taktech (viz andi bit,7)
11(12) brne kontroluj_se0 ;bitů ještě nebylo 8, přijmeme další
12,13 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
14,15 rjmp prijmi_bit ;pokračujeme dalšími bity
konec:
Nedostatky cyklu jsou především v jeho délce (15 taktů - 22,5MHz). Existují zde ale zbytečné instrukce (například clc a ror lze nahradit lsr - logickým posunem). Dále je chyba, že nevyužíváme hodnot, které vyrotují z registru data (to je informace, která může nahradit počítání bitů). Spolu s dalšími metodami můžeme běh programu urychlit.
Metody časové optimalizace kódu
- rozvinutí cyklu (unbundling)
Cyklus je opakované provádění části kódu. Počet provedených iterací se udržuje v pomocné proměnné (iterátoru). Celkový požadovaný počet iterací (průchodů) nemusí být dopředu znám a zastavovací podmínka (podmínka pro ukončení provádění cyklu) nemusí být na hodnotu iterátoru vázána. Sledujme ale takové cykly, u kterých je předem znám počet iterací a jedná se o konstantu. V různých jazycích by se takový cyklus řešil konstruktem for.
Doba provádění jednoho průchodu je zvýšena o kontrolu zastavovací podmínky. Rozvinutím se rozumí zopakování těla cyklu několikrát za sebou bez počítání iterátoru. Tím se ušetří jednak počítání (1 takt) a pak skok (2 takty). V našem případě ale není výhodné rozvinout cyklus úplně, protože bychom spotřebovali 8x více programové paměti. Unbundling 1. a 8. cyklu by vypadal takto:
Kromě toho, že jsme ušetřili takty při počítání a kontrole iterátoru, zmizely nám také podmíněné skoky. U prvního a posledního rozvinutého průchodu totiž víme přesně, jaký podmíněný skok by se provedl a jaký ne. Můžeme tedy odstranit podmínky a skok provést rovnou. Časová úspora už je poměrně značná - takt z podmínky, která vyjde, a dva takty z podmínky, která nevyjde. Rozvíjení cyklu je speciální případ zabudování dat do programu - obecnějšího přístupu transformujícího proměnné na pozici programu.
- Přeměna dat do pozice v programu
Procesor je stavový automat, který přechází mezi stavy na základě instrukcí. Počet stavů lze spočítat jako počet všech možností, které můžou paměťová místa procesoru (registry, paměti, výstupy) nabývat. Procesor s jedním 8-bitovým registrem a jedním 8-bitovým programovým ukazatelem (PC, program counter) má celkem 65536 stavů. Nahlížet na procesor jako na stavový automat může být výhodné - splývají zde totiž data, registry a pozice v programu do jediného pojmu - stavu procesoru.
Představme si, že potřebujeme uchovávat jednobitovou informaci, ale nemůžeme nebo nechceme ji držet v registru. Program, který hospodaří s touto informací nahrajeme do dolní poloviny paměti, jeho kopii do horní poloviny paměti. Prohlásíme, že pokud je programový ukazatel v dolní polovině, pak je naše proměnná nulová, pokud je programový ukazatel v horní polovině paměti, je proměnná rovna jedné.
Program Counter | Proměnná |
dolní polovina paměti | 0 |
horní polovina paměti | 1 |
Místo operací, které by přiřazovaly naší proměnné hodnotu, budeme provádět skoky mezi oběma polovinami paměti. Protože jsou obě verze programu totožné, nebude mít skok žádný vliv na provádění programu, ale bude mít vliv na hodnotu proměnné. Naše proměnná nemá žádné konkrétní místo, kde by ležela. Je jistým způsobem zakódována pro Program Counteru. V tomto případě, kdy jsme programovou paměť rozdělili na poloviny, by se dalo říct, že naši proměnnou jsme přesunuli do nejvýznamnějšího bitu Program Counteru. Nemusíme ale lpět na takovémto rozdělení paměti. Můžeme si definovat různé oblasti programu, nespojité, prolnuté, ve kterých vždy bude jednoznačně definováno, jakou hodnotu má naše proměnná. Taková definice nemá žádné místo, kam by se zapisovala, je totiž zakódována v programu a jeho chování. Je například zbytečné (dokonce nemožné) provádět podmíněné skoky podle naší proměnné. Jednak neexistuje mechanismus, jak takovou podmínku zakódovat (naše proměnná neleží v registru ani vlajce), a hlavně taková podmínka je buď splněna vždy, nebo nikdy. Měl-li by se v horní části provést skok za podmínky, že naše proměnná je nulová, pak se může úplně vynechat, protože v horní polovině paměti je z definice naše proměnná rovna jedné vždy.
- Změna směru čítání
Procesor disponuje vlajkami (flags) - jednobitovými hodnotami, ve kterých jsou uloženy informace o výsledku poslední operace. Jednou z vlajek je ZF (zero flag) která je nastavena v případě, že výsledekem poslední aritmetické operace byla nula.
Jednoduché čítání od jedné do deseti se pak nechá úsporněji přepsat na čítání od deseti do jedné.
- Čítání jako vedlejší produkt deserializace
Čítání nemusí být binární. Jako čítač může sloužit i posuvný registr. Naplníme-li posuvný registr hodnotou 0x80, pak je jisté, že právě po osmi pravých rotacích bude výstup z registru (vlajka carry) roven jedné. Přitom neklademe požadavky na hodnoty, které budou do posuvného registru zleva nasouvány. V našem případě můžeme využít tuto rotaci k deserializaci dat a přitom nepočítat klasickým způsobem počet bitů, které ještě musíme do registru vložit.
- Přesuny větví programu
Skoky jsou časově náročné operace a proto je potřeba řadit za sebe jednotlivé části programu tak, aby co nejvíce navazovaly. Využívá se tím přirozeného postupného zpracování programu. Je například zbytečné umísťovat nepodmíněné skoky, pokud jde celá větev, kam se má skočit, přesunout pod skokovou instrukci. Za cenu mírného obsazení programové paměti získáme k dobru dva hodinové takty.
Vhodným přeskládáním programu se může docílit i toho, aby se sobě délky jednotlivých iterací rovnaly.
Přijímač pro 19,5MHz
Abychom mohli snížit časové nároky 15-taktového neoptimalizovaného cyklu, musíme metodou čítání při deserializaci odstranit proměnnou bit a integrovat ji do proměnné data. Také bude nutné mírně upravit tok programu a některé bloky zopakovat, protože nebude čas na skok zpět.
8 ldi Xl,0x00 ;inicializace X ukazatele (někam do paměti)
9 ldi Xh,0x01
10 ldi data,0x7f ;inicializace shift registru
11 ldi stuff,6 ;hledáme 6 jedniček za sebou
12,13 rjmp prijmi_bit ;můžeme přijímat
zapis_1:
9 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
10 nop
kontroluj_se0:
11 nop
12 andi line,(1<<DP)|(1<<DM) ;vymaskujeme pouze bity DP a DM
13 breq konec ;pokud jsou oba nulové, je to konec paketu
prijmi_bit:
1 in line,PINB ;načtení vstupů do registru line
2 eor minule, line ;výpočet inverzní NRZI, výstup je ale negován (AVR nemá instrukci XNOR)
3 bst minule, DP ;uložíme dekódovaný bit do vlajky T
4 mov minule, line ;stav linky musíme uchovat pro příští iteraci
5(6) brts prisla_nula ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
6 dec stuff ;přijali jsme jedničku, spočítáme ji
7(8) breq cekej ;jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
navrat_z_cekani: ;do tohoto místa se vrátíme po čekání, které vzniká bit stuffingem
8 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
9(10) brcs kontroluj_se0 ;pokud je carry (výstup rotace) jedna, přijmeme další bit
10,11 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
12 sec ;do carry potřebujeme dostat jedničku (aby mohla proběhnout další rotace)
13 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout
1 in line,PINB ;načtení vstupů do registru line
2 eor minule, line ;výpočet inverzní NRZI, výstup je ale negován (AVR nemá instrukci XNOR)
3 bst minule, DP ;uložíme dekódovaný bit do vlajky T
4 mov minule, line ;stav linky musíme uchovat pro příští iteraci
5(6) brts prisla_nula ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
6 dec stuff ;přijali jsme jedničku, spočítáme ji
7(8) brne zapis_1 ;pokud jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
8,9 rjmp cekej10
prisla_nula:
7 ldi stuff,6 ;nula nám vymaže předchozí přijatou řadu jedniček
8 lsr data ;pravá logická rotace, na MSb se dostane nula
9(10) brcs kontroluj_se0 ;bitů ještě nebylo 8, přijmeme další
10,11 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
12 sec ;do carry potřebujeme dostat jedničku (aby mohla proběhnout další rotace)
13 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout
1 in line,PINB ;načtení vstupů do registru line
2 eor minule, line ;výpočet inverzní NRZI, výstup je ale negován (AVR nemá instrukci XNOR)
3 bst minule, DP ;uložíme dekódovaný bit do vlajky T
4 mov minule, line ;stav linky musíme uchovat pro příští iteraci
5(6) brts prisla_nula ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
6 dec stuff ;přijali jsme jedničku, spočítáme ji
7(8) brne zapis_1 ;jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
8,9 rjmp cekej10
cekej:
9 nop
cekej10:
10 com minule ;po příchodu bit-stuffingové nuly se převrátí hodnoty signálů DM a DP
11 sec ;carry byla zničena předchozí negací
12,13,1 jmp cekej2 ;zbývá nám 7 taktů čekání
cekej2:
2,3,4 jmp cekej5 ;zbývají nám 4 takty čekání
cekej5:
5 nop ;zbývá takt čekání
6,7 rjmp navrat_z_cekani
konec:
Přijímač pro 18MHz
Vyjdeme z předchozího návrhu pro 19,5MHz a pokusíme se ušetřit jeden takt. Nabízí se 11. instrukce - nop. Tím zpracování pro 1.-7. bit. Přesunutím sec z 12. instrukce před pravou rotaci dat vyřešíme nedostatek času ve větvi přijaté nuly.
7 ldi Xl,0x00 ;inicializace X ukazatele (někam do paměti)
8 ldi Xh,0x01
9 ldi data,0x7f ;inicializace shift registru
10 ldi stuff,6 ;hledáme 6 jedniček za sebou
11,12 rjmp prijmi_bit ;můžeme přijímat
zapis_1:
9 sec ;do carry potřebujeme dostat jedničku (aby mohla proběhnout další rotace)
10 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
kontroluj_se0:
11 andi line,(1<<DP)|(1<<DM) ;vymaskujeme pouze bity DP a DM
12 breq konec ;pokud jsou oba nulové, je to konec paketu
prijmi_bit:
1 in line,PINB ;načtení vstupů do registru line
2 eor minule, line ;výpočet inverzní NRZI, výstup je ale negován (AVR nemá instrukci XNOR)
3 bst minule, DP ;uložíme dekódovaný bit do vlajky T
4 mov minule, line ;stav linky musíme uchovat pro příští iteraci
5(6) brts prisla_nula ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
6 dec stuff ;přijali jsme jedničku, spočítáme ji
7(8) breq cekej ;jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
navrat_z_cekani: ;do tohoto místa se vrátíme po čekání, které vzniká bit stuffingem
8 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
9(10) brcs kontroluj_se0 ;pokud je carry (výstup rotace) jedna, přijmeme další bit
10,11 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
12 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout
1 in line,PINB ;načtení vstupů do registru line
2 eor minule, line ;výpočet inverzní NRZI, výstup je ale negován (AVR nemá instrukci XNOR)
3 bst minule, DP ;uložíme dekódovaný bit do vlajky T
4 mov minule, line ;stav linky musíme uchovat pro příští iteraci
5(6) brts prisla_nula ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
6 dec stuff ;přijali jsme jedničku, spočítáme ji
7(8) brne zapis_1 ;jedniček nebylo bylo za sebou šest, pokračujeme nahoře
8,9 rjmp cekej10 ;tento odskok je sice delší, ale ušetří čas jinde
prisla_nula:
7 ldi stuff,6 ;nula nám vymaže předchozí přijatou řadu jedniček
8 lsr data ;pravá logická rotace, na MSb se dostane nula
9(10) brcs kontroluj_se0 ;bitů ještě nebylo 8, přijmeme další
10,11 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
12 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout
1 in line,PINB ;načtení vstupů do registru line
2 eor minule, line ;výpočet inverzní NRZI, výstup je ale negován (AVR nemá instrukci XNOR)
3 bst minule, DP ;uložíme dekódovaný bit do vlajky T
4 mov minule, line ;stav linky musíme uchovat pro příští iteraci
5(6) brts prisla_nula ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
6 dec stuff ;přijali jsme jedničku, spočítáme ji
7 brne zapis_1 ;jedniček nebylo za sebou šest
8,9 rjmp cekej10 ;odskok je delší, čekání bude kratší
cekej:
9 nop ;zbývá takt čekání
cekej10:
10 com minule ;po příchodu bit-stuffingové nuly se převrátí hodnoty signálů DM a DP
11 sec ;carry byla zničena předchozí negací
12,1,2 jmp cekej0 ;zbývá nám 6 taktů čekání
cekej3:
3,4,5 jmp cekej4 ;zbývají nám 3 takty čekání
6,7 rjmp navrat_z_cekani
konec:
Přijímač pro 15MHz
Chceme-li redukovat čas běhu na 10 taktů, musíme podle metody převodu proměnné do pozice v programu odstranit proměnnou minule. Vytvoření dvou kopií programu a náhrada dekódování NRZI jinou posloupností instrukcí umožní ušetřit přesně dva takty.
5 ldi Xl,0x00 ;inicializace X ukazatele (někam do paměti)
6 ldi Xh,0x01
7 ldi data,0x7f ;inicializace shift registru
8 ldi stuff,6 ;hledáme 6 jedniček za sebou
9,10 rjmp prijmi_bit_m0 ;můžeme přijímat
;zde platí minule=0
zapis_1_m0:
7 sec ;do carry potřebujeme dostat jedničku (aby mohla proběhnout další rotace)
8 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
kontroluj_se0_m0:
9 andi line,(1<<DP)|(1<<DM) ;vymaskujeme pouze bity DP a DM
10 breq konec ;pokud jsou oba nulové, je to konec paketu
prijmi_bit_m0:
1 in line,PINB ;načtení vstupů do registru line
2 bst line, DP ;uložíme hodnotu signálu DP do vlajky T
3(4) brts prisla_nula_m0 ;pokud je vlajka T=1 (přijatý bit je 0), skoč
4 dec stuff ;přijali jsme jedničku, spočítáme ji
5(6) breq cekej_m0 ;jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
navrat_z_cekani_m0: ;do tohoto místa se vrátíme po čekání, které vzniká bit stuffingem
6 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
7(8) brcs kontroluj_se0_m1 ;přijmeme další bit, současnou hodnotu DP přesuneme do minule
8,9 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
10 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout. Mění se i minule.
;zde platí minule=1
1 in line,PINB ;načtení vstupů do registru line
2 bst minule, DP ;uložíme přijatý bit do vlajky T
3(4) brtc prisla_nula_m1 ;pokud je vlajka T=0 (čili příchozí bit po dekódování je 0), skoč
4 dec stuff ;přijali jsme jedničku, spočítáme ji
5(6) brne zapis_1_m1 ;pokud jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
6 rjmp cekej_8_m1
;zde platí minule=0
prisla_nula_m0:
5 ldi stuff,6 ;nula nám vymaže předchozí přijatou řadu jedniček
6 lsr data ;pravá logická rotace, na MSb se dostane nula
7(8) brcs kontroluj_se0_m1 ;bitů ještě nebylo 8, přijmeme další, měníme minule
8,9 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
12 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout
;zde platí minule=1
1 in line,PINB ;načtení vstupů do registru line
2 bst minule, DP ;uložíme přijatý bit do vlajky T
3(4) brtc prisla_nula_m1 ;pokud je vlajka T=0 (čili příchozí bit po dekódování je 0), skoč
4 dec stuff ;přijali jsme jedničku, spočítáme ji
5(6) brne zapis_1_m1 ;pokud jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
6 rjmp cekej_8_m1
cekej_m0:
7 nop
cekej8_m0:
8 nop
9 nop
10 nop
1 nop
2 nop
3 nop
4,5 rjmp navrat_z_cekani_m1 ;minule se mění
cekej_m1:
7 nop
cekej8_m1:
8 nop
9 nop
10 nop
1 nop
2 nop
3 nop
4,5 rjmp navrat_z_cekani_m0 ;minule se mění
; druhá polovina, téměř shodná až na hodnoty minule
;zde platí minule=1
zapis_1_m1:
7 sec ;do carry potřebujeme dostat jedničku (aby mohla proběhnout další rotace)
8 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
kontroluj_se0_m1:
9 andi line,(1<<DP)|(1<<DM) ;vymaskujeme pouze bity DP a DM
10 breq konec ;pokud jsou oba nulové, je to konec paketu
prijmi_bit_m1:
1 in line,PINB ;načtení vstupů do registru line
2 bst line, DP ;uložíme hodnotu signálu DP do vlajky T
3(4) brtc prisla_nula_m1 ;pokud je vlajka T=0 (přijatý bit je 0), skoč
4 dec stuff ;přijali jsme jedničku, spočítáme ji
5(6) breq cekej_m1 ;jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
navrat_z_cekani_m1: ;do tohoto místa se vrátíme po čekání, které vzniká bit stuffingem
6 ror data ;pravá rotace (bity jsou posílány LSb first), carry(1) se ocitne na 7. bitu
7(8) brcs kontroluj_se0_m0 ;přijmeme další bit, současnou hodnotu DP přesuneme do minule
8,9 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
10 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout. Mění se i minule.
;zde platí minule=0
1 in line,PINB ;načtení vstupů do registru line
2 bst minule, DP ;uložíme přijatý bit do vlajky T
3(4) brts prisla_nula_m0 ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
4 dec stuff ;přijali jsme jedničku, spočítáme ji
5(6) brne zapis_1_m0 ;pokud jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
6 rjmp cekej_8_m0
;zde platí minule=1
prisla_nula_m1:
5 ldi stuff,6 ;nula nám vymaže předchozí přijatou řadu jedniček
6 lsr data ;pravá logická rotace, na MSb se dostane nula
7(8) brcs kontroluj_se0_m0 ;bitů ještě nebylo 8, přijmeme další, měníme minule
8,9 st X+,data ;bitů bylo 8, uložíme data s postinkrementací ukazatele X
12 ldi data,0x7f ;v datech očekáváme na MSb nulu, aby se mohly počítat přijaté bity
;zde už není čas na skok, takže se musí cyklus rozvinout
;zde platí minule=0
1 in line,PINB ;načtení vstupů do registru line
2 bst minule, DP ;uložíme přijatý bit do vlajky T
3(4) brts prisla_nula_m1 ;pokud je vlajka T=1 (čili příchozí bit po dekódování je 0), skoč
4 dec stuff ;přijali jsme jedničku, spočítáme ji
5(6) brne zapis_1_m0 ;pokud jedniček bylo za sebou šest, vynecháme jeden bit (bit stuffing)
6 rjmp cekej_8_m0
konec:
Přijímač pro 12MHz s redukovanými schopnostmi
Zvýšíme požadavky na postprocessing a z příchozích signálů zaznamenáme průběh DP. Nebudeme provádět žádné dekódování, pouze bity deserializujeme a uložíme do paměti. Ušetříme tak značnou část programové paměti, přičemž zesložitění parseru, který bude pracovat na nedekódovaných datech, nebude zdaleka tak podstatné. Tento algoritmus je založen na předpokladu, že na portu B je signál DP uložen v nultém bitu.
4 ldi Xl,0x00 ;inicializace X ukazatele (někam do paměti)
5 ldi Xh,0x01
6 ldi data,0x7f ;inicializace shift registru, počítat musíme do osmi
7,8 rjmp prijmi_bit ;můžeme přijímat
zapis_bit:
5 ldi data,0x7f
6 ror line
7 ror data
prijmi_bit8:
8 nop
prijmi_bit:
1 in line,PINB
2 andi line,(1<<DP)|(1<<DM)
3(4) breq konec_a_ulozit
4 ror line
5 ror data
6(7) brcc prijmi_bit8
7,8 st X+,data
1 in line,PINB
2 andi line,(1<<DP)|(1<<DM)
3(4) brne zapis_bit
4,5 rjmp konec
konec_a_ulozit:
ror data
brcs konec_a_ulozit
st X+,data
konec:
Start paketu
Každý paket začíná bytem 0x80, což včetně kódování je posloupnost 10101011 (zleva doprava s rostoucím časem). Přijímací rutina je volána přerušením (Pin Change Interrupt). Program, který bude zajišťovat synchronizaci, musí odhalit hranu a nastavit 1. instrukci cyklu (in line,PINB) tak, aby časově odpovídala polovině bitu. Při příchodu dvou jedniček po sobě může synchronizační kód předat řízení dál - přijímači paketu.
Synchronizace funguje tak, že se snaží co nejrychleji zareagovat na změnu hodnoty na portu B. Abychom si mohli být úplně jisti, že program zachytí hrany správně, musíme provést hledání hrany několikrát za sebou. U prvního hledání nuly totiž nemůžeme vědět, zda zpoždění způsobené reakcí procesoru na přerušení nebylo natolik dlouhé, aby se úplně přeskočil 1. bit. Představme si, že program dostane řízení tak nešikovně, že první hledání hrany připadne na poslední takt 2. bitu (uvažujme hodiny 12MHz):
Třetí hrana bude tedy odhalena již dobře a první instrukce, kterou budeme moci provést bude zaručeně 3-5 taktů za hranou.
;... tabulka vektorů
; sem se skoci ctyri takty po zaznamenani zmeny
; registry line,pom,minule,data,stuff nesmějí být používány jinými rutinami
PCInt: jmp prijem
prijem:
;zapamatovat vlajky - 3 takty
in pom, SREG
push pom
;zapamatovat některé registry - 4 takty
push Xl
push Xh
; zde mohou být další inicializace, synchronizace je dostatečně dlouhá
hledej_bit2:
sbic PINB,DP
rjmp hledej_bit2
hledej_bit3:
sbis PINB,DP
rjmp hledej_bit3
hledej_bit4:
sbic PINB,DP
rjmp hledej_bit4
;zde jsme 3-5 taktů za hranou
hledej_dp1:
4 in line,PINB
5 andi line,(1<<DP)|(1<<DM)
6 cpi line,(1<<DP)
7 nop
8 nop
hledej_dp1_cekej:
1 nop
2(3) brne hledej_dp1
3 nop
4 in line,PINB
5 andi line,(1<<DP)|(1<<DM)
6 cpi line,(1<<DP)
7(8) brne hledej_dp1_cekej
;zde jsme našli konec synchronizace, následuje skok na příjem
8 nop
1 nop
2 nop
3,4 rjmp prijimaci_rutina
Synchronizační rutiny na vyšších frekvencích budou doplněny čekáním - nop.
|