Diferencias
Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previaÚltima revisiónAmbos lados, revisión siguiente | ||
cursos:ensamblador:rutinas_save_load [19-01-2024 07:58] – [Rutina de LOAD de la ROM] sromero | cursos:ensamblador:rutinas_save_load [19-01-2024 11:58] – [Ejemplo completo] sromero | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
- | ====== Save y Load: almacenamiento en cinta ====== | ||
- | |||
- | ¿En qué formato se almacenan los datos en una cinta de cassette para que el Spectrum pueda después cargar desde ellas pantallas de presentación, | ||
- | |||
- | En este capítulo mostraremos cómo se graban y estructuran los datos en las cintas, y qué hace el Spectrum para acceder a ellos mediante las rutinas de que nos provee la ROM. Como aplicación | ||
- | |||
- | \\ | ||
- | ===== Formato de los datos en cinta ===== | ||
- | |||
- | | ||
- | |||
- | Un '' | ||
- | |||
- | \\ | ||
- | - Un bloque de 19 bytes de tamaño fijo, conocido como cabecera. Este bloque es cargado muy rápidamente, | ||
- | - Un bloque de longitud variable que contiene los datos concretos y reales a cargar. | ||
- | \\ | ||
- | |||
- | Ambos bloques son en realidad " | ||
- | |||
- | \\ | ||
- | * Un byte inicial, que como veremos se llama Flag Byte. | ||
- | * Los datos en sí mismos: 17 bytes para cabeceras, o la longitud concreta de los datos para los bloques de datos. | ||
- | * Un byte de checksum o CRC. | ||
- | \\ | ||
- | |||
- | | ||
- | |||
- | Cada bloque se inicia con una serie de pulsos de 2168 t-stados cada uno, que constituyen el tono guía. La cantidad de pulsos (la duración) de este tono guía es de 8063 pulsos para los bloques de cabecera, y 3223 pulsos para los bloques de datos. Es por eso que la duración del tono guía (el famoso pitido inicial de la carga) es mayor para la carga de la cabecera que para el de los datos en sí mismos. Es decir, el tono guía está presente tanto para los bloques de cabecera como para los de datos, salvo que su duración es menor en los bloques de datos. | ||
- | |||
- | Un t-stado (t-state) es 1 ciclo de reloj, y equivale a 1 / 3.500.000 segundos. | ||
- | |||
- | \\ | ||
- | {{ cursos: | ||
- | \\ | ||
- | |||
- | Tras el tono guía de la cabecera viene la cabecera en sí misma (que no dejan de ser datos). Su carga, como ya hemos dicho, tarda un tiempo muy corto en realizarse, ya que son sólo 19 bytes a ser leídos desde el cassette. | ||
- | |||
- | \\ | ||
- | {{ cursos: | ||
- | \\ | ||
- | |||
- | En la imagen anterior podemos ver el aspecto de la pantalla una vez terminado el tono guía y cargada la cabecera. Esta cabecera, mediante sus 19 bytes, le indican al Spectrum la naturaleza de los datos a cargar en el bloque de datos que sigue a la misma, con el siguiente formato: | ||
- | |||
- | |< 70% 20% 20% 60% >| | ||
- | ^ Byte ^ Longitud ^ Descripción ^ | ||
- | | 0 | 1 | Byte Flag ($00 ó $ff) | | ||
- | | 1 | 1 | Tipo de bloque (0-3)| | ||
- | | 2 | 10 | Nombre de fichero (rellenado con espacios en blanco) | | ||
- | | 12 | 2 | Longitud del bloque de datos a cargar | | ||
- | | 14 | 2 | Parámetro 1 | | ||
- | | 16 | 2 | Parámetro 2 | | ||
- | | 18 | 1 | Checksum / Suma de comprobación | | ||
- | |||
- | | ||
- | |||
- | El byte de flag (Byte 0) y el de Checksum (byte 18) no forman parte exactamente de la cabecera, sino del bloque cargado en sí mismo (también están presente cuando cargamos datos y no cabeceras), pero se han incluído dentro de la tabla para hacerla de lectura más sencilla. Puede decirse que el byte-flag es el byte prefijo de todo bloque de datos (considerando una cabecera de 17 bytes como un bloque de datos) y el checksum es el byte sufijo de ese mismo bloque. Concretamente, | ||
- | |||
- | El byte de tipo de bloque indica qué datos se van a cargar a continuación, | ||
- | |||
- | |< 40% 30% 70% >| | ||
- | ^ Valor ^ Significado ^ | ||
- | | 0 | Programa | | ||
- | | 1 | Array de números | | ||
- | | 2 | Array de caracteres | | ||
- | | 3 | CODE (datos a cargar en memoria)| | ||
- | |||
- | En el caso de bloques de tipo CODE, el byte " | ||
- | |||
- | Para bloques de tipo PROGRAM, el Parámetro 1 contiene el valor de la línea BASIC de autostart (o un número mayor de 32768 si no se dio un parámetro LINE al hacer el SAVE), y el Parámetro 2 contiene el inicio del área de variables relativa al inicio del programa. | ||
- | |||
- | Un detalle: una pantalla de datos (SCREEN$) se define en esta cabecera como un bloque de tipo CODE de 6912 bytes a cargar sobre la dirección 16384. | ||
- | |||
- | Tras la cabecera (tono guía + bloque datos) viene el bloque de datos en sí mismo, que vuelve a comenzar con un tono guía. | ||
- | |||
- | \\ | ||
- | {{ cursos: | ||
- | \\ | ||
- | |||
- | Ahora bien ... ¿cómo es posible que se almacenen como audio datos digitales? ¿Cómo entiende el Spectrum si los datos que está cargando son unos o ceros y los agrupa en bloques de bytes que podemos interpretar de forma lógica? | ||
- | |||
- | Como ya hemos comentado al hablar del tono guía, la clave está en la temporización precisa a la hora de leer datos desde la cinta. Aparte de tonos guía y pulsos de sincronización ... ¿qué es un cero en la cinta? ¿qué es un uno? ¿Cómo se almacena (y lee) un byte de 8 bits? ¿Y cómo se almacena (y lee) un conjunto de bytes? | ||
- | |||
- | * Un cero (bit=0) se codifica en cinta como 2 pulsos de una duración de 855 t-stados cada uno. | ||
- | * Un uno (bit=1) se codifica en cinta como 2 pulsos de una duración de 1710 t-stados cada uno. | ||
- | * Para almacenar los 8 bits de un byte, se almacenan bit a bit de mayor a menor peso (primero el bit 7, luego el 6, el 5, el 4, el 3, el 2, el 1 y finalmente el LSB o bit 0). | ||
- | * Cuando se almacena más de un byte (un bloque) se guardan primero los datos del primer byte del bloque, luego el segundo, etc. | ||
- | |||
- | Los pulsos son ondas con un aspecto como el siguiente (aspecto de un tono guía en TAPER): | ||
- | |||
- | \\ | ||
- | {{ cursos: | ||
- | \\ | ||
- | |||
- | Es decir, si tenemos que almacenar en cinta los siguientes bytes: | ||
- | |||
- | < | ||
- | abcdefgh ijklmnop | ||
- | </ | ||
- | |||
- | Se almacenarían en cinta en este orden: | ||
- | |||
- | < | ||
- | a b c d e f g h i j k l m n o p | ||
- | </ | ||
- | |||
- | Así pues, la rutina de la ROM del Spectrum se encarga (tanto al grabar como al leer) de codificar pulsos de diferentes duraciones para almacenar los ceros y unos de forma consecutiva. Nosotros aprovecharemos (como veremos a continuación) dicha rutina para cargar o salvar bloques de datos a nuestro antojo sin tener que programar esas temporizaciones y lecturas/ | ||
- | |||
- | El nivel más bajo al que necesitamos llegar es el siguiente: | ||
- | |||
- | * Cada bloque tiene la siguiente estructura física: | ||
- | * Un tono guía de 8063 (cabeceras) ó 3223 pulsos (datos) de 2168 t-stados cada uno. | ||
- | * Un pulso de sincronización de 667 t-stados. | ||
- | * Un segundo pulso de sincronización de 735 t-stados. | ||
- | * El bloque de datos en sí mismo, bit a bit (0 = 2 pulsos de 855 t-stados, 1 = 2 pulsos de 1710 t-stados). | ||
- | |||
- | * Cada bloque tiene la siguiente estructura lógica (formato de los datos DENTRO de un bloque): | ||
- | * Flag byte, con un valor de $00 para bloques de cabecera o $ff para bloques de datos. | ||
- | * Los datos en sí mismos: 17 bytes para cabeceras, o la longitud concreta de los datos para los bloques de datos. | ||
- | * Un byte de checksum, calculado de forma que haciendo un '' | ||
- | |||
- | | ||
- | |||
- | \\ | ||
- | * Los datos salvados en cinta constan de tono guía, seguido de un bloque de datos de 19 bytes denominado cabecera, seguido de un tono guía de menor duración que el inicial (por ser de datos), seguido de los datos en sí mismos. | ||
- | * Esa cabecera proporciona información sobre el nombre, duración y ciertos parámetros relativos a los datos en sí mismos (el segundo bloque cargado). | ||
- | * Los datos se leen de cinta secuencialmente, | ||
- | * Los 1s y los 0s se almacenan en cinta como pulsos de duraciones concretas. | ||
- | * Las rutinas de la ROM nos permiten leer y escribir en cinta bloques de datos, realizando ellas la temporización adecuada para convertir nuestros "datos en memoria" | ||
- | * Sólo necesitaríamos escribir una rutina propia de carga (que detecte pulsos, temporice, etc) si quisiéramos programar nuestra propia carga, por ejemplo para cargar a diferente velocidad que el Spectrum (ultracargas), | ||
- | |||
- | \\ | ||
- | ===== Ejemplo de volcado de un SAVE ===== | ||
- | |||
- | En la FAQ de comp.sys.sinclair y WorldOfSpectrum tenemos un ejemplo muy interesante que muestra el formato lógico de los datos grabados con un '' | ||
- | |||
- | < | ||
- | SAVE " | ||
- | </ | ||
- | |||
- | Este comando BASIC salvaría (SAVE), un total de 2 bytes (2) de datos (CODE), empezando en 0 (0) a cinta. En resumen, salvaría el contenido de la dirección de memoria 0x0000 y 0x0001 en cinta. Esto produciría los siguientes datos en cinta: | ||
- | |||
- | < | ||
- | 00 03 52 4f 4d 20 20 20 20 20 20 20 02 00 00 00 00 80 f1 ff f3 af a3 | ||
- | </ | ||
- | |||
- | | ||
- | |||
- | |< 70% 80% 80% >| | ||
- | ^ cabecera ^ bloque de datos ^ | ||
- | | 00 03 52 4f 4d 20 20 20 20 20 20 20 02 00 00 00 00 80 f1 | ff f3 af a3 | | ||
- | |||
- | | ||
- | |||
- | \\ | ||
- | |< 70% >| | ||
- | ^ Byte Flag ^ datos cabecera ^ checksum ^ byte flag ^ datos ROM ^ checksum ^ | ||
- | | 00 | 03 52 4f 4d 20 20 20 20 20 20 20 02 00 00 00 00 80 | f1 | ff | f3 af | a3 | | ||
- | \\ | ||
- | |||
- | | ||
- | |||
- | |< 70% >| | ||
- | ^ Tipo de bloque ^ Nombre de fichero ^ Longitud bloque ^ Parámetro 1 ^ Parametro 2 ^ | ||
- | | 03 | 52 4f 4d 20 20 20 20 20 20 20 | 02 00 | 00 00 | 00 80 | | ||
- | |||
- | Como véis el nombre " | ||
- | |||
- | | ||
- | |||
- | |< 60% >| | ||
- | ^ Byte Flag ^ Datos grabados ^ Checksum ^ | ||
- | | ff | f3 af | a3 | | ||
- | |||
- | En este caso el byte flag es 0xFF (bloque de tipo " | ||
- | |||
- | \\ | ||
- | ===== Rutinas de carga de la ROM ===== | ||
- | |||
- | Ya sabemos cómo se almacenan los datos en cinta, así que nuestra próxima misión es conocer cómo cargarlos o grabarlos de una manera sencilla. Para hacer esto usaremos las funciones de la ROM del Spectrum para carga y grabación de datos a cinta: hablamos de 2 subrutinas (de LOAD y SAVE) a las que podremos llamar con unos parámetros concretos. | ||
- | |||
- | \\ | ||
====== Save y Load: almacenamiento en cinta ====== | ====== Save y Load: almacenamiento en cinta ====== | ||
Línea 385: | Línea 203: | ||
<code z80> | <code z80> | ||
- | scf ; Set Carry Flag -> CF=1 -> LOAD | + | scf |
- | ld a, 255 | + | ld a, $ff ; A = $FF (cargar datos) |
- | ld ix, 16384 | + | ld ix, 16384 ; Destino del load = 16384 |
- | ld de, 6912 ; Tamaño a cargar = 6912 | + | ld de, 6912 |
- | call 1366 ; Llamamos a la rutina de carga | + | call 1366 |
</ | </ | ||
Línea 395: | Línea 213: | ||
<code z80> | <code z80> | ||
- | scf | + | scf ; Set Carry Flag (LOAD) |
- | ld a, 255 ; A = 0xFF (cargar datos) | + | ld a, $ff |
- | ld ix, 32768 ; Destino de la carga | + | ld ix, 32768 |
- | ld de, 12000 ; Nuestro " | + | ld de, 12000 |
- | call $0556 ; Recordemos que 0556h = 1366d | + | call $0556 |
- | jp 32768 ; Saltamos al programa código máquina cargado | + | jp 32768 |
</ | </ | ||
Línea 431: | Línea 249: | ||
scf ; Set Carry Flag -> CF=1 -> LOAD | scf ; Set Carry Flag -> CF=1 -> LOAD | ||
- | ld a, 255 ; A = 0xFF (cargar datos) | + | ld a, $ff ; A = $FF (cargar datos) |
ld ix, direccion_destino | ld ix, direccion_destino | ||
ld de, tamaño_a_cargar | ld de, tamaño_a_cargar | ||
Línea 554: | Línea 372: | ||
scf ; Set Carry Flag -> CF=1 -> LOAD | scf ; Set Carry Flag -> CF=1 -> LOAD | ||
- | ld a, 255 ; A = 0xFF (cargar datos) | + | ld a, $ff ; A = $FF (cargar datos) |
ld ix, 16384 ; Destino del load = 16384 | ld ix, 16384 ; Destino del load = 16384 | ||
- | ld de, 6912 ; Tamaño a cargar = 6912 | + | ld de, 6912 ; Tamaño a cargar = 6912 bytes |
call 1366 ; Llamamos a la rutina de carga | call 1366 ; Llamamos a la rutina de carga | ||
Línea 601: | Línea 419: | ||
El código comentado está extraído del documento "//The Complete Spectrum ROM Disassembly//", | El código comentado está extraído del documento "//The Complete Spectrum ROM Disassembly//", | ||
- | <code z80> | + | <code z80> |
; THE ' | ; THE ' | ||
; This subroutine is called to LOAD the header information (from 07BE) | ; This subroutine is called to LOAD the header information (from 07BE) | ||
Línea 607: | Línea 425: | ||
0556 LD-BYTES: | 0556 LD-BYTES: | ||
inc | inc | ||
- | ; cannot hold +FF.) | + | ; cannot hold $FF.) |
- | ex af,a'f' | + | ex af, af' |
- | ; header and +FF for a block of | + | ; header and $FF for a block of |
; data. | ; data. | ||
; The carry flag is reset for | ; The carry flag is reset for | ||
Línea 618: | Línea 436: | ||
di ; The maskable interrupt is now | di ; The maskable interrupt is now | ||
; disabled. | ; disabled. | ||
- | ld a,+0F ; The border is made WHITE. | + | ld a,$0f ; The border is made WHITE. |
- | | + | |
- | ld hl,+053F ; Preload the machine stack | + | ld hl,$053f ; Preload the machine stack |
push hl ; with the address - SA/LD-ret. | push hl ; with the address - SA/LD-ret. | ||
- | | + | |
rra ; Rotate the byte obtained but | rra ; Rotate the byte obtained but | ||
- | | + | |
- | | + | |
ld c,a ; Store the value in the C register. - | ld c,a ; Store the value in the C register. - | ||
- | ; (+22 for ' | + | ; ($22 for ' |
; - the present EAR state.) | ; - the present EAR state.) | ||
cp a ; Set the zero flag. | cp a ; Set the zero flag. | ||
Línea 637: | Línea 455: | ||
; being pressed. | ; being pressed. | ||
056C LD-START | 056C LD-START | ||
- | jr nc,056B, | + | jr nc,056b, |
; approx. 14,000 T states. But if | ; approx. 14,000 T states. But if | ||
; an ' | ; an ' | ||
Línea 645: | Línea 463: | ||
; that the signal is still pulsing. | ; that the signal is still pulsing. | ||
- | ld hl,+0415 ; The length of this waiting | + | ld hl,$0415 ; The length of this waiting |
0574 LD-WAIT | 0574 LD-WAIT | ||
dec | dec | ||
Línea 652: | Línea 470: | ||
jr nz, | jr nz, | ||
call 05E3, | call 05E3, | ||
- | jr nc,056B, | + | jr nc,056b, |
; period. | ; period. | ||
; Now accept only a ' | ; Now accept only a ' | ||
- | 0580 LD-LEADER | + | 0580 LD-LEADER |
call 05E3, | call 05E3, | ||
jr nc, | jr nc, | ||
; period. | ; period. | ||
- | ld a,+C6 ; However the edges must have | + | ld a,$c6 ; However the edges must have |
cp b ; been found within about | cp b ; been found within about | ||
- | jr nc,056C, | + | jr nc,056c, |
inc | inc | ||
jr nz, | jr nz, | ||
Línea 670: | Línea 488: | ||
; After the leader come the ' | ; After the leader come the ' | ||
- | 058F LD-SYNC | + | 058F LD-SYNC |
call 05E7, | call 05E7, | ||
- | jr nc,056B, | + | jr nc,056b, |
ld a,b ; together - these will be the | ld a,b ; together - these will be the | ||
- | CP | + | CP |
- | jr nc,058F, | + | jr nc,058f, |
- | call 05E7, | + | call 05e7, |
ret | ret | ||
; (Return carry flag reset.) | ; (Return carry flag reset.) | ||
Línea 684: | Línea 502: | ||
ld a,c ; The border colours from now | ld a,c ; The border colours from now | ||
- | XOR +03 ; on will be BLUE & YELLOW. | + | XOR $03 ; on will be BLUE & YELLOW. |
ld c,a | ld c,a | ||
- | ld h,+00 ; Initialise the ' | + | ld h,$00 ; Initialise the ' |
; byte to zero. | ; byte to zero. | ||
- | ld b,+B0 ; Set the timing constant for the | + | ld b,$b0 ; Set the timing constant for the |
; flag byte. | ; flag byte. | ||
jr 05C8, | jr 05C8, | ||
Línea 698: | Línea 516: | ||
; the last byte is the ' | ; the last byte is the ' | ||
- | 05A9 LD-LOOP | + | 05A9 LD-LOOP |
jr nz, | jr nz, | ||
; handling the first byte. | ; handling the first byte. | ||
Línea 730: | Línea 548: | ||
05C2 LD-NEXT | 05C2 LD-NEXT | ||
- | 05C4 LD-dec | + | 05C4 LD-dec |
- | ex af,a'f' | + | ex af,af' |
- | ld b,+B2 ; Set the timing constant. | + | ld b,$b2 ; Set the timing constant. |
- | 05C8 LD-MARKER | + | 05C8 LD-MARKER |
; from a ' | ; from a ' | ||
Línea 742: | Línea 560: | ||
ret | ret | ||
; exceeded. (Carry flag reset.) | ; exceeded. (Carry flag reset.) | ||
- | ld a,+CB ; Compare the length against | + | ld a,$cb ; Compare the length against |
; approx. 2,400 T states; resetting | ; approx. 2,400 T states; resetting | ||
cp b ; the carry flag for a ' | cp b ; the carry flag for a ' | ||
Línea 748: | Línea 566: | ||
rl l ; Include the new bit in the L | rl l ; Include the new bit in the L | ||
; register. | ; register. | ||
- | ld b,+B0 ; Set the timing constant for the | + | ld b,$b0 ; Set the timing constant for the |
; next bit. | ; next bit. | ||
jp nc, | jp nc, | ||
Línea 765: | Línea 583: | ||
ld a,h ; Fetch the ' | ld a,h ; Fetch the ' | ||
; byte. | ; byte. | ||
- | CP | + | CP |
ret ; if the value is zero. | ret ; if the value is zero. | ||
; (Carry flag reset if in error.) | ; (Carry flag reset if in error.) | ||
Línea 784: | Línea 602: | ||
ret | ret | ||
; is an error. | ; is an error. | ||
- | 05E7 LD-EDGE-1 | + | 05E7 LD-EDGE-1 |
05E9 LD-DELAY | 05E9 LD-DELAY | ||
jr nz, | jr nz, | ||
Línea 795: | Línea 613: | ||
ret | ret | ||
; ' | ; ' | ||
- | ld a,+7F ; Read from port +7FFE. | + | ld a,$7F ; Read from port $7FFE. |
- | IN A,(+FE) ; i.e. BREAK & EAR. | + | IN A,($fe) ; i.e. BREAK & EAR. |
rra ; Shift the byte. | rra ; Shift the byte. | ||
ret | ret | ||
; if BREAK was pressed. | ; if BREAK was pressed. | ||
xor | xor | ||
- | AND +20 ; 'last edge-type'; | + | AND $20 ; 'last edge-type'; |
jr z, | jr z, | ||
Línea 810: | Línea 628: | ||
cpl ; and border colour. | cpl ; and border colour. | ||
ld c,a | ld c,a | ||
- | AND +07 ; Keep only the border colour. | + | AND $07 ; Keep only the border colour. |
- | OR | + | OR |
- | OUT (+FE),A ; Change the border colour (RED/ | + | OUT ($fe),A ; Change the border colour (RED/ |
; CYAN or BLUE/ | ; CYAN or BLUE/ | ||
scf ; Signal the successful search | scf ; Signal the successful search | ||
Línea 823: | Línea 641: | ||
; allowance is made for ten additional passes through the sampling loop. | ; allowance is made for ten additional passes through the sampling loop. | ||
; The search is thereby for the next edge to be found within, roughly, | ; The search is thereby for the next edge to be found within, roughly, | ||
- | ; 1,100 T states (465 + 10 * 58 + overhead). This will prove successful | + | ; 1,100 T states (465 $ 10 * 58 $ overhead). This will prove successful |
; for the sync ' | ; for the sync ' | ||
</ | </ | ||
- | |||
- | \\ | ||
- | ===== Ficheros ===== | ||
- | |||
- | * {{cursos: | ||
- | * {{cursos: | ||
- | * {{cursos: | ||
- | * {{cursos: | ||
- | |||
- | \\ | ||
- | ===== Enlaces ===== | ||
- | |||
- | * [[http:// | ||
- | * [[http:// | ||
- | * [[http:// | ||
- | * [[http:// | ||
- | * [[http:// | ||
- | * [[http:// | ||
- | * [[http:// | ||
- | * [[http:// | ||
- | |||
- | \\ | ||
- | **[ [[.: | ||
- | ==== Rutina de SAVE de la ROM ==== | ||
- | La rutina '' | + | |
- | + | ||
- | |< 70% 20% 80% >| | + | |
- | ^ Registro ^ Valor ^ | + | |
- | | IX | Dirección inicio de memoria de los datos que se van a grabar. | | + | |
- | | DE | Longitud del bloque de datos a grabar (se grabarán los datos desde IX a IX+DE). | | + | |
- | | A | Flag Byte, 0x00 para grabar cabeceras o 0xFF (255) para grabar datos. | | + | |
- | | CF (CarryFlag) | 0 (SAVE) | | + | |
- | + | ||
- | Lo normal es que no tengamos que recurrir en prácticamente ninguna ocasión a la rutina de grabación de datos, de modo que nos centraremos, | + | |
- | + | ||
- | \\ | + | |
- | ==== Cargando o Ignorando la cabecera ==== | + | |
- | + | ||
- | | + | |
- | + | ||
- | En ocasiones, podemos ignorar el bloque de cabecera totalmente, sobre todo cuando sabemos qué vamos a cargar desde cinta, qué destino tiene, y qué tamaño tiene, y lo especificamos directamente en nuestro programa ASM. En ese caso, podemos cargar la cabecera con el CARRY FLAG a cero (verify), con lo cual la leemos pero no la almacenamos en memoria, y después cargar los valores adecuados en IX, DE, A, etc, poner el CF a 1, y cargar los datos que vienen tras la cabecera. | + | |
- | + | ||
- | | + | |
- | + | ||
- | <code z80> | + | |
- | and a ; CF = 0 (verify) | + | |
- | call 1366 ; Cargamos e ignoramos la cabecera | + | |
- | + | ||
- | scf ; Set Carry Flag -> CF=1 -> LOAD | + | |
- | ld a, 255 ; A = 0xFF (cargar datos) | + | |
- | ld ix, direccion_destino | + | |
- | ld de, tamaño_a_cargar | + | |
- | call 1366 ; Llamamos a la rutina de carga | + | |
- | </ | + | |
- | + | ||
- | | + | |
- | + | ||
- | | + | |
- | + | ||
- | No obstante, en el caso de un juego, normalmente se conoce con antelación el tamaño de los datos a cargar, por lo que se puede ignorar felizmente la cabecera del bloque de cinta.. | + | |
- | + | ||
- | \\ | + | |
- | ===== Convirtiendo datos en cinta ===== | + | |
- | + | ||
- | Lo primero que necesitamos saber es, ¿cómo convertimos nuestros datos (gráficos, pantalla de carga, números, tablas precalculadas, | + | |
- | + | ||
- | Para empezar, podemos hacerlo desde el mismo BASIC del Spectrum, usando el comando '' | + | |
- | + | ||
- | <code basic> | + | |
- | SAVE " | + | |
- | </ | + | |
- | + | ||
- | En la mayoría de los casos, muchos de nosotros programamos hoy en sistemas PC usando compiladores cruzados, ensambladores cruzados y emuladores, por lo que normalmente lo que nos interesará es obtener ficheros TAP para poder concatenarlos con nuestros cargadores o programas. | + | |
- | + | ||
- | | + | |
- | + | ||
- | Así, nuestro '' | + | |
- | + | ||
- | Pero en dicho TAP o TZX tenemos que añadir (al final del mismo) los datos que el programa espera cargar. Imaginemos que estos datos son una pantalla gráfica (.scr) de 6912 bytes. Tendremos un fichero " | + | |
- | + | ||
- | Para ello, con el objetivo de hacerlo de una manera muy sencilla, utilizaremos ficheros TAP. El formato de este tipo de ficheros es muy sencillo, simplemente contienen bloques de datos precedidos por 2 bytes que indican el tamaño del bloque. Supongamos que tenemos en un fichero los 2 primeros bytes de la ROM que vimos anteriormente: | + | |
- | + | ||
- | < | + | |
- | f3 af | + | |
- | </ | + | |
- | + | ||
- | Este fichero de 2 bytes de tamaño ('' | + | |
- | + | ||
- | < | + | |
- | 00 03 52 4f 4d 20 20 20 20 20 20 20 02 00 00 00 00 80 f1 | + | |
- | + | ||
- | + | + | |
- | + | ||
- | ff f3 af a3 | + | |
- | </ | + | |
- | + | ||
- | | + | |
- | + | ||
- | < | + | |
- | 13 00 00 03 52 4f 4d 20 20 20 20 20 20 20 02 00 00 00 00 80 f1 | + | |
- | + | ||
- | + | + | |
- | + | ||
- | 04 00 ff f3 af a3 | + | |
- | </ | + | |
- | + | ||
- | Es decir, "13 00" (número 19 en formato WORD, indicando el tamaño del bloque que viene a continuación) seguido de los 19 bytes, y "04 00" (número 4 en formato WORD) seguido de los 4 bytes del bloque. | + | |
- | + | ||
- | El contenido en binario de nuestro inicio_rom.tap sería, pues: | + | |
- | + | ||
- | < | + | |
- | 13 00 00 03 52 4f 4d 20 20 20 20 20 20 20 02 00 00 00 00 80 f1 04 00 ff f3 af a3 | + | |
- | </ | + | |
- | + | ||
- | Y el tamaño resultante en bytes del fichero serían 2 + 19 + 2 + 4= 27 bytes. | + | |
- | + | ||
- | | + | |
- | + | ||
- | < | + | |
- | Linux: | + | |
- | Windows: | + | |
- | </ | + | |
- | + | ||
- | | + | |
- | + | ||
- | Para convertir un fichero .bin en un fichero tap sin cabecera, creamos un pequeño programa ASM ('' | + | |
- | + | ||
- | < | + | |
- | INCBIN " | + | |
- | </ | + | |
- | + | ||
- | A continuación, | + | |
- | + | ||
- | < | + | |
- | pasmo --tap rom.asm rom.tap | + | |
- | </ | + | |
- | + | ||
- | Con esto, obtendremos un fichero '' | + | |
- | + | ||
- | \\ | + | |
- | ===== Ejemplo completo ===== | + | |
- | + | ||
- | | + | |
- | + | ||
- | Los pasos a seguir para generar el ejemplo son los siguientes: | + | |
- | + | ||
- | | + | |
- | + | ||
- | | + | |
- | + | ||
- | < | + | |
- | $ ls -l zxcolumns.* | + | |
- | -rw-r--r-- 1 sromero sromero 6912 2007-10-08 13:01 zxcolumns.scr | + | |
- | -rw-r--r-- 1 sromero sromero 6937 2007-10-08 13:02 zxcolumns.tap | + | |
- | </ | + | |
- | + | ||
- | Ahora ya tenemos una pantalla SCR guardada en formato TAP (en cinta). Nótese cómo podríamos cargar este TAP desde BASIC con un '' | + | |
- | + | ||
- | Lo siguiente que necesitamos es el programa propiamente dicho, el cual hará la carga de la pantalla en videomemoria: | + | |
- | + | ||
- | <code z80> | + | |
- | ; | + | |
- | ; Loadscr.asm : Demostración de las rutinas LOAD de la ROM, con | + | |
- | ; la carga de un fichero SCR (desde cinta) en videomemoria. | + | |
- | ; | + | |
- | + | ||
- | ORG 32000 | + | |
- | + | ||
- | and a ; CF = 0 (verify) | + | |
- | call 1366 ; Cargamos e ignoramos la cabecera | + | |
- | + | ||
- | scf ; Set Carry Flag -> CF=1 -> LOAD | + | |
- | ld a, 255 ; A = 0xFF (cargar datos) | + | |
- | ld ix, 16384 ; Destino del load = 16384 | + | |
- | ld de, 6912 ; Tamaño a cargar = 6912 | + | |
- | call 1366 ; Llamamos a la rutina de carga | + | |
- | + | ||
- | ret | + | |
- | + | ||
- | END 32000 | + | |
- | </ | + | |
- | + | ||
- | Al respecto del código fuente, como habréis notado, realizamos 2 llamadas a la rutina de la ROM. La primera carga (pero no almacena en ningún sitio) el primer bloque de datos existente (la cabecera de la pantalla de carga). La rutina de la ROM ignorará esta carga porque el CARRY FLAG está a cero (0=VERIFY). La segunda llamada a 1366 realizará la carga de los datos propiamente dichos. Al cargarlos sobre la dirección de destino 16384 (la dirección de la videoram), veremos cómo se van cargando sobre la pantalla directamente desde la cinta. | + | |
- | + | ||
- | | + | |
- | + | ||
- | * Un TAP ('' | + | |
- | * Un TAP ('' | + | |
- | + | ||
- | Si cargamos en el Spectrum, o en un emulador, el fichero '' | + | |
- | + | ||
- | < | + | |
- | $ cat loadscr.tap zxcolumns.tap > programa.tap | + | |
- | </ | + | |
- | + | ||
- | Ahora sí, cargando '' | + | |
- | + | ||
- | \\ | + | |
- | {{ cursos: | + | |
- | \\ | + | |
- | + | ||
- | Si os fijáis durante la carga, veréis como primero se carga el LOADER, luego el código máquina de nuestro programa, y después la pantalla. Contando los tonos guía de carga también encontraréis el lugar donde se lee, pero ignora, la cabecera (19 bytes, carga muy corta) de la pantalla SCR. | + | |
- | + | ||
- | Un apunte: tanto en el caso de la carga, como de la grabación de datos, recordad que las rutinas de la ROM no indican al usuario que debe pulsar PLAY o REC, por lo que debemos indicar al usuario cuándo debe pulsar PLAY o REC dentro de nuestros programas o juegos. Incluso, cuando acabemos de cargar los datos relativos a nuestro juego, resulta conveniente indicarle al usuario cuándo debe detener la cinta (especialmente en juegos multicarga) e insertar los segundos de " | + | |
- | + | ||
- | | + | |
- | + | ||
- | De la misma forma que hemos cargado una pantalla SCR, podemos organizar los gráficos y mapeados de nuestro juego en 1 " | + | |
- | + | ||
- | + | ||
- | \\ | + | |
- | ===== La rutina LD-BYTES (Load) ===== | + | |
- | + | ||
- | A continuación podemos ver el código desensamblado de la rutina '' | + | |
- | + | ||
- | El primero es poder comprender de forma exácta cómo funciona la carga de datos desde cinta y los tiempos que se manejan en dicha carga. El segundo es el de reproducir la rutina en nuestro programa y añadir funciones adicionales como un contador de carga o incluso algún tipo de minijuego durante la misma. | + | |
- | + | ||
- | El código comentado está extraído del documento "//The Complete Spectrum ROM Disassembly//", | + | |
- | + | ||
- | <code z80> | + | |
- | ; THE ' | + | |
- | ; This subroutine is called to LOAD the header information (from 07BE) | + | |
- | ; and later LOAD, or VERIFY, an actual block of data (from 0802). | + | |
- | 0556 LD-BYTES: | + | |
- | inc | + | |
- | ; cannot hold +FF.) | + | |
- | ex af, | + | |
- | ; header and +FF for a block of | + | |
- | ; data. | + | |
- | ; The carry flag is reset for | + | |
- | ; VERIFYing and set for | + | |
- | ; LOADing. | + | |
- | dec | + | |
- | + | ||
- | di ; The maskable interrupt is now | + | |
- | ; disabled. | + | |
- | ld a,+0F ; The border is made WHITE. | + | |
- | OUT | + | |
- | ld hl, | + | |
- | push hl ; with the address - SA/ | + | |
- | IN A, | + | |
- | rra ; Rotate the byte obtained but | + | |
- | AND | + | |
- | OR +02 ; Signal ' | + | |
- | ld c,a ; Store the value in the C register. - | + | |
- | ; (+22 for ' | + | |
- | ; - the present EAR state.) | + | |
- | cp a ; Set the zero flag. | + | |
- | + | ||
- | ; The first stage of reading a tape involves showing that a pulsing | + | |
- | ; signal actually exist (i.e. ' | + | |
- | + | ||
- | 056B LD-BREAK | + | |
- | ; being pressed. | + | |
- | 056C LD-START | + | |
- | jr nc, | + | |
- | ; approx. 14,000 T states. But if | + | |
- | ; an ' | + | |
- | ; will go CYAN. | + | |
- | + | ||
- | ; The next stage involves waiting a while and then showing | + | |
- | ; that the signal is still pulsing. | + | |
- | + | ||
- | ld hl, | + | |
- | 0574 LD-WAIT | + | |
- | dec | + | |
- | ld a,h | + | |
- | or l | + | |
- | jr nz, | + | |
- | call 05E3, | + | |
- | jr nc, | + | |
- | ; period. | + | |
- | + | ||
- | ; Now accept only a ' | + | |
- | + | ||
- | 0580 LD-LEADER | + | |
- | call 05E3, | + | |
- | jr nc, | + | |
- | ; period. | + | |
- | ld a,+C6 ; However the edges must have | + | |
- | cp b ; been found within about | + | |
- | jr nc, | + | |
- | inc | + | |
- | jr nz, | + | |
- | ; been found. | + | |
- | + | ||
- | ; After the leader come the ' | + | |
- | + | ||
- | 058F LD-SYNC | + | |
- | call 05E7, | + | |
- | jr nc, | + | |
- | ld a,b ; together - these will be the | + | |
- | CP +D4 ; start and finishing edges of | + | |
- | jr nc, | + | |
- | call 05E7, | + | |
- | ret | + | |
- | ; (Return carry flag reset.) | + | |
- | + | ||
- | ; The bytes of the header or the program/ | + | |
- | ; VERIFied. But the first byte is the type flag. | + | |
- | + | ||
- | ld a,c ; The border colours from now | + | |
- | XOR | + | |
- | + | ||
- | ld c,a | + | |
- | ld h,+00 ; Initialise the ' | + | |
- | ; byte to zero. | + | |
- | ld b,+B0 ; Set the timing constant for the | + | |
- | ; flag byte. | + | |
- | jr 05C8, | + | |
- | ; LOADING loop. | + | |
- | + | ||
- | ; The byte LOADing loop is used to fetch the bytes one at a time. | + | |
- | ; The flag byte is first. This is followed by the data bytes and | + | |
- | ; the last byte is the ' | + | |
- | + | ||
- | 05A9 LD-LOOP | + | |
- | jr nz, | + | |
- | ; handling the first byte. | + | |
- | jr nc, | + | |
- | ; tape. | + | |
- | ld (ix+00), | + | |
- | ; required. | + | |
- | jr 05C2, | + | |
- | ; next byte. | + | |
- | 05B3 LD-FLAG | + | |
- | ; place temporarily. | + | |
- | xor | + | |
- | ret | + | |
- | ; tape. (Carry flag reset.) | + | |
- | ld a,c ; Restore the carry flag now. | + | |
- | rra | + | |
- | ld c,a | + | |
- | inc | + | |
- | jr 05CA, | + | |
- | ; after the jump. | + | |
- | + | ||
- | ; If a data block is being verified then the freshly loaded byte is | + | |
- | ; tested against the original byte. | + | |
- | + | ||
- | 05BD LD-VERlFY | + | |
- | xor | + | |
- | ret | + | |
- | ; flag reset.) | + | |
- | + | ||
- | ; A new byte can now be collected from the tape. | + | |
- | + | ||
- | 05C2 LD-NEXT | + | |
- | 05C4 LD-dec | + | |
- | ex af, | + | |
- | ld b,+B2 ; Set the timing constant. | + | |
- | 05C8 LD-MARKER | + | |
- | ; from a ' | + | |
- | + | ||
- | ; The ' | + | |
- | + | ||
- | 05CA LD-8-BITS | + | |
- | ; ' | + | |
- | ret | + | |
- | ; exceeded. (Carry flag reset.) | + | |
- | ld a,+CB ; Compare the length against | + | |
- | ; approx. 2,400 T states; resetting | + | |
- | cp b ; the carry flag for a ' | + | |
- | ; setting it for a ' | + | |
- | rl l ; Include the new bit in the L | + | |
- | ; register. | + | |
- | ld b,+B0 ; Set the timing constant for the | + | |
- | ; next bit. | + | |
- | jp nc, | + | |
- | ; bits to be fetched. | + | |
- | + | ||
- | ; The ' | + | |
- | + | ||
- | ld a,h ; Fetch the ' | + | |
- | xor | + | |
- | ld h,a ; Save it once again. | + | |
- | ; Passes round the loop are made until the ' | + | |
- | ; At that point the ' | + | |
- | ld a,d ; Make a further pass if the DE | + | |
- | or e ; register pair does not hold | + | |
- | jr nz, | + | |
- | ld a,h ; Fetch the ' | + | |
- | ; byte. | + | |
- | CP +01 ; Return with the carry flat set | + | |
- | ret ; if the value is zero. | + | |
- | ; (Carry flag reset if in error.) | + | |
- | + | ||
- | ; THE ' | + | |
- | ; These two subroutines form the most important part of the LOAD/ | + | |
- | ; operation. The subroutines are entered with a timing constant in the B | + | |
- | ; register, and the previous border colour and ' | + | |
- | ; The subroutines return with the carry flag set if the required number | + | |
- | ; of ' | + | |
- | ; value in the B register shows just how long it took to find the ' | + | |
- | ; The carry flag will be reset if there is an error. The zero flag then | + | |
- | ; signals 'BREAK pressed' | + | |
- | ; The entry point LD-EDGE-2 is used when the length of a complete pulse | + | |
- | ; is required and LD-EDGE-1 is used to find the time before the next ' | + | |
- | + | ||
- | 05E3 LD-EDGE-2 | + | |
- | ret | + | |
- | ; is an error. | + | |
- | 05E7 LD-EDGE-1 | + | |
- | 05E9 LD-DELAY | + | |
- | jr nz, | + | |
- | and a | + | |
- | + | ||
- | ; The sampling loop is now entered. The value in the B register is | + | |
- | ; incremented for each pass; ' | + | |
- | + | ||
- | 05ED LD-SAMPLE | + | |
- | ret | + | |
- | ; ' | + | |
- | ld a,+7F ; Read from port +7FFE. | + | |
- | IN A, | + | |
- | rra ; Shift the byte. | + | |
- | ret | + | |
- | ; if BREAK was pressed. | + | |
- | xor | + | |
- | AND | + | |
- | jr z, | + | |
- | + | ||
- | ; A new ' | + | |
- | ; So change the border colour and set the carry flag. | + | |
- | + | ||
- | ld a,c ; Change the 'last edge-type' | + | |
- | cpl ; and border colour. | + | |
- | ld c,a | + | |
- | AND | + | |
- | OR +08 ; Signal 'MIC off' | + | |
- | OUT | + | |
- | ; CYAN or BLUE/ | + | |
- | scf ; Signal the successful search | + | |
- | ret ; before returning. | + | |
- | + | ||
- | ; Note: The LD-EDGE-1 subroutine takes 465 T states, plus an additional 58 T | + | |
- | ; states for each unsuccessful pass around the sampling loop. | + | |
- | + | ||
- | ; For example, therefore, when awaiting the sync pulse (see LD-SYNC at 058F) | + | |
- | ; allowance is made for ten additional passes through the sampling loop. | + | |
- | ; The search is thereby for the next edge to be found within, roughly, | + | |
- | ; 1,100 T states (465 + 10 * 58 + overhead). This will prove successful | + | |
- | ; for the sync ' | + | |
- | </ | + | |
\\ | \\ | ||
===== Ficheros ===== | ===== Ficheros ===== |