Atunci cand am inceput aventura cu Snoopy si HC-91, una din probleme a fost transferul de fisiere intre computerele contemporane si HC. Este posibil, multumita CPMIMG, dar, cel putin in cazul meu, laborios. Mai intai trebuie creata sau modificata imaginea de disketa pe PC-ul de lucru, transferata imaginea pe un computer mai vechi (cu DOS) care are unitate de disketa si apoi scrisa intreaga imagine pe disketa. Odata ce fisierele de baza au fost transferate, a fost posibil sa folosesc doar HC-ul ca masina de lucru (si de fapt chiar asa am si facut pentru un timp) dar tot aparea nevoia de a mai transfera cate un fisier. Aveam nevoie de un mod mai simplu de transfer de date (chiar daca doar fisiere text) intre PC si HC. Si da, a fost si o provocare. Rezultatul (surse si imagine de disketa) se gaseste la sfarsitul istoriei de fata.
Dupa cum am povestit in istoria precedenta, "Interfata 1" include un port serial avand nivele de tensiune compatibile RS-232 dar cu dispunere nestandard a pinilor. Tabelul de mai jos ilustreaza pinii si conexiunile necesare pentru a construi un cablu sau un adaptor:
pin HC | DB-9 (DCE) | DB-9 (DTE) | Semnal | Sens (HC) |
1 | 8 | 7 | RTS | -> |
2 | 7 | 8 | CTS | -< |
3 | 2 | 3 | TxD | -> |
4 | 3 | 2 | RxD | -< |
6 | 5 | 5 | GND | -- |
Pentru a construi un cablu de conectare intre HC si PC, se foloseste un conector tata DB-9 pentru conectare la HC si un conector mama DB-9 pentru conectare la PC (folosind conexiunile din coloana "DCE"). Pentru a construi un adaptor, se folosesc conectori tata DB-9 la ambele capete ale cablului si conexiunile din coloana "DTE". Mie imi este mai comod sa tin un cablu conectat la PC si acest cablu sa il conectez la diverse alte computere. In acest caz este mai convenabil ca HC-ul sa aiba un conector serial avand aceiasi pini ca oricare alt computer.
La inceput aveam impresia ca placa IF-1 (care e tot atat de mare cat placa principala) are un circuit UART, dat fiind ca include un port serial. Transmisia dinspre HC spre PC folosind apeluri de sistem CP/M functioneaza bine. Receptia, pe de alta parte, desi este disponibila prin apeluri BIOS CP/M, a fost mai dificila. Aleator caracterele erau receptionate incorect, chiar si la viteze mici (2400bps), si cand transmisia dinspre PC se facea tastand individual fiecare caracter. Aceasta manifestare sugereaza probleme de incadrare (framing) pe partea de receptie. Am intrebat pe grupul RomanianHomeComputer daca exista vreun program de transfer serial pentru HC-91 cu CP/M dar nimeni nu stia sa existe. In cele din urma am inceput investigatia documentatiei IF-1 pentru a incepe sa imi scriu propriul program de transfer. In acest moment am realizat ca nu exista circuit UART ci doar niste adaptoare care convertesc nivelele de tensiune intre RS-232 si TTL. Toate comunicatiile seriale sunt implementate strict prin software, "bit-banging" cum s-ar spune.
Aceasta descoperire a lamurit rata mare de erori pe care o intampinam. Aparent problema tine de tratarea intreruperilor, s-ar putea ca intreruperile sa fie dezactivate doar in timpul receptiei unui caracter dar nu si in timpul asteptarii pentru acesta. Daca transmisia incepe in timp ce procesorul HC-ului trateaza o intrerupere se poate pierde bitul de start al caracterului, ceea ce duce la receptie desincronizata. Solutia aleasa a fost sa imi scriu propriile rutine de comunicatie seriala.
Pentru inceput am experimentat cum sa controlez linia TxD. Principala problema a fost ca BIOS-ul (si poate unele intreruperi) reseteaza linia de transmisie. Interfata-1 foloseste acelasi port (si acelasi bit) atat pentru comunicatiile de "retea" cat si pentru portul serial. Mai mult, datorita decodificarii incomplete a adreselor de acces la porturi, accesul la memoria video are ca efect secundar comutarea iesirii pe "retea" si dezactivarea iesirii TxD. Solutia a fost dezactivarea intreruperilor si evitarea apelurilor BIOS pe parcursul testarii.
Inima fiecarei operatiuni seriale (asincrone) este generatorul tactului de transmisie/receptie (BAUD rate generator). Singura posibilitate de a il implementa in configuratia data este o bucla de intarziere. Prima proba dupa ce am obtinut iesirea dorita pe pinul serial a fost legata de acest aspect. Imi era teama ca s-ar putea ca programul sa intampine timpi de asteptare suplimentari (sau mai rau: aleatori!) care ar perturba temporizarile, dar din fericire nu a fost cazul. Verificarea a constat intr-un test simplu care comuta pinul TxD in bucla (cu intreruperile dezactivate) si monitorizarea iesirii cu osciloscopul.
Odata rezolvata predictibilitatea temporizarilor, metoda cea mai simpla (dar mai putin riguroasa) a fost sa decid o rata de transfer si sa calculez bucla de intarziere pentru aceasta. Nu e cea mai eleganta solutie dar suficienta pentru necesitatile de moment. Stiind frecventa de ceas a procesorului (3.5MHz1) am putut sa calculez cu aproximatie numarul de cicluri de ceas necesare per bit. Numarul de tacti (T states) per bit se calculeaza impartind frecventa in Hertzi a procesorului la rata de transfer dorita (3500000/19200 =~ 182 T/bit pentru 19200bps).
SHLOOP: XOR A ; 4 - sterge A SRL D ; 8 - D deplasat la dreapta, bitul 0 in CARRY RLA ; 4 - CARRY intra in bit 0 din A SBIT: OR E ; 4 - SETez bitul 5 din A (E=00100000b) OUT (0EFH),A ; 11 LD B,CBAUD ; 7 - constanta BAUD SBITDL: DJNZ SBITDL ; 13*CBAUD-5 - bucla de intarziere NOP ; 4 - aliniere temporizare DEC C ; 4 - se decrementeaza contorul de biti JR NZ,SHLOOP ; 12 - repeta daca C!=0
Bucla de mai sus dureaza 183 de tacti/bit, suficient de aproape de valoarea calculata. Este obligatoriu ca bitul 5 (sincronizare retea) sa fie pe 1 la orice OUT pentru comunicatii seriale, de aceea comanda "OR E" - registrul E este preincarcat cu valoarea necesara inainte de inceperea buclei de transmisie. Pe langa aceasta bucla mai sunt cateva instructiuni pentru bitii de start/stop precum si pentru a asigura polaritatea corecta si am o rutina functionala de transmisie seriala.
Receptia s-a dovedit putin mai dificila. O problema este lipsa documentatiei, nu imi este clar daca e posibil sa se genereze o intrerupere la schimbarea starii pinului de receptie (presupun ca nu), deci codul de receptie trebuie sa verifica continuu starea pinului pentru a detecta bitul de START, iar aceasta poate conduce la unele probleme de sincronizare. In plus, am dorit sa evit asteptarea la infinit pentru a receptiona ceva, deci am adaugat un contor pentru "time-out". In lipsa bitului de start, rutina de receptie se intoarce la apelant dupa aproximativ o secunda, mai exact dupa 65536 de citiri ale RxD.
Folosirea pinilor suplimentari pentru controlul transferului (flow-control) simplifica lucrurile pe partea de HC: semnalul RTS (cerere de transmisie) este activat doar cand se citeste starea pinului de receptie iar PC-ul este configurat sa foloseasca controlul hardware al transferului (RTS/CTS2). Cu toate acestea, porturile seriale moderne au memorii tampon interne a.i. desi nu incep transmisia cata vreme semnalul RTS este inactiv, odata ce au inceput continua sa transmita cateva caractere inclusiv dupa dezactivarea acestuia. In consecinta, in cazul receptiei unui bloc de date, timpul total disponibil intre doi octeti este cel mult durata unui bit STOP. La 19200 bps, sunt disponibili cel mult 182 de tacti (20-30 de instructiuni). Inteleg acum de ce unele echipamente vechi necesitau folosirea a 2 biti de STOP. Din fericire, rutina mea de receptie bloc parea destul de rapida si odata finalizata am decis sa o testez receptionand un intreg ecran (SCREEN) intr-un singur bloc (6912 octeti). Desigur asta a necesitat sa cercetez cum se acceseaza memoria ecran in CP/M dar deja ajungem la alta istorie.
Principalul scop al acestui exercitiu era transmisia de fisiere de pe PC pe HC, eventual cu detectia erorilor. Testele precedente au dovedit ca transferurile sunt destul de sigure chiar si la cantitati relativ mari de date deci pentru scopul meu versiunea initiala a protocolului Xmodem ar fi de ajuns. Protocolul este destul de simplu si s-a dovedit relativ usor de implementat. Mai mult, utilitarele "sx" si "rx" (transmisie si receptie de fisiere prin Xmodem) sunt incluse implicit in distributia Slackware Linux pe care o folosesc, deci partea de PC este deja rezolvata. In cele din urma tot am scris un program "sx" pentru a verifica programul de receptie in conditii mai dificile, de exemplu transmisie cu erori aleatoare, intarziata etc.
Programul RX pentru CP/M (Receptie Xmodem) este oarecum rudimentar, a fost si un exercitiu in invatarea CP/M (ex. operatii cu fisiere, preluarea argumentelor din linia de comanda samd). Se apeleaza cu
RX <fisier.ext>Incepe prin a sterge fisier.ext si a-l recrea, apoi initiaza protocolul. De avut in vedere ca protocolul Xmodem presupune ca transmitatorul porneste primul - receptorul incepe prin a transmite (!) un caracter NAK si apoi asteapta primul pachet de date. Dupa receptia fiecarui pachet, afiseaza numarul acestuia (in hexazecimal) si, daca suma de control se verifica, il scrie pe disc si afiseaza OK. (In caz contrar notifica utilizatorul si trimite NAK la transmitator, pentru retransmisie)
stty -F /dev/ttyUSB0 19200 crtscts sx fisier </dev/ttyUSB0 >/dev/ttyUSB0 rx fisier </dev/ttyUSB0 >/dev/ttyUSB0
stty -F /dev/ttyUSB0 19200 crtscts -icrnl cat /dev/ttyUSB0 | tee fisier
Fisierul zip cu surse include:
1 Am realizat abia mai tarziu ca pentru HC frecventa reala de tact este 3.5468 MHz, ceea ce inseamna ca durata unui bit este aproximativ 184 tacti; oricum, diferenta este nesemnificativa, iar bucla de intarziere este de 183 de tacti.
2 Rutina de transmisie nu verifica semnalul CTS; implementarea ar fi necesitat folosirea unui time-out si pe partea de transmisie, iar PC-ul are UART, buffere si viteza, ceea ce reduce necesitatea "flow-control".