RS232 pro ZX Spectrum 128k a Didaktik Gama 192k

Hardware

SIF je skvělý sériový interface pro ZX Spectrum, ale je to hardware navíc a někdy může být výhodnější použít sériový port, který už je součástí ZX Spectra 128k od výroby. Není to zcela bez problémů a o tom je tento článek.

ZX Spectrum 128+ toastrack (podle žeber chladiče na pravém boku) a šedá ZX Spectrum 128k +2 od Amstradu byly první počítače s tímto sériovým portem. Přinejmenším první ZX Spectra. Jejich RS232 je připojená k I/O portu AY-3-8912 a je realizována převodníky úrovní MC1488 a MC1489, které převádějí úrovně RS232 na TTL a zpět. Každý integrovaný obvod má v sobě 4 převodníky. Dva z každého z nich jsou využité pro RS232/MIDI konektor a dva pro KEYPAD konektor.

Didaktik Gama 192k je modifikací Didaktiku Gama 80k od CSS a kromě větší paměti kompatibilní se ZX Spectrum 128k, obsahuje i AY-3-8912 a k ní připojený MAX232 podobně, jako převodníky v originál ZX Spectrech.

Všechny tyto sériové porty jsou softwarové. To znamená, že počítač neobsahuje žádný hardware, který by provedl serializaci vysílaného bytu z osmi bitů na jejich správnou časovou posloupnost. To musí udělat software změnou stavů I/O portů na AY-3-8912 ve správný okamžik. Podobně i opačným směrem při přijímání sekvence bitů musí číst stavy ve správný okamžik a z těchto stavů složit byte. To je samozřejmě náročné na procesor, který během toho nemůže dělat něco jiného, jako je tomu u hardwarové RS232, která si toto řeší sama a CPU předá už hotový byte. V lepším případě skupinu bytů uložených v paměti čipu jako je např. 16C650 v SIFu.

Další problém softwarové RS232 bývá nižší rychlostní limit. Při rychlosti 57600bps na jeden bit / baud vychází přibližně 61.5 taktů, při 115200 polovina, nejkratší instrukce Z80 trvají 4 takty, často 7 a více taktů, nezbytný out (c),a dokonce 12 taktů, kromě přijetí jednoho bytu je nutné dělat i další věci, uložit byte do paměti, nebo celý blok dat na externí úložiště a mezitím pozastavit vysílání dalších bytů z druhého počítače.

A ano, vím o skvělé práci Pavla Vymetálka a pro testování zkompilovaných rutin na reálném hardwaru jsem ho používal, ale můj cíl je maličko jiný. Rád bych lépe zdokumentoval možnosti hardwaru a trochu i softwaru, aby mohl mnohem snadněji RS232 ve svých programech použít kdokoli další.

Co o sériovém portu říká manuál?

Manuál k ZX Spectrum 128k je dostupný online na WoSu, není toho v něm o RS232 mnoho (strany 3 a 14). Zdá se, že celý manuál je orientovaný hlavně na popis odlišností od ZX Spectrum 48k.

K DG192k manuál nikdy neexistoval a i kdyby, vše, co se týká BASICu by mělo být shodné.

Mám papírový manuál k šedé +2 od Amstradu, ten je už na první pohled přepracovaný a kompletní včetně základů programování v 48k BASICu, byť graficky zjednodušený, ale co se týče RS232 je podobně žalostně strohý.

Manuály v podstatě říkají, že existují nějaké sériové tiskárny, na které lze z BASICu tisknout přes RS232 a jejich seznam si máme vyžádat od výrobce. Viděl někdy někdo ten seznam? Taktéž kabel lze objednat od výrobce. Dále se v manuálech zmiňuje možnost připojit MIDI zařízení, přičemž uvedený pinout je krajně podezřelý, protože uvádí signál RETURN na vývodu 1, kde je podle pinoutu o pár řádků výše GND. Zatímco pinout RS232 dává smysl, pinout MIDI zařízení považuji za něco jako pinout na druhé straně kabelu, ne pinout konektoru v ZX Spectru, který je označený RS232/MIDI. Pinout konektoru KEYPAD není uveden vůbec. Přitom je to taky RS232, jen podle mého měření na reálném hardwaru má zpřeházené signály, kde je na RS232 vstup, tam je na KEYPAD výstup a naopak.

pinout RS232 podle manuálu k šedé +2

konektor RS232/MIDI
pinRS232/MIDI
1GND
2TxD
3RxD
4DTR
5CTS
6+12V

Vývod číslo 1 (GND) je opravdu vpravo a 6 (+12V) vlevo u zářezu pro páčku, tj. číslování na náčrtku konektoru je správně a náčrtek je orientovaný stejně jako snímek konektorů na zadní straně počítače, navzdory tomu, že se na internetu dají najít číslování vývodů telefonního konektoru BT631 přesně opačná. Lze snadno ověřit multimetrem.

foto konektor BT631W na ZXS vyrez schematu

Napájecí napětí 12V je na konektor připojeno přes 100Ω, nejspíš proto, že měnič uvnitř ZX Spectra není schopen dodat moc velký proud a při přetížení hrozí zničení tranzistoru v měniči.

napětí naměřená na konektorech šedé +2

Toto měření mi říká, aniž bych hlouběji zkoumal schéma a připojení ke konvertorům RS232 úrovní, kde je vstup a kde výstup. Výstup je tam, kde je nějaké napětí v mezích úrovní RS232 (tj. -3 až -15V pro log. 1, +3V až +15V pro log. 0). Naopak, kde se napětí blíži nule, ale vývod není propojený s GND, tam lze očekávat vstup ve stavu vysoké impedance.

pinsignál RS232na RS232na KEYPAD
1GND0V/GND0V/GND
2TxD0.1V-11.6V
3RxD-11.6V0.1V
4DTR0.1V-11.6V
5CTS-11.6V0.1V
6+12V11.8V11.8V

Pohledem do schématu (viz výřez výše) to lze snadno ověřit. MC1488 je převodník z TTL na RS232 a jsou na něj připojené výstupy. MC1489 je převodník z RS232 na TTL a jsou na něj připojené vstupy. Jen pozor na zpřeházené signály ve schématu, je opravdu nutné koukat na čísla.

To také znamená, že signály jsou na konektoru RS232/MIDI pojmenované obráceně, TxD (transmit data) má být na počítači výstup (vysílač), nikoli vstup, RxD (receive data) má být vstup (přijímač) atd... ZX Spectrum se zdá být označené jako koncové zařízení, ne jako počítač.

Proč jsou vstupy a výstupy na RS232/MIDI opačně vůči konektoru KEYPAD netuším. Špatné je, že kabel/redukci pro RS232/MIDI nepůjde přímo/vždy použít i pro KEYPAD.

TODO: lze skrze KEYPAD jiným ZX Spectrem ovládat BASIC druhého ZX Spectra? Jaký baudrate používá KEYPAD? Jaký protokol?

Kde mám sehnat kompatibilní konektor?

Typ konektoru manuál nezmiňuje. Naštěstí mi v diskuzi poradil z00m a následně jsem dohledal, že Sinclair pro sériový port použil konektory BT631W podobné telefonním, které v Británii používá British Telecom s označením BT631A, rozdíly mezi variantami konektoru jsou popsané zde. Špatné je, že BT631W je obtížně sehnatelný a když už, tak jsou potřeba zvláštní krimpovací kleště, podobně jako na RJ45 a další u nás mnohem běžnější konektory. Proto je lepší kupovat konektory už nakrimpované na kabel, podaří-li se je sehnat.

Naštěstí se mnohem snadněji dají sehnat telefonní kabely s konektorem BT631A. Ten do ZX Spectra nejde osadit přímo, brání tomu tvar plastu, který nemá zářez, kde by měl být a naopak má zářez, kde být nemusí. Ale to není velký problém, při troše šikovnosti lze potřebnou drážku do plastu vyhlodat nožem. Jen si musíte dát pozor, aby vám prodejce prodal kabel s opravdu všemi šesti dráty. Pro připojení telefonu, nebo modemu jich je potřeba méně, typicky jen dva, nebo čtyři.

foto konektor BT631A foto redukce v ZXS

Správně upravený konektor by měl jít zasunout lehce až na doraz včetně zacvaknutí jistící páčky. Určitě se ho nesnažte natlačit dovnitř větší silou, rámeček konektoru v ZX Spectru by mohl prasknout. Naopak, konektor by měl sedět i po okrájení přesně a neviklat se. Ten tenký proužek zbylý mezi zářezy je důležitý pro správný přítlak kontaktů.

RS232 obecněji na jiných počítačích

Abych mohl správně zapojit 9 pinový DSUB konektor v Didaktiku Gama 192k (dále jen DG192k) a abych mohl správně vyrobit redukci pro ZX Spectrum 128k ke které půjdou připojit standardní propojovací kabely, mrknul jsem jednak na Wikipedii, a druhak jsem zkusil proměřit pár sériových portů stejným způsobem, jako jsem to udělal se ZX Spectrem.

konektor RS232 konektor RS232 konektor RS232

Měření jsem provedl víc, ale všechna se stejnými výsledky. Liší se pouze úroveň napětí na výstupech. USB redukce a notebooky mívají napětí nižší než plných +-12V. Stejně tak někdy je a někdy není připojen plech konektoru na GND. Důležité je, že kde má být výstup je výstup a kde vstup je vstup.

DSUB-9 pinsignálsměrnaměřeno stolní PCnaměřeno UC-232Anaměřeno notebook Dell
1DCDvstup0.15V0V0V
2RxDvstup0.15V0V0V
3TxDvýstup-11.9V-6.3V-5.6V
4DTRvýstup-11.9V-6.3V-5.6V
5GNDvstup0.15V0V0V
6DSRvstup0.15V0V0V
7RTSvýstup-11.9V-6.3V-5.6V
8CTSvstup0.15V0V0V
9RIvstup0.15V0V0V

Signály DCD, DTR, DSR a RI nezbývá než ignorovat. Tolik signálů na ZX Spectru ani v DG192k k dispozici není a pro přenos dat s hardwarovým řízením toku naštěstí stačí kromě RxD a TxD signály RTS a CTS.

TODO: prozkoumat a doplnit jaké signály chce RS232 myš. jestli nebude chybět DTR posunuté na RTS.

RS232 ze ZX Spectra 128k na DSUB-9

Po všem zkoumání jsem skončil s následujícím propojením. Nejdůležitějším kriteriem byla úvaha vstup na výstup a výstup na vstup při použití překříženého RS232 kabelu mezi dvěma počítači, nebo kabelu 1:1 mezi počítačem a koncovým zařízením, jako je např. tiskárna, plotr, nebo modem. A to, pokud možno tak, aby bylo možné hardwarové řízení toku dat softwarovým přepínáním stavu signálu RTS, nikoli posíláním celých bytů Xon, Xoff.

BT631 pinRS232/MIDI dle manuáluRS232 přeznačenéAY-3-8912 signálsměrbarvaDSUB-9 pin
1GNDGNDGNDGNDmodrá5
2TxDRxDI/O A7vstupžlutá2
3RxDTxDI/O A3výstupzelená3
4DTRCTSI/O A6vstupčervená8
5CTSRTSI/O A2výstupčerná7
6+12V+12V-+12Vbílá-

Barva vodičů samozřejmě může být různá, ale v mém případě to tak v hotovém kabelu, ze kterého jsem vyráběl redukci, s nalisovaným konektorem BT631 zrovna vyšlo. Později jsem zjistil, že konektor na druhém konci kabelu má vodiče přesně v opačném pořadí. Je to tak normální u Britských telefonních kabelů? Zapisuji jen pro případ, že bych vyráběl další.

RS232 z Didaktiku Gama 192k na DSUB-9

Nejlépe bude to kvůli kompatibilitě udělat po vzoru ZX Spectra 128k (resp. RS232 ROM), není ani důvod to zapojit jinak. DG192k má na desce dva 3 pinové konektory označené jako SER-IN a SER-OUT s GND uprostřed a dvěma signály CH1 a CH2 na každém z nich. KEYPAD neřeší. Převodník úrovní MAX232CPE (IC13 na schématu) v DG192k má stejnak jen 2 kanály z RS232 na TTL a 2 kanály naopak, stačí k zapojení jednoho ze sériových portů.

AY-3-8912 signál AY-3-8912 pin MAX232 TTL pin MAX232 RS232 pin DG192k header barva (foto) směr RS232 signál DSUB-9 pin
I/O A7 7 9 8 CH2 in hnědá vstup RxD 2
I/O A3 11 10 7 CH2 out hnědočerná výstup TxD 3
GND 6 15 15 GND bíločerná GND GND 5
I/O A2 12 11 14 CH1 out bílooranžová výstup RTS 7
I/O A6 8 12 13 CH1 in světle zelená vstup CTS 8

A k nudné tabulce ještě přidávám snímky skutečné realizace včetně schéma související části zapojení v DG192k.

foto konektor RS232 foto konektor RS232 vyrez schematu DG192k

Umístění DSUB konektoru na pravém boku se mi zdá takto nejpraktičtější. Nejenom proto, že už jsem tam z doby před přestavbou na DG192k konektor přibastlený měl, jen ne pro sériový port, ale pro Kempston Joystick. Především proto, že tam je blízko konektorům na základní desce počítače a ničemu nepřekáží, jako by překážel na zadní stěně.

Podezříval jsem zapojení v Didaktiku Gama, že je špatně a sériový port se nechová stejně, jako v ZX Spectru 128k+/+2. To se později potvrdilo. Protože mi fungoval správně výstup, fungoval správně vstup, ale pouze pokud byla AY-3-8912 jako vstup nakonfigurovaná, nefungovalo čtení v režimu výstupu s hodnotou 255 na portu.

Fungovalo:

10 OUT 65533,7: OUT 49149,191
20 OUT 65533,14: OUT 49149,255
30 PRINT IN 65533

Nefungovalo a mělo fungovat, protože tohoto způsobu se na originál ZX Spectrum 128k využívá.

10 OUT 65533,7: OUT 49149,255
20 OUT 65533,14: OUT 49149,255
30 PRINT IN 65533

Vyřešil jsem to nakonec tak, že jsem rezistory R84 a R85 přemostil paralelně připojeným 68R a tím zmenšil odpor mezi AY a MAXem na cca 63 až 64Ω.

foto konektor RS232

V tuto chvíli netuším, jestli to nebude mít na některý z čipů negativní efekt, ale pomohlo to. Alespoň prozatím.

RS232 ze ZX Spectrum 48k s AY-3-8912

Kromě ZX Spectrum 128k a Didaktiku Gama 192k jsem chtěl připojit i ZX Spectrum 48k. V diskuzi jsme spekulovali o tom, že to je triviální, ale dosud jsem od nikoho neslyšel, že mu to funguje, takže proto jsem to vyzkoušel sám.

K vyzkoušení jsem zvolil ZX Spectrum 48k+ o kterém vím, že funguje spolehlivě, s DivIDE a nainstalovaným ESXDOSem 0.8.7 s utilitou sercp. Měl jsem připojený i Jiiirův RTC, Jiiirův UPI a Jiiirův SimpleAY s AY-3-8912 v roztrojce. Na SimpleAY jsem připájel kolíčky nalámané z lišty kromě signálů i na +5V a GND, protože konvertor úrovní potřebuje napájení a vše propojil. Signály z AY jsem připojil přes rezistory 200Ω a signály do AY přes 12Ω. Ty hodnoty nemají žádný zvláštní význam, kromě toho, že jsem je měl při ruce a nechtěl jsem propojovat RS232 redukci s AY natvrdo pro případ, že by něco šlo do zkratu. Směrem z AY pravděpodobně postačí propojení vodičem bez přidaného odporu.

Propojení signálů bylo stejné jako je v tabulkách výše, ale pro snazší orientaci přidávám propojení odpovídající následujícím snímkům.

AY-3-8912 signál AY-3-8912 pin barva (foto) směr RS232 signál konvertor WaveShare
I/O A7 7 oranžová + bílá vstup RxD 3
I/O A6 8 žlutá + bílá vstup CTS 5
I/O A3 11 šedá + šedá výstup TxD 4
I/O A2 12 bílá + šedá výstup RTS 6
GND - černá - GND 2
+5V - červená - Vcc 1

Fungovalo to na první zapojení. Přenesl jsem pomocí sercp pár souborů tam i zpět.

foto RS232 na ZX Spectrum 48k se SimpleAY foto RS232 na ZX Spectrum 48k se SimpleAY foto RS232 na ZX Spectrum 48k se SimpleAY

V tomto případě jsem nepoužil překřížený propojovací kabel, ale prostý prodlužovák. Tj. v podstatě jsem konvertor úrovní zapojoval jakoby rovnou do portu na zadní stěně PC. Na prostředním snímku to je vidět. Může se lišit podle použitého konvertoru. Mnou použitý konvertor má na sobě z druhé strany značku WaveShare a čip s těžko čitelným označením, pravděpodobně SP3232E.

V tuto chvíli netuším, jestli to nebude mít na některý z čipů negativní efekt, ale fungovalo to.

Vlastní konvertor RS232 - TTL

Protože konvertor úrovní koupený z eBay mi nevyhovoval a protože jsem měl v šuplíku nějaké MAX232, vyrobil jsem si vlastní, lépe přizpůsobené použití se ZX Spectrem a AY-3-8912. Asi nemá smysl se o něm rozepisovat detailněji. Ke stažení je tady (nebo v sekci download na konci stránky), balíček obsahuje kompletní projekt pro Eagle 6.4 včetně gerberů, náhledy v PNG a ve schématu detailní popis.

Kabel k propojení počítačů

Aby mohly dva počítače spolu komunikovat je nutné propojit přinejmenším signály TxD a RxD (výstup jednoho na vstup druhého) a samozřejmě GND, ke kterému se napětí signálu vztahuje. Totéž v opačném směru. Tím zajistíme, aby druhý počítač mohl i odpovídat. Ale to není vše. Počítač, který není dost rychlý, aby vždy spolehlivě dokázal přijímat data, kdykoli vysílající strana nějaká pošle, musí vysílajícímu nějak signalizovat připravenost přijímat. K tomu slouží signály RTS (ready to send) a CTS (clear to send). Wikipedia říká Logickou jedničkou na tomto vstupu protistrana signalizuje, že DTE může vysílat data. Tzn. nastavíme-li RTS do logické 0, tak by druhá strana, která čte stav na CTS neměla začít vysílat. Tím lze pozastavovat datový tok v případě, že přijímač potřebuje na zpracování přijatých dat delší čas, než trvání jednoho, nebo dvou stop bitů. Což může být v případě ZX Spectra velmi často.

Poznámka: Citovaná věta z Wikipedia o logické jedničce možná není pravdivá, jak se ukázalo měřením pomocí osciloskopu a logického analyzátoru dále. Možná byla myšlena pro trochu jinou situaci, než jak ji chápu já? Nebo došlo jen k záměně logického stavu?

Z toho vyplývá, že pro propojení dvou rovnocenných počítačů (DTE-DTE) je nutné použít křížený kabel (nullmodem) a pro propojení počítače a periferie (DTE-DCE) kabel propojení přimo 1:1 (jsou-li na obou koncích DSUB-9 konektory tak 2 na 2, 3 na 3 atd...).

Pro propojení dvou ZX Specter a pravděpodobně i většiny PC postačí zjednodušená varianta. U plně zapojeného nullmodem kabelu prostě některé vodiče nebudou využité.

DSUB-9 pinsignálsignálDSUB-9 pin
2RxDTxD3
3TxDRxD2
5GNDGND5
7RTSCTS8
8CTSRTS7

A na rozdíl od konektoru na redukci, na zadním panelu, nebo na šasi počítače se tentokrát jedná o konektory se zdířkami. Viz foto, po zvětšení je vidět i číslování vývodů v opačném pořadí, aby čísla odpovídala stejným číslům na protikusu.

foto DSUB-9 F

Další možnosti připojení

Na sériových portech je hezké, že není nutné se omezovat pouze na připojení ZX Spectra ke starým počítačům s COM/RS232 porty od výroby, ale existuje přehršel nejrůznějších redukcí RS232-USB, USB-USART s TTL výstupy, nebo RS232 na TTL. Viz následující obrázky. Nebývají drahé. Jen pozor na to, že mnohé z těch velmi levných nepočítají se signály CTS a RTS, mají pouze RxD a TxD. Týká se to i některých kabelových "redukcí", které lze koupit v kamenných, resp. serióznějších tuzemských obchodech, nejenom na Fleabay.

USB - USART USB - USART RS232 - TTL

Redukce z USB na USART s TTL úrovněmi, nebo redukce z RS232 úrovní na TTL by mělo být možné přímo připojit k AY-3-8912 a nahradit jimi konvertory úrovní MC1488/MC1489, nebo MAX232. Což může usnadnit použití i jiných interfaců s AY-3-8912. Jen opatrně na 3.3V redukce, jestli jsou 5V tolerantní. V každém případě, díky USB redukcím můžeme k ZX Spectru připojovat i notebooky, které by jinými způsoby byly připojitelné jen obtížně.

USB-RS232 redukce, které mi fungovaly

Malý USB dongle bez pouzdra s pinovou lištou a TTL výstupy, koupeno z Číny na eBay. Piny pro RTS a CTS bylo nutné dopájet.

ID 10c4:ea60 Cygnal Integrated Products, Inc. CP2102/CP2109 UART Bridge Controller [CP210x family]

USB kablík s DSUB 9 na straně RS232, pěkné robustní provedené, koupil jsem v tuzemském obchodě s výpočetní technikou pod označením Aten UC-232A (K-UC-232A).

ID 0557:2008 ATEN International Co., Ltd UC-232A Serial Port [pl2303]

USB kablík s DSUB 9 na straně RS232 koupený kdysi dávno v tuzemském obchodě, původní označení si nepamatuji a dohledat už nelze. Na šedém plastovém pouzdře konektoru je cedulka s modelem U232-P9.

ID 067b:2303 Prolific Technology, Inc. PL2303 Serial Port

USB-RS232 redukce, které mi nefungovaly

USB kablík s DSUB 9 na straně RS232 prodávaný spolu s 9 - 25 pin redukcí v průhledném modro azurovém provedení. Nepodařilo se mi u ní zprovoznit hardwarové pozastavování toku dat a to ani se ZX Spectrum ani s podomácku vyrobeným plotrem, kde přenos řídil AT Mega 32. Koupeno v tuzemském obchodě pod označením i-tec USB to serial adapter RS232.

ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter

Software a první pokusy s AY-3-8912

Máte-li zapojené kabely, redukce, konektory a připravené jiné nezbytnosti, je čas prozkoumat sériový port z pohledu softwaru. Přesněji, osahat si softwarem hardware a zjistit, jak se to vlastně chová než začneme tvořit něco komplikovanějšího na základě mylných předpokladů.

AY-3-8912 má kromě tří zvukových kanálů i jeden osmibitový port, tj. 8 vstupně výstupních linek, které lze číst a zapisovat na ně. Bohužel, pro potřeby RS232 jsou buď všechny nastavené jako vstup, nebo všechny jako výstup. Ale s RS232 potřebujeme vždy jeden vstupní bit a jeden výstupní bit zároveň. Například RxD jako vstup a RTS jako výstup, nebo naopak TxD jako výstup a CTS jako vstup. To s AY-3-8912, alespoň dle datasheetu, nelze realizovat jinak, než přepínat směry tam a zpět podle toho v jaké části časové posloupnosti sériové komunikace se právě nacházíme.

foto AY v +2 foto AY v DG192k foto AY v Simple AY

Datasheet k AY o práci s I/O porty říká (velmi volný překlad): Pro výstup dat z CPU do periferie připojené k I/O portu AY jsou potřeba následující kroky. Zvolit registr 7 a povolit výstup nastavením bitu 6 registru 7 do log. 1. Zvolit registr 14. Zapsat data do registru 14. Přičemž datasheet z nějakého důvodu čísluje registry jinak. Já si dovolil použít hodnotu stejnou, jakou zapisujeme do portu, kterým se registr vybírá na ZX Spectru.

Analogicky k čtení: Je potřeba zvolit registr 7 a nastavit bit 6 registru 7 do log. 0. Zvolit registr 14. Přečíst z registru 14 data, tj. stav bitů I/O portu. Datasheet dále poznamenává, že bity I/O portu ve výstupním režimu zůstávají v takovém stavu, jaká data na ně byla zapsána, dokud nejsou zapsána jiná data. A naopak, čtením I/O portu se přečtou taková data, jaké logické stavy byly na vstupech v okamžiku čtení i když se stavy mohou mezitím měnit. Nic neobvyklého, takto se chovají i jiné porty.

Při čtení I/O portu AY je všech 8 vstupů vybaveno pullup rezistory (hodnotu datasheet neuvádí), proto AY, není-li k ní něco připojeno, vrací na všech vstupech I/O portu log. 1 (255). Vstupy lze díky tomu snadno otestovat připojením na GND a příslušný bit by se měl změnit na log. 0.

Ale nebude nakonec problém s přepínáním režimu I/O portu jako vstup a výstup?

Ovládání AY-3-8912 z BASICu

AY-3-8912 v ZX Spectrech se ovládá na dvou portech, jeden slouží k výběru registru, nebo čtení obsahu portu a nachází se na adrese 65533, druhý slouží k zápisu do registru vč. I/O portu a nachází se na adrese 49149. Z následující tabulky je zřejmé, proč nelze AY adresovat pouze osmibitově. Nižší byte obou adres je shodný a není možné tak rozlišit do kterého z portů mají být data zapsána.

Pro své experimenty jsem si použil šedé ZX Spectrum 128k +2, protože jeho zapojení je přesně shodné se ZX Spectrum 128k+ toastrack a to lze považovat ve světě Sinclairů za defakto standard.

význam portu dec hex bin
volba registru, nebo čtení registru 65533 0xFFFD 0b1111 1111 1111 1101
zápis obsahu registru 49149 0xBFFD 0b1011 1111 1111 1101

Na oba porty lze samozřejmě zapisovat i v BASICu. Pro nejjednodušší pokusy není nutné používat strojový kód. Viz příklad čtení I/O portu - všimněte si, že to je takřka doslovně do BASICu přepsaný postup z datasheetu.

5 REM  read AY port
10 OUT 65533,7: OUT49149,191
20 OUT 65533,14
30 PRINT AT 0,0; IN 65533;"  ": GO TO 30

Program na reálném ZX Spectru vrací 255, pokud není nic na RS232 připojeno. Pokud je, mohou být bity 7 a 6 v logické 0, podle toho, jaké napětí je přivedeno na vstupy RS232. Ve Fuse emulátoru verze 1.5.7 kdo ví proč program vrací trvale 191. Ani v jednom případě přitom nezáleží, jestli po volbě registru 14 na port 49149 zapíšu 0, nebo 255, nebo nic.

screenshot screenshot

Zajímavé je, že když propojím ZX Spectrum s PC a spustím čtení portu v BASICu, tak bit 6 v log. 0 signalizuje připravenost PC na příjem dat. Nedělám-li na PC nic a nečtu port, pak BASIC zobrazuje 255, ale pokud na PC spustím cat /dev/ttyS0 (analogicky cat /dev/ttyUSB0 pro USB sériák), tak se zobrazí 191, tzn. změní se stav signálu CTS (RTS na straně PC). Viz relevantní výcuc z výše uvedených tabulek.

AY-3-8912 směr RS232 DSUB-9 pin
I/O A7 vstup RxD 2
I/O A6 vstup CTS 8

Zkusme pro změnu na AY-3-8912 zapisovat. Ale protože stav nelze na PC číst tak snadno jako v BASICu, budu tentokrát měřit napětí výstupních signálů multimetrem. Měřit budu na vývodech 3 a 7 DSUB konektoru s kolíky. A opět, pro zjednodušení viz výcuc z výše uvedených tabulek.

AY-3-8912 směr RS232 DSUB-9 pin
I/O A3 výstup TxD 3
I/O A2 výstup RTS 7

V následujícím příkladu se nastaví I/O port do výstupního režimu a pak se cyklicky přepínají stavy s intervalem asi 4s, tak akorát aby i digitální multimetr stihl zobrazovat napětí na příslušném výstupu.

10 REM  write to AY port
20 LET d=200
30 OUT 65533,7: OUT 49149,255
40 BORDER 6: OUT 65533,14: OUT 49149,4+8: PAUSE 200
50 BORDER 7: OUT 65533,14: OUT 49149,0: PAUSE 200
60 GOTO 40

Po spuštění programu se bude pomalu střídat žlutý a bílý okraj obrazovky. Po dobu žlutého okraje byste měli naměřit na obou výstupech záporné napětí, pravděpodobně něco okolo -10V, nebo -11.5V, tzn. log 1 v úrovních RS232. Po dobu bílého okraje byste měli naměřit stejně velké napětí, ale kladné, tzn. v úrovních RS232 logickou 0.

screenshot screenshot

Můžete samozřejmě zkusit zapisovat log. 1 jen na jeden z bitů (místo 4+8 jen 4, nebo 8) a tím si ověřit, že máte port, nebo redukci správně zapojené.

RS232 ve 128k BASICu

Udělejme malou odbočku. Hádáte-li, že BASIC pro ZX Spectrum 128k umí s RS232 pracovat, hádáte správně. Bohužel v manuálu není skoro nic. Vzpomínám, že jsem kdysi dávno měl propojená dvě ZX Spectra prostým překřížením signálů TxD s RxD a DTR s CTS dle pinoutu konektoru BT631W z manuálu. V manuálu se taky lze dočíst, jak nastavit baudrate příkazem FORMAT.

FORMAT "p"; baudrate

Možné hodnoty baudrate v manuálu uvedeny nejsou, pouze je tam zmínka, že 9600bps je rychlost výchozí, 19200bps je rychlost maximální. Prý s RS232 fungují příkazy LPRINT, LLIST a COPY, jsou tam dvě jednoduché ukázky jak poslat na tiskárnu pár znaků a to je vše. Nezbývá, než zkoumat vlastními silami.

10 PRINT "This program..."'
20 LLIST
30 LPRINT '"...prints out the character set, ie..."'
40 FOR n=32 to 255
50 LPRINT CHR$ n;
60 NEXT n

Domnívám se, že by měl fungovat i příkaz INPUT #3 pro příjem bytů z druhého ZX Spectra. Nutno ověřit. Je to už dávno, co jsem to zkoušel. Naštěstí se po internetu potuluje disassemblovaný 128k BASIC, kam lze nakouknout a s trochou úsilí pochopit, co to vlastně umí.

A kromě toho existuje i RS232 ROM, což je upravený BASIC 48k, rozšířený o možnost přenášet data přikazy LOAD a SAVE kromě magnetofonu přes EAR/MIC i přes oba RS232 porty v ZX 128k (i přes KEYPAD) a dokonce i přes sériák v ZX Interface 1 o kterém ale mé povídání není.

Jednoduchý pokus s AY ve strojovém kódu

Následující příklad je záměrně napsaný s ohledem na srozumitelnost. Vím, že by se dal zmenšit, ale ztratil by na přehlednosti. Zkuste nejprve nastavit sériový port na PC (s Linuxem) příkazem stty (viz manuál). Nastaví pouhých 300bps, ale nastaví hardwarové řízení toku dat signály RTS a CTS.

Zařízení /dev/ttyS0 označuje skutečný sériový port, třeba na základní desce, nebo na přídavné PCI/PCIe kartě. USB převodníky budou obvykle /dev/ttyUSB0. Případně místo nuly bude vyšší číslo, máte-li převodníků víc. Nahraďte si v příkladech sami.

stty -F /dev/ttyS0 300 cs8 clocal cread cstopb parenb parodd crtscts raw

Pak můžete zkompilovat a spustit následující kód. Je psaný v Prometheovi na ZX Spectru (a vyzkoušený v kompileru AS na Linuxu) a po spuštění by měl zobrazit dva červené čtverce v levém horním rohu obrazovky. Levý signalizuje změnou barvy na žlutou stav bitu 7 a pravý stav bitu 6 na I/O portu AY.

Stejný kód se zvýrazněním syntaxe zde.

		cpu	z80undoc
		org	50000		; pseudoinstrukce - přelož a ulož na 50000
					; musí běžet v rychlé RAM

START		di			; nesmí být zpomalováno a přerušováno
		call	READAY
		ei			; před návratem do BASICu přerušení povol
		ret

READAY		ld	bc,65533	; zvol registr 7 v AY
		ld	a,7
		out	(c),a
	
		ld	bc,49149	; vypni zvuk a nastav I/O port jako výstup (bit 6 v log. 1)
		ld	a,255
		out	(c),a
	
		ld	bc,65533	; zvol registr 14 v AY
		ld	a,14
		out	(c),a

		ld	bc,49149	; nastav na I/O portu bit 2 do log. 0 0b11111011
		ld	a,251		; tím se signalizuje na RTS připravenost přijímat data
		out	(c),a

		ld	b,10		; počkej 10 cyklů
WAIT		djnz	WAIT

		ld	bc,65533	; zvol registr 7 v AY
		ld	a,7
		out	(c),a

		ld	bc,49149	; nastav I/O port jako vstup (bit 6 v log 0)
		ld	a,191		; 0b10111111
		out	(c),a		; touto instrukcí se RTS pulz ukoncuje
	
		ld	bc,65533	; zvol registr 14 v AY
		ld	a,14
		out	(c),a

		ld	hl,16384+6144	; adresa prvního barvového atributu
	
		in	a,(c)
		ld	d,a
	
		bit	7,d
		ld	a,6*8		; žlutý PAPER pro 0 v bitu 7
		jr	z,READAY_1
		ld	a,2*8		; červený PAPER pro 1 v bitu 7
READAY_1	ld	(hl),a
		inc	hl
		inc	hl

		bit	6,d
		ld	a,6*8		; žlutý PAPER pro 0 v bitu 6
		jr	z,READAY_2
		ld	a,2*8		; červený PAPER pro 1 v bitu 6
READAY_2	ld	(hl),a
		inc	hl
		inc	hl

		call	8020
		ret	nc		; nc pokud byl stisknutý BREAK
		jr	READAY

Teď zkuste pozorovat, co se stane, když na PC zkusíte data číst příkazem cat.

cat /dev/ttyS0

Viz vlevo ukázka kódu přes kompilací a spuštěním v Prometheovi a vpravo běžící program.

screenshot screenshot

Změnil pravý čtverec barvu z červené na žlutou? Tak to je správně. Tím PC signalizuje ZX Spectru, že je připraveno přijímat data. Podobně naopak, ZX Spectrum signalizuje PC krátkým pulzem, že je připraveno přijímat data při každém průchodu cyklem, dokud není stisknutý BREAK. Pošlete-li data jedním z následujících příkazů, tak by se levý čtverec měl rozblikat podle přenášených bitů, zatímco po dobu přenosu pravý svítí žlutě. Proto je taky důležité, aby přenosová rychlost byla v tuto chvíli velmi nízká a tedy aby blikání bylo pozorovatelné.

cat textovy_soubor.txt > /dev/ttyS0
echo "nejaky zkusebni text" > /dev/ttyS0

Pokud zůstanou nějaká data v bufferu na PC a zkusíte příkazem stty změnit parametry portu, příkaz stty bude čekat, dokud se buffer nevyprázdní. Pak se výše uvedený příklad může taky hodit. Pomůže zahodit připravená data.

Vypozoroval jsem, že množství krátkých pulzů signalizujících připravenost přijímat data může pro PC fungovat jako by RTS bylo ve stavu připravenosti trvale. Aby bylo vidět např. logickým analyzátorem, že se tok dat opravdu pozastavuje, resp. PC reaguje vysláním jednoho bytu a pak počká na další pulz, měl by pulz RTS trvat přiměřeně dlouho a měly by mezi nimi být mezery v závislosti na nastaveném baudrate. Ale k tomu se dostanu později.

Trocha teorie o sériovém přenosu a časování

Rychlost sériového portu se udává v baudech, nikoli v bitech za sekundu. Jednotka se jmenuje podle pana Baudota, který stál u zrodu datových přenosů po telefonních linkách, resp. kódování abecedy pomocí Baudotova kódu ještě před vznikem dálnopisů (anglicky teletype, proto máme dodnes v *nixech TTY). Z Baudotova kódování se později vyvinulo ASCII a pulzy sériového přenosu zůstaly po panu Baudotovi pojmenované.

Proč baudy a ne bity? Protože přenos každého bytu je složený kromě přenášených datových bitů i ze start bitu a nejméně jednoho stop bitu. Každý z pulzů trvá stejný čas a počet těchto pulzů za sekundu se nazývá baudrate. Kdyby udávaná rychlost byla odvozená od přeneseného objemu dat měnila by se s počtem bitů, kterých může být klidně jen 5, nebo 7, měnila by se s počtem stop bitů, které mohou být 2, nebo dokonce 1.5 či mnohem víc, přijímací strana může počkat, protože stop bity považuje za klidový stav linky. A nakonec paritní bit, který může a nemusí být přenášen. Všechno závisí na tom, jak si to uživatel zvolí a na propojených strojích nastaví. Jediná podmínka je, že to musí být na obou stranách nastavené stejně. Počítače si většinou přenosovou rychlost neumí domluvit a když ano, tak to je s nejistotou a možnou ztrátou části přenášených znaků.

Viz obrázek záznamu logickým analyzátorem. Jsou na něm vidět 4 přenášené byty, v hexu 0xaa, 0x55, 0x00 a 0xff. Každý přenos začíná start bitem, kdy se linka na dobu jednoho baudu přepne z klidového stavu do opačné logické úrovně, tj. z log. 1 do log. 0. Podle start bitu přijímací strana pozná, kdy má začít číst bity a čte je v pravidelných časových intervalech,

screenshot logickeho analyzatoru diagram

Použitý logický analyzátor umí sériový přenos dekódovat a dekódovaný byte vypisuje v modrém rámečku nad záznamem, bity kódující informaci vyznačuje puntíkem. Díky tomu můžeme např. na první pohled vidět, že po každém bytu následují dva stop bity. Což sice zpomaluje přenos informace, ale dává přijímacímu stroji maličko víc času na zpracování přijatého bytu.

Díky puntíkům taky můžeme vidět, že se jako první přenáší LSB - nejméně významný bit 0 a jako poslední MSB - nejvýznamnější bit 7. První byte je binárně 10101010 (0xaa, dekadicky 170) a přitom při sériovém přenosu začíná log. 0. Analogicky u ostatních bytů.

Budete-li místo logickým analyzátorem měřit osciloskopen na straně RS232, ne TTL, pak viz druhý obrázek. Světle červeným pozadím je vyznačená přibližná úroveň napětí odpovídající log. 1, podobně jako na logických sondách, zeleně log. 0. Svislou červenou čarou optimální okamžik čtení hodnoty (s výjimkou start bitu, tam lze očekávat reakci na hranu signálu).

Přenosové rychlosti vs ZX Spectrum

Rychlost sériového přenosu může být takřka libovolná, zvládne-li ji hardware, ale za tu dlouhou dobu, co se sériový přenos používá, se hodnoty rychlostí ustálily na malé množině nejčastějších a dnešní zařízení, nebo software leckdy ani neumožní nastavit jiné. Např. příkaz stty dovolí nastavit tyto rychlosti - viz odkaz. I následující tabulka obsahuje několik běžně používaných rychlostí. Později se budu zabývat jen některými z nich.

baudrate μs/baud taktů ZXS 48k taktů ZXS 128k
75 13333.333 46666.66 47292.00
300 3333.333 11666.67 11823.00
1200 833.333 2916.67 2955.75
4800 208.333 729.17 738.93
9600 104.167 364.58 369.47
19200 52.083 182.29 184.73
38400 26.041 91.15 92.37
57600 17.361 60.76 61.57
115200 8.681 30.38 30.79

V tabulce kromě času v μs uvádím i počet taktů procesoru Z80. ZX Spectrum 48k, stejně jako DG192k má 14MHz krystal, ze kterého je odvozena taktovací frekvence Z80 3.5MHz. V ZX Spectru 128k je krystal 35.4690MHz a odvozená taktovací frekvence Z80 je maličko vyšší 3.5469MHz. Toto bude důležité při časování přenosového programu. Všechny časově kritické operace je samozřejmě nutné spouštět v RAM nezpomalované ULA a se zakázaným přerušením, jinak program nejenom poběží pomaleji, ale hlavně se nedá spolehnout jakou přesně rychlostí v ten který okamžik proběhne.

počítač takt procesoru μs/takt
ZX Spectrum 48k gumák / 48k+ 3500000Hz 0,2857
ZX Spectrum 128k+ / +2 3546900Hz 0,2819

Při znalosti, kolik taktů trvají jednotlivé instrukce Z80 se nejvyšší rychlost zdá být 115200bps nedosažitelná. Rychlost 57600bps dosáhnout určitě lze.

Instrukce na Z80 trvají celý počet taktů, ne zlomky, ale desetiny uváděné u počtu taktů by mohly mít vliv na rozhodování, jestli se přiklonit k vyšší, nebo nižší hodnotě. Chyba se při čtení 8 bitů po sobě nasčítá.

Rozdíl v taktech mezi ZX Spectrum 48k a ZX Spectrum 128k je přibližně 1.3%, totéž v rozdílech trvání jednoho bitu při různých baudrate. Dejme tomu, že optimem by proto mohl být počet taktů cca uprostřed mezi hodnotami pro ZXS 48k a 128k? Rozhodně by chyba v trvání bitu neměla být větší než malé jednotky %.

Nejrychlejší zápis na I/O porty AY v cyklu

Pojďme si vyzkoušet, jak dlouho trvají instrukce a jak nejrychleji je možné zapisovat na I/O port AY. Resp. jakou nejvyšší frekvenci lze dosáhnout změnou logických stavů, protože teorie je hezká, ale neuškodí si ji ověřit prakticky. V ZX Spectru existují kromě zpomalené paměti (oblast VRAM 16384 až 32767 a půlka stránek ZX Spectra 128k od 49152 do 65535) i takové pasti, jako třeba zpomalené porty. To se naštěstí netýká portů AY-3-8912.

Stejný kód se zvýrazněním syntaxe zde.

		cpu	z80undoc
		org	32768

START		di

		ld	bc,65533	; zvol registr 7
		ld	a,7
		out	(c),a
		ld	bc,49149	; vypni zvuk, I/O port výstup (bit 6 v log. 1)
		ld	a,255
		out	(c),a
		ld	bc,65533	; zvol registr 14
		ld	a,14
		out	(c),a
		ld	bc,49149	; a bude se zapisovat na I/O port

LOOP		xor	a		;  4T
		out	(c),a		; 12T
		jp	PRESKOK		; 10T

PRESKOK		cpl			;  4T
		out	(c),a		; 12T
		jp	LOOP		; 10T

Program pouze nastaví porty AY, vezme do BC adresu datového portu a pak už jen donekonečna střídá zápis 0 a 255. Celá perioda by měla trvat 52 taktů procesoru, přibližně 52 * 0.2819 = 14.66μs, čemuž odpovídá frekvence 3546900/52 = 68209Hz.

screenshot logic analyzer foto měření frekvence z IO portu AY

Na výstupech šedé ZX Spectrum 128k +2 jsem naměřil 14.67μs a 68.18kHz analyzerem (resp. 68.2kHz multimetrem), takže hodnoty v rámci tolerance odpovídají předpokladu. Divné bylo jen to, že střída nebyla přesných 50%, ale něco málo přes 55%, log. 1 trvala 8.17μs a log. 0 6.5μs. Nevím, co je příčinou, jestli na to nemají vliv převodníky z TTL na RS232 a jejich rozhodovací úroveň napětí na vstupu z TTL. V každém případě délka pulzů mírně kolísá v řádu setin až desetin μs.

Více způsoby jsem měřil na ZX Spectrum 48k+ a zároveň na ZX Spectrum Sparrow Lite prototype 2, což je v podstatě taky 100% ZX Spectrum s originál ULA i Z80. Rozdíly mezi nimi byly nepatrné, na hranici chyby měření. Teoreticky by frekvence měla být 67.307kHz, protože cyklus trvá 52 taktů na 3.5MHz Z80, čemuž odpovída 14,86μs.

  vypočteno naměřeno
Aneng AN8008
naměřeno
Siglent SDS1204X-E
naměřeno
Saleae analyzer
frekvence 67.307kHz 67.28kHz až 67.30kHz 67.3074kHz 67.23 až 67.42kHz
perioda 14,86μs neukazuje
z frekv. 14,86μs
14.86μs 14.83μs až 14.88μs
střída ideálně 50% 49.7% 49.65% 49.72 až 49.86%

Rozdíl mezi trváním log 0 a log 1 se nepotvrdil, pomalejší náběžná hrana z log 0 na log 1 nad 3V by snad mohla způsobit prodloužení log 0. Nutno prozkoumat s připojeným převodníkem. Rychlost překlopení výstupu je pro obě logické úrovně stejná, viz měření signálu BDIR na pinu 18, od náběžné hrany signálu trvá v obou případech zhruba stejných 280ns než se změní stav na výstupu.

Rozptyl hodnot logického analyzeru je nejspíš způsobený nízkým samplerate, naměřené hodnoty dost často oscilují mezi dvěma okolo předpokládané.

screenshot z osciloskopu Siglent screenshot z osciloskopu Siglent

TODO: Porovnat s výsledky na DG192k. Mělo by být stejné, jako ZX Spectrum 48k+.

TODO: Porovnat trvání log 0 a log 1 přímo na AY proti RS232 úrovním. Liší se?

Sériový přenos na ZX Spectru

Teď se konečně dostáváme k něčemu praktickému. Když už máme ověřeno, že se lze na instrukce out (c),a spolehnout, je čas sestavit program, který serializuje a odesílá alespoň jeden byte, protože odesílaná data se dají mnohem lépe měřit než jejich příjem. Pro svůj pokus jsem si vybral 57600bps.

Cygnusovo vysílání dat 57600bps bez řízení toku

Prozkoumal jsem několik cizích zdrojáků a nakonec jsem zkusil napsat následující program. Zdál se mi přehledný, srozumitelný, instrukce jp trvá vždy stejný čas, nemusím řešit rozdíl v počtu taktů, když je podmínka splněna a když není. Nepřesnost trvání cyklu 62 taktů je tolerovatelná, v přenosu celého bytu udělá skoro 2.3μs a to je pořád výrazně méně než čtvrtina z trvání jednoho bitu 17.361μs. Později se ukázalo, že to je řešení naivní a nedokonalé. Ale data to přenáší a dodalo mi odvahy.

Kód odesílající jeden byte se zvýrazněním syntaxe zde.

Kód odesílající sekvenci dat se zvýrazněním syntaxe zde.

;==================================================================================================
; RS232 - transmitting through AY-3-8912 without data flow control
;
; 57600bps (17,3611μs)	61.57813T na ZX128k, 61T bude trvat 17.19811μs, chyba -0.9% (58146bps)
;					     62T bude trvat 17.48005μs, chyba +0.7% (57208bps)
;			60.76389T na ZX48k,  61T bude trvat 17.42857μs, chyba +0,4% (57377bps)
;					     62T bude trvat 17.71429μs, chyba +2.0% (56451bps)
;==================================================================================================

		cpu	z80undoc
		org	32768			; musí běžet v rychlé RAM

START		di				; nesmí být zpomalováno a přerušováno
		ld	bc,65533		; zvol registr 7 v AY
		ld	a,7
		out	(c),a
		ld	bc,49149		; vypni zvuk a nastav I/O port jako výstup (bit 6 v log. 1)
		ld	a,255
		out	(c),a
		ld	bc,65533		; zvol registr 14 v AY
		ld	a,14
		out	(c),a

SEND_STRING	ld	hl,DATA			; 10T	data, která budeme přenášet
		ld	bc,DATA_LENGTH		; 10T
SEND_STRING_L	push	bc			; 10T
		push	hl			; 10T

; celý cyklus 10+10+11+11+6+6+4+4+10=72T	projeví se na prodloužení stop bitu po dokončení přenosu

AYSER_SENDBYTE	ld	bc,49149		; 10T	registr 14 pro výstup zvolen, budu do něj zapisovat
		ld	e,128			; 7T	E bude počítadlo, 8x rotovat, nastane carry
		ld	a,247			; 7T	do A 0b11110111 = TxD do log.0, protože start bit
		out	(c),a			; 12T	zde začíná start bit a zde začíná záležet na taktech
		ld	a,(hl)			; 7T	vezmi byte, který má být vyslán
		ld	d,a			; 4T
		ld	l,247			; 7T	a teď můžu L změnit, před zavoláním AYSER_SENDBYTE
						;	bude muset být HL na zásobníku

; od outu sem 12+7+4+7 = 30T + k dalšímu outu bude 32T = 62T, trvání start bitu

AYSER_SENDB_L	rrc	d			; 8T	rotuj data skrz carry flag
		jp	c,AYSER_SENDB_1		; 10T
		ld	a,l			; 4T	do A 0 = TxD do log. 0
		jp	AYSER_SENDB_2		; 10T

AYSER_SENDB_1	ld	a,b			; 4T	do A B = TxD do log. 1 (B = 0b10111111)
		jp	AYSER_SENDB_2		; 10T

; od rrc d, bit = 1	8+10+4+10 = 32T
; od rrc d, bit = 0	8+10+4+10 = 32T

AYSER_SENDB_2	out	(c),a			; 12T
		rrc	e			; 8T
		jp	nc,AYSER_SENDB_L	; 10T

; celý cyklus trvá od AYSER_SENDB_L 8+10+4+10+12+8+10 = 62T, o takt víc, než by měl

		ld	a,255			; 7T	protože log. 1 do stop bitu i do RTS
		nop				; 4T	(použít raději rrc e 8T 3x?)
		nop				; 4T
		nop				; 4T
		nop				; 4T
		nop				; 4T
		nop				; 4T	zpoždění celkem 24T v 6x nop

; od minulého outu sem 12+8+10+7+6*4 = 61T, trvání posledního bitu (MSB)

		out	(c),a			; 12T	zde začíná stop bit
		nop				; 4T	zpoždění
		nop				; 4T	zpoždění
		nop				; 4T	zpoždění
		nop				; 4T	zpoždění	4x nop = 16T
		pop	hl			; 11T
		pop	bc			; 11T
		inc	hl			; 6T
		dec	bc			; 6T
		ld	a,b			; 4T
		or	c			; 4T
		jp	nz,SEND_STRING_L	; 10T

; od outu do jp včetně	12+4*4+11+11+6+6+4+4+10 = 80T
; začátek cyklu 44T prodlouží trvání stop bitu, celkem vychází 128T, nepatrně víc než 2 stop bity,
; naměřeno 36.6us

		ei				; 4T	před návratem do BASICu přerušení povol
		ret				; 10T


DATA		db	"Hello RS232 world! ZX Spectrum 128 is sending data."
		db	13,10			; CR, LF

DATA_LENGTH	equ	$ - DATA

Ověřil jsem, že přenos funguje analyzérem, proto je čas kód vylepšit a posílat celou sekvenci bytů. To už má praktické využití. Na prvním obrázku je oproti programu kód 0x55, protože jsem v tu chvíli chtěl vidět i trvání start bitu, není to způsobeno chybou v programu a opačným pořadím bitů.

screenshot logic analyzer screenshot logic analyzer screenshot logic analyzer

Na screenshotu je vidět, že i bez řízení toku dat je stop bit maličko delší, ale nijak extrémně a stále lze zkrátit vynecháním zpomalovacích instrukcí jp. Přenos dat je pořád na poměry ZX Spectra velmi rychlý a pokud ZX Spectrum posílá data do PC, které je stíhá spolehlivě přijímat, není řízení toku dat vždy nezbytné. Je na uživateli, jak si kontrolu správnosti dat zařídí, když už ne lépe, tak by měl vždy alespoň porovnat počet přenesených bytů. Ať už je datový tok nějak řízen, nebo ne.

stty -F /dev/ttyS0 57600 cs8 clocal cread cstopb parenb parodd -crtscts -echo raw

K výpočtu odchylky jsem sečetl takty start bitu a datových bitů 62+7*62+61=557, z toho jsem spočítal čas trvání, vydělil 9 a vypočítal časovou odchylku odchylku trvání jednoho bitu. Vydělením této odchylky 1/100 času, kterého chci dosáhnout spočítám chybu v %.

rychlost 57600bps
řízení toku není
trvání bitu 128k 17,4487μs - průměrně cca +0,5%
trvání bitu 48k 17,6825μs - průměrně cca +1,85%

Vysílání dat 57600bps podle Martina1

Další způsob, jak odesílat data rychlostí 57600bps poslal do diskuze na OldComp.cz Martin1. Jeho způsob je kratší a přesnější, pro všechny bity mu vychází 61T, přesně jak má. Resp. původně měl obráceně rotaci datového bytu, ale základní myšlenka byla v jádru správná. Upravil jsem ho do zkompilovatelné podoby vhodné k testování a změřil chování na reálném hardwaru.

Kód odesílající jeden byte se zvýrazněním syntaxe zde.

;==================================================================================================
; RS232 - transmitting through AY-3-8912 without data flow control
;
; 57600bps (17,3611μs)	61.57813T na ZX128k, 61T bude trvat 17.19811μs, chyba -0.9% (58146bps)
;			60.76389T na ZX48k,  61T bude trvat 17.42857μs, chyba +0,4% (57377bps)
;==================================================================================================

		cpu	z80undoc
		org	32768			; musí běžet v rychlé RAM

START		di				; nesmí být zpomalováno a přerušováno
		ld	bc,65533		; zvol registr 7 v AY
		ld	a,7
		out	(c),a
		ld	bc,49149		; vypni zvuk a nastav I/O port jako výstup (bit 6 v log. 1)
		ld	a,255
		out	(c),a
		ld	bc,65533		; zvol registr 14 v AY
		ld	a,14
		out	(c),a
		
TRANSMIT_57600	ld	bc,49149		; 10T	port AY-3-8912
		ld	h,8			; 7T	H poslouží jako počítadlo 8 bitů
		ld	a,01010101b		; 7T	data k odeslání do A
		ld	l,a			; 4T	zkopíruj data z A do L
		rrc	l			; 8T	4x rotuj data vpravo (LSB na pozici bitu 3)
		rrc	l			; 8T	dvě rotace před outem, aby vyšly takty
; start bit
		ld	a, 11110111b		; 7T	maska
		out	(c),a			; 12T	zapiš na port
		rrc	l			; 8T	a dvě rotace ze 4 po outu
		rrc	l			; 8T
; datové bity
TRANSMIT_LOOP	ld	a,0			; 7T	zpoždění
		ld	a,0			; 7T	zpoždění
		rrc	l			; 8T	další bit na pozici bitu 3
		ld	a,11110111b		; 7T	připrav masku
		or	l			; 4T	přidej masku

; od začátku start bitu	12+8+8+7+7+8+7+4 = 61T
; od minulého bitu	12+4+12+7+7+8+7+4 = 61T

		out	(c),a			; 12T	zapiš na port
		dec	h			; 4T	počítadlo bitů
		jr	nz,TRANSMIT_LOOP	; 12/7T	opakuj
; stop bit
		ld	a,(ix+0)		; 19T	zpoždění
		inc	hl			; 6T	zpoždění
		inc	hl			; 6T	zpoždění
		ld	a,11111111b		; 7T

; od posledního outu v cyklu 12+4+7+19+6+6+7 = 61T

		out	(c),a			; 12T	zapiš na port
		ei				; 4T
		ret				; 10T	prodlouží trvání stop bitu

Nastavení sériového portu v Linuxu bude pro tento kód vypadat následovně.

stty -F /dev/ttyS0 57600 cs8 clocal cread cstopb parenb parodd -crtscts -echo raw

Měřením jsem zjistil, že bity trvají cca 18.02μs v log. 1 a 16.42μs v log. 0, toto chování je stále stejné. Průměrně tak čas vychází zhruba na 17.22μs, což je opravdu o kousek míň, než cílových 17.361μs a blíží se vypočtené chybě -0.9% (měřením -0.8%). To je vlastně dobře, protože na ZX Spectru 48k s pomaleji taktovanou Z80 se chyba vyrovná a přenos bude naopak o maličko pomalejší, než by měl být. Obojí tak bude v tolerovatelných mezích.

screenshot 57600bps
rychlost 57600bps
řízení toku není
trvání bitu 128k 17.1981μs - cca -0.94%
trvání bitu 48k 17.4286μs - cca +0,38%

TODO: Vyzkoušet na DG192 s pomalejším taktem Z80.

Vysílání dat 115200bps podle Martina1

Martin1 poslal na OldComp ještě jeden velmi zajímavý návrh, jak posílat data, tentokrát rychlostí 115200 baudů. Upravil jsem jeho "nástřel od boku" pro přenos sekvence bytů, opravil chybu v rotaci a dodatečně doplnil Martinův nápad použít exx místo push a pop. Výsledek funguje lépe než jsem si dovedl představit. Sice stále bez řízení toku dat, ale opravdu to přenáší rychlostí 115200bps, resp. jen nepatrně pomaleji s docela zanedbatelnou chybou na ZX Spectrum 128k a stále tolerovatelnou chybou na ZX Spectrum 48k. Délka stop bitu mezi byty přitom vychází jen málo přes dobu trvání 3.1 baudů v pomalejší variantě a přibližně 2.7 baudů v rychlejší variantě. To je na 3.5MHz Z80 velmi pěkný výsledek.

Kód s déle trvajícím stop bitem se zvýrazněním syntaxe zde.

Kód s použitím sekundární sady registrů se zvýrazněním syntaxe zde.

;==================================================================================================
; RS232 - transmitting through AY-3-8912 without data flow control
;
; 115200bps (8,68056μs)	30.78906T na ZX128, 31T bude trvat 8,7400μs, chyba +0.7% (114416bps)
;			30,38194T na ZX48k, 31T bude trvat 8,8571μs, chyba +2,0% (112903bps)
;==================================================================================================

		cpu	z80undoc
		org	32768

TRANSMIT_115200	di				;	nepřerušovat
		ld	bc,65533		;	zvol registr 7 v AY
		ld	a,7
		out	(c),a
		ld	bc,49149		;	vypni zvuk a nastav I/O port jako výstup (bit 6 v log. 1)
		ld	a,255
		out	(c),a
		ld	bc,65533		;	 zvol registr 14 v AY
		ld	a,14
		out	(c),a

		ld	hl,DATA			;	adresa odkud
		ld	de,DATA_LENGTH		;	počet bytů k přenesení
		exx
		ld	bc,49149		;	adresa datového portu do sekundárního BC'
		exx

; odsud se bude opakovat, násl. instrukce prodlouží stop bit

TRANSMIT_LOOP	ld	a,(hl)			; 7T	vezmi byte,který má být vyslán
		exx				; 4T	sekundární sada registrů s připraveným BC, abych neměnil důležité HL
		rrca				; 4T 	předrotuj data 4x vpravo z bitu 0 do bitu 4
		rrca				; 4T
		rrca				; 4T
		rrca				; 4T
		ld	d,a			; 4T	a uschovej A do D (na HL' nesahat, abych ho nemusel ukládat při návratu do BASICu)
; start bit
		ld	a,11110111b		; 7T	start bit
		out	(c),a			; 12T	zapiš na port (vždy log. 0 = start bit)

		rrc	d			; 8T	rotuj L, bit 0 na pozici bitu 3 (LSB)
		ld	a,11110111b		; 7T	priprav masku
		or	d	 		; 4T	vymaskuj, vše kromě významného bitu nastav na 1
		out	(c),a			; 12T	zapiš na port, celkem 31T od předchozího out (c),a

		rrc	d			; 8T	bit 1 na pozici bitu 3
		ld	a,11110111b		; 7T
		or	d	 		; 4T	vymaskuj
		out	(c),a			; 12T	zapiš na port
		 
		rrc	d			; 8T	bit 2
		ld	a,11110111b		; 7T
		or	d	 		; 4T	vymaskuj
		out	(c),a			; 12T	zapiš na port
		 
		rrc	d			; 8T	bit 3
		ld	a,11110111b		; 7T
		or	d	 		; 4T	vymaskuj
		out	(c),a			; 12T	zapiš na port
		 
		rrc	d			; 8T	bit 4
		ld	a,11110111b		; 7T
		or	d	 		; 4T	vymaskuj
		out	(c),a			; 12T	zapiš na port
		 
		rrc	d			; 8T	bit 5
		ld	a,11110111b		; 7T
		or	d	 		; 4T	vymaskuj
		out	(c),a			; 12T	zapiš na port
		 
		rrc	d			; 8T	bit 6
		ld	a,11110111b		; 7T
		or	d	 		; 4T	vymaskuj
		out	(c),a			; 12T	zapiš na port

		rrc	d			; 8T	bit 7
		ld	a,11110111b		; 7T
		or	d	 		; 4T	vymaskuj
		out	(c),a			; 12	zapiš na port, poslední bit (MSB)

		rrc	d			; 8T	zpomalení
		nop				; 4T	zpomalení
		ld	a,11111111b		; 7T	vymaskuj stop bit na 1
		out	(c),a			; 12T	zapiš na port, zde začíná stop bit

		exx				; 4T	vrať primární sadu registrů s adresou v HL a délkou v DE
		inc	hl			; 6T	další adresa v RAM
		dec	de			; 6T	o byte ke zpracování míň
		ld	a,d			; 4T	testuj, jestli byly zpracovány všechny
		or	e			; 4T
		jp	nz,TRANSMIT_LOOP	; 10T	a opakuj, dokud nejsou přenesena všechna data

; opakování nastane zpoždění 12+4+6+6+4+4+10 = 46T
; plus na začátku cyklu dalších 7+4+4+4+4+4+4+7 = 38T
; celkem 84T délka stop bitu při přenosu sekvence, tj. asi 2.7 délky trvání jednoho bitu

		ei
		ret

; a to je vse, pratele!

DATA		db	"Hello RS232 world! ZX Spectrum 128 is sending data."
		db	13,10		; CR, LF

DATA_LENGTH	equ	$ - DATA

Nastavení sériového portu v Linuxu bude pro tento kód vypadat následovně.

stty -F /dev/ttyS0 115200 cs8 clocal cread cstopb parenb parodd -crtscts -echo raw

Zkusil jsem přenést ze ZX Spectra i větší blok dat, konkrétně obsah ROM asi 20x a následně porovnat md5sum, i sha256sum a vše bylo správně, bez chyb. Zdá se, že se na přenos dá do značné míry spolehnout (alespoň na ZXS128k).

screenshot 115200bps screenshot 115200bps

Zkoumáním logickým analyzerem je ale vidět, že náchylnost kombinace AY-3-8912 s RS232 převodníky setrvat déle v log. 1 (přepnout rychleji do log. 1?) začíná nabývat na významu. Nejkratší bit v log. 1 mezi dvěma log. 0 trvá přibližně 9.5μs, naopak log. 0 mezi dvěma log. 1 trvá přibližně 7.9μs. Obojí by mělo trvat 8.6806μs.

rychlost 115200bps
řízení toku není
trvání bitu 128k 8,7400μs - cca +0.7%
trvání bitu 48k 8,8571μs - cca +2,0%

TODO: Změřit na DG192 s pomalejším taktem Z80. Vyzkoušeno bez měření analyzerem, přenos funguje spolehlivě.

Řízení toku dat při vysílání ze ZX Spectra

Řízení toku dat nemusí být potřeba při přenosu do rychlého PC a ukládání do souboru, nebo zobrazování v terminálu. Ale může být nezbytné při přenosu do zařízení, které data průběžně zpracovává. Například do plotru, který může zpracovávat jeden příkaz mnohonásobně delší dobu, než trvá přijetí třeba i desítek dalších příkazů.

Proto je nutné číst signál CTS a v tom se skrývá další výzva. AY neumí pracovat jako vstup a výstup zároveň a protože AY-3-8912 má jen jeden osmibitový I/O port, nebylo ani v ZX Spectru možné RS232 převodníky zapojit lépe, než jsou.

V režimu čtení jsou na vstupech AY pullup odpory, které zajišťují, že signál TxD zůstává v log. 1 a tím signalizuje klidový stav linky a signál RTS taky v log. 1 a tím signalizuje nepřipravenost přijímat data. V ZX Spectrum +2 si to konstruktéři pojistili přidáním dalších rezistorů.

Zkusil jsem ke kódu Martina1 přidat čekání na signál CTS s možností přerušení čekání stiskem mezerníku každé 256. čtení I/O portu. Funguje to. Ale přepnutí I/O portu AY z režimu vstupu na výstup trvá dlouho. Dle logického analyzeru trvá zhruba něco přes 50μs než po sestupné hraně CTS ZX Spectrum začne start bitem vysílaných dat. A ještě déle trvá mezera mezi byty, která i když je CTS na přijímací straně trvale v log. 0, tak celkem trvá okolo 82μs, což odpovídá přibližně 4.7 bitům.

Idea - viz časová posloupnost na náčrtku.

diagram

A realita viz screenshoty z logického analyzátoru. Zdá se, že to funguje, přenos se opravdu zastavuje, když není CTS v log. 0. Se skutečnou RS232 v PC mi to fungovalo lépe než TTL-USB převodníkem, kde jsem měl CTS z nějakého důvodu trvale v log. 0, ale to není chyba na straně ZX Spectra.

První dva obrázky ukazují stav před zjednodušením práce s AY-3-8912. Nejprve jsem slepě následoval instrukce z datasheetu a přepínal směr přenosu zápisem do registru 7. To zdržovalo stop bit. V poslední verzi nastavuji AY trvale jako výstup a zápisem hodnoty 255 v podstatě vypínám výstupní tranzistory I/O portu tak, aby nestahovaly bit k logické 0 a výstupní úroveň tak určuje pouze kombinace pullup odporů, kterou může RS232 - TTL převodník přetlačit.

Třetí obrázek pak ukazuje rychlost přenosu, resp. zkrácení stop bitů mezi byty po zjednodušení práce s AY. Klidový stav linky mezi byty začínající stop bity se tak zkrátil z 82μs na zhruba 41μs. Reálná přenosová rychlost mírně překračuje 5000 bytů/s (změřil jsem analyzerem na malém vzorku a ověřil přenosem po dobu dvě minuty, i tímto způsobem mi vyšlo 5070 bytů/s, přeneslo se 608417 bytů). To není špatný výsledek vzhledem k teoretickému limitu 5236 bytů/s pro 57600bps se dvěma stop bity.

screenshot z analyzátoru screenshot z analyzátoru screenshot z analyzátoru

Kód programu se zvýrazněním syntaxe je k nahlédnutí zde.

Zakomentovaná změna okraje může signalizovat žlutě ukončení programu po přenesení dat, nebo červeně po přerušení přenosu mezerníkem. Testování BREAK i s caps shiftem by program ještě víc zpomalilo, ponechal jsem pouze čtení mezerníku, na přerušení přenosu stačí a je to v podstatě stejné, jako při práci s páskou.

rychlost 57600bps stopbit 145T (cca 40.9μs)
řízení toku čeká na CTS, lze přerušit mezerníkem
trvání bitu 128k 17.1981μs - cca -0.94%
trvání bitu 48k 17.4286μs - cca +0,38%

Přenos dat v RS232 ROM Paula Farrowa

Neodolal jsem zvědavosti a nakoukl jsem, jak to vyřešit Paul Farrow ve své RS232 ROM. Nejprve jsem autorovi zkusil napsat a poprosil ho o zdrojový kód, ale odpověděl mi až později. Mezitím jsem binární image jím poskytované RS232 ROM stihl disassemblovat. Což nebylo složité, ale ze strojového kódu nejsou vidět autorovy záměry a důvody. Vidím, co kód dělá, ale nevím, proč přesně to tak autor napsal.

V tuto chvíli mám disassemblováno vše, čím se RS232 ROM liší od standardní ZX Spectrum 48k ROM do té míry, že po kompilaci je binární kód identický. Tím jsem si ověřil, že ve zdrojovém kódu nemám chybu. Z něj jsem vybral jen tu část, která bezprostředně souvisí s RS232 přenosem. Dovolím si při tom ignorovat sériový přenos přes Interface 1, který jsem viděl naživo jen párkrát a rozhodně v našich krajích nepatří mezi běžně rozšířené interface. Na druhou stranu, je fajn, že i pro Interface 1 něco takového existuje.

Disassemblovaný a okomentovaný kód RS232 přenosu z RS232 ROM Paula Farrowa se zvýrazněním syntaxe, ale bez dalších úprav, je k nahlédnutí zde.

Zaujalo mě, jakým způsobem Paul Farrow pracuje s AY. V předchozí kapitole jsem zmínil naivní způsob přepínání mezi režimem čtení a zápisu I/O portu AY, protože jsem vycházel z popisu v katalogu, kdežto Paul Farrow neztrácel čas volbou registru 7 a zápisem na něj, ale četl port i když byl přepnutý pro zápis, jen do něj před čtením zapsal 255.

Jeho metodu jsem si ověřil na zjednodušeném programu, který nedělá nic víc, než že vyšle pulz na signálu RTS trvající zhruba stejně dlouho jako bit odpovídající přenosové rychlosti, v tomto případě cca 18μs. Po vyslání pulzu jsem četl port 4096x a každý přečtený byte zapsal do VRAM. Fungovalo to, aniž bych musel AY přepínat složitěji. Proto jsem podle toho zjednodušil přepínání i v předchozím programu.

Zdá se to být banalita, ale toto chování AY-3-8912 jsem nikde nenašel popsané a přitom je zcela zásadní pro rychlejší přenos dat po RS232 v ZX Spectru 128k s řízením toku dat.

screenshot z analyzeru

Pro další zkoumání jsem oddělil kód Paula Farrowa vysílající data a upravil ho, aby byl samostatně použitelný a zkompilovatelný, přidal jsem k němu minimální smyčku pro přenos krátké sekvence bytů. Řízení datového toku s čtením signálu CTS jsem zachoval. Ale taky jsem neprováděl žádnou další optimalizaci.

Posčítáním trvání instrukcí v taktech jsem došel k 62T pro trvání start bitu i datových bitů. U stop bitu je to těžší, ale určitě nebude kratší než 120T. Celé to komplikuje test BREAku, který může trvat různý čas, podle toho jestli je stisknuta klávesa SPACE bez CAPS SHIFTu, nebo ne.

Logickým analyzerem jsem naměřil trvání log. 1 mezi nulami 18.3μs a log.0 mezi jedničkami 16.7μs, trvání stop bitu, resp. klidového stavu linky mezi byty se pohybovalo nejčastěji okolo 66.2μs, občas až k 73μs.

Správné fungování řízení toku dat jsem ještě nezkoušel.

Vysílání dat s pomocí 8255 a TTL-USB převodníku.

Didaktik Gama nabízí oproti továrnímu ZX Spectru zajímavou možnost připojit sériový převodník z úrovní TTL na USB přímo k I/O portům paralelního portu a tím zcela vynechat konverzi z TTL úrovní na napětí kompatibilní s RS232. V nejjednodušším případě stačí připojit dva vodiče. GND a TxD směrem z DG192k ven. Napájení převodníku obstará USB.

Vysílání dat 57600bps podle Busysofta s paritou

Kód pro použití s 8255, tj. Didaktiky, UPI, UR-4 a podobnými interface poslal na OldComp.cz Busysoft. S jeho dovolením zveřejňuji.

60     8051 f3         bytput di                       Odoslanie jedneho bajtu
61     8052 5f                ld   e,a                 57600 Bd ... 1 bit = 61.57 T
62     8053 0609              ld   b,9                 stop bity
63     8055 10fe              djnz #fe
64     8057 af                xor  a
65     8058 d35f              out  (#5f),a             start bit
66     805a c35d80            jp   $+3                 w
67     805d c36080            jp   $+3                 w
68     8060 00                nop                      w 24
69     8061 0608              ld   b,#08
70     8063 cb0b       bbpp1  rrc  e                   /
71     8065 9f                sbc  a,a
72     8066 e608              and  #08
73     8068 d35f              out  (#5f),a             \30
74     806a c36d80            jp   $+3                 w
75     806d 00                nop                      w
76     806e 00                nop                      w 18
77     806f 10f2              djnz bbpp1
78     8071 af                xor  a                   parita
79     8072 ab                xor  e
80     8073 3e08              ld   a,#08
81     8075 e27980            jp   po,bbpp2
82     8078 af                xor  a
83     8079 d35f       bbpp2  out  (#5f),a
84     807b c37e80            jp   $+3                 w
85     807e c38180            jp   $+3                 w
86     8081 c38480            jp   $+3                 w
87     8084 c38780            jp   $+3                 w
88     8087 00                nop                      w 44
89     8088 3e08              ld   a,#08               stop bity - na zaciatku
90     808a d35f              out  (#5f),a
91     808c c9                ret

Busyho kód ale není přímo použitelný v Prometheu, nebo AS. V Prometheu budou vadit instrukce jp $+3, které je potřeba přepsat na jp $+2, protože Prometheus vyhodnocuje výraz jinak a $ velmi zjednodušeně bude znamenat nikoli adresu instrukce, ale adresu prvního bytu operandu. Zápis hexadecimálních čísel, adresy, kódy instrukcí a komentáře bez středníku znamenají jen hodně přepisování. Provedl jsem za vás.

;===================================================================================================
; RS232 - transmitting through paralell interface with 8255 without data flow control
; 
; 57600bps (17,3611μs)	61.57813T na ZX128k, 61T bude trvat 17.19811μs, chyba -0.9% (58146bps)
;			60.76389T na ZX48k,  61T bude trvat 17.42857μs, chyba +0,4% (57377bps)
;===================================================================================================

		cpu	z80undoc
		org	32768

bytput		di			; 4T
		ld	e,a		; 4T
		ld	b,9		; 7T
BYTPUT_WAIT_0	djnz	BYTPUT_WAIT_0	; B * 13T + 9T
		xor	a		; 4T
		out	(95),a		; 11T	zápis na port 8255 (start bit, log. 0)
		jp	BYTPUT_WAIT_1	; 10T	zpoždění
BYTPUT_WAIT_1	jp	BYTPUT_WAIT_2	; 10T	zpoždění
BYTPUT_WAIT_2	nop			; 4T	zpoždění (celkem 24T)
		ld	b,8		; 7T
BYTPUT_1	rrc	e		; 8T
		sbc	a,a		; 4T
		and	8		; 7T

; k tomuto outu 11+10+10+4+7+8+4+7 = 61T

		out	(95),a		; 11T	zápis na port 8255
		jp	BYTPUT_WAIT_3	; 10T	zpoždění
BYTPUT_WAIT_3	nop			; 4T	zpoždění
		nop			; 4T	zpoždění (celkem 18T)
		djnz	BYTPUT_1	; 13/9T
		xor	a		; 4T	parita
		xor	e		; 4T
		ld	a,8		; 7T
		jp	po,BYTPUT_2	; 10T
		xor	a		; 4T

; k tomuto outu 11+10+4+4+9+4+4+7+10+4 = 67T	nenastane skok
; k tomuto outu 11+10+4+4+9+4+4+7+10 = 63T	nastane skok

BYTPUT_2	out	(95),a		; 11T
		jp	BYTPUT_WAIT_4	; 10T	zpoždění
BYTPUT_WAIT_4	jp	BYTPUT_WAIT_5	; 10T	zpoždění
BYTPUT_WAIT_5	jp	BYTPUT_WAIT_6	; 10T	zpoždění
BYTPUT_WAIT_6	jp	BYTPUT_WAIT_7	; 10T	zpoždění
BYTPUT_WAIT_7	nop			; 4T	zpoždění (celkem 44T)
		ld	a,8		; 7T
		out	(95),a		; 11T
		ret			; 10T

TODO: Upravit do kompilovatelné podoby a změřit reálné chování na DG192k. Vyzkoušet i funkčnost se superlevným fake převodníkem, který jsem dostal na Bytefestu.

Busyho kód počítá s trváním instrukce out (N),a o takt méně než out (c),a, který využívá adresu v registru BC, při úpravě pro AY-3-8912 by bylo vhodné o ten jeden takt trvání kódu zkrátit.

Může být lepší se vyhnout použití portu C na 8255, aby program byl použitelný nejenom s DG192k, ale i s továrním a neupraveným Didaktik Gama 80k, který port C, resp. bit 0 z portu C, používá ke stránkování paměti.

Jak přijímat data?

Velmi zjednodušeně? Poslouchat RxD a po detekci start bitu v přesných časových intervalech načíst sérii bitů a z nich sestavit byte, rychle zapsat do RAM a co nejrychleji být připraven opět poslouchat na RxD jestli nepřichází další start bit. V praxi se situace samozřejmě komplikuje. Dat může přijít víc, než se do RAM vejde. Nebo dokonce může velmi snadno nastat situace, že po každém přijatém bytu nebude počítač schopen přijmout další dřív než předchozí zpracuje. Pak bude řízení toku dat nevyhnutelné.

Příjem dat 57600bps podle Martina1

I tady odvedl Martin1 většinu práce a nakonec jsme skončili následujícím kódem. Se zvýrazněním syntaxe k nahlédnutí zde.

Opět jsem doplnil smyčku pro příjem bloku dat, kterou jsme optimalizovali do současné podoby, doplnil jsem inicializaci AY-3-8912 a důkladně ověřil na reálném hardwaru. Tato varianta je bez řízení toku dat. To znamená, že ZX Spectrum musí být připraveno data přijímat a teprve potom může druhý počítač data poslat, jinak se část dat ztratí.

Pokud dojde k rozsynchronizování, nebo vysílající počítač vysílá pomaleji, program umí detekovat log. 0 na vstupu RxD v době, kdy očekává klidový stav linky (stop bit) před start bitem, oznámí to ukončením a červeným okrajem obrazovky. Ale je to detekce chyby značně nespolehlivá a spíš náhodná.

;===================================================================================================
; RS232 - receiving data through AY-3-8912 without flow control - 2 stop bits
;
; 57600bps (17,3611μs)	61.57813T na ZX128k, 61T bude trvat 17.19811μs, chyba -0.9% (58146bps)
;			60.76389T na ZX48k,  61T bude trvat 17.42857μs, chyba +0,4% (57377bps)
;===================================================================================================
;	AY I/O	ZX name	dir.	better name
;
;	A2	CTS	out	RTS	->
;	A3	RxD	out	TxD
;	A6	DTR	in	CTS
;	A7	TxD	in	RxD	<-

		cpu	z80undoc
		org	32768

START		di				; 4T
		exx				; 4T	sekundární sada
		push	hl			; 11T
		exx				; 4T	primární sada
		call	AY_INIT			;	dlouho
		ld	hl,16384		; 10T	data buffer - sem se bude ukládat
		ld	de,6912			; 10T	a uloží se 6912 bytů
		call	RECEIVE57600		;	mělo by trvat zhruba 1.32s (6912*11/57600)

; 		ld	hl,49152		; 10T	načtená data zkopíruj do VRAM, pokud nejsou
; 		ld	de,16384		; 10T
; 		ld	bc,6912			; 10T
; 		ldir				; 21*6911+16 = 145147T (40.9ms, tj. 2/50s)

		exx				; 4T	sekundární sada
		pop	hl			; 10T	obnov HL', BASIC potřebuje
		exx				; 4T	primární sada
		ei				; 4T
		ret				; 10T

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

RECEIVE57600	dec	hl			; 6T	bude zvýšeno během čekání
		ld	bc,65533		; 10T	datový port AY-3-8912 do BC

; kontrola jestli nezačal přenos - pak by byla špatně synchronizace se start bitem
RECEIVE_START	in	a,(c)			; 12T	čti port, nastaví sign flag dle MSB
		jp	p,RECEIVE_ERROR		; 10T	chyba, RxD má být v log. 1 (klidový stav/stop bit)
; zpoždění detekcí 12+10 = 22T

; čekání na start bit
RECEIVE_WFSB	in	a,(c)			; 12T	čti port
		rlca				; 4T	bit 7 do carry flagu
		jp	c,RECEIVE_WFSB		; 10T	opakuj do start bitu

; smyčka čekání 12+4+10 = 26T

; start bit začal, čeká se na bit 0
; dlzka cakania je zvolena tak, aby bit 0 bol niekde vo svojej polovici
; od hrany startbitu uplynulo najmenej 15T, najviac 41T, berieme priemer 28T
; treba este cakat 1.5 x 61T - 28T = 63.5T (z toho 11T na dalsiu instrukciu IN)

		dec	de			; 6T	sniž počítadlo bytů, testovat se bude později
		inc	hl			; 6T	zvyš adresu v RAM, kam se byte uloží
		exx				; 4T	sekundární sada
		ld	bc,65533		; 10T	datový port AY-3-8912 do BC' (a zpoždění zároveň)
		inc	hl			; 6T	zpoždění
		dec	hl			; 6T	zpoždění
		ld	a,0			; 7T	zpoždění
		ld	h,7			; 7T	v cyklu bude čteno 7 bitů a 8. po skončení cyklu

; 6+6+4+10+6+6+7+7 = 52T
; teraz je bit 0 niekde v polovici, mozme citat 8 krat s odstupom 61T

RECEIVE_LOOP	in	a,(c)			; 12T	načti port
		rlca				; 4T	bit 7 do carry flagu
		rr	l			; 8T	buduj bajt v L
		ld	a,0			; 7T	zpoždění
		ld	a,0			; 7T	zpoždění
		ld	a,r			; 9T	zpoždění
		dec	h			; 4T	počítadlo
		jp	nz,RECEIVE_LOOP		; 10T	čti další bity

; 12+4+8+7+7+9+4+10 = 61T opakuje-li se (bity 0 až 6)

; následuje čtení MSB, bit 7
		in	a,(c)			; 12T	načti port (MSB je někde v půlce trvání, do stopbitu cca 31 ± 13T ± 2T odchylka přesnosti 3%)
		rlca				; 4T	bit 7 do carry flagu
		ld	a,l			; 4T	téměř sestavený byte do A
		rra				; 4T	narotuj MSB do bitu 7 v A

; 12+4+4+4 = 24T

		exx				; 4T	primární sada
		ld	(hl),a			; 7T	ulož byte do RAM
		ld	a,d			; 4T	všechny byty přijaté?
		or	e			; 4T	zkontroluj
		jp	nz,RECEIVE_WFSB		; 10T	čekej na další byte
		ret				; 10T

; uložení bytu trvá 4+7+4+4+10 = 29T
; celkem 24+29=53T
; včetně detekce stavu linky, 75T s ověřením před start bitem
; jinak lze rovnou čekat na další startbit

RECEIVE_ERROR	exx				; 4T	primární sada
		ld	a,2			; 7T	DEBUG
		out	(254),a			; 11T	DEBUG
		ei				; 4T
		ret				; 10T

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

AY_INIT		ld	bc,65533		; 10T	BC = 65533 (11111111 11111101)
		ld	a,7			; 7T	zvol registr 7 v AY
		out	(c),a			; 12T	65533,7

		ld	a,b			; 4T	A = 255, vypni zvuk a nastav I/O port jako výstup (bit 6 v log. 1)
		ld	b,191			; 7T	BC = 49149 (10111111 11111101)
		out	(c),a			; 12T	49149,255

		ld	b,a			; 4T	BC = 65533
		ld	a,14			; 7T	zvol registr 14 v AY
		out	(c),a			; 12T	65533,14

		ld	a,b			; 4T	A = 255
		ld	b,191			; 7T	BC = 49149
		out	(c),a			; 12T	49149,255 = zapiš 255 a nastav pullupy na H, teď je možné port i číst

		ret				; 10T

Nastavení sériového portu v Linuxu pro tento kód může vypadat takto s jedním stop bitem

stty -F /dev/ttyS0 57600 cs8 clocal cread -cstopb -parenb -parodd -crtscts raw

Nebo takto, se dvěma stop bity.

stty -F /dev/ttyS0 57600 cs8 clocal cread -cstopb -parenb -parodd -crtscts raw

Kód stíhá přijímat data i s jedním stop bitem, ale druhý stop bit by mohl být potřeba, pokud do smyčky budete přidávat např. reakci na stisk klávesy, aby přenos dat mohl přerušit uživatel. V současné podobě je nutné do ZX Spectra poslat očekávaný počet bytů, jinak se smyčka neukončí.

Krátké video z testování jedné z předchozích verzí programu ke shlédnutí na YouTube.

rychlost 57600bps
řízení toku není, čeká zadaný počet bytů
trvání bitu 128k 17.1981μs - cca -0.94%
trvání bitu 48k 17.4286μs - cca +0,38%

TODO: Zprovoznit i na Didaktiku Gama 192k.

Řízení toku dat při příjmu z PC.

Ačkoli předchozí program dokáže přijímat data docela spolehlivě, paměť pro ukládání dat není nekonečná a jakmile je potřeba s daty udělat víc, než jen uložit byte, 3.5MHz Z80 nestíhá. V takovém případě je potřeba pozastavit příjem dat a vysílajícímu počítači signálem RTS oznámit, že další data vysílat nemá.

Krátce jsem s několika sériovými porty experimentoval, abych viděl, jak dlouho trvá, než vysílající počítač zareaguje a kolik dat ještě pošle po změně stavu RTS. V ideálním případě začne vysílat se zpožděním jednoho bitu po nastavení RTS do log. 0 a po nastavení RTS do log. 1 další byte nezačne, nebo dokončí vysílání pokud, už vysílat začal, ale nepokračuje dalším bytem. Viz obrázky z osciloskopu.

screenshot osciloskopu screenshot osciloskopu

Ačkoli byla přenosová rychlost během pořizování screenshotů velmi nízká - 300bps (přenos bytu trval zhruba 33ms), princip by měl být pro všechny rychlosti stejný.

Příjem dat v RS232 ROM Paula Farrowa

Lorem Ipsum.


nastavení COM ve Windows

https://batchloaf.wordpress.com/2013/02/12/simple-trick-for-sending-characters-to-a-serial-port-in-windows/
https://www.computerhope.com/modehlp.htm
https://ss64.com/nt/mode.html

Závěrem

TODO: Napsat.

Lorem Ipsum.

Download

Související odkazy

Historie změn článku

  • 2019-11-22 - napsáno, zveřejněno a ještě zcela nedopsáno, ale i tak už to je zatraceně dlouhé
  • 2020-07-07 - doplněn test na ZX Spectrum 48k+ s AY
  • 2020-08-31 - doplněn projekt vlastního konvertoru úrovní s MAX232

[ Zpět na hlavní stránku ]

Tento web je převážně o ZX Spectru, kompatibilních počítačích a jiném zajímavém hardwaru. Naleznete-li chybu, nebo byste rádi cokoliv co s tímto souvisí, můžete mi napsat email. Stručně o mém webu zde.