\ Přijímač USB

Přijímač USB
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:
  1. načíst bit z portu B
  2. provést dekódování NRZI
  3. dekódovat bit stuffing (po šesti jedničkách vynechat cyklus)
  4. deserializovat bity (vrotovat je do registru)
  5. po 8 bitech zapsat data do SRAM
  6. 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

  1. 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.
    Assembler:
    ldi i,8
    cyklus:
     cpi i,8
     breq ... začáteční akce
     cpi i,1
     breq ... závěrečná akce
     ; ... tělo cyklu
     dec i
    brne cyklus
    
    jazyk C:
    byte i;
    for(i=8;i>0;i--){
     if (i==8){
      // ... akce na začátku
     }
     if (i==1){
      // ... akce na konci
     }
     // ... tělo cyklu
    }
    
    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:
    Assembler:
    rjmp ... začáteční akce
    ; ... tělo cyklu
    ldi i,6
    cyklus:
     ; ... tělo cyklu
     dec i
    rjmp cyklus
    breq ... závěrečná akce
    ; ... tělo cyklu
    
    jazyk C:
    byte i;
    // ... akce na začátku
    // ... tělo cyklu
    for(i=6;i>0;i--){
     // ... tělo cyklu
    }
    // ... akce na konci
    // ... tělo cyklu
    
    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.

  2. 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 CounterProměnná
    dolní polovina paměti0
    horní polovina paměti1
    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.
    Blikač přes proměnnou a:
    ldi i,8
    ldi a,0
    cyklus:
     neg a
     cpi a,0
     brne jedna
      cbi PINB,0	; ukázková operace
      rjmp endif
     jedna:
      sbi PINB,0	; ukázková operace
     endif:
     dec i         
    brne cyklus	; přepneme 8x
    
    Transformace:
    ldi i,8
    rjmp cyklus0	; nahrazení ldi a,0
    
    ;zde platí a=0
    cyklus0:
     rjmp cyklus1neg ; náhrada za neg a
     cyklus0neg:
     cbi PINB,0	; ukázková operace
     dec i         
    brne cyklus0	; přepneme 8x
    
    ; zde platí a=1
    cyklus1:
     rjmp cyklus0neg ; náhrada za neg a
     cyklus1neg:
     sbi PINB,0	; ukázková operace
     dec i         
    brne cyklus1	; přepneme 8x
    

  3. 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é.
    Od 1 do 10:
    ldi i,1
    cyklus:
     ; ... tělo cyklu
    inc i
    cpi i,11
    brne cyklus
    
    Od 10 do 1:
    ldi i,10
    cyklus:
     ; ... tělo cyklu
     dec i
    brne cyklus
    

  4. Čítání jako vedlejší produkt deserializace
  5. Čí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.
    Binární čítání:
    ldi bit,8
    cyklus:
     in line,PINB ; přijmeme data
     ror line      ; přesun LSb do CF
     ror data      ; deserializace LSb first
     dec bit       ; už jich bylo 8?
    brne cyklus    ; pokud ne, tak znovu
    
    Posuvné čítání:
    ldi data, 0x80
    cyklus:
     in line,PINB ; přijmeme data
     ror line      ; přesun LSb do CF
     ror data      ; deserializace LSb first
    brcc cyklus    ; pokud z registru vystopila nula, tak znovu
    

  6. 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.
    if-else v přirozeném pojetí:
    ldi bit,8
    
    cyklus:
     ; ... práce
     cpi bit,4
     brne else
      ; ... zpracování, bit=4
      rjmp ifend
     else:
      ; ... zpracování, bit<>4
     ifend:
     dec bit
    brne cyklus    
    
    
    Přesun větve:
    ldi bit,8
    
    cyklus:
     ; ... práce
     cpi bit,4
     breq ctyri
      ; ... zpracování, bit<>4
     dec bit
    brne cyklus    
     rjmp ... je hotovo
    
    ctyri:
      ; ... zpracování, bit=4
      dec bit
      brne cyklus	; není to zbytečné?
    		; zde se totiž skočí vždy
    
    Lepší přesun větve:
    ldi bit,8
    rjmp cyklus
    
    ctyri:
      ; ... zpracování, bit=4
      dec bit
    cyklus:
     ; ... práce
     cpi bit,4
     breq ctyri
      ; ... zpracování, bit<>4
     dec bit
    brne cyklus    
    
    

    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):

    DP=0   8  sbic
    DP=1   1  rjmp
    DP=1   2  rjmp
    DP=1   3  sbic
    DP=1   4  sbic-skip
    DP=1   5  sbis
    DP=1   6  sbis-skip
    DP=1   7  sbic
    DP=1   8  rjmp
    DP=0   1  rjmp
    DP=0   2  sbic
    DP=0   3  sbic-skip
    
    Obr. 1 - Tolerance hledání hrany
    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.