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
- data k odeslání jsou spojitě za sebou uložena ve SRAM včetně synchronizačního bytu 0x80
- wordy a longwordy jsou uloženy od nejméně po nejvíce významný byte (LSB first)
- registr X ukazuje na místo výskytu prvního bytu k odeslání (0x80)
- 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.
|