cursos:ensamblador:teclado

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:teclado [13-10-2010 07:35] sromerocursos:ensamblador:teclado [22-01-2024 08:01] (actual) – [Redefinicion de teclas] sromero
Línea 1: Línea 1:
- 
 ====== Lectura del teclado en el Spectrum ====== ====== Lectura del teclado en el Spectrum ======
  
- Este capítulo está íntegramente dedicado a la lectura del teclado mediante la instrucción de lectura de puertos "**IN**". A lo largo del mismo veremos cómo responde el estado del puerto a los cambios del teclado, y subrutinas útiles para la gestión del mismo.+ Este capítulo está íntegramente dedicado a la lectura del teclado mediante la instrucción de lectura de puertos ''IN''. A lo largo del mismo veremos cómo responde el estado del puerto a los cambios del teclado, y subrutinas útiles para la gestión del mismo
 + 
 + Tras este capítulo seremos capaces de consultar el estado del teclado, ya sean teclas concretas predefinidas en el código o redefinidas por el usuario, permitiéndonos interactuar con él y cubriendo una de las principales necesidades a la hora de programar juegos para Spectrum: el control, tanto de los menúes como del juego en sí. 
 + 
 +\\  
 +===== Uso de las rutinas de la ROM ===== 
 + 
 +Como ya vimos en el capítulo dedicado a las rutinas de la ROM y variables del sistema, tenemos diferentes rutinas y mecanismos para leer el teclado mediante los procesos de la ROM. 
 + 
 + La ROM dispone de diferentes rutinas para la lectura del teclado y el tratamiento de scancodes (por ejemplo, conversión a ASCII, guardar la última tecla pulsada en una variable de sistema, etc). 
 + 
 + Como veremos en la entrega dedicada a las Interrupciones del procesador, ciertas rutinas de servicio (ISR), en concreto ''rst $38'', son llamadas regularmente por el procesador en el modo de interrupciones 1. Estas rutinas son utilizadas por el intérprete de BASIC, por ejemplo, pero pueden ser utilizadas por nosotros siempre y cuando no cambiemos del modo de Interrupción 1 (por defecto) al modo de Interrupción 2 (un modo que nos permite programar nuestras propias rutinas ISR personalizadas). 
 + 
 + Estando en modo 1, la ROM lee regularmente el teclado y actualiza ciertas variables del sistema como ''LAST_K'' (variable ubicada en la dirección de memoria $5c08 ó 23560), que contiene el código ASCII de la última tecla que se haya pulsado. La tecla pulsada permanece en dicha variable del sistema incluso aunque ya no esté siendo pulsada. La rutina de la ROM ya nos asegura una correcta gestión de los microrebotes del teclado y su conversión a un ASCII válido. 
 + 
 + Mientras estemos en modo de interrupciones im1 (el modo en que arranca el Spectrum), podemos hacer uso de ''LAST_K'' si lo consideramos conveniente, ya que como se ha comentado, la Rutina de Interrupciones por defecto del Spectrum se ejecutará regularmente y actualizará su valor. 
 + 
 + No obstante, no es lo normal utilizar las rutinas de teclado de la ROM en juegos, puesto que en la mayoría de ellos necesitaremos entrar en el modo 2 de Interrupciones, en el cual no se ejecuta rst $38 y por lo tanto no se actualizan variables como ''LAST_K''. Además, es posible que nuestro programa tenga que hacer uso de la zona de memoria en que las rutinas de la ROM guardan los datos de las teclas leídas (de 23552 a 23560), lo que no haría factible el uso de estas rutinas (ni siquiera llamando a rst $38) a menos que preservemos el contenido de esta zona de memoria y de otros registros del procesador antes de volver a a im1, llamar a rst $38, y saltar de nuevo a im 2. Seguramente todo este proceso es totalmente innecesario en un juego si éste ya dispone de rutinas para leer el teclado y podemos aprovecharlas. 
 + 
 + En la sección de ficheros se incluye un listado de las rutinas de la ROM desensambladas y comentadas, obtenidas del libro "//The Complete Spectrum ROM Disassembly//". Estas rutinas incluyen funciones de lectura de scancodes y decodificación de los mismos para convertirlos en ASCIIs. Podemos aprovecharlas en programas que no requieran precisión con el teclado (por ejemplo, para aplicaciones en lugar de para juegos). 
 + 
 + Las rutinas de la ROM están en general orientadas a devolver si hay una tecla pulsada, y cuál es, pero no a leer el estado de múltiples teclas, como son necesarias para mover a un personaje en diagonal o andar mientras disparamos. 
 + 
 + Este capítulo está orientado a mostrar cómo hacer unas rutinas propias de lectura de teclado sin pasar por la ROM.
  
- Tras este artículo seremos capaces de consultar el estado del teclado, ya sean teclas concretas predefinidas en el código o redefinidas por el usuario, permitiéndonos interactuar con él y cubriendo una de las principales necesidades a la hora de programar juegos para Spectrum: el control, tanto de los menúes como del juego en sí.+ Así pues, veamos cómo leer el teclado directamente accediendo al puerto E/S que lo conecta al Z80.
  
 \\  \\ 
 ===== El teclado a nivel hardware ===== ===== El teclado a nivel hardware =====
  
-El teclado del Spectrum es una matriz de 40 pulsadores (40 teclas) que proporcionan al microprocesador, a través de las líneas de Entrada/Salida, para diferentes filas, un valor de 8 bits en el rango 0-255, donde cada bit indica el estado de una determinada tecla. Estrictamente hablando, la encargada de la lectura del teclado (como de otros periféricos) es realmente la ULA, pero para nosotros esto es transparente y podremos utilizar las funciones estándar de IO del microprocesador Z80 para conocer el estado del teclado.+El teclado del Spectrum es una matriz de 40 pulsadores (40 teclas) que proporcionan al microprocesador, a través de las líneas de Entrada/Salida, para diferentes filas, un valor de 8 bits en el rango 0-255, donde cada bit indica el estado de una determinada tecla. Estrictamente hablando, la encargada de la lectura del teclado (como de otros periféricos) es realmente la ULA, pero para nosotros esto es transparente y podremos utilizar las funciones estándar de IO del microprocesador Z80 para conocer el estado del teclado. Para leer el estado de cada una de las teclas del teclado (0 = pulsada, 1 = no pulsada), debemos obtener el estado del puerto $fe.
  
 + La pregunta en este momento debe de ser: si $fe es el puerto de lectura del teclado, y el Z80A es un microprocesador con un bus de datos de 8 bits, ¿cómo leemos el estado de 40 teclas en un registro de 8 bits?
  
-{{ cursos:ensamblador:teclado.jpg |Teclado del Spectrum}}+ La respuesta esorganizando el teclado en filas de teclas y seleccionando qué fila leer mediante el registro B.
  
 +\\ 
 +{{ cursos:ensamblador:teclado.jpg |Teclado del Spectrum}}
 +\\ 
  
  Si abrimos físicamente nuestro Spectrum, veremos que el teclado está conectado a la placa base del mismo mediante 2 cintas de datos: una de ellas de 8 líneas y la otra de 5. La de 8 líneas (8 bits) se puede considerar como el "byte de direcciones" del teclado, y es la que está  Si abrimos físicamente nuestro Spectrum, veremos que el teclado está conectado a la placa base del mismo mediante 2 cintas de datos: una de ellas de 8 líneas y la otra de 5. La de 8 líneas (8 bits) se puede considerar como el "byte de direcciones" del teclado, y es la que está
-unida al byte alto (bits 8 al 15) del bus de direcciones del Spectrum. La de 5 bits está conectado a los 5 bits inferiores (del 0 al 4) del bus de datos. Mediante la primera seleccionamos qué queremos leer, y mediante la segunda leemos el estado del teclado. +unida al byte alto (bits 8 al 15) del bus de direcciones del Spectrum. La de 5 bits está conectado a los 5 bits inferiores (del 0 al 4) del bus de datos. Mediante la primera seleccionamos qué fila queremos leer (con el registro B), y mediante la segunda leemos el estado del teclado (en ceros y unos).
  
 +\\ 
 {{ cursos:ensamblador:teclado_hw.jpg |El aspecto de la membrana de teclado}} {{ cursos:ensamblador:teclado_hw.jpg |El aspecto de la membrana de teclado}}
 +\\ 
  
 + Así, en nuestros programas podemos leer el estado del teclado accediendo a los puertos de Entrada / Salida del microprocesador a los que están conectadas las diferentes líneas de dicha matriz.
  
-Así, en nuestros programas podemos leer el estado del teclado +\\ 
-accediendo a los puertos de Entrada / Salida del microprocesador a los +
-que están conectadas las diferentes líneas de dicha matriz. +
 {{ cursos:ensamblador:08_plantillatec.jpg |Plantilla eléctrica del teclado}} {{ cursos:ensamblador:08_plantillatec.jpg |Plantilla eléctrica del teclado}}
 +\\ 
  
- + Rescatemos el siguiente programa BASIC de uno de los capítulos iniciales del curso:
- Rescatemos el siguiente programa BASIC del capítulo dedicado a la +
-Introducción al Microprocesador:+
  
 <code basic> <code basic>
-5  REM Mostrando el estado de la fila 1-5 del teclado+5  REM Mostrando el estado de la fila 1-5 del teclado ($f7fe)
 10 LET puerto=63486 10 LET puerto=63486
 20 LET V=IN puerto: PRINT AT 20,0; V ; "  " : GO TO 20 20 LET V=IN puerto: PRINT AT 20,0; V ; "  " : GO TO 20
 </code> </code>
  
- Este ejemplo lee (en un bucle infinito) una de las filas del teclado, + Este ejemplo lee (en un bucle infinito) una de las filas del teclado, concretamente la fila de las teclas del 1 al 5. Esta fila tiene sus 
-concretamente la fila de las teclas del 1 al 5. Esta fila tiene sus +diferentes bits de estado conectados al puerto 63486 ($f7feh), y como podéis suponer, mediante la instrucción ''IN'' de BASIC realizamos la 
-diferentes bits de estado conectados al puerto 63486 ($F7FEh), y como +misma función que con su equivalente ensamblador: consultar el valor contenido en dicho puerto.
-podéis suponer, mediante la instrucción IN de BASIC realizamos la +
-misma función que con su equivalente ensamblador: consultar el valor +
-contenido en dicho puerto.+
  
- Por defecto, sin pulsar ninguna tecla, los diferentes bits del valor + El valor $fe se corresponde con el puerto del teclado, mientras que $f7 se corresponde con 11110111 en binariodonde el cero selecciona la fila concreta del teclado que queremos leeren este caso, la fila de teclas del al 5.
-leído en dicho puerto estarán a 1. Cuando pulsamos una tecla, el valor +
-del bit correspondiente a dicha tecla aparecerá como 0y soltándola +
-volverá a su valor original.+
  
- Al ejecutar el ejemplo en BASICveremos que la pulsación de + Por defectosin pulsar ninguna tecla, los diferentes bits del valor leído en dicho puerto estarán 1. Cuando pulsamos una tecla, el valor del bit correspondiente a dicha tecla aparecerá como 0, soltándola volverá a su valor 1 original.
-cualquier tecla modifica el valor numérico que aparece en pantalla. +
-Si pasamos dicho valor numérico formato binario veremos cómo el +
-cambio del valor se corresponde con las teclas que vamos pulsando y +
-liberando.+
  
- Si no hay ninguna tecla pulsada, los 5 bits más bajos del byte que + Al ejecutar el ejemplo en BASICveremos que la pulsación de cualquier tecla modifica el valor numérico que aparece en pantalla. Si pasamos dicho valor numérico formato binario veremos cómo el cambio del valor se corresponde con las teclas que vamos pulsando y liberando.
-hay en el puerto estarán todos a 1mientras que si se pulsa alguna de +
-las teclas del 1 al 5, el bit correspondiente dicha tecla pasará a +
-estado 0. Nosotros podemos leer el estado del puerto y saber, mirando +
-los unos y los ceros, si las teclas están pulsadas o no. Los 3 bits +
-más altos del byte debemos ignorarlos para este propósito, ya que no tienen relación +
-alguna con el teclado.+
  
- Asípor ejemplo, leyendo del puerto 63486 obtenemos un byte cuyos últimos bits tienen + Si no hay ninguna tecla pulsadalos 5 bits más bajos del byte que hay en el puerto estarán todos a 1, mientras que si se pulsa alguna de las teclas del 1 al 5, el bit correspondiente a dicha tecla pasará a estado 0. Nosotros podemos leer el estado del puerto y saber, mirando los unos y los ceros, si las teclas están pulsadas o no. Los 3 bits más altos del byte debemos ignorarlos para este propósito, ya que no tienen relación alguna con el teclado (son los bits de acceso al cassette, a la unidad de cinta y al altavoz del Spectrum).
-como significado el estado de cada una de las teclas de la semifila del "1al "5".+
  
 + Así, por ejemplo, leyendo del puerto 63486 ($f7fe) obtenemos un byte cuyos 5 últimos bits tienen como significado el estado de cada una de las teclas de la semifila del "1" al "5".
  
 +\\ 
 +|< 50% >|
 ^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ ^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^
 | Teclas: | XX | XX | XX | "5" | "4" | "3" | "2" | "1" | | Teclas: | XX | XX | XX | "5" | "4" | "3" | "2" | "1" |
 +\\ 
  
- Como ya hemos dicho, los bits D7 a D5 no nos interesan en el caso del teclado (por + Como ya hemos dicho, los bits D7 a D5 no nos interesan en el caso del teclado (por ejemplo, D6 tiene relación con la unidad de cinta), mientras que los bits de D4 a D0 son bits de teclas (0=pulsada, 1=no pulsada). Tenemos pues el teclado dividido en filas de teclas y disponemos de una serie de puertos para leer el estado de todas ellas:
-ejemplo, D6 tiene relación con la unidad de cinta), mientras que los +
-bits de D5 a D0 son bits de teclas (0=pulsada, 1=no pulsada). Tenemos +
-pues el teclado dividido en filas de teclas y disponemos de una serie +
-de puertos para leer el estado de todas ellas:+
  
 +\\ 
 +|< 50% >|
 ^ Puerto ^ Teclas ^ ^ Puerto ^ Teclas ^
-| 65278d (FEFEh) | de CAPS SHIFT a V | +| 65278d ($fefe) | de CAPS SHIFT a V | 
-| 65022d (FDFEh) | de A a G | +| 65022d ($fdfe) | de A a G | 
-| 64510d (FBFEh) | de Q a T | +| 64510d ($fbfe) | de Q a T | 
-| 63486d (F7FEh) | de 1 a 5 (and JOYSTICK 1) | +| 63486d ($f7fe) | de 1 a 5 (JOYSTICK 1) | 
-| 61438d (EFFEh) | de 6 a 0 (and JOYSTICK 2) | +| 61438d ($effe) | de 6 a 0 (JOYSTICK 2) | 
-| 57342d (DFFEh) | de P a Y | +| 57342d ($dffe) | de P a Y | 
-| 49150d (BFFEh) | de ENTER a H | +| 49150d ($bffe) | de ENTER a H | 
-| 32766d (7FFEh) | de (space) a B |+| 32766d ($7ffe) | de (space) a B | 
 +\\ 
  
- la hora de leer estos puertos, el bit menos significativo (D0) + En el resultado de la lectura de  estos puertos, el bit menos significativo (D0) siempre hace referencia a la tecla más alejada del centro del teclado ("1" en nuestro ejemplo), mientras que el más significativo de los 5 (D5) lo hace a la tecla más cercana al centro del teclado.
-siempre hace referencia a la tecla más alejada del centro del teclado +
-("1" en nuestro ejemplo), mientras que el más significativo de los 5 +
-(D5) lo hace a la tecla más cercana al centro del teclado.+
  
  Concretamente:  Concretamente:
  
 +\\ 
 +|< 70% >|
 ^ Puerto ^ Bits: ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ ^ Puerto ^ Bits: ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^
-| 65278d (FEFEh) | Teclas: | "V" | "C" | "X" | "Z" | CAPS | +| 65278d ($fefe) | Teclas: | "V" | "C" | "X" | "Z" | CAPS | 
-| 65022d (FDFEh) | Teclas: | "G" | "F" | "D" | "S" | "A" |  +| 65022d ($fdfe) | Teclas: | "G" | "F" | "D" | "S" | "A"
-| 64510d (FBFEh) | Teclas: | "T" | "R" | "E" | "W" | "Q"+| 64510d ($fbfe) | Teclas: | "T" | "R" | "E" | "W" | "Q"
-| 63486d (F7FEh) | Teclas: | "5" | "4" | "3" | "2" | "1"+| 63486d ($f7fe) | Teclas: | "5" | "4" | "3" | "2" | "1"
-| 61438d (EFFEh) | Teclas: | "0" | "9" | "8" | "7" | "6" | +| 61438d ($effe) | Teclas: | "6" | "7" | "8" | "9" | "0" | 
-| 57342d (DFFEh) | Teclas: | "Y" | "U" | "I" | "O" | "P"+| 57342d ($dffe) | Teclas: | "Y" | "U" | "I" | "O" | "P"
-| 49150d (BFFEh) | Teclas: | "H" | "J" | "K" | "L" | ENTER | +| 49150d ($bffe) | Teclas: | "H" | "J" | "K" | "L" | ENTER | 
-| 32766d (7FFEh) | Teclas: | "B" | "N" | "M" | SYMB | SPACE | +| 32766d ($7ffe) | Teclas: | "B" | "N" | "M" | SYMB | SPACE | 
- SINCLAIR 1 y 2 (las mismas teclas 0-9) ^ ^ ^ ^ ^ ^ ^ +^ SINCLAIR 1 y 2\\ (las mismas teclas 0-9)^ ^ ^ ^ ^ ^ ^ 
-| 61438d (EFFEh) | SINCL1 | LEFT | RIGHT | DOWN | UP | FIRE | +| 61438d ($effe) | SINCL1 | LEFT | RIGHT | DOWN | UP | FIRE | 
-| 63486d (F7FEh) | SINCL2 |  FIRE | DOWN | UP | RIGHT | LEFT |+| 63486d ($f7fe) | SINCL2 | FIRE | DOWN | UP | RIGHT | LEFT | 
 +\\ 
  
 + Como puede verse en la tabla, la parte baja de los 16 bits del puerto representan siempre $fe (254d), el puerto al que está conectado el teclado.
  
- ¿De dónde salen estos valores tan extraños? ¿Existe alguna relación + La parte alta esla única que varía según la semifila a leer, y su valor consiste, como hemos visto, en la puesta a cero de la semifila deseada, teniendo en cuenta que cada semifila de teclas está conectada a uno de los bits del bus de direcciones:
-entre FDFEh, por ejemplo, y la semifila a la que representa? +
-Efectivamente, existe. Como véis, la parte baja de los 16 bits del +
-puerto representan siempre FEh (254d), el puerto al que está conectado +
-el teclado. +
- +
- La parte alta es la única que varía según la semifila a leer, y su valor consiste, +
-simplemente, en la puesta a cero de la semifila deseada, teniendo en cuenta que +
-cada semifila de teclas está conectada a uno de los bits del bus de direcciones:+
  
 +|< 70% >|
 ^ FILA DE TECLAS ^ LINEA BUS ^ VALOR ^ VALOR BINARIO ^ ^ FILA DE TECLAS ^ LINEA BUS ^ VALOR ^ VALOR BINARIO ^
-| CAPSSHIFT a V | A8 | FE | 1 1 1 1 1 1 1 0 | +| CAPSSHIFT a V | A8 | $fe | 1 1 1 1 1 1 1 0 | 
-| A-G | A9 | FD | 1 1 1 1 1 1 0 1 | +| A-G | A9 | $fd | 1 1 1 1 1 1 0 1 | 
-| Q-T | A10 | FB | 1 1 1 1 1 0 1 1 | +| Q-T | A10 | $fb | 1 1 1 1 1 0 1 1 | 
-| 1-5 | A11 | F7 | 1 1 1 1 0 1 1 1 | +| 1-5 | A11 | $f7 | 1 1 1 1 0 1 1 1 | 
-| 6-0 | A12 | EF | 1 1 1 0 1 1 1 1 | +| 6-0 | A12 | $ef | 1 1 1 0 1 1 1 1 | 
-| Y-P | A13 | DF | 1 1 0 1 1 1 1 1 | +| Y-P | A13 | $df | 1 1 0 1 1 1 1 1 | 
-| H-ENTER | A14 | BF | 1 0 1 1 1 1 1 1 | +| H-ENTER | A14 | $bf | 1 0 1 1 1 1 1 1 | 
-| B-SPACE | A15 | 7F | 0 1 1 1 1 1 1 1 | +| B-SPACE | A15 | $7f | 0 1 1 1 1 1 1 1 |
  
- Así, al mandar estos "extraños valoresen la lectura de puerto (la parte alta del mismo), lo que hacemos realmente es seleccionar cuál de las líneas del bus de direcciones (cada una de + Así, al mandar estos valores en la lectura de puerto (en la parte alta del mismo), lo que hacemos realmente es seleccionar cuál de las líneas del bus de direcciones (cada una de ellas conectada a una fila del teclado) queremos leer.
-ellas conectada a una fila del teclado) queremos leer.+
  
- En resumen: es posible obtener el estado de una tecla determinada leyendo de un puerto de Entrada / Salida del microprocesador. Este puerto de 16 bits se compone poniendo en su parte alta el valor de la semifila de teclado a leer (todo "1"s excepto un "0" en la semifila de interés, según la tabla vista anteriormente), y poniendo en su parte baja el valor FEh. El IN de dicho puerto nos proporcionará un valor de 8 bits cuyos 5 últimos bits indicarán el estado de las 5 teclas de la semifila seleccionada (1=no pulsada, 0=pulsada).+ En resumen: es posible obtener el estado de una tecla determinada leyendo de un puerto de Entrada / Salida del microprocesador. Este puerto de 16 bits se compone poniendo en su parte alta el valor de la semifila de teclado a leer (todo "1"s excepto un "0" en la semifila de interés, según la tabla vista anteriormente), y poniendo en su parte baja el valor $fe. El ''IN'' de dicho puerto nos proporcionará un valor de 8 bits cuyos 5 últimos bits indicarán el estado de las 5 teclas de la semifila seleccionada (1=no pulsada, 0=pulsada).
  
-(Nótese que es posible leer el estado de 2 o más semifilas simultáneamente haciendo 0 a la vez valor de los bits del byte alto del puerto. El resultado obtenido será un AND del estado de los bits de todas las semifilas leídas).+(Nótese que es posible leer el estado de 2 o más semifilas simultáneamente haciendo 0 a la vez el valor de los bits del byte alto del puerto. El resultado obtenido será un AND del estado de los bits de todas las semifilas leídas).
  
 \\  \\ 
-===== Ejemplo practico: leyendo el teclado =====+===== Ejemplo práctico: leyendo el teclado =====
  
- Veamos un ejemplo en ASM que se queda en un bucle infinito hasta que + Veamos un ejemplo en ASM que se queda en un bucle infinito hasta que pulsamos la tecla "p":
-pulsamos la tecla "p":+
  
 <code z80> <code z80>
-  ; Lectura de la tecla "P" en un bucle +; Lectura de la tecla "P" en un bucle 
-  ORG 50000+    ORG 50000
  
 bucle: bucle:
-  LD BC, $DFFE         ; Semifila "P" a "Y" +    ld bc, $dffe         ; Semifila "P" a "Y" 
-  IN A, (C)            ; Leemos el puerto +    in a, (c)            ; Leemos el puerto 
-  BIT 0,             ; Testeamos el bit 0 +    bit 0,             ; Testeamos el bit 0 
-  JR Z, salir          ; Si esta a 0 (pulsado) salir. +    jr z, salir          ; Si esta a 0 (pulsado) salir. 
-  JR bucle             ; Si no (a 1, no pulsado) repetimos+    jr bucle             ; Si no (a 1, no pulsado) repetimos
  
 salir: salir:
-  RET+    ret
  
-  END 50000+    END 50000
 </code> </code>
  
- De nuevo ensamblamos nuestro programa con "//pasmo --tapbas keyb1.asm + De nuevo ensamblamos nuestro programa con ''pasmo <nowiki>--</nowiki>tapbas keyb1.asm keyb1.tap'', y lo cargamos en el Spectrum o en un emulador.
-keyb1.tap//", y lo cargamos en el Spectrum o en un emulador.+
  
- Efectivamente, el programa se mantendrá en un bucle infinito hasta + Efectivamente, el programa se mantendrá en un bucle infinito hasta que se ponga a cero el bit 0 del puerto $dffe, que se corresponde con el estado de la tecla "P". Al pulsar esa tecla, la comparación hecha con BIT hará que el Zero Flag se active y el ''JR Z'' saldrá de dicho bucle, retornando al BASIC.
-que se ponga a cero el bit 0 del puerto $DFFE, que se corresponde con +
-el estado de la tecla "P". Al pulsar esa tecla, la comparación hecha +
-con BIT hará que el Zero Flag se active y el "JR Zsaldrá de dicho +
-bucle, retornando al BASIC.+
  
- Nótese cómo en ciertos casos puede ser más recomendable la + En ocasiones puede ser más recomendable la utilización de ''IN'' en su formato alternativoRecordemos que tenemos 2 opcodes para IN, con 2 formas diferentes equivalentes de especificar el puerto:
-utilización de una u otra forma de IN. Por ejemplonuestro ejemplo +
-anterior se podría escribir con "IN A, (N)", sería más recomendable, +
-puesto que evita el tener que utilizar el registro B:+
  
 <code z80> <code z80>
-  ; Lectura de la tecla "P" en un bucle (FORMA 2) +; Forma 1 
-  ORG 50000+ld bc, $fffe 
 +in a, (c)       ; A = Lectura de puerto $fffe 
 + 
 +; Forma 2 
 +ld a, $ff 
 +in a, ($fe)     ; A = Lectura de puerto $fffe 
 +</code> 
 + 
 + Por ejemplo, nuestro ejemplo anterior se podría escribir con ''IN A, (N)'', y sería más recomendable, puesto que evita el tener que utilizar el registro B: 
 + 
 +<code z80> 
 +; Lectura de la tecla "P" en un bucle (FORMA 2) 
 +    ORG 50000
  
 bucle: bucle:
-  LD A, $DF            ; Semifila "P" a "Y" +    ld a, $df            ; Semifila "P" a "Y" 
-  IN A, ($FE)          ; Leemos el puerto +    in a, ($fe)          ; Leemos el puerto 
-  BIT 0,             ; Testeamos el bit 0 +    bit 0,             ; Testeamos el bit 0 
-  JR Z, salir          ; Si esta a 0 (pulsado) salir. +    jr z, salir          ; Si esta a 0 (pulsado) salir. 
-  JR bucle             ; Si no (a 1, no pulsado) repetimos+    jr bucle             ; Si no (a 1, no pulsado) repetimos
  
 salir: salir:
-  RET +    ret 
-  END 50000+ 
 +    END 50000
 </code> </code>
  
- En este ejemplo B no se usa, usamos A para albergar la semifila a +En este ejemploB no se usa, usamos A para albergar la semifila a leer, que no nos afecta puesto que ya íbamos a perder su valor tras 
-leer, que no nos afecta puesto que ya íbamos a perder su valor tras +la lectura (como resultado de ella). Si estamos usando B, por ejemplo como contador de un bucle, nos interesa más esta forma de uso.
-la lectura (como resultado de ella). Si estamos usando B, por ejemplo +
-como contador de un bucle, nos interesa más esta forma de uso.+
  
 +Nótese como la manera en que hemos determinado si una tecla está o no está pulsada es comprobando el estado del bit en cuestión, y no simplemente comparando el valor devuelto con el valor numérico esperado para cada tecla. Esto es muy importante porque como veremos más adelante, si no lo hacemos así podemos tener problemas en algunos modelos de Spectrum (**ISSUE 2** vs **ISSUE 3**).
  
 \\  \\ 
-===== Esperar pulsacion y esperar liberacion de tecla =====+===== Esperar pulsación y esperar liberación de tecla =====
  
- Veamos otras 2 rutinas interesantes para nuestros programas. La + Veamos otras 2 rutinas interesantes para nuestros programas. La primera espera a que se pulse cualquier tecla (por ejemplo, para 
-primera espera a que se pulse cualquier tecla (por ejemplo, para +realizar una pausa), y la segunda espera a que se suelte la tecla pulsada (esta la podemos usar tras detectar una pulsación para esperar 
-realizar una pausa), y la segunda espera a que se suelte la tecla +a que el usuario suelte la tecla y no volver a leer la misma tecla en una segunda iteración de nuestro bucle).
-pulsada (esta la podemos usar tras detectar una pulsación para esperar +
-a que el usuario suelte la tecla y no volver a leer la misma tecla en +
-una segunda iteración de nuestro bucle).+
  
- Ambas rutinas se basan en el hecho de que, realmente, es posible leer más + Ambas rutinas se basan en el hecho de que, realmente, es posible leer más de una semifila del teclado simultáneamente. Como ya vimos, el valor que mandamos como byte alto del puerto tiene a valor 0 el bit que representa a la línea de datos que queremos leer.
-de una semifila del teclado simultáneamente. Como ya vimos, el valor que +
-mandamos como byte alto del puerto tiene a valor 0 el bit que representa +
-a la línea de datos que queremos leer.+
  
- Pero como vamos a ver, podemos leer más de una línea simultáneamente, + Pero como vamos a ver, podemos leer más de una línea simultáneamente, poniendo a cero tantos bits como deseemos en la parte alta del puerto. En estos ejemplos, con el ''xor a'' (que equivale a ''ld a, 0'' dado que un ''XOR'' de un registro con sí mismo es cero) dejamos a 0 todo el byte alto con lo que leemos la información de todas las semifilas simultáneamente.
-poniendo a cero tantos bits como deseemos en la parte alta del puerto. En +
-estos ejemplos, con el XOR A (que equivale a "LD A, 0dado que un XOR de +
-un registro con sí mismo es cero) dejamos a 0 todo el byte alto con lo +
-que leemos la información de todas las semifilas simultáneamente.+
  
- Este tipo de "multilectura" no nos permite saber de forma exacta + Este tipo de "multilectura" no nos permite saber de forma exacta qué tecla ha sido pulsada. Por ejemplo, si intentamos leer simultaneamente las semifilas 1-5 y Q-T, el bit 0 del resultado valdrá "0" (tecla pulsada) si se pulsa "1", se pulsa "Q", o se pulsan ambas.
-qué tecla ha sido pulsada. Por ejemplo, si intentamos leer simultaneamente +
-las semifilas 1-5 y Q-T, el bit 0 del resultado valdrá "0" (tecla pulsada) +
-si se pulsa "1", se pulsa "Q", o se pulsan ambas.  +
- +
- Así, podemos saber que una de las teclas de las semifilas está siendo leída, +
-pero no cuál de ellas. Para saber qué valor debemos enviar al puerto para +
-leer varias semifilas de forma simultánea, recordemos la tabla vista en +
-el anterior apartado:+
  
 + Así, podemos saber que una de las teclas de las semifilas está siendo leída, pero no cuál de ellas. Para saber qué valor debemos enviar al puerto para leer varias semifilas de forma simultánea, recordemos la tabla vista en el anterior apartado:
  
 +|< 70% >|
 ^ FILA DE TECLAS ^ BIT A CERO ^ VALOR BINARIO ^ ^ FILA DE TECLAS ^ BIT A CERO ^ VALOR BINARIO ^
 | CAPSSHIFT a V | A8 | 1 1 1 1 1 1 1 0 | | CAPSSHIFT a V | A8 | 1 1 1 1 1 1 1 0 |
Línea 237: Línea 226:
 | B-SPACE | A15 | 0 1 1 1 1 1 1 1 | | B-SPACE | A15 | 0 1 1 1 1 1 1 1 |
  
 + Así, para leer tanto 1-5 como Q-T, necesitamos tener 2 ceros: uno en el bit 10 y otro en el bit 11. El valor decimal que corresponde con
 +(11110011d), 243d, nos permite la lectura de ambas semifilas de forma simultánea.
  
- Así, para leer tanto 1-5 como Q-T, necesitamos tener 2 ceros: uno en el + En el caso de una rutina de espera de pulsación o liberación de tecla, podemos hacer la parte alta del byte igual a cero (activar 
-bit 10 y otro en el bit 11. El valor decimal que corresponde con +todas las semifilas) para leer todo el teclado, ya que no nos importa cuál de las teclas ha sido pulsada sino el hecho de que lo haya sido o no.
-(11110011d), 243d, nos permite la lectura de ambas semifilas de forma +
-simultánea. +
- +
- +
- En el caso de una rutina de espera de pulsación o liberación de +
-tecla, podemos hacer la parte alta del byte igual a cero (activar +
-todas las semifilas) para leer todo el teclado, ya que no nos importa +
-cuál de las teclas ha sido pulsada sino el hecho de que lo haya sido o +
-no.+
  
  
Línea 255: Línea 237:
 ; Esta rutina espera a que haya alguna tecla pulsada para volver, ; Esta rutina espera a que haya alguna tecla pulsada para volver,
 ; consultando las diferentes filas del teclado en un bucle. ; consultando las diferentes filas del teclado en un bucle.
-(por devil_net) +;-----------------------------------------------------------------------
-;+
 Wait_For_Keys_Pressed: Wait_For_Keys_Pressed:
- XOR A                        ; A = 0 +    xor a                     ; A = 0 => leer todas las semifilas 
- IN A, (254+    in a, ($fe              ; Leer del puerto del teclado 
- OR 224 +    or %11100000              ; Poner a 1 los 3 bits más altos 
- INC +    inc a                     ; Comprobamos el estado del teclado con: A=A+1.  
- JR Z, Wait_For_Keys_Pressed +                              ; Si A=0 => ZF = 1 => no hay tecla pulsada 
- RET+                              ; Si A!=0 => ZF = 0 => hay alguna tecla pulsada 
 +    jr z, Wait_For_Keys_Pressed 
 +    ret
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 ; Esta rutina espera a que no haya ninguna tecla pulsada para volver, ; Esta rutina espera a que no haya ninguna tecla pulsada para volver,
 ; consultando las diferentes filas del teclado en un bucle. ; consultando las diferentes filas del teclado en un bucle.
-(por devil_net) +;-----------------------------------------------------------------------
-;+
 Wait_For_Keys_Released: Wait_For_Keys_Released:
- XOR +    xor a                     ; = 0 => leer todas las semifilas 
- IN A, (254+    in a, ($fe              ; Leer del puerto del teclado 
- OR 224 +    or %11100000              ; Poner a 1 los 3 bits más altos 
- INC +    inc a                     ; Comprobamos el estado del teclado con: A=A+1.  
- JR NZ, Wait_For_Keys_Released +                              ; Si A=0 => ZF = 1 => no hay tecla pulsada 
- RET+                              ; Si A!=0 => ZF = 0 => hay alguna tecla pulsada 
 +    jr nz, Wait_For_Keys_Released 
 +    ret
 </code> </code>
  
- Existe otra forma de codificar //Wait_For_Keys_Pressed// consistente en utilizar CPL para "complementar" los bits del resultado leído:+Si necesitamos preservar el valor de A y de los flags en las llamadas a estas rutinas, podemos hacernos versiones como las utilizadas en ''utils.asm'':
  
 <code z80> <code z80>
-Wait_For_Key_Pressed+;----------------------------------------------------------------------- 
-  XOR                        ; Leer todas las teclas +Wait_For_Keys_Pressed
-  IN A, (FEh+    push af 
-  CPL +wait_for_keypressed_loop: 
-  AND 1Fh                      Comprobar todos los unos +    xor a                     ; = 0 => leer todas las semifilas 
-  JR ZWait_For_Key_Pressed +    in a, ($fe)               ; Leer del puerto del teclado 
-  RET+    or %11100000              ; Poner a 1 los 3 bits más altos 
 +    inc a                     ; Comprobamos el estado del teclado con: A=A+1.  
 +                              ; Si A=0 => ZF = 1 => no hay tecla pulsada 
 +                              ; Si A!=0 => ZF = 0 => hay alguna tecla pulsada 
 +    jr z, wait_for_keypressed_loop 
 +    pop af 
 +    ret 
 + 
 +;----------------------------------------------------------------------- 
 +Wait_For_Keys_Released: 
 +    push af 
 +wait_for_no_pressedkey_loop: 
 +    xor a                     ; A = 0 => leer todas las semifilas 
 +    in a, ($fe              ; Leer del puerto del teclado 
 +    or %11100000              Poner a 1 los 3 bits más altos 
 +    inc a                     ; Comprobamos el estado del teclado con: A=A+1.  
 +                              ; Si A=0 => ZF = 1 => no hay tecla pulsada 
 +                              ; Si A!=0 => ZF = 0 => hay alguna tecla pulsada 
 +    jr nzwait_for_no_pressedkey_loop 
 +    pop af 
 +    ret
 </code> </code>
  
Línea 294: Línea 298:
 ===== Leyendo todas las direcciones ===== ===== Leyendo todas las direcciones =====
  
- En el siguiente ejemplo veremos cómo leer las 4 direcciones más + En el siguiente ejemplo veremos cómo leer las 4 direcciones más la tecla de disparo en un juego y codificar la información de la lectura en una variable para cada tecla.
-la tecla de disparo en un juego y codificar la información de la lectura en un +
-formato usable en el bucle principal del programa (para poder chequear cómodamente el +
-estado de las teclas consultadas).+
  
- La idea sería que, utilizando los diferentes bits de un byte, podemos + Esto es poco eficiente en términos de espacio y posteriormente veremos cómo almacenar todo en un único byte, pero la idea es ilustrar cómo podríamos chequear unas teclas predefinidas. 
-codificar el estado de las 5 teclas básicas (arriba, abajo, izquierda, + 
-derecha, disparo) en 5 de los 8 bits de un registro, y que nuestra + En este caso vamos a leer primero Q y luego A porque van en filas separadas, y luego veremos cómo O y P, que están en la misma semifila, sólo requiere de una lectura de puerto para ambas:
-función de detección de teclado sea algo parecido a lo siguiente:+
  
 <code z80> <code z80>
-; Lee el estado de O, P, Q, A, ESPACIO y devuelve +; Lee el estado de O, P, Q, A, ESPACIO. 
-; en A en A el estado de las teclas (1=pulsada, 0=no pulsada)+Leer_Teclado
-; El byte está codificado de forma que+    push af 
-; +    push bc 
-; BITS            4       2       0 +    push hl
-; SIGNIFICADO   FIRE LEFT RIGHT DOWN  UP +
-+
-LEER_TECLADO:+
  
-  LD D0                    ; Keyboard status flag register (D)+    ld hlestado_tecla_arriba 
 +    ld (hl), 0                 ; marcamos estado_tecla_arriba como no pulsada
  
-  LD BC, $FBFE +    ld bc, $fbfe 
-  IN A, (C+    in a, (c
-  BIT 0,                   ; Leemos la tecla Q +    bit 0,                   ; Leemos la tecla Q 
-  JR NZ, Control_no_up       ; No pulsada, no cambiamos nada en D +                               ; (podríamos haber usado "and %00000001"
-  SET 0, D                   ; Pulsada, ponemos a 1 el bit 0+    jr nz, Control_no_up       ; No pulsada, saltamos para no cambiar valor 0 
 +    ld (hl)1                 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1
 Control_no_up: Control_no_up:
  
-  LD BC, $FDFE +    ld hl, estado_tecla_abajo 
-  IN A, (C+    ld (hl), 0                 ; marcamos estado_tecla_abajo como no pulsada 
-  BIT 0,                   ; Leemos la tecla A +    ld bc, $fdfe 
-  JR NZ, Control_no_down     ; No pulsada, no cambianos nada en D +    in a, (c
-  SET 1D                   ; Pulsada, ponemos a 1 el bit 1+    bit 0,                   ; Leemos la tecla A 
 +    jr nz, Control_no_down     ; No pulsada, saltamos para no cambiar valor 0 
 +    ld (hl)1                 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1
 Control_no_down: Control_no_down:
  
-  LD BC, $DFFE +    ld hl, estado_tecla_derecha 
-  IN A, (C+    ld (hl), 0                 ; marcamos estado_tecla_derecha como no pulsada 
-  BIT 0,                   ; Leemos la tecla P +    ld bc, $dffe 
-  JR NZ, Control_no_right    ; No pulsada +    in a, (c
-  SET 2D                   ; Pulsada, ponemos a 1 el bit 2+    bit 0,                   ; Leemos la tecla P (aqui no usar AND) 
 +    jr nz, Control_no_right    ; No pulsada 
 +    ld (hl)1                 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1
 Control_no_right: Control_no_right:
- +                               ; O y P en misma fila, no leer puerto otra vez 
-                             BC ya vale $DFFE, (O y P en misma fila) +    ld hl, estado_tecla_izquierda 
-  BIT 1,                   ; Tecla O +    ld (hl), 0 
-  JR NZ, Control_no_left +    bit 1,                   ; Tecla O 
-  SET 3D+                               ; (podríamos haber usado "and %00000010"
 +    jr nz, Control_no_left     ; No pulsada, saltamos para no cambiar valor 0 
 +    ld (hl)1                 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1
 Control_no_left: Control_no_left:
  
-  LD BC, $7FFE +    ld hl, estado_tecla_disp 
-  IN A, (C+    ld (hl), 0 
-  BIT 0,                   ; Tecla Espacio +    ld bc, $7ffe 
-  JR NZ, Control_no_fire +    in a, (c
-  SET 4D+    bit 0,                   ; Tecla Espacio 
 +    jr nz, Control_no_fire     ; No pulsada, saltamos para no cambiar valor 0 
 +    ld (hl)1                 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1
 Control_no_fire: Control_no_fire:
  
-  LD A, D                    Devolvemos en A el estado de las teclas +    pop hl 
-  RET+    pop bc 
 +    pop af 
 +    ret 
 + 
 +Estado de cada tecla.  0 = no pulsada, 1 = pulsada 
 +estado_tecla_izq      DEFB  0 
 +estado_tecla_der      DEFB  0 
 +estado_tecla_arriba   DEFB 
 +estado_tecla_abajo    DEFB  0 
 +estado_tecla_disp     DEFB  0
 </code> </code>
  
- El bucle principal del programa deberá llamar a esta función de lectura del teclado y después, con el valor devuelto en el registro A, actuar consecuentemente de acuerdo al estado de los bits (aprovechando las funciones de testeo de bits del Z80). De esta forma, al volver de la llamada a esta subrutina sabremos si el usuario pretende mover el personaje en una dirección u otra, o si ha pulsado disparo, según el estado de los diferentes bits. La rutina que hemos visto trabaja con 5 teclas, pero todavía tenemos espacio para almacenar el estado de 3 teclas más en los bits 56 y 7 del registro A.+ El bucle principal del programa deberá llamar a esta función de lectura del teclado y después, con el valor 0/1 presente en las diferentes variables, actuar consecuentemente. De esta forma, al volver de la llamada a esta subrutina sabremos si el usuario pretende mover el personaje en una dirección u otra, o si ha pulsado disparo, según el estado de las variables. La rutina que hemos visto trabaja con 5 teclas, pero podríamos añadir más variables para más teclas como un segundo disparo, pausaetc.
  
- Con esta información sobre el teclado y las instrucciones IN y OUT, nada os impide dotar de interacción y movimiento vuestros programas, utilizando la lectura del teclado en menúes, acciones sobre los personajes, etc.+ Con esta información sobre el teclado y las instrucciones ''IN'' ''OUT'', nada nos impide dotar de interacción y movimiento vuestros programas, utilizando la lectura del teclado en menúes, acciones sobre los personajes, etc.
  
- Por otra parte, el código que acabamos de ver se basa en leer las semifilas de teclado y comprobar el estado de teclas concretas y definidas ("hardcodeadas") en el propio listado del programa. Esto quiere decir que las teclas de control son fijas y no seleccionables por el usuario. Este es el mecanismo de "lectura de teclado" más rápido y que menos espacio ocupa puesto que no es necesario crear rutinas para re-definir las teclas de juego, almacenarlas en variables de memoria y comparar el estado del teclado de los scancodes seleccionados por el usuario.+ Por otra parte, el código que acabamos de ver se basa en leer las semifilas de teclado y comprobar el estado de teclas concretas y definidas ("//hardcodeadas//", es decir, "//fijadas manualmente en el código//") en el propio listado del programa. Esto quiere decir que las teclas de control son fijas y no seleccionables por el usuario. Este es el mecanismo de "lectura de teclado" más rápido y que menos espacio ocupa puesto que no es necesario crear rutinas para re-definir las teclas de juego, almacenarlas en variables de memoria y comparar el estado del teclado de los scancodes seleccionados por el usuario.
  
  No obstante, una de las cosas que más agradecen los usuarios es la posibilidad de que las teclas de control se puedan redefinir y no sean fijas, por lo que a continuación veremos mecanismos para obtener del usuario las teclas de control y posteriormente poder detectar su pulsación en el transcurso de la lógica del programa o juego.  No obstante, una de las cosas que más agradecen los usuarios es la posibilidad de que las teclas de control se puedan redefinir y no sean fijas, por lo que a continuación veremos mecanismos para obtener del usuario las teclas de control y posteriormente poder detectar su pulsación en el transcurso de la lógica del programa o juego.
Línea 365: Línea 381:
 ===== Redefinicion de teclas ===== ===== Redefinicion de teclas =====
  
- Hasta ahora hemos visto cómo verificar el estado de unas teclas predeterminadas + Hasta ahora hemos visto cómo verificar el estado de unas teclas predeterminadas del teclado. Normalmente los juegos o programas suelen incluir la opción de redefinir las teclas asociadas a las acciones del juego, de forma que sea el jugador quien elija la combinación de teclas con la que se sienta más cómodo.
-del teclado. Normalmente los juegos o programas suelen incluir la opción de +
-redefinir las teclas asociadas a las acciones del juego, de forma que sea el +
-jugador quien elija la combinación de teclas con la que se sienta más cómodo.+
  
  Para ello necesitaremos una rutina que haga lo siguiente:  Para ello necesitaremos una rutina que haga lo siguiente:
  
 +\\ 
 +   * Antes de ser llamada, deberemos llegar a ella con el mensaje apropiado en pantalla ("//Pulse tecla para ARRIBA//", por ejemplo). La rutina se dedicará sólo al escaneo del teclado en sí mismo, de forma que pueda ser llamada tantas veces como teclas a redefinir. Además, tenemos que asegurarnos de  que cuando la llamamos no haya ninguna tecla pulsada. Para eso podemos usar la rutina ''Wait_For_Keys_Released''vista previamente.
  
-   * Antes de ser llamada, deberemos llegar a ella con el mensaje apropiado en pantalla ("Pulse tecla para ARRIBA", por ejemplo). La rutina se dedicará sólo al escaneo del teclado en sí mismo, de forma que pueda ser llamada tantas veces como teclas a redefinir. Además, tenemos que asegurarnos de  que cuando la llamamos no haya ninguna tecla pulsada. Para eso podemos usar la rutina Wait_For_Keys_Released vista previamente. 
    * La rutina deberá devolver un valor único para cada tecla pulsada, y asegurarse (o informarnos) de que no está siendo pulsada más de una tecla simultáneamente.    * La rutina deberá devolver un valor único para cada tecla pulsada, y asegurarse (o informarnos) de que no está siendo pulsada más de una tecla simultáneamente.
 +
    * Dicho valor devuelto por la rutina será almacenado para su posterior chequeo durante el juego, utilizando una rutina que nos indique si la tecla asociada a ese "valor único" está siendo pulsada.    * Dicho valor devuelto por la rutina será almacenado para su posterior chequeo durante el juego, utilizando una rutina que nos indique si la tecla asociada a ese "valor único" está siendo pulsada.
 +\\ 
  
- //David Webb// nos ofrece el siguiente conjunto de rutinas para este propósito. + //David Webb//, en su libro //Lenguaje máquina avanzado para el ZX Spectrum//, nos ofrece el siguiente conjunto de rutinas para este propósito. Existen bastantes posibilidades de realizar esta tarea (tablas con las semifilas y bits y sus correspondientes en ASCII, modificación en tiempo real de los opcodes que hacen los testeos, elegir entre un conjunto de combinaciones de teclas predeterminadas, etc), pero la que nos muestra David Webb es elegante y sencilla de utilizar.
-Existen bastantes posibilidades de realizar esta tarea (tablas con las semifilas +
-y bits y sus correspondientes en ASCII, modificación en tiempo real de los +
-opcodes que hacen los testeos, elegir entre un conjunto de combinaciones de +
-teclas predeterminadas, etc), pero la que nos muestra David Webb es elegante +
-y sencilla de utilizar.+
  
- Consiste en escanear el teclado completo y, al detectar la pulsación de + Consiste en escanear el teclado completo y, al detectar la pulsación de una tecla, codificar la semifila y el bit donde se han detectado en un mismo byte, utilizando los 3 bits más bajos para "el bit de la tecla pulsada" y los 3 siguientes para "la semifila (puerto)" en que se ha detectado la pulsación.
-una tecla, codificar la semifila y el bit donde se han detectado en un +
-mismo byte, utilizando los 3 bits más bajos para "el bit de la tecla pulsada" +
-y los 3 siguientes para "la semifila (puerto)" en que se ha detectado la +
-pulsación.+
  
 <code z80> <code z80>
 +;-----------------------------------------------------------------------
 ; Chequea el teclado para detectar la pulsación de una tecla. ; Chequea el teclado para detectar la pulsación de una tecla.
 +; Modifica A, H, DE y BC.
 ; Devuelve un código en el registro D que indica: ; Devuelve un código en el registro D que indica:
 ; ;
-;    Bits 0, 1 y 2 de "D": Fila de teclas (puerto) detectada. +;    Bits 0, 1 y 2 de "D": Semifila de teclas (puerto) detectada. 
-;    Bits 3, 4 y 5 de "D": Posición de la tecla en esa media fila+;    Bits 3, 4 y 5 de "D": Tecla (posición) en esa semifila 
 +;    => (00TTTSSS)
 ; ;
 ; Así, el valor devuelto nos indica la semifila a leer y el bit a testear. ; Así, el valor devuelto nos indica la semifila a leer y el bit a testear.
-; El registro D valdrá 255 ($FF) si no hay ninguna tecla pulsada. 
 ; ;
-; Flags: ZF desactivado: Más de una tecla pulsada +; El registro D valdrá 255 ($ff) si no hay ninguna tecla pulsada. 
-;        ZF activado: Tecla correctamente leída+
 +; Flags: ZF 0: Más de una tecla pulsada 
 +;        ZF 1: Tecla correctamente leída 
 +;-----------------------------------------------------------------------
 Find_Key: Find_Key:
- +    ld de, $ff2f         ; Valor inicial "ninguna tecla" 
-   LD DE, $FF2F         ; Valor inicial "ninguna tecla" +    ld bc, $fefe         ; Puerto
-   LD BC, $FEFE         ; Puerto+
  
 NXHALF: NXHALF:
-   IN A, (C+    in a, (c
-   CPL  +    cpl 
-   AND $1F +    and %00011111 
-   JR Z, NPRESS         ; Saltar si ninguna tecla pulsada+    jr z, NPRESS         ; Saltar si ninguna tecla pulsada
  
-   INC D                ; Comprobamos si hay más de 1 tecla pulsada +    inc d                ; Comprobamos si hay más de 1 tecla pulsada 
-   RET NZ               ; Si es así volver con Z a 0+    ret nz               ; Si es así volver con Z a 0
  
-   LD H             ; Cálculo del valor de la tecla +    ld h             ; Cálculo del valor de la tecla 
-   LD AE+    ld ae
  
 KLOOP: KLOOP:
-   SUB +    sub 
-   SRL H +    srl h 
-   JR NC, KLOOP+    jr nc, KLOOP
  
-   RET NZ               ; Comprobar si más de una tecla pulsada+    ret nz               ; Comprobar si más de una tecla pulsada
  
-   LD D             ; Guardar valor de tecla en D+    ld d             ; Guardar valor de tecla en D
  
-NPRESS:                 ; Comprobar el resto de semifilas +NPRESS:                  ; Comprobar el resto de semifilas 
-   DEC E +    dec e 
-   RLC B +    rlc b 
-   JR C, NXHALF         ; Repetimos escaneo para otra semifila+    jr c, NXHALF         ; Repetimos escaneo para otra semifila
  
-   CP A                 ; Ponemos flag a zero +    cp a                 ; Ponemos flag a zero 
-   RET Z                ; Volvemos+    ret z                ; Volvemos
 </code> </code>
  
  La forma en que llamaríamos a esta subrutina sería la siguiente:  La forma en que llamaríamos a esta subrutina sería la siguiente:
- 
  
 <code z80> <code z80>
-  ;; Pedimos tecla ARRIBA +    ;; Pedimos tecla ARRIBA 
-  CALL Imprimir_Texto_Pulse_Arriba +    call Imprimir_Texto_Pulse_Arriba 
-  CALL Wait_For_Keys_Released     ; Esperamos teclado libre+    call Wait_For_Keys_Released     ; Esperamos teclado libre
  
 Pedir_Arriba: Pedir_Arriba:
  
-  CALL Find_Key                   ; Llamamos a la rutina +    call Find_Key                   ; Llamamos a la rutina 
-  JR NZ, Pedir_Arriba             ; Repetir si la tecla no es válida +    jr nz, Pedir_Arriba             ; Repetir si la tecla no es válida 
-  INC D +    inc d 
-  JR Z, Pedir_Arriba              ; Repetir si no se pulsó ninguna tecla +    jr z, Pedir_Arriba              ; Repetir si no se pulsó ninguna tecla 
-  DEC D+    dec d
  
-  LD AD +    ld ad 
-  LD (tecla_arriba), A +    ld (tecla_arriba), a 
-   + 
-  ;; Pedimos siguiente tecla (ABAJO) +    ;; Pedimos siguiente tecla (ABAJO) 
-  CALL Imprimir_Texto_Pulse_Abajo +    call Imprimir_Texto_Pulse_Abajo 
-  CALL Wait_For_Keys_Released     ; Esperamos teclado libre+    call Wait_For_Keys_Released     ; Esperamos teclado libre
  
 Pedir_Abajo: Pedir_Abajo:
  
-  CALL Find_Key                   ; Llamamos a la rutina +    call Find_Key                   ; Llamamos a la rutina 
-  JR NZ, Pedir_Abajo              ; Repetir si la tecla no es válida +    jr nz, Pedir_Abajo              ; Repetir si la tecla no es válida 
-  INC D +    inc d 
-  JR Z, Pedir_Abajo               ; Repetir si no se pulsó ninguna tecla +    jr z, Pedir_Abajo               ; Repetir si no se pulsó ninguna tecla 
-  DEC D +    dec d 
-  + 
-  LD AD +    ld ad 
-  LD (tecla_abajo), A+    ld (tecla_abajo), a
  
-  ;;; Repetir el mismo código para IZQ, DERECHA, DISPARO, etc.+    ;;; Repetir el mismo código para IZQ, DERECHA, DISPARO, etc.
  
 tecla_arriba DEFB 0 tecla_arriba DEFB 0
Línea 477: Línea 487:
 </code> </code>
  
- A continuación podemos ver un **ejemplo** (scancode.asm) que "dibuja" en pantalla de + A continuación podemos ver un ejemplo (''scancode.asm'') que imprime en pantalla el SCANCODE que devuelve la función ''Find_Key'':
-forma gráfica el SCANCODE que devuelve la función Find_Key:+
  
 <code z80> <code z80>
 ; Visualizando los scancodes de las teclas codificadas con "Find_Key" ; Visualizando los scancodes de las teclas codificadas con "Find_Key"
  
-  ORG 50000+    ORG 50000
  
 Bucle_entrada: Bucle_entrada:
-  CALL Wait_For_Keys_Released+    call Wait_For_Key 
  
 Pedir_Tecla: Pedir_Tecla:
-  CALL Find_Key                   ; Llamamos a la rutina +    call Find_Key                   ; Llamamos a la rutina
-  JR NZ, Pedir_Tecla              ; Repetir si la tecla no es valida +
-  INC D +
-  JR Z, Pedir_Tecla               ; Repetir si no se pulsa ninguna tecla +
-  DEC D+
  
-  LD AD                         Guardamos en A copia del resultado +    jr nzPedir_Tecla              Repetir si la tecla no es valida 
-  CALL PrintBin                   Imprimimos el scancode bin en pantalla+    inc d 
 +    jr z, Pedir_Tecla               Repetir si no se pulsa ninguna tecla 
 +    dec d
  
-  LD A                        ; Guardamos en A copia del resultado +    ld a                        ; Guardamos en A copia del resultado
-  CALL PrintHex                   ; Imprimimos el scancode hex en pantalla+
  
-  CP $21                          ; Comprobamos si A == 21h (enter) +    cp $21                          ; Comprobamos si A == 21h (enter) 
-  JR NZ, Bucle_entrada            ; Si no lo es, repetir+    ret z                           ; Si no lo es, repetir
  
-  RET                             Si es enter, fin del programa+    call PrintHex                   Imprimimos el scancode hex en pantalla
  
 +    call PrintSpace                 ; Espacio para separar
  
-;----------------------------------------------------------------------- +    call Wait_For_No_Key            Esperamos a que el usuario SUELTE la tecla 
-PrintBin: Imprime en la pantalla un patron para visualizar el valor +    jr Pedir_Tecla                  Repetir hasta que arriba un ENTER 
-de A en binario, usando 8 pixels "puros" para "1" y punteados para "0" +                                   ($21 pulsado) haga el ret z BASIC
-+
-; Entrada: A = valor "imprimir" en binario +
-;----------------------------------------------------------------------- +
-PrintBin: +
-  PUSH AF +
-  PUSH HL +
-  PUSH BC                            ; Preservamos los registros que se usará+
  
-  LD HL, 20704                       ; Esquina (0,24) de la pantalla +    INCLUDE "utils.asm"
-  LD C, A                            ; Guardamos en C copia de A +
-  LD B, 8                            ; Imprimiremos el estado de los 8 bits+
  
-printbin_loop: +Debemos incluirademás, el código de Find_Key dentro de 
-  LD A, $FF                          Para bit = 1todo negro +este ejemplo para que ensamble correctamente. 
-  BIT 7C                           ; Chequeamos el estado del bit 7 +</code>
-  JR NZ, printbin_es_uno             Dejamos A = 255 +
-  LD A, $55                          ; Para bit = 0, punteado/gris+
  
-printbin_es_uno: + Este ejemplo proporcionará en pantalla (hasta que se pulse ENTERuna salida como la siguiente:
-  LD (HL), A                         ; Lo "imprimimos" (A) y pasamos a la +
-  INC HL                             ; Siguiente posició en memoria +
-  RLC C                              ; Rotamos C a la izq para que podamos +
-                                     ; usar de nuevo el BIT 7 en el bucle +
-  DJNZ printbin_loop                 ; Repetimos 8 veces+
  
-  POP BC +\\  
-  POP HL +{{ cursos:ensamblador:scancode.png?640 |Representación binaria de las teclas pulsadas}} 
-  POP AF +\\ 
-  RET+
  
 + Los scancodes asociados a las diferentes teclas son:
 +
 +\\ 
 +|< 80% >|
 +^ Teclas: ^ 1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 ^ 9 ^ 0 ^
 +^ Scancodes: | $24 | $1c | $14 | $0c | $04 | $03 | $0b | $13 | $1b | $23 |
 +
 +|< 80% >|
 +^ Teclas: ^ Q ^ W ^ E ^ R ^ T ^ Y ^ U ^ I ^ O ^ P ^
 +^ Scancodes: | $25 | $1d | $15 | $0d | $05 | $02 | $0a | $12 | $1a | $22 |
 +
 +|< 80% >|
 +^ Teclas: ^ A ^ S ^ D ^ F ^ G ^ H ^ J ^ K ^ L ^ ENTER ^
 +^ Scancodes: | $26 | $1e | $16 | $0e | $06 | $01 | $09 | $11 | $19 | $21 |
 +
 +|< 80% >|
 +^ Teclas: ^ CAPS ^ Z ^ X ^ C ^ V ^ B ^ N ^ M ^ SYMB ^ SPACE ^
 +^ Scancodes: | $27 | $1f | $17 | $0f | $07 | $00 | $08 | $10 | $18 | $20 |
 +\\ 
 +
 + O, en forma visual sobre un teclado de 48K, extraído del libro //Mastering Machine Code on your ZX Spectrum// (los valores numéricos están en formato hexadecimal pese a no llevar prefijo ni sufijo):
 +
 +\\ 
 +{{ :cursos:ensamblador:scancodes_gomas.jpg }}
 +\\ 
 +
 + Estos valores nos serán necesarios si queremos establecer unos scancodes por defecto para las teclas del programa, de forma que si el usuario no las redefine, tengan unos valores de comprobación determinados para la rutina de chequeo que veremos a continuación.
 +
 +\\ 
 +==== Procedimiento de redefinición de las teclas ====
 +
 + En las secciones anteriores hemos visto las rutinas ''Find_Key'' y ''Check_Key'' para detectar las pulsaciones de teclas y chequear el estado de una tecla concreta. Estas teclas concretas las guardamos en variables de memoria y así podemos permitir al jugador redefinirlas.
 +
 + El menú principal deberá de tener una opción que permita modificar el contenido de estas variables de memoria con aquellos scancodes que el jugador elija para controlar el juego.
 +
 + El sistema de redefinición de teclas debe:
 +
 +A.- Establecer en el arranque del programa unos valores por defecto para las teclas:
 +
 +<code z80>
 +tecla_arriba   DEFB  $25
 +tecla_abajo    DEFB  $26
 +tecla_izq      DEFB  $1a
 +tecla_der      DEFB  $22
 +tecla_disp     DEFB  $20
 +</code>
 +
 +B.- Repetir N veces (uno por cada control a redefinir):
 +
 +   * Esperar a que ninguna tecla del teclado esté pulsada (para evitar que la tecla de selección del menú para entrar en la redefinición, o la anterior tecla pulsada, se seleccione como tecla pulsada por el usuario).
 +
 +   * Mostrar por pantalla el mensaje de "//Pulse una tecla para (dirección a redefinir)//".
 +
 +   * Esperar una pulsación de teclado del usuario.
 +
 +   * Opcionalmente, comprobar que esa pulsación no se corresponda con ninguna de las teclas anteriores, para evitar que el usuario seleccione la misma dirección para, por ejemplo, izquierda y derecha. Este paso es opcional porque el usuario, si se equivoca, siempre puede redefinir de nuevo el teclado con las teclas adecuadas, y para nosotros esta comprobación representa tiempo de programación y espacio ocupado innecesariamente en el programa.
 +
 +   * Mostrar al usuario la tecla que ha pulsado a la derecha del mensaje impreso pidiendo dicha tecla. Para eso tenemos que convertir el Scancode en un código ASCII imprimible en pantalla.
 +
 +   * Modificar la variable en memoria que deba almacenar el scancode de la tecla pulsada para poder usarla posteriormente en el transcurso del juego (es decir, guardar el scancode obtenido en tecla_arriba, tecla_abajo, tecla_izq, o en la variable que corresponda).
 +
 + Tenemos hasta ahora todos los mecanismos necesarios para crear nuestra propia rutina de redefinición de teclas, salvo la rutina para convertir un scancode en su correspondiente ASCII. A continuación tenemos una rutina Scancode2Ascii basada en una tabla que relaciona cada scancode con su ASCII (40 bytes más adelante en la misma tabla):
 +
 +<code z80>
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-PrintHexImprime en la pantalla un numero de 1 byte en hexadecimal. +Scancode2Asciiconvierte un scancode en un valor ASCII 
-;        Para ello convierte el valor numérico en una cadena llamando +IN:  D = scancode de la tecla analizar 
-       Byte2ASCII_Hex y luego llama a RST 16 para imprimir cada +OUT: A = Codigo ASCII de la tecla (0-9 A-Z) 
-       caracter por separado. Imprime un $ delante ESPACIO detrás. +     minusculasENTER, s = SPACE, c = CAPSSHIFT e = SYMBOLSHIFT
-; +
-; Entradavalor a "imprimir" en binario+
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-PrintHex+Scancode2Ascii
-  PUSH HL +    push hl 
-  PUSH AF +    push bc
-  PUSH DE+
  
-  LD HA +    ld hl0 
-  CALL Byte2ASCII_Hex            ; Convertimos A en Cadena HEX +    ld bc, TABLA_Scancode2ASCII 
-  LD HLByte2ASCII_output       ; HL apunta la cadena+    add hlbc                    ; HL apunta al inicio de la tabla
  
-  LD A, "$" +    ; buscamos en la tabla un max de 40 veces por el codigo 
-  RST 16                         ; Imprimimos un "$"+    ; le sumamos 40 a HL, leemos el valor de (HL) y ret A 
 +SC2Ascii_1: 
 +    ld a(hl)                   ; leemos un byte de la tabla 
 +    cp '1'                       ; Si es '1' (caracter) fin de la rutina (porque 
 +                                 ; en la tabla habriamos llegado a los ASCIIs) 
 +    jr z, SC2Ascii_Exit          ; (y es condicion de forzado de salida) 
 +    inc hl                       ; incrementamos puntero de HL 
 +    cp d                         ; comparamos si A==D (nuestro scancode) 
 +    jr nz, SC2Ascii_1
  
-  LD A, (HL+SC2Ascii_Found: 
-  RST 16                         Imprimimos primer valor HEX+    ld bc39                    ; Sumamos 39(+inc hl=40para ir a la 
 +    add hl, bc                   ; seccion de la tabla con el codigo ASCII 
 +    ld a, (hl)                   leemos el codigo ASCII de esa tabla
  
-  INC HL                         ; Avanzar en la cadena +SC2Ascii_Exit: 
-  LD A, (HL) +    pop bc 
-  RST 16                         ; Imprimimos segundo valor HEX+    pop hl 
 +    ret
  
-  LD A, " " +    ; 40 scancodes seguidos de sus ASCIIs equivalentes 
-  RST 16                         Imprimimos un espacio+TABLA_Scancode2ASCII: 
 +    DEFB $24, $1c, $14, $0c, $04, $03, $0b, $13, $1b, $23 
 +    DEFB $25, $1d, $15, $0d, $05, $02, $0a, $12, $1a, $22 
 +    DEFB $26, $1e, $16, $0e, $06, $01, $09, $11, $19, $21 
 +    DEFB $27, $1f, $17, $0f, $07, $00, $08, $10, $18, $20 
 +    DEFB "1234567890QWERTYUIOPASDFGHJKLecZXCVBNMys" 
 +</code> 
 + 
 + La rutina recibe en el registro D el scancode obtenido con la rutina ''Find_Key'' y devuelve en el registro el código ASCII correspondiente directamente imprimible. Los primeros 40 bytes de la tabla contienen los Scancodes y la última línea DEFB (últimos 40 bytes) los ASCIIs a los que corresponden, en orden, los anterior 40 códigos. 
 + 
 + Nótese que las teclas se devuelven como ASCIIs en mayúsculas, aprovechando las letras minúsculas para los caracteres especiales no directamente representables: 
 + 
 +\\  
 +   * e = ENTER 
 +   * c = CAPS SHIFT 
 +   * y = SYMBOL SHIFT 
 +   * s = SPACE 
 +\\  
 + 
 + De esta forma, "Ese corresponde a la tecla E y "e" a la tecla de ENTER. Podemos utilizar estos ASCIIs en minúsculas para mostrar en pantalla cadenas como "ENTER" o "SPACE" durante la redefinición de las teclas. También podríamos color UDGs preparados para ENTER, ESPACIO, CS o SS, o bien usar cadenas en vez de caracteres para poder imprimir "ENTER" en lugar de un símbolo de Enter. 
 + 
 + A continuación podemos ver un ejemplo que utiliza las rutinas ''Find_Key'' y ''Scancode2Ascii'' para mostrar en pantalla el código ASCII de cualquier tecla pulsada: 
 + 
 + 
 +<code z80> 
 +; Prueba de conversion de Scancode a ASCII 
 + 
 +    ORG 50000 
 + 
 +    call CLS 
 + 
 +START: 
 + 
 +    call Wait_For_No_Key 
 + 
 +chequear_teclas: 
 +    call Find_Key                   ; Llamamos a la rutina 
 +    jr nz, chequear_teclas          ; Repetir si la tecla no es válida 
 +    inc d 
 +    jr z, chequear_teclas           ; Repetir si no se pulsó ninguna tecla 
 +    dec d 
 + 
 +    ; En este punto D es un scancode valido 
 +    call Scancode2Ascii 
 + 
 +    ; En este punto A contiene el ASCII del scancode en D 
 +    ; lo imprimimos por pantalla con rst 16
 +    rst 16 
 + 
 +    jr START                        vuelta a empezar 
 + 
 +    INCLUDE "utils.asm" 
 + 
 +;;--- Introducir aquí las rutinas Find_Key y Scancode2ASCII ------------ 
 + 
 +    END 50000 
 +</code> 
 + 
 + Una vez en ejecución y tras pulsar múltiples teclas, este es el aspecto del programa anterior: 
 + 
 +\\  
 +{{ cursos:ensamblador:sc2ascii.png?640 |Conversión de Scancode a ASCII}} 
 +\\  
 + 
 +Juntemos ahora todas las funciones que hemos desarrollado en un **programa de ejemplo que permite al usuario redefinir la teclas**. 
 + 
 +El programa deberá: 
 + 
 +   * Imprimir los diferentes mensajes al usuario ("Izquierda?" "Derecha?"
 + 
 +   * Después de cada mensaje, esperar a que se pulse una tecla. 
 + 
 +   * Almacenar el scancode en la variable de tecla correspondiente. 
 + 
 +   * Convertir el scancode en un ASCII e imprimirlo por pantalla para informar al usuario visualmente de la tecla que pulsó. 
 + 
 +   * Al acabar el proceso, tendremos los scancodes en las variables ''tecla_*''
 + 
 +\\  
 +<code z80> 
 +; Redefinir teclas y ver el estado de las teclas elegidas "en el juego"
 +    ORG 33500 
 + 
 +    call CLS 
 +    call Redefinir_Teclas 
 + 
 +bucle: 
 +    jr bucle
  
-  POP DE +; Mensajes del programa 
-  POP AF +msg_izq        DEFB  "Izquierda? ", _EOS 
-  POP HL+msg_der        DEFB  "Derecha? ", _EOS 
 +msg_arriba     DEFB  "Arriba? ", _EOS 
 +msg_abajo      DEFB  "Abajo? ", _EOS 
 +msg_disp       DEFB  "Disparo? ", _EOS
  
-  RET+; Teclas por defecto si no se redefine: O P Q A SPACE 
 +tecla_izq      DEFB  $1a 
 +tecla_der      DEFB  $22 
 +tecla_arriba   DEFB  $25 
 +tecla_abajo    DEFB  $26 
 +tecla_disp     DEFB  $20
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-Byte2ASCII_Hex: Convierte el valor del registro H en una cadena +Utiliza Redefine_Key para obtener en las variables "tecla_*" los 
-; de texto de max. 2 caracteres hexadecimales, para poder imprimirla+scancodes de las diferentes teclas que selecciona el usuario
-Rutina adaptada de Num2Hex en http://baze.au.com/misc/z80bits.html .+Llama a "Redefine_Key" para mostrar el mensaje y pedir la tecla.
 ; ;
-IN  H = Numero a convertir +ENTRADANada 
-OUT:  [Byte2ASCII_output] = Espacio de 2 bytes con los ASCIIs+SALIDA:  Nada 
 +; MODIFICA: AF, DE, HL, Flags
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-Byte2ASCII_Hex:+Redefinir_Teclas: 
 +    ; Los siguientes textos se podrian haber impreso tambien usando un bucle 
 +    ; con inc de (avanzar cadena) e inc hl (avanzar tecla a escribir) 
 +    ld de, msg_izq 
 +    call PrintString              ; Imprimir mensaje "Izquierda?" 
 +    call Redefine_Key             ; Esperar pulsacion (e imprimir ASCII) 
 +    ld hl, tecla_izq              ; Apuntamos HL a la variable tecla_izq 
 +    ld (hl), a
  
-   ld de, Byte2ASCII_output +    ld de, msg_der                ; Siguiente mensaje: "Derecha?" 
-   ld a, h +    call PrintString 
-   call B2AHex_Num1 +    call Redefine_Key             ; Esperar pulsacion (e imprimir ASCII) 
-   ld ah +    ld hltecla_der 
-   call B2AHex_Num2 +    ld (hl), a                    ; Guardamos tecla pulsada
-   ret+
  
-B2AHex_Num1: +    ld de, msg_arriba             ; Repetimos con ARRIBA 
-   rra +    call PrintString 
-   rra +    call Redefine_Key 
-   rra +    ld hl, tecla_arriba 
-   rra+    ld (hl), a
  
-B2AHex_Num2: +    ld de, msg_abajo              ; Repetimos con ABAJO 
-   or $F0 +    call PrintString 
-   daa +    call Redefine_Key 
-   add a$A0 +    ld hltecla_abajo 
-   adc a, $40 +    ld (hl), a 
-   ld (de), a + 
-   inc de +    ld demsg_disp               ; Repetimos con DISPARO 
-   ret+    call PrintString 
 +    call Redefine_Key 
 +    ld hl, tecla_disp 
 +    ld (hl), a 
 +    ret
  
-Byte2ASCII_output DB 0, 0 
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 +; Utiliza Find_Key para obtener una tecla válida. Se queda en un bucle
 +; de espera hasta que una sola tecla esté pulsada, y la devuelve en A.
 +; Además, imprime por pantalla el codigo ASCII de la tecla.
 +;
 +; ENTRADA: Nada
 +; SALIDA:  A = scancode
 +; MODIFICA: Flags
 +;-----------------------------------------------------------------------
 +Redefine_Key:
 +    push de
 +    push hl
 +    call Wait_For_No_Key
 +
 +wait_for_scan_loop:
 +    call Find_Key
 +    jr nz, wait_for_scan_loop          ; Mas de una tecla leida, repetir
 +    ld a, d
 +    cp $ff                             ; si A es $ff => ninguna tecla pulsada
 +    jr z, wait_for_scan_loop           ; Repetimos hasta que A != $ff
 +    ld h, d                            ; Nos hacemos copia de D en H
 +    call Scancode2Ascii                ; Convertir D (scancode) en A (ASCII)
 +
 +    cp 'e'                             ; ¿Es 'e'? Imprimir "ENTER"
 +    jr nz, redef_key_NO_ENTER
 +    ld de, redef_key_enter
 +    call PrintString
 +    jr redef_key_end                   ; Impreso texto, salimos
 +redef_key_NO_ENTER:
 +
 +    cp 's'                             ; ¿Es 's'? Imprimir "SPACE"
 +    jr nz, redef_key_NO_SPACE
 +    ld de, redef_key_space
 +    call PrintString
 +    jr redef_key_end
 +redef_key_NO_SPACE:
 +
 +    cp 'c'                             ; ¿Es 'c'? Imprimir "CS"
 +    jr nz, redef_key_NO_CAPSSHIFT
 +    ld de, redef_key_cs
 +    call PrintString
 +    jr redef_key_end
 +redef_key_NO_CAPSSHIFT:
 +
 +    cp 'y'                             ; ¿Es 'y'? Imprimir "SS"
 +    jr nz, redef_key_NO_SYMBOLSHIFT
 +    ld de, redef_key_ss
 +    call PrintString
 +    jr redef_key_end
 +redef_key_NO_SYMBOLSHIFT:
 +                                       ; Si llegamos aqui no era tecla especial.
 +    rst 16                             ; Ninguna tecla especial => Print ASCII
 +    ld a, d
 +
 +redef_key_end:
 +    ld a, h                            ; Recuperamos scancode
 +    call PrintCR                       ; Imprimir retorno de carro
 +
 +    pop hl
 +    pop de
 +    ret                                ; Volver con registros preservados
 +
 +redef_key_enter DB "ENTER", _EOS
 +redef_key_space DB "SPACE", _EOS
 +redef_key_ss    DB "SS", _EOS
 +redef_key_cs    DB "CS", _EOS
 +
 +;;; Insertar aqui el codigo de Find_Key
 +;;; Insertar aqui el codigo de Scancode2ASCII
  
-Debemos incluir, además, el código de Wait_For_Keys_Released y +    ;-- Incluir libreria de utilidades -- 
-; de Find_Key dentro de este ejemplo para que ensamble correctamente.+    INCLUDE "utils.asm"
  
-; Nota: recuerda que acabando el programa con END 50000 no sería necesario +    END 33500
-; ejecutarlo manualmente con randomize usr 50000 al ensamblarlo con PASMO.+
 </code> </code>
  
- Este ejemplo proporcionará en pantalla (hasta que se pulse ENTER) +Para esto, hemos creado 2 nuevas funciones:
-una salida como la siguiente:+
  
-{{ cursos:ensamblador:scancode.png |Representación binaria de las teclas pulsadas}}+   * **Redefinir_Teclas**Imprime los mensajes y llama a una función para redefinir cada tecla.
  
- Mediante Byte2ASCII_Hex convertimos el scancode en una cadena de 2 caracteres, que imprimimos en pantalla con PrintHexEsta funciónPrintHexhace uso de la RST 16 para trazar caracteres por pantalla en la posición actual del cursor.+   * **Redefine_Key**: Lee el teclado con ''Find_Key'' en un bucle que se repite mientras no haya ninguna tecla pulsada, y después imprimir por pantalla el ASCII pulsadoSi el ASCII es 'e''s''c' o 'y' es porque estamos ante una de las teclas especiales (ENTER, SPACE, CAPS-SHIFT o SYMBOL-SHIFT) por lo que en ese caso lo que se imprime no es el ASCII sino una cadena.
  
- Por otra parte, se incluye una rutina PrintBin para mostrar el estado de los diferentes bits del valor del scancode mediante pixeles "encendidos" y "apagados". Como véis, la rutina PrintBin es una forma rudimentaria de mostrar en +Este es el aspecto del programa en ejecución:
-pantalla el valor del registro A en binario. Lo que muestra es una representación +
-gráfica binaria de las teclas pulsadas.+
  
- Los scancodes asociados a las diferentes teclas son:+\\  
 +{{ cursos:ensamblador:09_teclpack_01.png?640 |Redefinición del teclado}} 
 +\\ 
  
  
-^ Teclas: ^ 1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 ^ 9 ^ 0 ^  +\\  
-^ Scancodes: | $24 | $1C | $14 | $0C | $04 | $03 | $0B | $13 | $1B | $23 |+==== UDGs para las teclas especiales ====
  
-^ Teclas: ^ Q ^ W ^ E ^ R ^ T ^ Y ^ U ^ I ^ O ^ P ^  +Si en lugar de imprimir las cadenas "ENTER", "SPACE", "SS" y "CS" (como se hace en el ejemplo anterior) queremos imprimir unos caracteres con símbolos compactos para las teclas, podemos utilizar UDGs para la tabla de Scancode2Ascii.
-^ Scancodes: | $25 | $1D | $15 | $0D | $05 | $02 | $0A | $12 | $1A | $22 |+
  
-^ Teclas^ A ^ S ^ D ^ F ^ G ^ H ^ J ^ K ^ L ^ ENTER ^  +Nuestro compañero //Juan Antonio Rubio// nos proporciona los siguientes UDGs que podemos "pokear" en la dirección adecuada para utilizarlos con los código de carácter que deseemos:
-^ Scancodes: | $26 | $1E | $16 | $0E | $06 | $01 | $09 | $11 | $19 |  $21  |+
  
-^ Teclas: ^ CAPS ^ Z ^ X ^ C ^ V ^ B ^ N ^ M ^ SYMB ^ SPACE ^  +<code z80> 
-^ Scancodes: |  $27  | $1F | $17 | $0F | $07 | $00 $08 | $10 |  $18  |  $20  |+    DB $10,$28,$44,$C6,$28,$28,$38,$00 ; Caps Shift 
 +    DB $60,$80,$46,$28,$C4,$02,$0C,$00 ; Symbol Shift 
 +    DB $05,$05,$25,$5D,$81,$5E,$20,$00 ; Enter 
 +    DB $00,$00,$00,$00,$82,$82,$FE,$00 ; Espacio 
 +</code>
  
-  +Los cuales tienen el siguiente aspecto:
- Estos valores nos serán necesarios si queremos establecer unos scancodes por +
-defecto para las teclas del programa, de forma que si el usuario no las +
-redefine, tengan unos valores de comprobación determinados para la rutina +
-de chequeo que veremos a continuación.+
  
 +\\ 
 +{{ :cursos:ensamblador:udgs_teclas.png?106 }}
 +\\ 
  
 \\  \\ 
 ===== Chequeando las teclas redefinidas ===== ===== Chequeando las teclas redefinidas =====
  
- Llegados a este punto tenemos una función que nos devuelve un "scancode"  + Llegados a este punto disponemos de una función que nos devuelve el scancode de una tecla pulsada, y una función de redefinición de teclas que permite que el usuario pulse la tecla correspondiente, y nosotros podamos almacenar en variables de memoria los valores que nos devuelve dicha función. En lugar de dar a estas variables un valor de 0 por defecto, tenemos una tabla de "scancodes" que nos permitiría definir unas "teclas iniciales" (como **O, P, Q, A, ESPACIO**) si el jugador decide no utilizar la función de redefinición de teclado:
-propio (creado a nuestra medida) de una tecla pulsada. De esta formapodemos +
-almacenar en variables de memoria (por ejemplo: "tecla_arriba DEFB 0"los +
-valore que nos devuelve dicha función. En lugar de dar a estas variables un valor de 0 por defecto, tenemos una tabla de "scancodes" que nos permitiría definir unas "teclas iniciales" como:+
  
 <code z80> <code z80>
-tecla_arriba DEFB $25 +tecla_izq     DEFB  $1a       ; O 
-tecla_abajo  DEFB $26 +tecla_der     DEFB  $22       ; P 
-tecla_izq    DEFB $1A +tecla_arriba  DEFB  $25       ; Q 
-tecla_der    DEFB $22 +tecla_abajo   DEFB  $26       ; A 
-tecla_disp   DEFB $20+tecla_disp    DEFB  $20       ; SPACE
 </code> </code>
  
- Dichos valores podrán ser modificados (o no) por la rutina de redefinición + Como hemos dicho, estos valores podrán ser modificados (o no) por la rutina de redefinición del teclado en el menú del juego.
-del teclado.+
  
- Lo único que nos falta para un control total del teclado en nuestro juego + Lo único que nos falta para desarrollar la lectura del teclado en el juego sería una rutina que reciba un scancode y nos indique si dicho scancode está pulsado o no. De esta forma, llamaríamos a la rutina 5 veces, poniendo el valor de las diferentes teclas (tecla_arriba, tecla_abajo, etc.) en el registro A antes de cada llamada, para conocer el estado de las mismas.
-sería una rutina que reciba un scancode y nos indique si dicho scancode +
-está pulsado o no. De esta forma, llamaríamos a la rutina 5 veces, poniendo +
-el valor de las diferentes teclas (tecla_arriba, tecla_abajo, etc.) en el +
-registro A antes de cada llamada, para conocer el estado de las mismas.+
  
- Llamaremos a esta rutina Check_Key:+ Llamaremos a esta rutina ''Check_Key'':
  
 <code z80> <code z80>
 +;-----------------------------------------------------------------------
 ; Chequea el estado de una tecla concreta, aquella de scancode ; Chequea el estado de una tecla concreta, aquella de scancode
 ; codificado en A (como parametro de entrada). ; codificado en A (como parametro de entrada).
 ; ;
 +; Entrada:     A = scancode (00SSSTTT).
 ; Devuelve:    CARRY FLAG = 0 -> Tecla pulsada ; Devuelve:    CARRY FLAG = 0 -> Tecla pulsada
 ;              CARRY FLAG = 1 y BC = 0 -> Tecla no pulsada ;              CARRY FLAG = 1 y BC = 0 -> Tecla no pulsada
-;+;-----------------------------------------------------------------------
 Check_Key: Check_Key:
-   LD CA          ; Copia de A+    push bc 
 +    ld ca             ; Copia de A
  
-   AND 7 +                        ; Operaciones para extraer la semifila 
-   INC +                        ; y la tecla de (00SSSTTT), para leer 
-   LD BA          ; B = 16 - (num. linea dirección)+                        ; el puerto y chequear el bit adecuados 
 +    and %00000111       ; Nos quedamos con la tecla (bit) 
 +    inc a 
 +    ld ba             ; B = 16 - (num. linea direccion) 
 +    srl c 
 +    srl c 
 +    srl c 
 +    ld a, 5 
 +    sub c 
 +    ld c, a             ; C = (semifila + 1)
  
-   SRL C +    ld a$fe
-   SRL C +
-   SRL C +
-   LD A+
-   SUB C +
-   LD C, A          ; C = (semifila + 1)+
  
-   LD A, $FE+CKHiFind:               ; Calcular el octeto de mayor peso del puerto 
 +    rrca 
 +    djnz CKHiFind 
 +    in a($fe)         ; Leemos la semifila
  
-CKHiFind          Calcular el octeto de mayor peso del puerto +CKNXKey: 
-   RRCA +    rra 
-   DJNZ CKHiFind+    dec c 
 +    jr nz, CKNXKey      Ponemos el bit de tecla en el CF 
 +    pop bc
  
-   IN A, ($FE)      ; Leemos la semifila +    ret
- +
-CKNXKey: +
-   RRA +
-   DEC C +
-   JR NZ, CKNXKey   ; Ponemos el bit de tecla en el CF +
-    +
-   RET   +
 </code> </code>
  
- La forma en que se debe llamar a esta rutina sería la siguiente:+ La forma más básica en que se debería llamar a esta rutina en el código de nuestro programa sería la siguiente:
  
 <code z80> <code z80>
 Comprobar_tecla_izquierda: Comprobar_tecla_izquierda:
-  LD A, (teclaizq) +    ld a, (teclaizq) 
-  CALL Check_Key +    call Check_Key 
-  JR C, izq_no_pulsada            ; Carry = 1, tecla no pulsada +    jr c, izq_no_pulsada            ; Carry = 1, tecla no pulsada 
-   + 
-  (acciones a realizar si se pulso izq)+    (acciones a realizar si se pulso izq)
  
 izq_no_pulsada: izq_no_pulsada:
  
 Comprobar_tecla_derecha: Comprobar_tecla_derecha:
-  LD A, (teclader) +    ld a, (teclader) 
-  CALL Check_Key +    call Check_Key 
-  JR C, der_no_pulsada            ; Carry = 1, tecla no pulsada+    jr c, der_no_pulsada            ; Carry = 1, tecla no pulsada 
 + 
 +    (acciones a realizar si se pulso der)
  
-  (acciones a realizar si se pulso der) +    ; Repetir para arriba, abajo, disparo, etc.
-   +
-; Repetir para arriba, abajo, disparo, etc.+
 </code> </code>
  
- También podemos generar una rutina que combine lo que acabamos de ver con la codificación de las direcciones en los 5 bits de un único byte, de la misma forma que lo realizamos con teclas predefinidas. + Veamos a continuación un ejemplo final que permite modificar el valor de una variable en memoria ("valor") mediante las teclas Q y A (sumando o restando 1 a su valor de 8 bits). Cada vez que el valor de la variable cambie, se mostrará en pantalla con nuestra la rutina PrintNum.
- +
- Por supuesto, estas rutinas son sólo de ejemplo y pueden ser modificadas +
-para que devuelvan los resultados en otros flags (Zero, Carry), en otros +
-registros, sean llamadas con diferentes parámetros, etc. +
- +
- Veamos a continuación un ejemplo final que permite modificar el valor +
-de una variable en memoria ("valor") mediante las teclas Q y A (sumando o +
-restando 1 a su valor de 8 bits). Cada vez que el valor de la variable +
-cambie, se mostrará en pantalla con nuestra la sencilla rutina PrintBin.+
  
 <code z80> <code z80>
-  ; Controlando el valor de "valor" con Q y A +; Controlando el valor de "valor" con Q y A 
-  ORG 50000+    ORG 50000
  
-  LD A, (valor) +    call CLS 
-  CALL PrintBin                   ; Imprimimos el scancode en pantalla+Imprimir_Valor: 
 +    ld a, (valor)                   ; Guardamos en A copia del resultado 
 +    ld b, 0 
 +    ld c, a                         ; BC = A (B=0, C=A) 
 +    call PrintNum                   ; Imprimimos el valor en pantalla 
 +    call PrintSpace
  
-Bucle_entrada:+    call Wait_For_No_Key            ; Esperamos a que se suelte la tecla
  
-  LD BC, 20000                    ; Retardo (bucle 20000 iteraciones) +Bucle:
-retardo: +
-  DEC BC +
-  LD A, B +
-  OR C +
-  JR NZ, retardo                  ; Fin retardo+
  
 Comprobar_tecla_mas: Comprobar_tecla_mas:
-  LD A, (tecla_mas) +    ld a, (tecla_mas) 
-  CALL Check_Key +    call Check_Key
-  JR C, mas_no_pulsado            ; Carry = 1, tecla_mas no pulsada+
  
-  LD A(valor) +    jr cComprobar_tecla_menos     Carry = 1, tecla_mas no pulsada
-  INC A +
-  LD (valor), A                   Incrementamos (valor) +
-  JR Print_Valor+
  
-mas_no_pulsado:                   +    ld hl, valor 
 +    inc (hl) 
 +    jr Imprimir_Valor
  
 Comprobar_tecla_menos: Comprobar_tecla_menos:
-  LD A, (tecla_menos) +    ld a, (tecla_menos) 
-  CALL Check_Key +    call Check_Key 
-  JR Cmenos_no_pulsado          ; Carry = 1, tecla_menos no pulsada+    jr cBucle                     ; Carry = 1, tecla_menos no pulsada
  
-  LD A(valor) +    ld hl, valor 
-  DEC A +    dec (hl)
-  LD (valor), A                   ; Decrementamos (valor) +
-  JR Print_Valor+
  
-menos_no_pulsado:+    jp Imprimir_Valor
  
-  JR Bucle_entrada                Repetimos continuamente hasta que se +Variables de teclas 
-                                  ; pulse algo (tecla_mas tecla_menos)+tecla_mas    DEFB   $25 
 +tecla_menos  DEFB   $26
  
-Print_Valor: +; Variable para alojar el valor 
-  LD A, (valor)                   ; Guardamos en A copia del resultado +valor        DEFB  0
-  CALL PrintBin                   ; Imprimimos el scancode en pantalla+
  
-  LD A, (valor)                   ; Guardamos en A copia del resultado +    INCLUDE "utils.asm" 
-  CALL PrintHex                   Imprimimos el scancode HEX en pantalla+    ;; Nota: Incluir también en el código el código de Check_Key.
  
-  JR Bucle_entrada                ; Repetimos+    END 50000 
 +</code>
  
-valor        DEFB  0 +\\  
-tecla_mas    DEFB  $25 +{{ :cursos:ensamblador:08_checkkey.png?640 }} 
-tecla_menos  DEFB  $26+\\ 
  
 + Queda como ejercicio para el lector la modificación del programa para que, antes de entrar en el bucle principal, utilice nuestras funciones de redefinición de teclado y lea 2 teclas válidas para cambiar el valor de "//tecla_mas//" y "//tecla_menos//" con respecto a sus valores por defecto.
  
 +\\ 
 +===== Lectura del teclado con bits de estado =====
 +
 +En el bucle principal de nuestro juego deberemos leer el teclado para determinar si el usuario está pulsando alguna de las teclas de dirección redefinidas. Para ello deberemos utilizar ''Check_Key'' con cada una de las 5 teclas (5 scancodes) que tenemos guardados en las variables ''tecla_*'' (''tecla_izq'', ''tecla_der'', etc.) y actuar en consecuencia.
 +
 +Ya hemos hablado de la importancia de empaquetar las diferentes funciones en rutinas reutilizables, así que en este punto deberíamos desarrollar una rutina de nombre ''Leer_Teclado'' la cual realizara todas las llamadas a ''Check_Key'' y nos indicara de alguna forma qué teclas están pulsadas de las que hemos redefinido. ¿Cómo debería devolvernos esa rutina el estado de las diferentes teclas? ¿En 5 registros? ¿En variables de memoria (1 byte por cada tecla), como hemos hecho en un ejemplo anterior? Evidentamente, eso es un desperdicio de espacio.
 +
 +Lo más óptimo para almacenar ese estado es **empaquetar** en los diferentes estados como los bits de un byte, de un único registro de 8 bits. Por ejemplo, así:
 +
 +<code>
 +; BITS            4                0
 +; SIGNIFICADO   FIRE  LEFT  RIGHT  UP   DOWN
 +</code>
 +
 +Después de leer las 5 teclas redefinidas, dejaremos a 0 los bits correspondientes de cada tecla que no esté pulsada, y a 1 los bits de aquellas teclas pulsadas. Podemos devolver este valor en el registro A, por ejemplo, y al volver de la rutina almacenarlo en memoria en alguna variable de estado. Esto nos permitirá después en el resto del flujo del programa comprobar qué teclas están pulsadas:
 +
 +<code z80>
 +    call Leer_Teclado_Empaquetado
 +    ld (estado_teclas), a
 +
 +    ; (...)
 +
 +    ; Mas adelante en el programa
 +    ld a, (estado_teclas)
 +
 +    bit 4, a
 +    call nz, Disparo_Pulsado
 +
 +    bit 1, a
 +    call nz, Salto_Pulsado
 +
 +    ; etc...
 +
 +estado_teclas  DEFB 0
 +</code>
 +
 +Además, este mecanismo de empaquetado nos permite disponer más de las 5 teclas de las que hemos hablado, ya que nos quedan otros 3 bits libres (BIT 5, 6 y 7) los cuales podrían ser por ejemplo DISPARO_2, PAUSA y SALIR (o INVENTARIO, o AYUDA...).
 +
 +Veamos un ejemplo de rutina ''Leer_Teclado_Empaquetado'':
 +
 +<code z80>
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-Chequea el estado de una tecla concreta, aquella de scancode +Lee el estado de las teclas definidas en variables y almacena en A 
-; codificado en A (como parametro de entrada).+; dicho estado (1=pulsada, 0=no pulsada). El byte está codificado así:
 ; ;
-Devuelve:    CARRY FLAG = -> Tecla pulsada +BITS            4    3    2        0 
-             CARRY FLAG 1 y BC 0 -> Tecla no pulsada+SIGNIFICADO   FIRE LEFT RIGHT  UP DOWN 
 +
 +; ENTRADA: NADA (usa las variables tecla_*) 
 +;          Se podria modificar para recibir HL direccion tecla1 
 +; SALIDA: byte de estado de las teclas (empaquetadas en bits). 
 +; MODIFICA: A, HL y CarryFlag
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-Check_Key+Leer_Teclado_Empaquetado
-   LD CA          Copia de A+    push de 
 +    ld d0                       D = 0
  
-   AND 7 +    ld hltecla_izq              Apuntamos HL a la primera de las teclas (izq)
-   INC A +
-   LD BA          B = 16 - (num. linea direcció +
-   SRL C +
-   SRL C +
-   SRL C +
-   LD A, 5 +
-   SUB C +
-   LD C, A          ; C = (semifila + 1)+
  
-   LD A, $FE+    ld a, (hl)                    ; leemos su valor 
 +    call Check_Key 
 +    jr c, tecl_izq_notpressed 
 +    set 3, d                      ; Si pulsada, ponemos bit 3 a 1. 
 +                                  ; Usando podríamos usar "or %00001000" 
 +tecl_izq_notpressed: 
 +    inc hl                        ; inc hl a siguiente tecla en memoria (der) 
 +    ld a, (hl)                    ; leemos su valor 
 +    call Check_Key 
 +    jr c, tecl_der_notpressed 
 +    set 2, d                      ; Si pulsadaponemos bit 2 a 1.
  
-CKHiFind          Calcular el octeto de mayor peso del puerto +tecl_der_notpressed: 
-   RRCA +    inc hl                        apuntamos HL a siguiente tecla (arriba) 
-   DJNZ CKHiFind+    ld a, (hl)                    ; leemos su valor 
 +    call Check_Key 
 +    jr c, tecl_arr_notpressed 
 +    set 1, d                      ; Si pulsada, ponemos bit 1 a 1.
  
-   IN A, ($FE     Leemos la semifila+tecl_arr_notpressed: 
 +    inc hl                        ; apuntamos HL a siguiente tecla (abajo) 
 +    ld a, (hl                   leemos su valor 
 +    call Check_Key 
 +    jr c, tecl_aba_notpressed 
 +    set 0, d                      ; Si pulsada, ponemos bit 0 a 1.
  
-CKNXKey+tecl_aba_notpressed
-   RRA +    inc hl                        ; apuntamos HL a siguiente tecla (disparo) 
-   DEC C +    ld a(hl)                    leemos su valorprimera 
-   JR NZCKNXKey   Ponemos el bit de tecla en el CF +    call Check_Key 
-    +    jr c, tecl_fire_notpressed 
-   RET   +    set 4, d                      ; Si pulsada, ponemos bit 4 a 1.
  
 +tecl_fire_notpressed:
 +                                  ; Podriamos añadir codigo para disparo 2
 +    ld a, d
 +    pop de
 +    ret                           ; Devolvemos en A el estado de las teclas
 +</code>
  
-;; Nota: Incluir en el código las siguientes rutinas para re-ensamblarlo: + La rutina simplemente apunta HL a la primera de las teclas y llama a ''Check_Key'' para leer su estado. Si está a 1establece a 1 el bit 3 del registro D. Si está a 0salta a comprobar la siguiente tecla.
-;;       Wait_For_Keys_ReleasedPrintBinPrintHex y Byte2ASCII_Hex.+
  
- END 50000+ Utilizamos ''inc hl'' para saltar a la siguiente tecla porque las variables ''tecla_*'' están seguidas en memoria, por lo que podemos saltar de una a otra con INC o DEC.
  
-</code>+ Repetimos el proceso con las 5 teclas (podríamos añadir más chequeos para teclas adicionales después de comprobar el disparo) y antes de salir copiamos el valor de D en A para devolver el resultado en el acumulador.
  
- Queda como ejercicio para el lector la modificación del programa para que, + A continuación se puede ver un ejemplo de programa completo el cual utiliza la redefinición de teclas muestra por pantalla el valor de la variable de estado empaquetada tal cual la veríamos durante el bucle principal de nuestro juego:
-antes de entrar en el bucle principal, lea 2 teclas válidas y diferentes +
-del teclado para permitir la redefinición de "//tecla_mas//" "//tecla_menos//" +
-con respecto a sus valores por defecto.+
  
-\\  +<code z80> 
-===== Redefinición de las teclas =====+; Redefinir teclas y ver el estado de las teclas elegidas "en el juego"
 +    ORG 33500
  
- En las secciones anteriores hemos visto las rutinas Find_Key y Check_Key para detectar las pulsaciones de teclas y chequear el estado de una tecla concreta. Estas teclas concretas las guardamos en variables de memoria y así podemos permitir al jugador redefinirlas.+    call CLS 
 +    call Redefinir_Teclas 
 +    call CLS 
 +    call Wait_For_No_Key          ; Esperar a que no haya teclas pulsadas
  
- El menú principal deberá de tener una opción que permita modificar el contenido de estas variables de memoria con aquellos scancodes que el jugador elija para controlar el juego.+    ; Bucle del programa, lee nuestras teclas e imprime primero la 
 +    ; "leyenda" de las teclas y luego el byte de estado de teclado: 
 +bucle: 
 +    ld de, 0 
 +    call CursorAt                 ; Nos vamos a (0,0) 
 +    ld de, msg_keys 
 +    call PrintString              ; Imprimimos mensaje "*<>^v"
  
- El sistema de redefinición de teclas debe:+    call Leer_Teclado_Empaquetado 
 +    call PrintBin                 ; Imprimimos estado teclas (A) en binario
  
-A.- Establecer en el arranque del programa unos valores por defecto para las teclas:+    jr bucle                      ; Repetir hasta reset
  
-<code z80> +; Mensajes del programa 
-tecla_arriba DEFB $25 +msg_izq        DEFB  "Izquierda? ", _EOS 
-tecla_abajo  DEFB $26 +msg_der        DEFB  "Derecha? ", _EOS 
-tecla_izq    DEFB $1A +msg_arriba     DEFB  "Arriba? ", _EOS 
-tecla_der    DEFB $22 +msg_abajo      DEFB  "Abajo? ", _EOS 
-tecla_disp   DEFB $20 +msg_disp       DEFB  "Disparo? ", _EOS 
-</code>+msg_keys       DEFB    *<>^v", _CR, _EOS
  
-B.- Repetir N veces (uno por cada control a redefinir):+; Teclas por defecto si no se redefineO P Q A SPACE 
 +tecla_izq      DEFB  $1a 
 +tecla_der      DEFB  $22 
 +tecla_arriba   DEFB  $25 
 +tecla_abajo    DEFB  $26 
 +tecla_disp     DEFB  $20
  
-   * Esperar a que ninguna tecla del teclado esté pulsada (para evitar que la tecla de selección del menú para entrar en la redefinición, o la anterior tecla pulsada, se seleccione como tecla pulsada por el usuario).+;;; Insertar aqui el codigo de Redefinir_Teclas 
 +;;; Insertar aqui el codigo de Redefine_Key 
 +;;; Insertar aqui el codigo de Leer_Teclado_Empaquetado 
 +;;; Insertar aqui el codigo de Find_Key 
 +;;; Insertar aqui el codigo de Scancode2ASCII 
 +;;; Insertar aqui el codigo de Check_Key
  
-   * Mostrar por pantalla el mensaje de "Pulse una tecla para (dirección a redefinir)".+    ;-- Incluir libreria de utilidades -- 
 +    INCLUDE "utils.asm"
  
-   * Esperar una pulsación de teclado del usuario.+    END 33500 
 +</code>
  
-   * Opcionalmente, comprobar que esa pulsación no se corresponda con ninguna de las teclas anteriores, para evitar que el usuario seleccione la misma dirección para, por ejemplo, izquierda y derecha. Este paso es opcional porque el usuario, si se equivoca, siempre puede redefinir de nuevo el teclado con las teclas adecuadas, y para nosotros esta comprobación representa tiempo de programación y espacio ocupado innecesariamente en el programa.+\\  
 +{{ cursos:ensamblador:09_teclpack_02.png?640 |Programa de ejemplo estado del teclado empaquetado en byte}} 
 +\\ 
  
-   * Mostrar al usuario la tecla que ha pulsado a la derecha del mensaje impreso pidiendo dicha tecla. Para eso tenemos que convertir el Scancode en un código ASCII imprimible en pantalla. 
  
-   * Modificar la variable en memoria que deba almacenar el scancode de la tecla pulsada para poder usarla posteriormente en el transcurso del juego (es decir, guardar el scancode obtenido en tecla_arriba, tecla_abajo, tecla_izq, o en la variable que corresponda).+\\  
 +===== Optimizando la rutina de lectura de teclado =====
  
- Hasta ahora tenemos todos los mecanismos necesarios para crear nuestra propia rutina de redefinición de teclas, salvo la rutina para convertir un scancode en su correspondiente ASCII. A continuación tenemos una rutina Scancode2Ascii basada en una tabla que relaciona cada scancode con su ASCII (40 bytes más adelante en la misma tabla):+Hasta este punto hemos creado una serie de rutinas que hacen lo siguiente:
  
-<code z80>  +   * ''Find_Key''Devuelve el scancode de una tecla pulsada. Este scancode tiene codificada la semifila y el bit testear en un sólo byte, como (00SSSTTT). Sólo es llamada durante el proceso de redefinición del teclado.
-;----------------------------------------------------------------------- +
-; Scancode2Asciiconvierte un scancode en un valor ASCII +
-; IN:  D = scancode de la tecla analizar +
-; OUT: A = Codigo ASCII de la tecla +
-;----------------------------------------------------------------------- +
-Scancode2Ascii:+
  
-   push hl +   * ''Redefinir_Teclas'' y ''Redefine_Key'': funciones para redefinir el teclado que llaman a ''Find_Key'' y almacenan el scancode devuelto por esta en variables del tipo ''tecla_izq DEFB 0''. Estas funciones son llamadas para redefinir del teclado, en el menú del juego, y no durante el desarrollo del mismo.
-   push bc+
  
-   ld hl,0 +   * ''Check_Key'': Recibe como parámetro un scancode y hace una serie de operaciones con bits para extraer SSS (semifila) y TTT (tecla). Después realiza la lectura del puerto correspondiente y comprueba el bit correspondiendo (desplazándolo hasta el CarryFlag).
-   ld bc, TABLA_S2ASCII +
-   add hl, bc           ; hl apunta al inicio de la tabla+
  
-   ; buscamos en la tabla un max de 40 veces por el codigo +   * ''Leer_Teclado_Empaquetado'': Esta es la función la que llamamos en el bucle del juego para leer el estado de las diferentes teclas. Hace uso interno de ''Check_Key'' para cada una de las teclas que tenemos definidas en variables (conteniendo los scancodes para cada dirección o botón de acción).
-   ; le sumamos 40 HL, leemos el valor de (HL) y ret A +
-SC2Ascii_1: +
-   ld a, (hl)           ; leemos un byte de la tabla +
-   cp "1"               ; Si es "1" fin de la rutina (porque en +
-                        ; (la tabla habriamos llegado a los ASCIIs) +
-   jr z, SC2Ascii_Exit  ; (y es condicion de forzado de salida)  +
-   inc hl               ; incrementamos puntero de HL +
-   cp d                 ; comparamos si A==D (nuestro scancode) +
-   jr nz, SC2Ascii_1+
  
-SC2Ascii_Found: +Nótese que ''Find_Key'' y el resto de funciones asociadas para redefinir el teclado son llamadas en el menú del juegopor lo que no necesitamos que sean especialmente eficientes y optimizadas.
-   ld bc, 39            ; Sumamos 39(+INC HL=40) para ir a la seccion +
-   add hlbc           ; de la tabla con los codigos ASCII +
-   ld a,(hl)            ; leemos el codigo ASCII de esa tabla+
  
-SC2Ascii_Exit: +Por contra, con nuestra aproximación a la lectura del teclado, estamos llamando a ''Check_Key'' (a través de ''Leer_Teclado_Empaquetado'') N veces (por cada tecla que necesitamos leer). En cada una de estas llamadas estamos (dentro de ''Check_Key'') ejecutando el código que extrae la semifila (puerto) y número de tecla de cada scancode.
-   pop bc +
-   pop hl +
-   ret+
  
-   ; 40 scancodes seguidos de sus ASCIIs equivalentes +Esto es un tiempo valiosísimo desperdiciado dentro del bucle principal del programay que tenemos que repetir en cada iteración del mismo. Cada nueva ejecución del bucle del programatenemos que volver a sacar la semifila y la tecla de cada uno de los 5 scancodes (izquierdaderechaarriba y abajo) dentro de ''Check_Key''.
-TABLA_S2ASCII: +
-   defb $24$1C$14$0C$04, $03, $0B, $13, $1B, $23 +
-   defb $25, $1D, $15, $0D, $05, $02, $0A, $12, $1A, $22 +
-   defb $26, $1E, $16, $0E, $06, $01, $09, $11, $19, $21 +
-   defb $27, $1F, $17, $0F, $07, $00, $08, $10, $18, $20 +
-   defm "1234567890QWERTYUIOPASDFGHJKLecZXCVBNMys" +
-</code>+
  
- La rutina recibe en el registro D el scancode obtenido con la rutina Find_Key y devuelve en el registro A el código ASCII correspondiente directamente imprimible. Los primeros 40 bytes de la tabla contienen los Scancodes y la última línea defm (últimos 40 bytes) los ASCIIs a los que correspondenen orden, los anterior 40 códigos.+Lo lógico, dado que esos valores no cambian durante todo el juego, es que hagamos esa separación de scancode en semifila y columna durante el proceso de redefinición del juegoy no durante el bucle del mismo.
  
- Nótese que las teclas se devuelven como ASCIIs en mayúsculasaprovechando las letras minúsculas para los caracteres especiales no directamente representables:+**En lugar de guardarnos las teclas seleccionadas por el usuario como 1 byte de scancodes por tecla, los guardaremos en 2 bytes: semifila (representada directamente por el puerto a leer) y bit o posición de tecla a comprobar**.
  
-   * e = ENTER +Asi, el chequeo de la tecla durante el juego será mucho más rápido.
-   * c = CAPS SHIFT +
-   * y = SYMBOL SHIFT +
-   * s = SPACE+
  
- De esta forma, "E" se corresponde a la tecla E y "e" a la tecla de ENTERPodemos utilizar estos ASCIIs en minúsculas para mostrar en pantalla cadenas como "ENTER" o "SPACE" durante la redefinición de las teclas.+Al principio del curso comentamos que no debemos obsesionarnos con la optimización extrema de las rutinas que hagamos para nuestros programasDijimos que simplemente debemos de buscar que sean eficientes y que funcionen bien, y esto es correcto para la mayoría que rutinas que realicemos.
  
- A continuación podemos ver un ejemplo que utiliza las rutinas Find_Key y Scancode2Ascii para mostrar en pantalla el código ASCII de cualquier tecla pulsada:+Pero aquellas rutinas que se ejecutan en cada "fotograma", en cada "ejecución del bucle principal" del juego, como imprimir los gráficos del juego, o leer el teclado, deben de revisarse e intentar conseguir que sean lo más óptimas posibles.
  
 +Por eso en este caso, vamos a intentar extraer de las rutinas ''Leer_Teclado_Empaquetado'' y ''Check_Key'' la parte que extrae la semifila y la columna a una función que usaremos durante la redefinición del juego (una vez por tecla, un total de cinco veces, y en el menú de nuestro programa) en lugar de hacerlo cinco veces en cada iteración del bucle del juego.
  
 +A continuación podemos ver el programa del ejemplo anterior, pero reescrito de forma que al redefinir las teclas (en ''Redefinir_Teclas_SSSTTT'') extraemos del scancode de la tecla pulsada directamente el puerto a leer y la tecla a comprobar, lo cual simplificará mucho la rutina de ''Leer_Teclado_Empaquetado'' que deberemos usar en el bucle principal del juego, ya que sólo tendremos que leer de los 5 puertos indicados y comprobar las 5 teclas indicadas.
 +
 +\\ 
 <code z80> <code z80>
-;-------------------------------------------------------------- +Redefinir teclas y ver el estado de las teclas elegidas "en el juego". 
-; Prueba de conversion de Scancode a ASCII +Metodo optimizado con la extraccion de semifila (puerto) tecla durante 
-; Recuerda que para compilarla necesitarás añadir las rutinas +; la redefinicion de las teclas y no durante la lectura del teclado en 
-Find_Key Scancode2ASCII a este listado, se han eliminado +el bucle del juego.
-del mismo para reducir la longitud del programa. +
-;--------------------------------------------------------------+
  
-ORG 32768+    ORG 33500
  
-START: +    call CLS 
-  CALL Wait_For_Keys_Released+    call Redefinir_Teclas_SSSTTT  ; Redefinir teclado 
 +    call Wait_For_No_Key          ; Esperar a que no haya teclas pulsadas 
 +    call CLS
  
-chequear_teclas: +    Bucle del programa, lee nuestras teclas e imprime primero la 
-  CALL Find_Key                   Llamamos a la rutina +    "leyenda" de las teclas y luego el byte de estado de teclado: 
-  JR NZ, chequear_teclas          Repetir si la tecla no es válida +bucle: 
-  INC D +    ld de
-  JR Zchequear_teclas           Repetir si no se pulsó ninguna tecla +    call CursorAt                 Nos vamos a (0,0) 
-  DEC D+    ld de, msg_keys 
 +    call PrintString              ; Imprimimos mensaje "*<>^v"
  
-  ; En este punto D es un scancode valido  +    call Leer_Teclado_SSSTTT 
-  call Scancode2Ascii+    call PrintBin                 ; Imprimimos estado teclas (A) en binario
  
-  En este punto A contiene el ASCII del scancode en D +    jr bucle                      Repetir hasta reset
-  ; lo imprimimos por pantalla con rst 16. +
-  rst 16+
  
-  CALL Wait_For_Keys_Released +; Mensajes del programa 
-  jr START                        ; vuelta a empezar+msg_izq        DEFB  "Izquierda? ", _EOS 
 +msg_der        DEFB  "Derecha? ", _EOS 
 +msg_arriba     DEFB  "Arriba? ", _EOS 
 +msg_abajo      DEFB  "Abajo? ", _EOS 
 +msg_disp       DEFB  "Disparo? ", _EOS 
 +msg_keys       DEFB    *<>^v", _CR, _EOS
  
 +; Teclas por defecto si no se redefine: O P Q A SPACE
 +; Hay que declararlas aqui en el mismo orden en que queremos
 +; que aparezcan en los bits del byte de estado
 +teclas_player_1:
 +p1_puerto_disp       DEFB  $7f      ; Semifila ' ' = puerto $fefe
 +p1_tecla_disp        DEFB  $01      ; Tecla ' ' = bit 1
 +p1_puerto_izq        DEFB  $df      ; Semifila 'O' = puerto $dfFE
 +p1_tecla_izq         DEFB  $02      ; Tecla 'O' = bit 2
 +p1_puerto_der        DEFB  $df      ; Semifila 'P' = puerto $dfFE
 +p1_tecla_der         DEFB  $01      ; Tecla 'P' = bit 1
 +p1_puerto_arriba     DEFB  $fb      ; Semifila 'Q' = puerto $fbFE
 +p1_tecla_arriba      DEFB  $01      ; Tecla 'Q' = bit 1
 +p1_puerto_abajo      DEFB  $fd      ; Semifila 'A' = puerto $fdFE
 +p1_tecla_abajo       DEFB  $01      ; Tecla 'A' = bit 1
 +
 +P1_NUM_TECLAS        EQU          ; Usaremos 5 teclas: OPQA<ESP>
 +
 +; Estado de las tecla pulsadas
 +p1_teclas_pulsadas   DEFB  0
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-Esta rutina espera que no haya ninguna tecla pulsada para volver.+Utiliza Redefine_Key para obtener las semifilas y tecla (00TTTSSS) 
 +; del scancode seleccionado por el usuario pulsando una tecla. 
 +; La semifila se guarda como puerto leer  y la tecla como 
 +; el bit (posición) de esa tecla en la respuesta. 
 +; Ejemplo: 'O' = p1_puerto_izq = $df 
 +;                p1_tecla_izq = 2 (%00000010) 
 +; Llama a "Redefine_Key" para mostrar el mensaje y pedir la tecla. 
 +
 +; ENTRADA: Nada 
 +; SALIDA:  Nada 
 +; MODIFICA: AF, DE, HL, Flags
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-Wait_For_Keys_Released+Redefinir_Teclas_SSSTTT
- XOR A +    ; Los siguientes textos se podrian haber impreso tambien usando un bucle 
- IN A, (254+    ; con inc de (avanzar cadena) e inc hl (avanzar tecla a escribir) 
- OR 224 +    ld demsg_izq 
- INC A +    call PrintString              ; Imprimir mensaje "Izquierda?" 
- JR NZWait_For_Keys_Released +    call Redefine_Key             ; Esperar pulsacion (e imprimir ASCII
- RET+    call Scancode_To_Port_Key 
 +    ld hl, p1_puerto_izq          ; Apuntamos HL a la p1_puerto_izq 
 +    ld (hl)e                    ; Guardamos la semifila 
 +    inc hl                        ; Incrementamos HL => apuntamos a p1_tecla_x 
 +    ld (hl), d                    ; Guardamos la tecla
  
 +    ld de, msg_der                ; Siguiente mensaje: "Derecha?"
 +    call PrintString
 +    call Redefine_Key             ; Esperar pulsacion (e imprimir ASCII)
 +    call Scancode_To_Port_Key
 +    ld hl, p1_puerto_der          ; Apuntamos HL a la p1_puerto_X
 +    ld (hl), e                    ; Guardamos la semifila
 +    inc hl                        ; Incrementamos HL => apuntamos a p1_tecla_x
 +    ld (hl), d                    ; Guardamos la tecla
 +
 +    ld de, msg_arriba             ; Repetimos con ARRIBA
 +    call PrintString
 +    call Redefine_Key
 +    call Scancode_To_Port_Key
 +    ld hl, p1_puerto_arriba       ; Apuntamos HL a la p1_puerto_X
 +    ld (hl), e                    ; Guardamos la semifila
 +    inc hl                        ; Incrementamos HL => apuntamos a p1_tecla_x
 +    ld (hl), d                    ; Guardamos la tecla
 +
 +    ld de, msg_abajo              ; Repetimos con ABAJO
 +    call PrintString
 +    call Redefine_Key
 +    call Scancode_To_Port_Key
 +    ld hl, p1_puerto_abajo        ; Apuntamos HL a la p1_puerto_X
 +    ld (hl), e                    ; Guardamos la semifila
 +    inc hl                        ; Incrementamos HL => apuntamos a p1_tecla_x
 +    ld (hl), d                    ; Guardamos la tecla
 +
 +    ld de, msg_disp               ; Repetimos con DISPARO
 +    call PrintString
 +    call Redefine_Key
 +    call Scancode_To_Port_Key
 +    ld hl, p1_puerto_disp         ; Apuntamos HL a la p1_puerto_X
 +    ld (hl), e                    ; Guardamos la semifila
 +    inc hl                        ; Incrementamos HL => apuntamos a p1_tecla_x
 +    ld (hl), d                    ; Guardamos la tecla
 +    ret
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
-;--- Introducir aquí las rutinas Find_Keys Scancode2ASCII ------------+Scancode_To_Port_Key: Extrae de un scancode 00TTTSSS 
 +; los valores TTT SSS, para guardarlos después por separado como 
 +; PUERTO y TECLA 
 +; Escrita por Xor_A [Fer]. 
 +
 +; ENTRADA: A = scancode de teclado (00TTTSSS) 
 +; SALIDA:  D = NUMERO de bit de TECLA. 
 +;          E = PUERTO (parte alta) correspondiente a esa SEMIFILA 
 +; Ejemplo: 'O' = D = $df 
 +;                E = 2 (%00000010) 
 +
 +; MODIFICA: A, DE, E, FLAGS
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 +Scancode_To_Port_Key:
 +    ld d, a                  ; Hacer copia de scancode en D
 +    and %00000111            ; Eliminar todos los bits menos 00000SSS
 +    cpl                      ; Invertir valores (1/0)
 +    ld e, a                  ; E = contador para bucle
 +    ld a, $fe                ; Empezamos por primera semifila (determina puerto)
 +    jr z, ConvScn_tecla      ; Si Z==1 => es primera semifila
 +ConvScn_loop1:
 +    rlca                     ; Rotamos A: Siguiente semifila
 +    dec e
 +    jr nz, ConvScn_loop1     ; Si Z==0 => no es la semifila correcta
 +ConvScn_tecla:
 +    ld e, a                  ; Salvaguardamos semifila (puerto a leer) en E
 +    ld a, d                  ; Recuperamos A = 00TTTSSS de nuevo
 +    and %00111000            ; Eliminamos todos los valores menos 00TTT000
 +    ld d, $10                ; Inicializamos a la tecla mas interna
 +    ret z                    ; Si Z==1 => es la tecla interna
 +ConvScn_loop2
 +    rr d                     ; Rotamos: Siguiente tecla de la semifila
 +    sub 8
 +    jr nz, ConvScn_loop2     ; Si Z==0 => NO ES LA TECLA CORRECTA
 +    ret
 +
 +;-----------------------------------------------------------------------
 +; Lee el estado de las teclas definidas en variables y almacena en A
 +; dicho estado (1=pulsada, 0=no pulsada). El byte está codificado así:
 +;
 +; BITS            4    3    2        0
 +; SIGNIFICADO   FIRE LEFT RIGHT  UP DOWN
 +;
 +; ENTRADA: NADA (usa las variables p1_puerto_* y p1_tecla_*)
 +; SALIDA:  A = byte de estado de las teclas (empaquetadas en bits).
 +; MODIFICA: A, HL y CarryFlag
 +;-----------------------------------------------------------------------
 +Leer_Teclado_SSSTTT:
 +    ld de, P1_NUM_TECLAS*256     ; D = 5 (teclas a leer), E = 0
 +    ld hl, teclas_player_1       ; 1a tecla (p1_puerto_X) para bit + alto
 +    ld c, $fe                    ; Parte baja puerto teclado
 +
 +tecl_sssttt_loop:
 +    ld b, (hl)                   ; cogemos puerto a leer (parte alta)
 +    inc hl                       ; pasamos al siguiente byte (p1_tecla_X)
 +    in a, (c)                    ; leer del puerto de esta tecla
 +    cpl                          ; Ahora: 1 teclas pulsadas, 0 no pulsadas
 +    and %00011111                ; aislamos las teclas
 +    and (hl)                     ; comparo con la tecla en memoria (carry OFF)
 +    jr z, tecl_sssttt_nopulsada  ; Si Z == 1 => tecla no pulsada
 +    scf                          ; Ponemos Carry = 1 para meterlo en A al rotar
 +
 +tecl_sssttt_nopulsada:
 +    rl e                         ; meto el valor de la pulsacion de la tecla
 +                                 ; (carry OFF no pulsada/carry ON pulsada)
 +    inc hl                       ; apunto a siguiente puerto (semifila)
 +    dec d                        ; decrementamos el num. de teclas pendientes por leer
 +    jr nz, tecl_sssttt_loop      ; siempre necesita una pulsacion para salir del bucle
 +    ld a, e                      ; Recuperamos estado final
 +    ld (p1_teclas_pulsadas), a   ; Guardamos en variable el estado de las teclas
 +    ret                          ; Devolvemos en A el estado de las teclas
 +
 +;;; Insertar aqui el codigo de Redefine_Key
 +;;; Insertar aqui el codigo de Scancode2ASCII
 +;;; Insertar aqui el codigo de Find_Key
 +
 +    ;-- Incluir libreria de utilidades --
 +    INCLUDE "utils.asm"
 +
 +    END 33500
  
-END 32768 
 </code> </code>
 +\\ 
  
- Una vez en ejecución y tras pulsar múltiples teclaseste es el aspecto del programa anterior:+   * Al empezar el programa, llamamos a ''Redefinir_Teclas_SSSTTT'' para que repita 5 veces el mismo proceso:\\  
 +      * Se imprime por pantalla el mensaje informativo de cada tecla para el usuario ("Izquierda? ").\\  
 +      * Se llama a ''Redefine_Key'', la cual: 
 +        * Usa ''Find_Key'' para esperar una pulsación del teclado. 
 +        * Usa ''Scancode2Ascii'' para obtener el código ASCII correspondiente al scancode pulsado. 
 +        * Imprimir por pantalla el ASCII para que el usuario vea qué tecla ha sido pulsada. 
 +        * Devuelve el scancode pulsado por el usuario en A\\  
 +      * Se llama a la nueva rutina ''Scancode_To_Port_Key'' con el scancode obtenido. Esta rutina devuelve en E la parte alta del puerto (si devuelve **$XX**el puerto es **$XXFE**) y en D la posición (bit) de la tecla en cuestión, si quisiéramos leer la tecla correspondiente al scancode elegido por el usuario.\\  
 +      * Esta información de **puerto** y **posicion_de_tecla** se guarda en un conjunto de variables ''p1_puerto_*'' y ''p1_tecla_*'' que tienen que estar en memoria dispuestas de una forma determinada.\\  
 +\\  
 + 
 +Al acabar la redefinición de teclas, tendremos en estas variables los datos de puerto y tecla para cada una de los "botones de acción" del juego:
  
 \\  \\ 
-{{ cursos:ensamblador:sc2ascii.png |Conversión de Scancode ASCII}}+<code z80> 
 +; Teclas por defecto si no se redefineO P Q A SPACE 
 +; Hay que declararlas aqui en el mismo orden en que queremos 
 +; que aparezcan en los bits 
 +teclas_player_1: 
 +p1_puerto_disp       DEFB  $7f      ; Semifila ' ' = puerto $fefe 
 +p1_tecla_disp        DEFB  $01      ; Tecla ' ' = bit 1 
 +p1_puerto_izq        DEFB  $df      ; Semifila 'O' = puerto $dfFE 
 +p1_tecla_izq         DEFB  $02      ; Tecla 'O' = bit 2 
 +p1_puerto_der        DEFB  $df      ; Semifila 'P' = puerto $dfFE 
 +p1_tecla_der         DEFB  $01      ; Tecla 'P' = bit 1 
 +p1_puerto_arriba     DEFB  $fb      ; Semifila 'Q' = puerto $fbFE 
 +p1_tecla_arriba      DEFB  $01      ; Tecla 'Q' = bit 1 
 +p1_puerto_abajo      DEFB  $fd      ; Semifila 'A' = puerto $fdFE 
 +p1_tecla_abajo       DEFB  $01      ; Tecla 'A' = bit 1 
 + 
 +P1_NUM_TECLAS        EQU          ; Usaremos 5 teclas: OPQA<ESP> 
 +</code> 
 +\\  
 + 
 +La rutina ''Check_Key'', que leía el estado de una tecla y para hacerlo necesitaba realizar las mismas operaciones que hace ''Scancode_To_Port_Key'', ya no es necesaria. 
 + 
 +Ahora, en el bucle principal del programa, usaremos ''Leer_Teclado_SSSTTT'' para que lea las 5 teclas rápidamente, ya que sabemos sus puertos y sus posiciones en el valor leído del puerto, lo cual es muchísimo más óptimo. 
 + 
 +Esta rutina lo que hace es un bucle de N iteraciones (siendo N el valor de ''P1_NUM_TECLAS'') y va recorriendo con HL toda la tabla de puertos/teclas para leer el puerto y comprobar el estado de la tecla. Si encuentra la tecla pulsada, pone un 1 en el CARRY FLAG, y si no está pulsada lo deja en 0. A continuación rota con ''rl e'' el registro donde vamos almacenando el estado de las teclas para que entre ese 1 o ese 0 desde la derecha. Este es el motivo por el cual el orden en que definimos las teclas en el bloque de puertos/teclas es importante, ya que la primera tecla se quedará en el BIT ''P1_NUM_TECLAS-1'' por el bucle que estamos realizando. 
 + 
 +\\  
 +<code> 
 +; BITS            4    3    2        0 
 +; SIGNIFICADO   FIRE LEFT RIGHT  UP DOWN 
 +</code> 
 +\\  
 + 
 +Nótese que **el orden de los bits de estado no es el orden en que hemos redefinido la teclas** (ese orden no importa, podemos pedirle las teclas al usuario en el orden en que queramos), sino que **se corresponde con el orden en que aparecen las teclas (DEFB) en el programa**, junto la variable ''P1_NUM_TECLAS''
 + 
 +Como esta variable en nuestro ejemplo vale 5, el primer bit de la lista de teclas (disparo) será el 5º bit (bit 4), la siguiente tecla (izquierda) será el 4º (BIT 3), y así hasta la última variable. 
 + 
 +Si quisiéramos añadir una segunda tecla de disparo, deberíamos aumentar ''P1_NUM_TECLAS'' a 6, y añadir las variables ''p1_puerto_disp2'' y ''p1_tecla_disp2'' las primeras de la lista, justo bajo ''teclas_player_1'', para que su estado aparezca en el 6º bit (bit 5) del registro de estado de teclas. Si queremos añadir dos teclas más, el procedimiento sería similar, y se usaría otro bit del byte de estado. 
 + 
 +\\  
 +<code z80> 
 +; Teclas por defecto si no se redefine: O P Q A SPACE 
 +; Hay que declararlas aqui en el mismo orden en que queremos 
 +; que aparezcan en los bits 
 +teclas_player_1: 
 +p1_puerto_pause      DEFB  $fe      ; Semifila 'C' = puerto $feFE 
 +p1_tecla_pause       DEFB  $08      ; Tecla 'C' = bit 4 
 +p1_puerto_disp2      DEFB  $fe      ; Semifila 'V' = puerto $feFE 
 +p1_tecla_disp2       DEFB  $10      ; Tecla 'V' = bit 5 
 +p1_puerto_disp       DEFB  $7f      ; Semifila ' ' = puerto $fefe 
 +p1_tecla_disp        DEFB  $01      ; Tecla ' ' = bit 1 
 +p1_puerto_izq        DEFB  $df      ; Semifila 'O' = puerto $dfFE 
 +p1_tecla_izq         DEFB  $02      ; Tecla 'O' = bit 2 
 +p1_puerto_der        DEFB  $df      ; Semifila 'P' = puerto $dfFE 
 +p1_tecla_der         DEFB  $01      ; Tecla 'P' = bit 1 
 +p1_puerto_arriba     DEFB  $fb      ; Semifila 'Q' = puerto $fbFE 
 +p1_tecla_arriba      DEFB  $01      ; Tecla 'Q' = bit 1 
 +p1_puerto_abajo      DEFB  $fd      ; Semifila 'A' = puerto $fdFE 
 +p1_tecla_abajo       DEFB  $01      ; Tecla 'A' = bit 1 
 + 
 +P1_NUM_TECLAS        EQU          ; Usaremos 7 teclas 
 +</code> 
 +\\  
 + 
 +Esto dejaría el byte de estado de la siguiente forma: 
 + 
 +\\  
 +<code> 
 +; BITS                      3    2        0 
 +; SIGNIFICADO  PAUSE FIRE2 FIRE LEFT RIGHT  UP DOWN 
 +</code> 
 +\\  
 + 
 +Este conjunto de rutinas es mucho más eficiente para utilizarla en el bucle principal del juego, ya que si examinamos ''Leer_Teclado_SSSTTT'', tiene muchas menos instrucciones y puede leer directamente los 5 puertos que necesitamos leer sin operaciones intermedias innecesarias que ya hemos hecho en el proceso de redefinición a costa de necesitar (sin que ello suponga un problema en términos de tamaño) doble número de variables (de N scancodes pasamos a 2*N puertos+teclas) 
 + 
 +Si quisiéramos tener teclas para el segundo jugador, podríamos replicar las funciones y añadir unas variables ''p2_puerto_*'' y ''p2_tecla_*'' para almacenar sus valores, y modificar ''Leer_Teclado_SSSTTT'' para que lea las teclas del segundo jugador. 
  
 \\  \\ 
 ===== ISSUE 2 vs ISSUE 3 ===== ===== ISSUE 2 vs ISSUE 3 =====
  
- Una recomendación a la hora de testear el estado de las teclas es que utilicemos las herramientas de que nos provee el Z80 para testear los bits del valor devuelto por IN, en lugar de, simplemente tratar de comparar el valor del estado del teclado con algún valor predefinido. Esto evitará que nuestro programa funcione de forma diferente en Spectrums con teclado ISSUE 2, ISSUE 3, o con algún periférico conectado.+ Una recomendación a la hora de verificar el estado de las teclas es que, como hemos hecho en nuestras rutinas, utilicemos las herramientas de testeo de bits (''BIT'' y ''AND''de que nos provee el Z80 para testear los bits del valor devuelto por ''IN'', en lugar de, simplemente tratar de comparar el valor del estado del teclado con algún valor predefinido. Esto evitará que nuestro programa funcione de forma diferente en Spectrums con teclado ISSUE 2, ISSUE 3, o con algún periférico conectado.
  
- Si alguna vez has cargado un snapshot de algún juego en un emulador y has visto que el personaje se movía "sólo", como si alguien estuviera pulsando teclas que realmente no están pulsadas (por ejemplo, Abu Simbel Profanation), y has tenido que activar la opción "ISSUE 2 KEYBOARD EMULATION" para que funcione adecuadamente, entonces ya has sufrido los efectos de una incorrecta lectura del teclado. + Si alguna vez has cargado un snapshot de algún juego en un emulador y has visto que el personaje se movía "sólo", como si alguien estuviera pulsando teclas que realmente no están pulsadas (por ejemplo, Abu Simbel Profanation), y has tenido que activar la opción "ISSUE 2 KEYBOARD EMULATION" para que funcione adecuadamente, entonces ya has sufrido los efectos de una incorrecta lectura del teclado.
  
  Ahora mismo veremos por qué, y empezaremos para ello recordando uno de los primeros párrafos de esta entrega:  Ahora mismo veremos por qué, y empezaremos para ello recordando uno de los primeros párrafos de esta entrega:
Línea 1014: Línea 1527:
 del "1" al "5". // del "1" al "5". //
  
 +|< 50% >|
 ^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ ^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^
 | Teclas: | XX | XX | XX | "5" | "4" | "3" | "2" | "1" | | Teclas: | XX | XX | XX | "5" | "4" | "3" | "2" | "1" |
  
- Con esta información "rescatada", volvamos al punto en que estábamos: A la hora de comprobar si la tecla "2" está pulsada, lo recomendable es testear (por ejemplo, con el nmemónico *BIT*), el bit 1 del valor devuelto por un IN del puerto 63486. Recordemos que dicho bit valdrá 0 si la tecla está pulsada, y 1 si no lo está.+ Con esta información "rescatada", volvamos al punto en que estábamos: A la hora de comprobar si la tecla "2" está pulsada, lo recomendable es testear (por ejemplo, con el nmemónico **BIT**), el bit 1 del valor devuelto por un IN del puerto 63486. Recordemos que dicho bit valdrá 0 si la tecla está pulsada, y 1 si no lo está.
  
- Teniendo en cuenta que un bit a "1" significa tecla no pulsada y "0" significa pulsada, si en nuestro teclado no está pulsada ninguna tecla del 1 al 5, los últimos 5 bits del valor leído del puerto serán "11111b", y que si está pulsada la tecla "2", tendremos "11101b"+ Teniendo en cuenta que un bit a "1" significa tecla no pulsada y "0" significa pulsada, si en nuestro teclado no está pulsada ninguna tecla del 1 al 5, los últimos 5 bits del valor leído del puerto serán "11111b", y que si está pulsada la tecla "2", tendremos "11101b".
  
  Viendo esto, podría surgirnos la tentación de utilizar COMPARACIONES para chequear el estado de la tecla "2". Pulsamos "2" en nuestro Spectrum, leemos el valor del puerto, y obtenemos 253 ("11111101b"), con lo cual basamos el chequeo de teclas de nuestro programa en cosas como el siguiente pseudocódigo:  Viendo esto, podría surgirnos la tentación de utilizar COMPARACIONES para chequear el estado de la tecla "2". Pulsamos "2" en nuestro Spectrum, leemos el valor del puerto, y obtenemos 253 ("11111101b"), con lo cual basamos el chequeo de teclas de nuestro programa en cosas como el siguiente pseudocódigo:
  
 <code> <code>
- valor = IN(63486) +valor = IN(63486) 
- SI valor == 253 ENTONCES TECLA_DOS_PULSADA+SIvalor == 253 
 +ENTONCESTECLA_DOS_PULSADA
 </code> </code>
  
  Comparando con 253 (11111101b), estamos asumiendo que los bits D7, D6 y D5 valen siempre 1, porque en *nuestro* Spectrum es así, pero ... ¿Qué valor tienen los bits D7, D6 y D5? La realidad es que la gran mayoría de las veces será, efectivamente, 1, pero este valor puede verse alterado si tenemos determinados periféricos hardware conectados al bus de expansión trasero, e incluso existen unos determinados modelos de placas (ISSUE 2) que contienen otros valores en estos bits.  Comparando con 253 (11111101b), estamos asumiendo que los bits D7, D6 y D5 valen siempre 1, porque en *nuestro* Spectrum es así, pero ... ¿Qué valor tienen los bits D7, D6 y D5? La realidad es que la gran mayoría de las veces será, efectivamente, 1, pero este valor puede verse alterado si tenemos determinados periféricos hardware conectados al bus de expansión trasero, e incluso existen unos determinados modelos de placas (ISSUE 2) que contienen otros valores en estos bits.
  
- Uno de los componentes del grupo //Mojon Twins//, na_th_an nos proporciona a través de su blog la siguiente prueba de concepto BASIC: + Uno de los componentes del grupo //Mojon Twins//, na_th_annos proporciona a través de su blog la siguiente prueba de concepto BASIC:
-\\ +
  
 +\\ 
 //Una ligera prueba en Spectaculator, que puede configurarse para que use el teclado de issue 2, nos da los valores que buscamos para los bits que desconocemos. Sólo tenemos que teclear y ejecutar este pequeño programa, y fijarnos como normalmente obtenemos 253 y 254 (255 sin pulsar nada) para las pulsaciones de O y P, respectivamente, y otros valores diferentes si activamos el teclado issue 2 en las opciones del emulador:// //Una ligera prueba en Spectaculator, que puede configurarse para que use el teclado de issue 2, nos da los valores que buscamos para los bits que desconocemos. Sólo tenemos que teclear y ejecutar este pequeño programa, y fijarnos como normalmente obtenemos 253 y 254 (255 sin pulsar nada) para las pulsaciones de O y P, respectivamente, y otros valores diferentes si activamos el teclado issue 2 en las opciones del emulador://
  
 <code basic> <code basic>
-10 PRINT AT 0,0; IN 57342; ” “+10 PRINT AT 0,0; IN 57342; " "
 20 GOTO 10 20 GOTO 10
 </code> </code>
  
-//Los valores que obtenemos para estas pulsaciones son 189 y 190 (con 191 sin pulsar), lo que significa que los bits desconocidos son nada más y nada menos que XXX = 101. Podremos garantizar que nuestro programa funcionará en todos los Spectrum si comparamos siempre con ambos valores (por ejemplo, para detectar O deberíamos mirar si IN 57342=253 OR IN 57342=189).//+//Los valores que obtenemos para estas pulsaciones son 189 y 190 (con 191 sin pulsar), lo que significa que los bits desconocidos son nada más y nada menos que XXX = 101. Podremos garantizar que nuestro programa funcionará en todos los Spectrum si comparamos siempre con ambos valores (por ejemplo, para detectar O deberíamos mirar si IN 57342=253 or iN 57342=189).//
  
  La solución que Na_th_an nos expone en el párrafo anterior está orientada a la creación de programas en BASIC, dado que la variante "ZX Spectrum" de este lenguaje no dispone de operaciones de testeo de bits, pero en nuestro caso, en ensamblador, la mejor opción para leer una tecla (pongamos "P" en el siguiente ejemplo) sería:  La solución que Na_th_an nos expone en el párrafo anterior está orientada a la creación de programas en BASIC, dado que la variante "ZX Spectrum" de este lenguaje no dispone de operaciones de testeo de bits, pero en nuestro caso, en ensamblador, la mejor opción para leer una tecla (pongamos "P" en el siguiente ejemplo) sería:
  
 <code z80> <code z80>
-  LD A, $DF            ; Semifila "P" a "Y" +    ld a, $df            ; Semifila "P" a "Y" 
-  IN A, ($FE)          ; Leemos el puerto +    in a, ($fe)          ; Leemos el puerto 
-  BIT 0,             ; Testeamos el bit 0 +    bit 0,             ; Testeamos el bit 0 
-  JR Z, pulsado        ; Si esta a 0 (pulsado) salir.+    jr z, pulsado        ; Si esta a 0 (pulsado) salir.
 </code> </code>
 +
 + Si queremos utilizar la comprobación por valor, o simplemente vamos a saltar con ''or a'' y luego ''JZ'' / ''JNZ'', podríamos simplemente enmascarar con ''and %00011111'' el valor de A antes de la comparación, y dejaríamos a cero los bits implicados.
  
 \\  \\ 
-===== Curiosidades con el teclado =====+===== Curiosidades sobre el teclado =====
  
- 1.- Debido al diseño del Spectrum, existen combinaciones de teclas que siendo pulsadas + 1.- Debido al diseño del Spectrum, existen combinaciones de teclas que siendo pulsadas simultáneamente no permiten la detección de teclas adicionales en la misma u otras filas. A la hora de definir unas teclas por defecto, deberemos de realizar pruebas con nuestro programa para asegurarnos de que podemos pulsar todas las combinaciones de teclas elegidas.
-simultáneamente no permiten la detección de teclas adicionales en la misma u otras +
-filas. A la hora de definir unas teclas por defecto, deberemos de realizar pruebas +
-con nuestro programa para asegurarnos de que podemos pulsar todas las combinaciones +
-de teclas elegidas.+
  
- 2.- Aparte, existen al menos 4 combinaciones de teclas que producen el mismo efecto + 2.- Aparte, existen al menos 4 combinaciones de teclas que producen el mismo efecto que pulsar la tecla BREAK:
-que pulsar la tecla BREAK:+
  
 <code> <code>
- CAPS SHIFT + Z + SYMBOL SHIFT +CAPS SHIFT + Z + SYMBOL SHIFT 
- CAPS SHIFT + X + M +CAPS SHIFT + X + M 
- CAPS SHIFT + C + N +CAPS SHIFT + C + N 
- CAPS SHIFT + V + B+CAPS SHIFT + V + B
 </code> </code>
  
- 3.- Para los casos en los cuales queramos comprobar sólo el bit 0 de una semifila, + 3.- Para los casos en los cuales queramos comprobar sólo el bit 0 de una semifila, podemos ahorrarnos la sentencia BIT utilizando rra para mover el bit b0 al carry flag. ¿La utilidad de esto? Sencillamente que rra se ejecuta en 4 ciclos de reloj mientras que BIT en 8.
-podemos ahorrarnos la sentencia BIT utilizando RRA para mover el bit b0 al +
-carry flag. ¿La utilidad de esto? Sencillamente que RRA se ejecuta en 4 ciclos +
-de reloj mientras que BIT en 8.+
  
 <code z80> <code z80>
- ; Ejemplo: leyendo la tecla espacio (bit 0) +    ; Ejemplo: leyendo la tecla espacio (bit 0) 
- LD A, $7F +    ld a, $7f 
- IN A, ($FE+    in a, ($fe
- RRA                    ; pone b0 en el carry flag +    rra                    ; pone b0 en el carry flag 
- JP NC, key_pressed+    jp nc, key_pressed
 </code> </code>
  
- 4.- Algunas compañías de videojuegos (por ejemplo, ULTIMATE), seleccionaban para + 4.- Algunas compañías de videojuegos (por ejemplo, ULTIMATE), seleccionaban para los juegos teclas como Q, W, E, R y T. Como habéis podido ver en este capítulo, esa selección no es casualidad: todas estas teclas están en la misma semifila del teclado, con lo que se puede leer el estado de todas ellas con una sóla lectura de puerto. Esto permitía ahorrar tanto memoria como tiempo de proceso.
-los juegos teclas como Q, W, E, R y T. Como habéis podido ver en este capítulo, +
-esa selección no es casualidad: todas estas teclas están en la misma semifila +
-del teclado, con lo que se puede leer el estado de todas ellas con una sóla +
-lectura de puerto. Esto permitía ahorrar tanto memoria como tiempo de proceso.+
  
- En ese sentido, la lectura de los joysticks Sinclair (1-5 y 0-9) también es + En ese sentido, la lectura de los joysticks Sinclair (1-5 y 0-9) también es muy cómoda para nuestros programas.
-muy cómoda para nuestros programas.+
  
 \\  \\ 
-===== Microrebotes en la lectura del teclado =====+===== Microrebotes y Ghosting en la lectura del teclado =====
  
- Miguel A. Rodríguez Jódar nos cuenta un detalle a tener en cuenta a la hora de leer las teclas en ensamblador si tenemos como objetivo el leer cadenas de texto y no simplemente "controlar" un personaje.+ Miguel A. Rodríguez Jódar nos cuenta dos detalles a tener en cuenta a la hora de leer las teclas en ensamblador.
  
- El problema: los microrebotes del teclado, provocarán el conocido efecto de "repetición de teclas" aún cuando sólo hayamos pulsado liberado + El primero está relacionado con la lectura de cadenas de texto no simplemente "controlar" un personaje.
  
- Citando a Miguel Ángel:+ El problema: **los microrebotes del teclado**, provocarán el conocido efecto de "repetición de teclas" aún cuando sólo hayamos pulsado y liberado una tecla. Citando a Miguel Ángel:
  
 <code> <code>
-Cuando se usa el teclado para mover un personaje, lo que se busca es ver qué tecla +Cuando se usa el teclado para mover un personaje, lo que se busca es ver qué tecla
 se pulsa, y mientras esté pulsada se mueve en una determinada dirección. Si se suelta se pulsa, y mientras esté pulsada se mueve en una determinada dirección. Si se suelta
 un momento pero después se vuelve a pulsar, el personaje se sigue moviendo, así que un momento pero después se vuelve a pulsar, el personaje se sigue moviendo, así que
 en este caso los microrrebotes parecen no afectar. en este caso los microrrebotes parecen no afectar.
  
-Otra cosa es cuando se usa el teclado para "teclear". En este caso la secuencia es: +Otra cosa es cuando se usa el teclado para "teclear". En este caso la secuencia es:
 esperar a que se pulse una tecla, recoger qué tecla es, almacenarla, esperar a que se esperar a que se pulse una tecla, recoger qué tecla es, almacenarla, esperar a que se
 suelte, y volver al principio. suelte, y volver al principio.
  
-En este caso es cuando ocurre el problema: si el bucle que implementa el algoritmo  +En este caso es cuando ocurre el problema: si el bucle que implementa el algoritmo 
-anterior es muy rápido, es posible escanear el teclado cada pocos microsegundos. Si +anterior es muy rápido, es posible escanear el teclado cada pocos microsegundos. Si
 una trama de microrrebotes dura más que el tiempo entre escaneos de teclado, el programa una trama de microrrebotes dura más que el tiempo entre escaneos de teclado, el programa
-puede detectar pulsaciones incorrectas, al estar leyendo datos que corresponden a un  +puede detectar pulsaciones incorrectas, al estar leyendo datos que corresponden a un 
-microrrebote. Dado que lo que buscamos son secuencias pulsado/no pulsado, estos  +microrrebote. Dado que lo que buscamos son secuencias pulsado/no pulsado, estos 
-microrrebotes se interpretarán erróneamente como pulsaciones y nos podemos encontrar +microrrebotes se interpretarán erróneamente como pulsaciones y nos podemos encontrar
 con que lo que tecleamos aparece repetido dos o tres veces. con que lo que tecleamos aparece repetido dos o tres veces.
  
Línea 1122: Línea 1626:
 EsperaSoltar:   xor a EsperaSoltar:   xor a
                 in a,(254)                 in a,(254)
-                and 00011111b +                and %00011111 
-                cp 00011111b+                cp %00011111
                 jr nz,EsperaSoltar                 jr nz,EsperaSoltar
  
 EsperaPulsar:   xor a EsperaPulsar:   xor a
                 in a,(254)                 in a,(254)
-                and 00011111b +                and %00011111 
-                cp 00011111b+                cp %00011111
                 jr z,EsperaPulsar                 jr z,EsperaPulsar
  
Línea 1135: Línea 1639:
                 jr Bucle                 jr Bucle
  
-Un bucle así ejecutándose en un Spectrum real podría enfrentarse con la siguiente +Un bucle así ejecutándose en un Spectrum real podría enfrentarse con la siguiente
 pulsación de teclado: pulsación de teclado:
  
 Teclado: 11111111111111111111111001011010000000000000000000000000 Teclado: 11111111111111111111111001011010000000000000000000000000
-Lectura: ^                           +Lectura: ^                           ^
  
-Esto es lo que quería destacar: si el intervalo entre dos lecturas es menor que el  +Esto es lo que quería destacar: si el intervalo entre dos lecturas es menor que el 
-tiempo que dura una trama de microrrebotes, se interpretarán como pulsaciones +tiempo que dura una trama de microrrebotes, se interpretarán como pulsaciones
 independientes. Aquí se comienza con la tecla soltada. En un determinado momento, independientes. Aquí se comienza con la tecla soltada. En un determinado momento,
 el usuario la pulsa y genera la secuencia que se ve. El bucle de lectura detecta el usuario la pulsa y genera la secuencia que se ve. El bucle de lectura detecta
-una pulsación dentro de la trama de microrrebotes, en la siguiente lectura  +una pulsación dentro de la trama de microrrebotes, en la siguiente lectura 
-detecta una no-pulsación, y en la siguiente, otra pulsación. El resultado final +detecta una no-pulsación, y en la siguiente, otra pulsación. El resultado final
 es que se almacenan dos pulsaciones de tecla en lugar de una. es que se almacenan dos pulsaciones de tecla en lugar de una.
  
 La descripción técnica del Spectrum apunta a que la supresión de rebotes de teclado La descripción técnica del Spectrum apunta a que la supresión de rebotes de teclado
-se hace por software, en la rutina de lectura de la ROM. Pero si no se usa dicha  +se hace por software, en la rutina de lectura de la ROM. Pero si no se usa dicha 
-rutina y se lee el teclado directamente, hay que tener en cuenta esto. En nuestro  +rutina y se lee el teclado directamente, hay que tener en cuenta esto. En nuestro 
-software basta con insertar una pausa de 1ms en la que se ignora al teclado.  +software basta con insertar una pausa de 1ms en la que se ignora al teclado. 
-1 milisegundo cuando el teclado se usa no para mover un personaje en un arcade, +1 milisegundo cuando el teclado se usa no para mover un personaje en un arcade,
 sino para registrar entrada del usuario, no afecta a la respuesta del programa. sino para registrar entrada del usuario, no afecta a la respuesta del programa.
  
 Bucle: Bucle:
 EsperaSoltar:   xor a EsperaSoltar:   xor a
-                in a,(254) +                in a, (254) 
-                and 00011111b +                and %00011111 
-                cp 00011111b +                cp %00011111 
-                jr nz,EsperaSoltar+                jr nz, EsperaSoltar
  
                 ;Hacer pausa AQUI                 ;Hacer pausa AQUI
  
 EsperaPulsar:   xor a EsperaPulsar:   xor a
-                in a,(254) +                in a, (254) 
-                and 00011111b +                and %00011111 
-                cp 00011111b +                cp %00011111 
-                jr z,EsperaPulsar+                jr z, EsperaPulsar
  
-                ;Hacer pausa AQUI+                ; Hacer pausa AQUI
  
                 ;Se registra la pulsacion...                 ;Se registra la pulsacion...
Línea 1181: Línea 1685:
 Lectura: ^                          ^         ^ Lectura: ^                          ^         ^
  
-Cuando se detecta una pulsación (sea en medio de un microrrebote o no), el +Cuando se detecta una pulsación (sea en medio de un microrrebote o no), el
 teclado deja de explorarse durante 1 ms (o quizás baste con menos). Al soltar teclado deja de explorarse durante 1 ms (o quizás baste con menos). Al soltar
-también se generar microrrebotes, que se amortiguarían con la segunda pausa. +también se generar microrrebotes, que se amortiguarían con la segunda pausa.
 </code> </code>
  
  
- Resumiendo: la lectura del teclado en ensamblador sólo está limitada, "físicamente", por la velocidad con la que responde la lectura del puerto con el comando IN. Como explica Miguel Ángel, electrónicamente existen una serie de rebotes de la señal que pueden inducir a generarnos pulsaciones de teclado "residuales" que realmente no se han dado. Esto hace necesario insertar "pausas" entre lecturas para no leer "microrebotes" de los estados del teclado al tomar caracteres de teclado en determinadas circunstancias (lectura de "cadenas de texto", por ejemplo).+ Resumiendo: la lectura del teclado en ensamblador sólo está limitada, "físicamente", por la velocidad con la que responde la lectura del puerto con el comando ''IN''. Como explica Miguel Ángel, electrónicamente existen una serie de rebotes de la señal que pueden inducir a generarnos pulsaciones de teclado "residuales" que realmente no se han dado. Esto hace necesario insertar "pausas" entre lecturas para no leer "microrebotes" de los estados del teclado al tomar caracteres de teclado en determinadas circunstancias (lectura de "cadenas de texto", por ejemplo).
  
  
 + El segundo problema se refiere al "Ghosting". Debido al funcionamiento interno del teclado, como matriz de pulsadores sin tener aislado cada uno de ellos con diodos (que hubieran encarecido el producto final al tener que acoplarlos al teclado de membrana del Spectrum), el estado de "0 voltios" se propaga por todas las líneas conectadas entre sí mediante los circuitos que han cerrado los pulsadores (teclas), por lo que en ciertas combinaciones de teclas podemos encontrar teclas no pulsadas con su línea a 0 voltios, interpretando erróneamente nuestro programa que dicha tecla está realmente pulsada.
 +
 + Tal y como nos cuenta Miguel A. Rodríguez Jódar en los foros de Speccy.org:
 +
 +<code>
 +Esto implica, por ejemplo, que al pulsar tres teclas que forman los tres vértices
 +de un cuadrado en la matriz, la cuarta tecla perteneciente al cuarto vértice también
 +aparece como pulsada, y por tanto no se puede detectar cuando NO está pulsada.
 +
 +Lo que ocurre exactamente es lo siguiente: cuando se pulsan dos teclas que pertenecen
 +a distintas filas, pero que pertenecen a la misma columna las filas de ambas teclas
 +adquieren el potencial de 0 voltios, así que aunque nosotros hayamos seleccionado
 +una fila para leer, en realidad se están seleccionando dos filas para leer. Si en la
 +fila que no pretendíamos leer hay más de una tecla pulsada (la I), ésta obviamente
 +aparecerá en la línea de salida.
 +
 +Esto es el "ghosting" en un teclado de matriz. Para detener este efecto, es necesario
 +impedir que se formen circuitos cerrados allí donde no queremos, o al menos que si
 +se forman sea porque la corriente deba circular por ese circuito en el sentido adecuado.
 +</code>
 +
 + La solución es meramente hardware, por lo que a nosotros nos queda simplemente la posibilidad de modificar la rutina de redefinición de teclas para impedir que el usuario seleccione teclas cuya combinación provoque la pulsación no real a nivel de línea de otra.
 +
 + El propio Miguel A. nos propone un programa en BASIC que nos puede mostrar las combinaciones de teclado que producen Ghosting y que podemos implementar en ASM si consideramos necesario que nuestro programa tenga en cuenta esta particularidad (Nota: se han partido los comentarios REM y las líneas largas en líneas múltiples para facilitar la lectura):
 +
 +<code basic>
 +       1 REM Datos de la matriz a cargar en T. No podremos usar CAPS SHIT y SYMBOL
 +             SHIFT porque la rutina de la ROM que usamos no las puede detectar
 +             "aisladas" asi que en su lugar ponemos CHR$ 0 (NOTA: ambas a la vez
 +             si puede, es CHR$ 14)
 +       2 DATA "b","n","m",CHR$ 0," ","h","j","k","l",CHR$ 13,"y","u","i","o","p",
 +              "6","7","8","9","0","5","4","3","2","1","t","r","e","w","q","g","f",
 +              "d","s","a","v","c","x","z",CHR$ 0
 +      10 DIM t(8,5,2):
 +         REM Estado de la matriz. t(f,c,s) es:f=fila, c=columna, s=codigo ascii tecla
 +      15 DIM r(7,2):
 +         REM Nuestra seleccion de teclas. Para cada una se guarda su fila y columna.
 +      17 FOR f=1 TO 8: FOR c=1 TO 5: READ t$: LET t(f,c,2)=CODE t$: NEXT c: NEXT f:
 +         REM Rellenamos la matriz T
 +      20 DATA "Arriba","Abajo","Izquerda","Derecha","Fuego","Pausa","Abortar"
 +      30 FOR n=1 TO 7
 +      40 READ t$
 +      50 PRINT "Elige tecla para ";t$;": ";
 +      60 PAUSE 0: LET tecl=PEEK 23560:
 +         REM Leemos tecla. Valdria tambien hacer LET tecl=CODE INKEY$
 +      65 BEEP .05,0:
 +         REM pitido de realimentacion al usuario para que sepa que su tecla
 +             ha sido leida y va a ser procesada
 +      70 FOR f=1 TO 8: FOR c=1 TO 5: IF t(f,c,2)=tecl THEN GO TO 90:
 +         REM La buscamos en la matriz
 +      80 NEXT c: NEXT f: PRINT "Fallo en la matriz! :(": STOP :
 +         REM Esto no deberia pasar...
 +      90 IF t(f,c,1)=1 THEN BEEP .5,-20: GO TO 60:
 +         REM Si ya estaba marcada, error! y a elegir otra
 +    100 LET t(f,c,1)=1:
 +         REM No esta marcada, asi que la aceptamos y la marcamos
 +    105 IF tecl=13 THEN PRINT "ENTER": GO TO 110
 +    106 IF tecl=32 THEN PRINT "SPACE": GO TO 110
 +    108 PRINT CHR$ tecl
 +    110 BEEP .1,20:
 +        REM pitido para indicar tecla OK
 +    120 LET r(n,1)=f: LET r(n,2)=c:
 +        REM La guardamos en nuestra matriz de teclas seleccionadas.
 +    130 FOR m=1 TO n: LET fil=r(m,1): LET col=r(m,2): GO sub 900: NEXT m:
 +        REM Repasamos la lista de teclas seleccionadas hasta el momento para
 +            actualizar la matriz con las teclas "fantasma" que encontremos
 +    140 NEXT n: STOP
 +    900 FOR i=1 TO 8:
 +        REM recorremos todas las teclas de la misma columna que nuestra tecla
 +    910 IF t(i,col,1)=1 THEN GO sub 1000:
 +        REM si alguna esta seleccionada, significa que tenemos dos teclas en
 +            una misma columna. Miramos si hay una tercera en la misma fila
 +    920 NEXT i: RETURN
 +    1000 FOR j=1 TO 5:
 +        REM Recorremos una fila buscando una tercera tecla seleccionada
 +    1010 IF t(i,j,1)=1 THEN LET t(fil,j,1)=1:
 +        REM Si la encontramos, entonces tenemos tres teclas en un cuadrado.
 +            Marcamos como seleccionada la cuarta tecla del cuadrado, para que
 +            no podamos elegirla
 +    1020 NEXT j: RETURN
 +</code>
 +
 + Por otra parte, es bastante complicado que los usuarios seleccionen combinaciones de teclado no estándar (OPQA, 6789, etc.) y que puedan suponer problemas de ghosting, por lo que lo más normal para evitar la inclusión de código adicional en nuestro programa será permitir al usuario que seleccione las teclas sin este tipo de comprobación.
 +
 +\\ 
 ===== Ficheros ===== ===== Ficheros =====
  
-  * {{cursos:ensamblador:08_keyb1.asm|Lectura de la tecla "P"}} +  * {{cursos:ensamblador:08_keyb1.asm|Lectura de la tecla "P"}}. 
-  * {{cursos:ensamblador:08_keyb1.tap|Tap del ejemplo anterior}} +  * {{cursos:ensamblador:08_keyb1.tap|Tap del ejemplo anterior}}. 
-  * {{cursos:ensamblador:08_keyb2.asm|Lectura de la tecla "P" (método 2)}} +  * {{cursos:ensamblador:08_keyb2.asm|Lectura de la tecla "P" (método 2)}}. 
-  * {{cursos:ensamblador:08_keyb2.tap|Tap del ejemplo anterior}} +  * {{cursos:ensamblador:08_keyb2.tap|Tap del ejemplo anterior}}. 
-  * {{cursos:ensamblador:08_scancode.asm|Scancodes de las diferentes teclas}} +  * {{cursos:ensamblador:08_scancode.asm|Scancodes de las diferentes teclas}}. 
-  * {{cursos:ensamblador:08_scancode.tap|Tap del ejemplo anterior}} +  * {{cursos:ensamblador:08_scancode.tap|Tap del ejemplo anterior}}. 
-  * {{cursos:ensamblador:08_checkkey.asm|Chequeando el estado de un scancode concreto}} +  * {{cursos:ensamblador:08_checkkey.asm|Chequeando el estado de un scancode concreto}}. 
-  * {{cursos:ensamblador:08_checkkey.tap|Tap del ejemplo anterior}} +  * {{cursos:ensamblador:08_checkkey.tap|Tap del ejemplo anterior}}. 
-  * {{cursos:ensamblador:08_key2ascii.asm|Conversión de scancodes en ASCIIs}} +  * {{cursos:ensamblador:08_key2ascii.asm|Conversión de scancodes en ASCIIs}}. 
-  * {{cursos:ensamblador:08_key2ascii.tap|Tap del ejemplo anterior}}+  * {{cursos:ensamblador:08_key2ascii.tap|Tap del ejemplo anterior}}
 +  * {{cursos:ensamblador:09_leertecl_packed.asm|Redefinición del teclado y lectura empaquetada}}. 
 +  * {{cursos:ensamblador:09_leertecl_packed.tap|Tap del ejemplo anterior}}. 
 +  * {{cursos:ensamblador:10_leertecl_packed2.asm|Redefinición del teclado y lectura empaquetada, optimizada}}. 
 +  * {{cursos:ensamblador:10_leertecl_packed2.tap|Tap del ejemplo anterior}}. 
 +  * {{cursos:ensamblador:rom_keyboard.asm|Desensamblado de las rutinas de teclado de la ROM}}. 
 +  * {{cursos:ensamblador:utils.asm|Librería de utilidades utils.asm usada en algunos ejemplos (ASM)}}.
  
 +\\ 
 ===== Enlaces ===== ===== Enlaces =====
  
Línea 1209: Línea 1805:
   * [[http://www.z80.info|Web del Z80]]   * [[http://www.z80.info|Web del Z80]]
   * [[http://www.worldofspectrum.org/faq/reference/z80reference.htm|Z80 Reference de WOS]]   * [[http://www.worldofspectrum.org/faq/reference/z80reference.htm|Z80 Reference de WOS]]
-  * [[http://ti86.acz.org/z80_ref.htm|Z80 Reference de TI86]]  +  * [[http://ti86.acz.org/z80_ref.htm|Z80 Reference de TI86]]
   * [[http://www.speccy.org/trastero/cosas/Fichas/fichas.htm|Microfichas de CM de MicroHobby]]   * [[http://www.speccy.org/trastero/cosas/Fichas/fichas.htm|Microfichas de CM de MicroHobby]]
   * [[http://www.robertp.net/MicroHobby/CodiMaquina/Pagina344.htm|Disposición electrónica del teclado del Spectrum]]   * [[http://www.robertp.net/MicroHobby/CodiMaquina/Pagina344.htm|Disposición electrónica del teclado del Spectrum]]
   * {{:cursos:ensamblador:plantilla_membrana_sp48k.pdf|Plantilla del teclado del Spectrum}} en formato PDF (por mcleod_ideafix).   * {{:cursos:ensamblador:plantilla_membrana_sp48k.pdf|Plantilla del teclado del Spectrum}} en formato PDF (por mcleod_ideafix).
   * [[http://www.ticalc.org/pub/text/z80/|Tablas de ensamblado y t-estados]] (pulsar en z80.txt, z80_reference.txt, z80time.txt).   * [[http://www.ticalc.org/pub/text/z80/|Tablas de ensamblado y t-estados]] (pulsar en z80.txt, z80_reference.txt, z80time.txt).
-  * [[http://www.arrakis.es/~ninsesabe/pasmo/|PASMO]]+  * The Complete Spectrum ROM Disassembly [[ftp://ftp.worldofspectrum.org/pub/sinclair/books/CompleteSpectrumROMDisassemblyThe.txt|TXT]] y [[ftp://ftp.worldofspectrum.org/pub/sinclair/books/CompleteSpectrumROMDisassemblyThe.pdf|PDF]].
  
 +\\ 
 +**[ [[.:indice|⬉]] | [[.:rutinas_save_load|⬅]] | [[.:interrupciones|➡]] ]**
  
  • cursos/ensamblador/teclado.1286955334.txt.gz
  • Última modificación: 13-10-2010 07:35
  • por sromero