Transmitir bytes desde el QL al Spectrum mediante conexión RS232

Marcos Cruz
Madrid, enero 1989

Voy a explicar cómo transmitir un bloque de bytes desde un QL a un Spectrum provisto de la Interface 1, mediante una conexión RS232.

Una vez visto, en un anterior artículo, cómo realizar la conexión RS232 entre ambos ordenadores, no debería necesitarse otra cosa que realizar la transmisión mediante dos sencillos programas en BASIC. Un bucle de envío en el QL leería los bytes de la memoria, o de un fichero, y los enviaría al Spectrum con PRINT#, mientras el Spectrum los recibiría con INPUT$ (INKEY$ no vale, pues no espera a recibir un carácter, en la Interface 1) y los iría "pokeando" en alguna parte. Pero, si lo hacemos así, nos encontraremos con que la fiabilidad de la transmisión deja bastante que desear; es probable que varios bytes queden alterados en el "trayecto".

A causa de ello, he diseñado un rudimentario protocolo de verificación de bytes transmitidos, con el objeto de minimizar los errores.

El protocolo es el siguiente:

El emisor, el QL, envía cada byte dos veces seguidas. El receptor, el Spectrum, tras recibir ambos bytes, comprueba si son o no iguales; si es así, envía un byte acordado para indicar que la recepción fue correcta y espera un nuevo par de bytes; si los dos bytes recibidos no son iguales, envía otro byte diferente acordado para indicar que se repita el envío. De este modo, la probabilidad de que el receptor acepte por bueno un byte erróneo es mínima (¡para ello, los dos bytes deberían haberse corrompido y quedar ambos iguales!).

Veamos, en primer lugar, el listado SuperBASIC del programa emisor para el QL, que conviene compilar, por cuestiones de velocidad:


100 :

110 REMark             TRANSMITUM Z80 Versión 0.21

120 REMark                   © Marcos 1988

130 :

140 REMark             TURBO Toolkit Versión 1.32

150 :

160 REPeat bucle_principal

170 :

180 CLEAR

190 :

200 REMark Constantes y variables inicializadas --------------------------

210 :

220 lon_nombre%=32

230 ventana%=4

240 fichero_objeto%=5

250 fichero_spectrum%=6

260 :

270 REMark Cadenas -------------------------------------------------------

280 :

290 DIM fichero_objeto$(lon_nombre%):fichero_objeto$=OPTION_CMD$

300 DIM fichero_spectrum$(lon_nombre%):fichero_spectrum$="ser2"

310 DIM byte_objeto$(1)

320 DIM letra$(1)

330 :

340 REMark Pantalla ------------------------------------------------------

350 :

360 OPEN#ventana%,con_512x14a0x225

370 PAPER#ventana%,4

380 INK#ventana%,0

390 CLS#ventana%

400 BORDER#ventana%,2,7,0

410 :

420 REMark Presentación --------------------------------------------------

430 :

440   AT#ventana%,0,25

450   PRINT#ventana%,"TRANSMITUM Z80 V 0.21  Marcos 1988"

460   AT#ventana%,0,82

470   letra$=EDIT$(#ventana%,"",0)

480 :

490 REMark Pedir ficheros ------------------------------------------------

500 :

510   REMark pedir nombre de fichero objeto a transmitir:

520   REPeat pide_fichero

530     CLS#ventana%

540     PRINT#ventana%," Fichero objeto a transmitir: ";

550     fichero_objeto$=EDIT$(#ventana%,fichero_objeto$,lon_nombre%)

560     IF fichero_objeto$="":NEXT bucle_principal

570     REMark detectar errores:

580     flag_fichero=DEVICE_STATUS(fichero_objeto$)

590     IF flag_fichero>-1:NEXT pide_fichero

600     SELect ON flag_fichero=-3,-6,-7,-9,-12,-16:NEXT pide_fichero

610     EXIT pide_fichero

620   END REPeat pide_fichero

630   REMark abrir fichero fuente:

640   OPEN_IN#fichero_objeto%,fichero_objeto$

650 :

660   CLS#ventana%

670   REMark detectar errores:

680   flag_fichero=DEVICE_STATUS(fichero_spectrum$)

690   SELect ON flag_fichero=0,-3,-6,-7,-9,-11,-12,-16,-20:NEXT bucle_principal

700   REMark abrir fichero de comunicación con Spectrum:

710   OPEN#fichero_spectrum%,fichero_spectrum$

720 :

730 REMark Transmitir ---------------------------------------------------

740 :

750   TRA 0

760   CLS#ventana%

770   PRINT#ventana%," Transmitiendo"!fichero_objeto$!"al Spectrum"

780 :

790   REPeat transmite_bytes

800     IF EOF(#fichero_objeto%):EXIT transmite_bytes

810     byte_objeto$=INPUT$(#fichero_objeto%,1)

820     REPeat repite_byte_objeto

830       PRINT#fichero_spectrum%,byte_objeto$;byte_objeto$;

840       IF INPUT$(#fichero_spectrum%,1)=CHR$(0):EXIT repite_byte_objeto

850     END REPeat repite_byte_objeto

860   END REPeat transmite_bytes

870 :

880   REMark enviar marca de final:

890   PRINT#fichero_spectrum%,CHR$(0);CHR$(255);

900 :

910   CLOSE#fichero_objeto%

920   CLOSE#fichero_spectrum%

930   TRA 1

940 :

950 END REPeat bucle_principal

960 :

En segundo lugar, veamos el listado del programa Basic receptor para el Spectrum:
 10 REM MARCOS 11/8/88

 20 REM Recibir codigo objeto

 30 REM con la interface 1

 40 REM y la interface Beta

125:

130 FORMAT "n";1: REM dummy

163 RANDOMIZE USR 15363: REM : LOAD "recibe1c"CODE 23296

164:

170 INPUT "Direccion=";org

175 RANDOMIZE org

180 IF org>23999 THEN  CLEAR org-1

190 LET org=PEEK 23670+256*PEEK 23671

195 LET f$="f"

200 REM  INPUT "Fichero=";f$

257 FORMAT "b";9600

300 LET final=USR 23296

503 LET fallos=PEEK 23298+256*PEEK 23299

505 PRINT final-org;" bytes recibidos"

507 PRINT fallos;" fallos de recepcion"

509 RANDOMIZE USR 15363: REM : ERASE f$CODE 

510 RANDOMIZE USR 15363: REM : SAVE f$CODE org,final-org,org

520 REM  ERASE "m";1;f$

530 REM  SAVE *"m";1;f$CODE org,final-org

Las órdenes de grabación y carga están escritas para la interface de disco Beta, pero pueden modificarse para la Interface 1, o el Disciple, o cualquier otra.

Por último, veamos el listado en ensamblador Z80 de la rutina receptora para el Spectrum:


       ;

       ;Recepción de código objeto del Z80 desde el QL

       ;por medio de la conexión RS232C de la Interface 1

       ;

       ;Marcos 5/8/1988

       ;

       ;El protocolo es el siguiente:

       ;  El QL envía cada byte dos veces seguidas al Spectrum

       ;  Si son iguales, el Spectrum le envía al QL un byte 0

       ;  Si son diferentes, envía al QL un byte 255

       ;  Si el QL recibe un byte 0, no repite el envío

       ;  Para indicar fin de transmisión, el QL envía un 0 y un 255

       ;

       ;La dirección+1 última en que se ha guardado un byte, queda en (SEED)

       ;El número de fallos queda en (23298)

       ;

SEED   EQU 23670

SER_FL EQU #5CC7

       ;

       ORG 23296 ;buffer de la impresora

       ;

       JR INICIO

FALLOS DEFW 0 ;número de fallos en la recepción, para que el Basic los lea

;

       ;Inicialización:

       ;

INICIO RST 8 ;llamar a la Interface 1...

       DEFB #31 ;...para insertar sus variables, por si acaso

       XOR A ;resetear...

       LD (SER_FL),A ;...la variable SER_FL para eliminar restos del RS232

       LD HL,0 ;resetear...

       LD (FALLOS),HL ;...el número de fallos

       LD HL,(SEED) ;tomar destino del código, fijado con RANDOMIZE desde Basic

       ;

       ;Leer los dos bytes del código objeto:

       ;

BUCLE  PUSH HL ;preservar dirección del código objeto

LEE1   RST 8 ;llamar a la Interface 1...

       DEFB #1D ;...para recibir el primer byte

       JR NC,LEE1 ;si no hay byte disponible, volver a intentarlo

       PUSH AF ;guardar primer byte recibido

LEE2   RST 8 ;llamar a la Interface 1...

       DEFB #1D ;...para recibir el segundo byte

       JR NC,LEE2 ;si no hay byte disponible, volver a intentarlo

       POP BC ;pasar a B el primer byte recibido

       POP HL ;recuperar dirección del código objeto

       ;

       CP B ;¿son iguales los dos bytes recibidos?

       JR Z,IGUALE ;si es así, saltar

       ;

       ;Bytes diferentes, comprobar si son indicadores de fin de transmisión:

       ;

       INC A ;¿el es segundo byte 255?

       JR NZ,DIFERE ;si no, saltar

       XOR A ;¿es cero...

       CP B ;...el primer byte recibido?

       JR NZ,DIFERE ;si no, saltar

       PUSH HL ;pasar la dirección actual...

       POP BC ;...a BC para...

       RET ;...pasarla al BASIC

       ;

       ;Bytes diferentes, por error en la transmisión:

       ;

DIFERE LD DE,(FALLOS) ;tomar el número de fallos que se han producido

       INC DE ;incrementarlo

       LD (FALLOS),DE ;volverlo a guardar

       LD A,255 ;indicador de error

       JR RESPUE ;saltar a enviar el indicador al QL

       ;

       ;Bytes iguales:

       ;

IGUALE LD (HL),A ;guardar byte en su dirección

       INC HL ;apuntar a la siguiente dirección

       XOR A ;indicador de que no hay error

       ;

       ;Enviar respuesta al QL:

       ;

RESPUE PUSH HL ;preservar dirección del código objeto

       RST 8 ;llamar a la Interface 1...

       DEFB 30 ;...para enviar el byte en A

       POP HL ;recuperar dirección del código objeto

       JR BUCLE ;seguir

;

Las siguientes rutinas en ensamblador Z80, que listo por curiosidad, las escribí para recibir código, como en el caso anterior, empleando igualmente la Interface 1 o bien la interface de Indescomp, pero sin protocolo de verificación de errores. Los bytes se reciben "tal cual" o bien en formato hexadecimal, según se explica en cada caso:
       ;

       ;Recepción de código objeto desde el QL

       ;por medio de la interface RS232C Indescomp

       ;

       ;Marcos 10/6/1988

       ;

       ;Cada byte del código está en formato hexadecimal,

       ;sin retorno de carro ni separación alguna entre ellos.

       ;La carga del software de la interface y la fijación de la

       ;tasa de baudios se hacen desde BASIC

       ;El código objeto debe finalizar con un caracter 26 de fin de fichero

       ;

       ORG 23296 ;buffer de la impresora

       ;

       LD HL,(23670) ;destino del código en SEED, para ser fijado con RANDOMIZE

       ;

       ;Bucle principal:

       ;

       ;Recibir nibble alto:

BYTE   CALL NIBBLE

       CP 26-48-7 ;¿fin de transmisión, era el caracter 26?

       JR NZ,BYTE2 ;si no, seguir

;

       ;Retornar al BASIC

       PUSH HL ;devolver la dirección final...

       POP BC ;...mediante BC...

       RET ;...al BASIC

;

       ;Guardar nibble alto:

BYTE2  RLCA

       RLCA

       RLCA

       RLCA ;pasar los bits bajos a la zona alta

       LD (HL),A ;guardar nibble alto para luego

;

       ;Recibir y guardar nibble bajo:

       CALL NIBBLE

       OUT (254),A ;cambiar color del borde

       OR (HL) ;mezclar nivel alto con el bajo...

       LD (HL),A ;...y guardar el byte completo

       INC HL ;siguiente dirección

       JR BYTE

;

       ;Recibir el byte:

;

NIBBLE PUSH HL

       CALL 64698 ;recibir un byte

       POP HL

       LD A,(64525) ;tomar byte recibido

;

       ;Traducir el dígito hexadecimal:

;

       SUB 48

       CP 10

       RET C

       SUB 7

       RET

;

       ;

       ;Recepción de código objeto desde el QL

       ;por medio de la Interface 1

       ;

       ;Marcos 25/6/1988

       ;

       ;Cada byte del código está en formato hexadecimal,

       ;sin retorno de carro ni separación alguna entre ellos.

       ;La fijación de la tasa de baudios se hace desde BASIC

       ;El código objeto debe finalizar con un caracter 26 de fin de fichero

       ;

       ORG 23296 ;buffer de la impresora

       ;

       RST 8 ;llamar a la Interface 1...

       DEFB #31 ;...para insertar sus variables, por si acaso

       XOR A ;resetear...

       LD (#5CC7),A ;...la variable SER_FL

       ;

       LD HL,(23670) ;destino del código en SEED, para ser fijado con RANDOMIZE

       ;

       ;Bucle principal:

       ;

       ;Recibir nibble alto:

BYTE   CALL NIBBLE

       CP 26-48-7 ;¿fin de transmisión, era el caracter 26?

       JR NZ,BYTE2 ;si no, seguir

;

       ;Retornar al BASIC

       PUSH HL ;devolver la dirección final...

       POP BC ;...mediante BC...

       RET ;...al BASIC

;

       ;Guardar nibble alto:

BYTE2  RLCA

       RLCA

       RLCA

       RLCA ;pasar los bits bajos a la zona alta

       LD (HL),A ;guardar nibble alto para luego

;

       ;Recibir y guardar nibble bajo:

       CALL NIBBLE

       OR (HL) ;mezclar nivel alto con el bajo...

       LD (HL),A ;...y guardar el byte completo

       INC HL ;siguiente dirección

       JR BYTE

;

       ;Recibir el byte:

;

NIBBLE PUSH HL

NIBBL2 RST 8 ;llamar a la interface 1...

       DEFB #1D ;...para recibir un caracter de la entrada RS232C

       AND A ;¿se ha recibido un caracter nulo?

       JR Z,NIBBL2 ;si es así, volver a leer

       POP HL

;

       ;Traducir el dígito hexadecimal:

;

       SUB 48

       CP 10

       RET C

       SUB 7

       RET

;

       ;

       ;Recepción de código objeto del Z80 desde el QL

       ;por medio de la conexión RS232C de la Interface 1

       ;

       ;Marcos 31/7/1988

       ;

       ;El código objeto está en formato "puro", tal cual, sin indicador de

       ;final ni nada de nada (se hace un BREAK y se acabó)

       ;La fijación de la tasa de baudios se hace desde BASIC

       ;La dirección+1 última en que se ha guardado un byte, queda en (SEED)

       ;

       ORG 23296 ;buffer de la impresora

       ;

       ;Variables del sistema

       ;

SEED   EQU 23670

SER_FL EQU #5CC7

       ;

       ;Inicialización:

       ;

       RST 8 ;llamar a la Interface 1...

       DEFB #31 ;...para insertar sus variables, por si acaso

       XOR A ;resetear...

       LD (SER_FL),A ;...la variable SER_FL para eliminar restos del RS232

       LD HL,(SEED) ;tomar destino del código, fijado con RANDOMIZE desde Basic

       ;

       ;Bucle principal:

       ;

BUCLE  PUSH HL ;preservar dirección del código objeto

RECIBE RST 8 ;llamar a la interface 1...

       DEFB #1D ;...para recibir un caracter de la entrada RS232C

       JR NC,RECIBE ;si no hay caracter disponible, volver a intentarlo

       POP HL ;recuperar dirección del código objeto

       LD (HL),A ;guardar byte en su dirección

       INC HL ;apuntar a la siguiente dirección

       LD (SEED),HL ;guardar la dirección para que el Basic la use luego

       JR BUCLE ;ir a leer otro byte

;


Sinclair QL Recursos en Castellano Alojado en / Hosted at:
Sinclair QL Recursos en Castellano
Sinclair QL Spanish Resources