\ Vysílač USB

Vysílač USB
Vyslání paketu je podstatně jednodušší než jeho přijetí. Tato asymetrie je způsobena především povahou NRZI kódování, které pro posloupnost jedniček nevyžaduje žádné změny na lince a tudíž žádné výpočty ani zápisy na port. Dále není potřeba synchronizovat běh programu s externí událostí - to je vždy úkol přijímače. Celý proces můžeme rozložit na tyto kroky:
  • příprava čistých dat k odvysílání a stanovení jejich počtu (resp. adresy, do které se až bude číst)
  • výpočet CRC16 pro datové pakety
  • real-time: načtení ze SRAM, přidání bit stuffingu, kanálové kódování, detekce konce dat, odvysílání EOP, vše v jedné smyčce
  • příprava na čtení handshakové odpovědi a případná změna stavu vysílače
Podobně jako u příjmu paketu, kritická operace je zajištění kanálového kódování a bit stuffingu. Přestože je výpočet časově méně náročný než u příjmu dat, na jeho správém navržení závisí úspěšné řešení celé naší úlohy. Výpočet kódování za běhu je nezbytný především proto, že při přípravě odpovědi (byť jen potvrzení) není čas na volání funkce předzpracování dat. Bit stuffing přidává do dat informaci, která délku zprávy zvětší (navíc o neceločíselný násobek bytu) a tudíž by nebylo možné předzpracování dat udělat na jednom kusu paměti (s přepisem již zpracovaných dat). Vždy by bylo potřeba zakódovaná data uložit na jiné místo v paměti (aby se nepřepisovala ještě nezpracovaná data) a vzhledem k předpokládané náročnosti 32-64B SRAM to není možné. I za cenu rozvinutí cyklů a větší spotřeby programové paměti budeme klást důraz na kompaktnost a elegantnost odesílací funkce. Použité metody optimalizace jsou detailně popsány v sekci o příjmu paketu.

Očekávané vstupy
  1. data k odeslání jsou spojitě za sebou uložena ve SRAM včetně synchronizačního bytu 0x80
  2. wordy a longwordy jsou uloženy od nejméně po nejvíce významný byte (LSB first)
  3. registr X ukazuje na místo výskytu prvního bytu k odeslání (0x80)
  4. registr EndXl obsahuje o jedničku (nebo o dvojku či o nulu) zvětšený dolní byte adresy v paměti, kde leží poslední byte paketu

Vysílač pro 19,5MHz

Čítač počtu odeslaných bitů nelze začlenit do posuvného registru, jako tomu bylo u přijímače. Musí se použít binární čítání v rozsahu 0-7, což se vynutí pravidelným logický součinem čítače s hodnotou 0x07.
     6  ldi ones,(1<<DP)|(1<<DM);pomocná hodnota pro negaci
     7  ldi line,(1<<DP) ;klidová hodnota USB signálů DP a DM
     8  out PORTB,line
     9  out DDRB,smer_out	;output enable, smer_out je nastaven tak, aby neohrožoval ostatní výstupy

dalsi_byte:
 10,11  ld data,X+		;načtení byte ze SRAM
    12  cp X,EndXl
 13(1)  breq konec		;EndXl musí být o 2 větší než výskyt posledního byte paketu	
dalsi_bit:
     1  ror data		;vysunutí bitu do CF
  2(3)  brcs jednicka		;pokud máme vysílat jedničku, skoč
     3  eor line,ones		;negace linky, odesíláme nulu
     4  out PORTB,line		;zápis na sběrnici
     5  ldi stuff,6		;bit stuffing se nuluje
     6  andi bit,7		;počítání bitů cykluje od 7 do 0
     7  dec bit
  8(9)  breq dalsi_byte	;bitů už bylo 8, načti další
     9  nop
    10  nop
    11  nop
 12,13  rjmp dalsi_bit		;odešli další bit

jednicka:
     4  andi bit,7		;čítač musí být v rozsahu 0 až 7
     5  dec stuff		;spočítej jedničky
  6(7)  breq vloz_stuff	;teď je šestá, musí následovat stuffing
navrat_ze_stuffu:
     7  dec bit		
  8(9)  breq dalsi_byte		;už bylo bitů 8?
     9  nop
    10  nop
    11  nop
 12,13  rjmp dalsi_bit		;odešli další bit

vloz_stuff:
     8  ldi stuff,6
     9  nop
    10  nop
    11  nop
    12  nop
    13  nop
     1  nop
     2  nop
     3  eor line,ones
     4  out PORTB,line
   5,6  rjmp navrat_ze_stuffu

konec:
     2  or line,ones
     3  eor line,ones		;DP i DM budou 0, EOP
     4  out PORTB,line
     5  ldi bit,11
cekej:
     1  dec bit
  2(3)  brne cekej
;musíme čekat alespoň 2.5 bitu = 33 takty
	or line,(1<<DM)
	out PORTB,line		;klidový stav
	out DDRB,smer_in	;smer_in je nastaven s ohledem na směr ostatních bitů portu B
Na cyklu je patrno, že je nevyvážený. 1. - 7. bit musí provádět nop, zatímco 8. bit jednak čte ze SRAM a také kontroluje zastavovací podmínku. Program navíc zabírá zbytečně moc programové paměti. Ukážeme nyní návrh pro 18MHz. Budeme nuceni provést některé optimalizační kroky a tím dosáhneme optimálnějšího kódu nejen z časového hlediska, ale také z objemového. Vysílač pro 18MHz pak půjde přidáním jednoho nop provozovat také na 19,5MHz.

Vysílač pro 18MHz

U odesílacího cyklu se vyskytuje problém v podobě současného čtení bytu z paměti a kontroly koncové podmínky. Tyto dvě operace se provádějí po odeslání každého 8. bitu. Výhodné by bylo například načítat byte z paměti každý 8. bit a kontrolovat koncovou podmínku každý 1.-7. bit. Tím by se vytížení 8. iterace rozprostřelo. Problém ale vzniká hned v 1. bitu, kdy teprve po jeho odeslání zjistíme, že byla splněna koncová podmínka a bit vůbec neměl být odeslán. Řešením je přesunutí instrukce out PORTB,line až za kontrolu koncové podmínky.

     6  ldi ones,(1<<DP)|(1<<DM);pomocná hodnota pro negaci
     7  ldi line,(1<<DP) ;klidová hodnota USB signálů DP a DM
     8  out PORTB,line
     9  out DDRB,smer_out	;output enable, smer_out je nastaven tak, aby neohrožoval ostatní výstupy

dalsi_byte:
 10,11  ld data,X+		;načtení byte ze SRAM
dalsi_bit:
    12  out PORTB,line		;zápis na sběrnici, při prvním průchodu se zapíše klidový stav (DP=0,DM=1)
zacatek:
     1  ror data		;posun bitu do CF
  2(3)  brcs jednicka		;pokud máme vysílat jedničku, skoč
     3  eor line,ones		;negace linky, odesíláme nulu
     4  ldi stuff,6		;bit stuffing se nuluje
     5  andi bit,7		;počítání bitů cykluje od 7 do 0
     6  nop
navrat_z_jednicky:     
     7  dec bit
  8(9)  breq dalsi_byte		;bitů už bylo 8, načti další
     9  cp Xl,EndXl		;EndXl 	
10(11)  brne dalsi_bit		;odešli další bit
;konec paketu, nutno odeslat SE0
se0:
    11  ldi line,0
    12  out PORTB,line
     1  ldi bit,10
cekej:
     2  dec bit
  3(4)  brne cekej
;musíme čekat alespoň 2.5 bitu = 30 taktů
	or line,(1<<DM)
	out PORTB,line		;klidový stav
	out DDRB,smer_in	;smer_in je nastaven s ohledem na směr ostatních bitů portu B
	
	rjmp konec		
	
jednicka:
     4  dec stuff		;spočítej jedničky
  5(6)  brne navrat_z_jednicky	;teď je šestá, musí následovat stuffing
     6  ldi stuff,6		;blok bit stuffingu
     7  andi bit,7
     8  cp Xl,EndXl
 9(10)  breq se0
    10  nop
    11  nop
    12  out PORTB,line		;jeste odesilame jednicku
     1  nop
     2  nop
     3  nop
     4  eor line,ones		;odesleme nulu
   5,6  rjmp navrat_z_jednicky
konec:
Je nutno si uvědomit, že v jedničkové větvi není instrukce andi bit,7, která by zajistila cyklické počítání bitů. Prakticky jediný okažmik, kdy je tento and potřeba provést je po přetočení čítače z 0 na 255. Poté to trvá ještě 7 cyklů, než je tato hodnota upotřebena k odskoku k načtení dalšího byte ze SRAM. Logický součin tedy musíme provést alespoň jednou za 8 taktů. Instrukce je přítomna také v bit stuffingové rutině, takže i v případě samých jedniček je zaručeno, že každých 6 cyklů se tato instrukce provede. Pokud budeme program předělávat na 19,5MHz, můžeme do jedničkové větve místo nop přidat právě tento logický součin (ačkoliv to nemá žádný význam).

Vysílač pro 15MHz

Vyjdeme z verze pro 18MHz a provedeme unbundle 1. cyklu. Tím nám zmizí potřeba logického součinu proměnné bit s číslem 7, protože budeme moci v 1. cyklu nastavit čítač na výchozí hodnotu a nebudeme potřebovat čítat cyklicky. Neboť je 1. cyklus odlehčen o operace kontroly počtu odeslaných bitů, můžeme v ušetřeném čase provést jak načtení dat z paměti, tak kontrolu koncové podmínky.
     3  ldi ones,(1<<DP)|(1<<DM);pomocná hodnota pro negaci
     4  ldi line,(1<<DP)	;klidová hodnota USB signálů DP a DM
     5  out PORTB,line
     6  out DDRB,smer_out	;output enable, smer_out je nastaven tak, aby neohrožoval ostatní výstupy
   7,8  rjmp zacatek

dalsi_bit:
    10  out PORTB,line		;zápis na sběrnici, při prvním průchodu se zapíše klidový stav (DP=0,DM=1)
dalsi_bit_bez_out:
     1  ror data		;posun bitu do CF
  2(3)  brcs jednicka		;pokud máme vysílat jedničku, skoč
     3  eor line,ones		;negace linky, odesíláme nulu
     4  ldi stuff,6		;bit stuffing se nuluje
     5  nop			;počítání bitů cykluje od 7 do 0
     6  nop
navrat_z_jednicky:     
     7  dec bit
  8(9)  brne dalsi_bit		;pokud bitů nebylo 8, načti další
zacatek:
     9  ldi bit,7		;příprava pro další čítání 2.-8. bitu
    10  out PORTB,line		;zápis na sběrnici, při prvním průchodu se zapíše klidový stav (DP=0,DM=1)
   1,2  ld data,X+		;načtení byte ze SRAM
     3  ror data		;posun bitu do CF
  4(5)  brcs jednicka_b1	;pokud máme vysílat jedničku, skoč
     5  eor line,ones		;negace linky, odesíláme nulu
     6  ldi stuff,6		;bit stuffing se nuluje
     7  cp Xl,EndXl		;EndXl 	
  8(9)  brne dalsi_bit		;odešli další bit

;konec paketu, nutno odeslat SE0
se0:
    9  ldi line,0
    10  out PORTB,line
     1  ldi bit,9
cekej:
     2  dec bit
  3(4)  brne cekej
;musíme čekat alespoň 2.5 bitu = 27 taktů
	or line,(1<<DM)
	out PORTB,line		;klidový stav
	out DDRB,smer_in	;smer_in je nastaven s ohledem na směr ostatních bitů portu B
	
	rjmp konec		

jednicka_b1:
     6  cp Xl,EndXl
  7(8)  breq se0
     8  dec stuff		;spočítej jedničky
 9(10)  breq dalsi_bit_bez_out	;teď je šestá, musí následovat stuffing
    10  ldi stuff,6
     1  ldi bit,8
   2,3  rjmp dokonceni_stuffingu
	
jednicka:
     4  dec stuff		;spočítej jedničky
  5(6)  brne navrat_z_jednicky	;teď je šestá, musí následovat stuffing
     6  cp Xl,EndXl
  7(8)  breq se0
     8  ldi stuff,6		;blok bit stuffingu
     9  nop
    10  out PORTB,line		;jeste odesilame jednicku
     1  nop
     2  nop
     3  nop
dokonceni_stuffingu:
     4  eor line,ones		;odesleme nulu
   5,6  rjmp navrat_z_jednicky
konec:

Vysílač pro 12MHz

Vyjdeme z verze pro 18MHz a provedeme unbundle 1. a 8. cyklu. Tím nám vznikne možnost kontrolovat koncovou podmínku v 8. cyklu - hodnota EndXl pak bude obsahovat adresu posledního bytu paketu. Nebude pak nastávat případ z předchozích verzí, kdy jsme začali zpracovávat byte, který už zpracováván být neměl, a kontrolu koncové podmínky jsme provedli těsně před odesláním dat na sběrnici. Určité zpoždení mezi zpracováním a odesláním nyní odstraníme, protože na kontroly máme vyhrazen čas v rozvinutém 8. cyklu.
     3  ldi ones,(1<<DP)|(1<<DM);pomocná hodnota pro negaci
     4  ldi line,(1<<DP)	;klidová hodnota USB signálů DP a DM
     5  out PORTB,line
     6  out DDRB,smer_out	;output enable, smer_out je nastaven tak, aby neohrožoval ostatní výstupy
     7  ldi stuff,6
     8  ldi bit,6

prijmi_b1:
   1,2  ld data,X+		;načtení ze SRAM
     3  ror data		;posun bitu do CF
  4(5)  brcs jednicka_b1	;pokud máme vysílat jedničku, skoč
     5  eor line,ones		;negace linky, odesíláme nulu
     6  out PORTB,line		;zápis na sběrnici
     7  ldi stuff,6		;bit stuffing se nuluje
     8  nop			;počítání bitů cykluje od 7 do 0

prijmi_b27:     
     1  ror data		;posun bitu do CF
  2(3)  brcs jednicka_b27	;pokud máme vysílat jedničku, skoč
navrat_ze_stuffu_b27:
     3  eor line,ones		;negace linky, odesíláme nulu
     4  ldi stuff,6		;bit stuffing se nuluje
     5  dec bit
     6  out PORTB,line
  7(8)  brne prijmi_b27
     8  ldi bit,6

prijmi_b8:
     1  ror data		;posun bitu do CF
  2(3)  brcs jednicka_b8	;pokud máme vysílat jedničku, skoč
navrat_ze_stuffu_b8:
     3  eor line,ones		;negace linky, odesíláme nulu
     4  ldi stuff,6		;bit stuffing se nuluje
     5  cp X,EndXl
     6  out PORTB,line
  7(8)  brne prijmi_b1
   8,1  rjmp se0

jednicka_b1:
     6  dec stuff
  7(8)  brne prijmi_b27
     8  ldi bit,7		;spravne skocit znamena vyresit stuffing
   1,2  rjmp navrat_ze_stuffu_b27

jednicka_b27:
     4  dec stuff
  5(6)  breq stuffing_b27
     6	dec bit
  7(8)  brne prijmi_b27
     8  ldi bit,6

     1  ror data		;posun bitu do CF
  2(3)  brcs jednicka_b8	;pokud máme vysílat jedničku, skoč
     3  eor line,ones		;negace linky, odesíláme nulu
     4  ldi stuff,6		;bit stuffing se nuluje
     5  cp X,EndXl
     6  out PORTB,line
  7(8)  brne prijmi_b1
   8,1  rjmp se0

jednicka_b8:
    4  dec stuff
 5(6)  breq stuffing_b8
     6  cp X,EndXl
  7(8)  brne prijmi_b1
   8,1  rjmp se0

stuffing_b27:
     7  inc bit			;spravne skocit znamena vyresit stuffing
     8  nop
   1,2  rjmp navrat_ze_stuffu_b27

stuffing_b8:
     7  nop			;spravne skocit znamena vyresit stuffing
     8  nop
   1,2  rjmp navrat_ze_stuffu_b8



;konec paketu, nutno odeslat SE0
se0:
     2  or line,ones
     3  eor line,ones
     4  nop
     5  nop
     6  out PORTB,line
     1  ldi bit,7
cekej:
     2  dec bit
  3(4)  brne cekej
;musíme čekat alespoň 2.5 bitu = 21 taktů
	or line,(1<<DM)
	out PORTB,line		;klidový stav
	out DDRB,smer_in	;smer_in je nastaven s ohledem na směr ostatních bitů portu B

konec:

Výpočet CRC

U datových přenosů je nutné doplnit paket dvěma byty cyklické redundantní kontroly. Mechanismus výpočtu CRC je popsán v kapitole Rozbor USB. Prakticky budeme chtět výpočet provádět voláním funkce. Vstupní data budou umístěna ve SRAM na adrese, na kterou ukazuje pointer X. Nultý byte dat obsahuje dolní část adresy, na které leží poslední byte paketu. Funkce postupně spočítá CRC16 ze všech bytů řetězce a v dvojregistru crc (r1:r0) zanechá výsledek.