Diferencias
Muestra las diferencias entre dos versiones de la página.
Próxima revisión | Revisión previa Próxima revisiónAmbos lados, revisión siguiente | ||
cursos:ensamblador:teclado [12-10-2010 15:00] – creado sromero | cursos:ensamblador:teclado [22-01-2024 07:57] – [Redefinicion de teclas] sromero | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
+ | ====== Lectura del teclado en el Spectrum ====== | ||
- | ====== Lectura | + | Este capítulo está íntegramente dedicado a la lectura |
- | Este capítulo | + | Tras este capítulo |
- | Tras este artículo seremos capaces | + | \\ |
+ | ===== Uso de las rutinas de la ROM ===== | ||
+ | |||
+ | Como ya vimos en el capítulo dedicado a las rutinas de la ROM y variables | ||
+ | |||
+ | 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 | ||
+ | |||
+ | Como veremos en la entrega dedicada a las Interrupciones del procesador, ciertas rutinas de servicio (ISR), en concreto '' | ||
+ | |||
+ | | ||
+ | |||
+ | | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | 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//" | ||
+ | |||
+ | 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. | ||
+ | |||
+ | 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, | + | El teclado del Spectrum es una matriz de 40 pulsadores (40 teclas) que proporcionan al microprocesador, |
+ | 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? | ||
+ | La respuesta es: organizando el teclado en filas de teclas y seleccionando qué fila leer mediante el registro B. | ||
+ | |||
+ | \\ | ||
{{ cursos: | {{ cursos: | ||
+ | \\ | ||
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" | 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" | ||
- | 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 |
+ | \\ | ||
{{ cursos: | {{ cursos: | ||
+ | \\ | ||
+ | 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: | {{ cursos: | ||
+ | \\ | ||
- | + | Rescatemos el siguiente programa BASIC de uno de los capítulos iniciales | |
- | 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 |
10 LET puerto=63486 | 10 LET puerto=63486 | ||
20 LET V=IN puerto: PRINT AT 20,0; V ; " | 20 LET V=IN puerto: PRINT AT 20,0; V ; " | ||
</ | </ | ||
- | 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 |
- | diferentes bits de estado conectados al puerto 63486 ($F7FEh), y como | + | misma función que con su equivalente ensamblador: |
- | podéis suponer, mediante la instrucción IN de BASIC realizamos la | + | |
- | misma función que con su equivalente ensamblador: | + | |
- | contenido en dicho puerto. | + | |
- | Por defecto, sin pulsar ninguna tecla, los diferentes bits del valor | + | El valor $fe se corresponde con el puerto |
- | leído en dicho puerto | + | |
- | del bit correspondiente a dicha tecla aparecerá como 0, y soltándola | + | |
- | volverá a su valor 1 original. | + | |
- | Al ejecutar el ejemplo en BASIC, veremos que la pulsación de | + | Por defecto, sin pulsar ninguna |
- | cualquier | + | |
- | Si pasamos | + | |
- | cambio del valor se corresponde con las teclas que vamos pulsando | + | |
- | liberando. | + | |
- | Si no hay ninguna tecla pulsada, los 5 bits más bajos del byte que | + | Al ejecutar el ejemplo |
- | hay en el puerto estarán todos a 1, mientras | + | |
- | las teclas del 1 al 5, el bit correspondiente | + | |
- | estado 0. Nosotros podemos leer el estado | + | |
- | los unos y los ceros, si las teclas | + | |
- | 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 5 últimos | + | Si no hay ninguna tecla pulsada, los 5 bits más bajos del byte que hay en el puerto estarán todos a 1, mientras que si se pulsa alguna |
- | como significado | + | |
+ | 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 " | ||
+ | \\ | ||
+ | |< 50% >| | ||
^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ | ^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ | ||
| Teclas: | XX | XX | XX | " | | Teclas: | XX | XX | XX | " | ||
+ | \\ | ||
- | 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 (y JOYSTICK 1) | |
- | | 61438d (EFFEh) | de 6 a 0 (and JOYSTICK 2) | | + | | 61438d ($effe) | de 6 a 0 (y 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 | |
+ | \\ | ||
- | A la hora de leer estos puertos, el bit menos significativo (D0) | + | En el resultado de la lectura |
- | siempre hace referencia a la tecla más alejada del centro del teclado | + | |
- | (" | + | |
- | (D5) lo hace a la tecla más cercana al centro del teclado. | + | |
| | ||
+ | \\ | ||
+ | |< 70% >| | ||
^ Puerto ^ Bits: ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ | ^ Puerto ^ Bits: ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ | ||
- | | 65278d (FEFEh) | Teclas: | " | + | | 65278d ($fefe) | Teclas: | " |
- | | 65022d (FDFEh) | Teclas: | " | + | | 65022d ($fdfe) | Teclas: | " |
- | | 64510d (FBFEh) | Teclas: | " | + | | 64510d ($fbfe) | Teclas: | " |
- | | 63486d (F7FEh) | Teclas: | " | + | | 63486d ($f7fe) | Teclas: | " |
- | | 61438d (EFFEh) | Teclas: | "0" | "9" | " | + | | 61438d ($effe) | Teclas: | "6" | "7" | " |
- | | 57342d (DFFEh) | Teclas: | " | + | | 57342d ($dffe) | Teclas: | " |
- | | 49150d (BFFEh) | Teclas: | " | + | | 49150d ($bffe) | Teclas: | " |
- | | 32766d (7FFEh) | Teclas: | " | + | | 32766d ($7ffe) | Teclas: | " |
- | ^ 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 | |
+ | \\ | ||
- | ¿De dónde salen estos valores tan extraños? ¿Existe alguna relación | + | Como puede verse en la tabla, la parte baja de los 16 bits del puerto representan siempre |
- | entre FDFEh, por ejemplo, y la semifila a la que representa? | + | |
- | Efectivamente, | + | |
- | puerto representan siempre | + | |
- | el teclado. | + | |
- | La parte alta es la única que varía según la semifila a leer, y su valor consiste, | + | 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 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. | ||
- | Así, al mandar estos "extraños valores" en la lectura | + | 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 " |
- | 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 " | + | (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 2 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 valor 2 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 | + | ===== Ejemplo |
- | | + | |
- | pulsamos la tecla " | + | |
<code z80> | <code z80> | ||
- | | + | ; Lectura de la tecla " |
- | ORG 50000 | + | ORG 50000 |
bucle: | bucle: | ||
- | LD BC, $DFFE ; Semifila " | + | ld bc, $dffe ; Semifila " |
- | IN A, (C) ; Leemos el puerto | + | in a, (c) ; Leemos el puerto |
- | | + | |
- | JR Z, salir ; Si esta a 0 (pulsado) salir. | + | jr z, salir ; Si esta a 0 (pulsado) salir. |
- | | + | |
salir: | salir: | ||
- | RET | + | ret |
- | | + | |
</ | </ | ||
- | De nuevo ensamblamos nuestro programa con "//pasmo --tapbas keyb1.asm | + | De nuevo ensamblamos nuestro programa con '' |
- | keyb1.tap//", y lo cargamos en el Spectrum o en un emulador. | + | |
- | | + | |
- | que se ponga a cero el bit 0 del puerto $DFFE, que se corresponde con | + | |
- | el estado de la tecla " | + | En ocasiones puede ser más recomendable la utilización de '' |
- | con BIT hará que el Zero Flag se active y el "JR Z" | + | |
- | bucle, retornando al BASIC. | + | <code z80> |
+ | ; Forma 1 | ||
+ | ld bc, $fffe | ||
+ | in a, (c) ; A = Lectura de puerto $fffe | ||
+ | |||
+ | ; Forma 2 | ||
+ | ld a, $ff | ||
+ | in a, ($fe) ; A = Lectura de puerto $fffe | ||
+ | </ | ||
- | Nótese cómo en ciertos casos puede ser más recomendable la | + | Por ejemplo, nuestro ejemplo anterior se podría escribir con '' |
- | utilización de una u otra forma de IN. 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> | <code z80> | ||
- | | + | ; Lectura de la tecla " |
- | ORG 50000 | + | ORG 50000 |
bucle: | bucle: | ||
- | LD A, $DF ; Semifila " | + | ld a, $df ; Semifila " |
- | IN A, ($FE) ; Leemos el puerto | + | in a, ($fe) ; Leemos el puerto |
- | | + | |
- | JR Z, salir ; Si esta a 0 (pulsado) salir. | + | jr z, salir ; Si esta a 0 (pulsado) salir. |
- | | + | |
salir: | salir: | ||
- | RET | + | ret |
- | END 50000 | + | |
+ | | ||
</ | </ | ||
- | En este ejemplo B no se usa, usamos A para albergar la semifila a | + | En este ejemplo, B 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 | + | ===== Esperar |
- | | + | |
- | 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" | + | |
- | 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 " | + | Este tipo de " |
- | qué tecla ha sido pulsada. Por ejemplo, si intentamos leer simultaneamente | + | |
- | las semifilas 1-5 y Q-T, el bit 0 del resultado valdrá " | + | |
- | si se pulsa " | + | |
- | + | ||
- | 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, | + | |
- | 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, | ||
+ | |< 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), | ||
- | 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), | + | |
- | 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 | + | xor a ; A = 0 => leer todas las semifilas |
- | IN A, (254) | + | in a, ($fe) ; Leer del puerto del teclado |
- | OR 224 | + | or %11100000 |
- | INC A | + | 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 a ; |
- | IN A, (254) | + | in a, ($fe) ; Leer del puerto del teclado |
- | OR 224 | + | or %11100000 |
- | INC A | + | 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 | ||
</ | </ | ||
- | | + | Si necesitamos preservar el valor de A y de los flags en las llamadas a estas rutinas, podemos hacernos versiones como las utilizadas |
<code z80> | <code z80> | ||
- | Wait_For_Key_Pressed: | + | ; |
- | | + | Wait_For_Keys_Pressed: |
- | IN A, (FEh) | + | push af |
- | CPL | + | wait_for_keypressed_loop: |
- | AND 1Fh | + | xor a ; |
- | JR Z, Wait_For_Key_Pressed | + | in a, ($fe) ; Leer del puerto del teclado |
- | RET | + | or %11100000 |
+ | 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 | ||
+ | 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 nz, wait_for_no_pressedkey_loop | ||
+ | pop af | ||
+ | ret | ||
</ | </ | ||
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 |
- | 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 |
- | codificar el estado de las 5 teclas | + | |
- | derecha, disparo) | + | 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 |
- | función | + | |
<code z80> | <code z80> | ||
- | ; Lee el estado de O, P, Q, A, ESPACIO | + | ; 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 |
- | ; | + | |
- | ; BITS 4 3 2 | + | push hl |
- | ; SIGNIFICADO | + | |
- | ; | + | |
- | LEER_TECLADO: | + | |
- | LD D, 0 ; Keyboard status flag register | + | ld hl, estado_tecla_arriba |
+ | ld (hl), 0 ; marcamos estado_tecla_arriba como no pulsada | ||
- | LD BC, $FBFE | + | ld bc, $fbfe |
- | IN A, (C) | + | in a, (c) |
- | | + | |
- | JR NZ, Control_no_up | + | ; (podríamos haber usado "and %00000001" |
- | SET 0, D ; Pulsada, ponemos a 1 el bit 0 | + | jr nz, Control_no_up |
+ | 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 |
- | | + | ld bc, $fdfe |
- | JR NZ, Control_no_down | + | in a, (c) |
- | SET 1, D ; Pulsada, ponemos a 1 el bit 1 | + | |
+ | jr nz, Control_no_down | ||
+ | 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 |
- | | + | ld bc, $dffe |
- | JR NZ, Control_no_right | + | in a, (c) |
- | SET 2, D ; Pulsada, ponemos a 1 el bit 2 | + | |
+ | jr nz, Control_no_right | ||
+ | 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 |
- | | + | ld (hl), 0 |
- | JR NZ, Control_no_left | + | |
- | SET 3, D | + | ; (podríamos haber usado "and %00000010" |
+ | jr nz, Control_no_left | ||
+ | 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 |
- | | + | ld bc, $7ffe |
- | JR NZ, Control_no_fire | + | in a, (c) |
- | SET 4, D | + | |
+ | jr nz, Control_no_fire | ||
+ | ld (hl), 1 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1 | ||
Control_no_fire: | Control_no_fire: | ||
- | LD A, D | + | pop hl |
- | | + | pop bc |
+ | pop af | ||
+ | ret | ||
+ | |||
+ | ; Estado | ||
+ | estado_tecla_izq | ||
+ | estado_tecla_der | ||
+ | estado_tecla_arriba | ||
+ | estado_tecla_abajo | ||
+ | estado_tecla_disp | ||
</ | </ | ||
- | El bucle principal del programa deberá llamar a esta función de lectura del teclado y después, con el valor devuelto | + | El bucle principal del programa deberá llamar a esta función de lectura del teclado y después, con el valor 0/1 presente |
- | 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 |
- | 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 (" | + | 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, "// |
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 ("// | ||
- | * Antes de ser llamada, deberemos llegar a ella con el mensaje apropiado en pantalla (" | ||
* 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" | * 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" | ||
+ | \\ | ||
- | // | + | // |
- | 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, | + | |
- | y sencilla de utilizar. | + | |
- | | + | |
- | 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)" | + | |
- | 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 " | + | ; Bits 0, 1 y 2 de " |
- | ; Bits 3, 4 y 5 de " | + | ; Bits 3, 4 y 5 de " |
+ | ; => (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 " | |
- | LD DE, $FF2F ; Valor inicial " | + | 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 | + | jr z, NPRESS |
- | 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, A ; Cálculo del valor de la tecla | + | ld h, a ; Cálculo del valor de la tecla |
- | LD A, E | + | ld a, e |
KLOOP: | KLOOP: | ||
- | | + | sub 8 |
- | 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, A ; Guardar valor de tecla en D | + | ld d, a ; Guardar valor de tecla en D |
- | NPRESS: | + | NPRESS: |
- | DEC E | + | dec e |
- | RLC B | + | rlc b |
- | JR C, NXHALF | + | jr c, NXHALF |
- | CP A ; Ponemos flag a zero | + | cp a ; Ponemos flag a zero |
- | RET Z ; Volvemos | + | ret z ; Volvemos |
</ | </ | ||
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> | ||
- | | + | |
- | | + | |
- | | + | |
Pedir_Arriba: | Pedir_Arriba: | ||
- | CALL Find_Key | + | call Find_Key |
- | JR NZ, Pedir_Arriba | + | jr nz, Pedir_Arriba |
- | INC D | + | inc d |
- | JR Z, Pedir_Arriba | + | jr z, Pedir_Arriba |
- | DEC D | + | dec d |
- | LD A, D | + | ld a, d |
- | | + | |
- | + | ||
- | ;; Pedimos siguiente tecla (ABAJO) | + | ;; Pedimos siguiente tecla (ABAJO) |
- | | + | |
- | | + | |
Pedir_Abajo: | Pedir_Abajo: | ||
- | CALL Find_Key | + | call Find_Key |
- | JR NZ, Pedir_Abajo | + | jr nz, Pedir_Abajo |
- | INC D | + | inc d |
- | JR Z, Pedir_Abajo | + | jr z, Pedir_Abajo |
- | DEC D | + | dec d |
- | + | ||
- | LD A, D | + | ld a, d |
- | | + | |
- | | + | |
tecla_arriba DEFB 0 | tecla_arriba DEFB 0 | ||
Línea 477: | Línea 487: | ||
</ | </ | ||
- | A continuación podemos ver un **ejemplo** (scancode.asm) que " | + | A continuación podemos ver un ejemplo ('' |
- | forma gráfica | + | |
<code z80> | <code z80> | ||
; Visualizando los scancodes de las teclas codificadas con " | ; Visualizando los scancodes de las teclas codificadas con " | ||
- | | + | |
Bucle_entrada: | Bucle_entrada: | ||
- | CALL Wait_For_Keys_Released | + | call Wait_For_Key |
Pedir_Tecla: | Pedir_Tecla: | ||
- | CALL Find_Key | + | call Find_Key |
- | JR NZ, Pedir_Tecla | + | |
- | INC D | + | |
- | JR Z, Pedir_Tecla | + | |
- | DEC D | + | |
- | LD A, D ; Guardamos en A copia del resultado | + | jr nz, Pedir_Tecla |
- | CALL PrintBin | + | inc d |
+ | jr z, Pedir_Tecla | ||
+ | dec d | ||
- | LD A, D ; Guardamos en A copia del resultado | + | ld a, d ; Guardamos en A copia del resultado |
- | CALL PrintHex | + | |
- | CP $21 ; Comprobamos si A == 21h (enter) | + | cp $21 ; Comprobamos si A == 21h (enter) |
- | JR NZ, Bucle_entrada | + | ret z ; Si no lo es, repetir |
- | RET ; Si es enter, fin del programa | + | call PrintHex |
+ | call PrintSpace | ||
- | ;----------------------------------------------------------------------- | + | call Wait_For_No_Key |
- | ; PrintBin: Imprime en la pantalla | + | jr Pedir_Tecla |
- | ; de A en binario, usando 8 pixels " | + | |
- | ; | + | |
- | ; Entrada: A = valor a " | + | |
- | ; | + | |
- | PrintBin: | + | |
- | PUSH AF | + | |
- | PUSH HL | + | |
- | PUSH BC ; Preservamos los registros que se usará | + | |
- | LD HL, 20704 ; Esquina (0,24) de la pantalla | + | INCLUDE " |
- | LD C, A ; Guardamos en C copia de A | + | |
- | LD B, 8 ; Imprimiremos el estado de los 8 bits | + | |
- | printbin_loop: | + | ; Debemos incluir, además, el código de Find_Key dentro de |
- | LD A, $FF | + | ; este ejemplo para que ensamble correctamente. |
- | BIT 7, C ; Chequeamos | + | </code> |
- | JR NZ, printbin_es_uno | + | |
- | LD A, $55 ; Para bit = 0, punteado/gris | + | |
- | printbin_es_uno: | + | Este ejemplo proporcionará en pantalla |
- | LD (HL), A ; Lo " | + | |
- | 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 | + | |
- | POP BC | + | \\ |
- | POP HL | + | {{ cursos: |
- | 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: | ||
+ | |||
+ | \\ | ||
+ | {{ : | ||
+ | \\ | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | 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 | ||
+ | tecla_abajo | ||
+ | tecla_izq | ||
+ | tecla_der | ||
+ | tecla_disp | ||
+ | </ | ||
+ | |||
+ | 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, | ||
+ | |||
+ | * Mostrar por pantalla el mensaje de "// | ||
+ | |||
+ | * Esperar una pulsación de teclado del usuario. | ||
+ | |||
+ | * Opcionalmente, | ||
+ | |||
+ | * 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, | ||
+ | |||
+ | | ||
+ | |||
+ | <code z80> | ||
; | ; | ||
- | ; PrintHex: Imprime | + | ; Scancode2Ascii: convierte un scancode |
- | ; Para ello convierte el valor numérico en una cadena llamando | + | ; IN: D = scancode de la tecla a analizar |
- | ; a Byte2ASCII_Hex y luego llama a RST 16 para imprimir cada | + | ; OUT: A = Codigo ASCII de la tecla (0-9 y A-Z) |
- | ; | + | ; |
- | ; | + | |
- | ; Entrada: A = valor a " | + | |
; | ; | ||
- | PrintHex: | + | Scancode2Ascii: |
- | PUSH HL | + | push hl |
- | PUSH AF | + | push bc |
- | PUSH DE | + | |
- | LD H, A | + | ld hl, 0 |
- | CALL Byte2ASCII_Hex | + | ld bc, TABLA_Scancode2ASCII |
- | LD HL, Byte2ASCII_output | + | add hl, bc |
- | 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 ' | ||
+ | ; en la tabla habriamos llegado a los ASCIIs) | ||
+ | jr z, SC2Ascii_Exit | ||
+ | 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 bc, 39 ; Sumamos 39(+inc hl=40) para 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 |
- | | + | 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 " | ||
+ | </ | ||
+ | |||
+ | La rutina recibe en el registro D el scancode obtenido con la rutina '' | ||
+ | |||
+ | | ||
+ | |||
+ | \\ | ||
+ | * e = ENTER | ||
+ | * c = CAPS SHIFT | ||
+ | * y = SYMBOL SHIFT | ||
+ | * s = SPACE | ||
+ | \\ | ||
+ | |||
+ | De esta forma, "E" | ||
+ | |||
+ | A continuación podemos ver un ejemplo que utiliza las rutinas '' | ||
+ | |||
+ | |||
+ | <code z80> | ||
+ | ; Prueba de conversion de Scancode a ASCII | ||
+ | |||
+ | ORG 50000 | ||
+ | |||
+ | call CLS | ||
+ | |||
+ | START: | ||
+ | |||
+ | call Wait_For_No_Key | ||
+ | |||
+ | chequear_teclas: | ||
+ | call Find_Key | ||
+ | jr nz, chequear_teclas | ||
+ | inc d | ||
+ | jr z, chequear_teclas | ||
+ | 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 | ||
+ | |||
+ | INCLUDE " | ||
+ | |||
+ | ;;--- Introducir aquí las rutinas Find_Key y Scancode2ASCII ------------ | ||
+ | |||
+ | END 50000 | ||
+ | </ | ||
+ | |||
+ | Una vez en ejecución y tras pulsar múltiples teclas, este es el aspecto del programa anterior: | ||
+ | |||
+ | \\ | ||
+ | {{ cursos: | ||
+ | \\ | ||
+ | |||
+ | 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 (" | ||
+ | |||
+ | * 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 '' | ||
+ | |||
+ | \\ | ||
+ | <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 |
- | | + | msg_izq |
- | | + | msg_der |
+ | msg_arriba | ||
+ | msg_abajo | ||
+ | msg_disp | ||
- | | + | ; Teclas por defecto si no se redefine: O P Q A SPACE |
+ | tecla_izq | ||
+ | tecla_der | ||
+ | tecla_arriba | ||
+ | tecla_abajo | ||
+ | tecla_disp | ||
; | ; | ||
- | ; Byte2ASCII_Hex: | + | ; Utiliza Redefine_Key para obtener |
- | ; de texto de max. 2 caracteres hexadecimales, | + | ; scancodes |
- | ; Rutina adaptada de Num2Hex en http:// | + | ; Llama a " |
; | ; | ||
- | ; IN: H = Numero a convertir | + | ; ENTRADA: Nada |
- | ; OUT: | + | ; SALIDA: |
+ | ; 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 | ||
+ | call Redefine_Key | ||
+ | ld hl, tecla_izq | ||
+ | ld (hl), a | ||
- | ld de, Byte2ASCII_output | + | |
- | ld a, h | + | call PrintString |
- | | + | call Redefine_Key |
- | | + | ld hl, tecla_der |
- | call B2AHex_Num2 | + | ld (hl), a ; Guardamos tecla pulsada |
- | ret | + | |
- | B2AHex_Num1: | + | ld de, msg_arriba |
- | rra | + | call PrintString |
- | rra | + | call Redefine_Key |
- | rra | + | ld hl, tecla_arriba |
- | rra | + | ld (hl), a |
- | B2AHex_Num2: | + | ld de, msg_abajo |
- | or $F0 | + | call PrintString |
- | daa | + | call Redefine_Key |
- | add a, $A0 | + | ld hl, tecla_abajo |
- | adc a, $40 | + | ld (hl), a |
- | | + | |
- | inc de | + | ld de, msg_disp |
- | ret | + | call PrintString |
+ | call Redefine_Key | ||
+ | ld hl, tecla_disp | ||
+ | | ||
+ | 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: | ||
+ | ; 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 | ||
+ | ld a, d | ||
+ | cp $ff ; si A es $ff => ninguna tecla pulsada | ||
+ | jr z, wait_for_scan_loop | ||
+ | ld h, d ; Nos hacemos copia de D en H | ||
+ | call Scancode2Ascii | ||
+ | |||
+ | cp ' | ||
+ | jr nz, redef_key_NO_ENTER | ||
+ | ld de, redef_key_enter | ||
+ | call PrintString | ||
+ | jr redef_key_end | ||
+ | redef_key_NO_ENTER: | ||
+ | |||
+ | cp ' | ||
+ | jr nz, redef_key_NO_SPACE | ||
+ | ld de, redef_key_space | ||
+ | call PrintString | ||
+ | jr redef_key_end | ||
+ | redef_key_NO_SPACE: | ||
+ | |||
+ | cp ' | ||
+ | jr nz, redef_key_NO_CAPSSHIFT | ||
+ | ld de, redef_key_cs | ||
+ | call PrintString | ||
+ | jr redef_key_end | ||
+ | redef_key_NO_CAPSSHIFT: | ||
+ | |||
+ | cp ' | ||
+ | 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 | ||
+ | |||
+ | pop hl | ||
+ | pop de | ||
+ | ret ; Volver con registros preservados | ||
+ | |||
+ | redef_key_enter DB " | ||
+ | redef_key_space DB " | ||
+ | redef_key_ss | ||
+ | redef_key_cs | ||
+ | |||
+ | ;;; Insertar aqui el codigo de Find_Key | ||
+ | ;;; Insertar aqui el codigo de Scancode2ASCII | ||
- | ; Debemos incluir, además, el código | + | |
- | ; de Find_Key dentro de este ejemplo para que ensamble correctamente. | + | |
- | ; Nota: recuerda que acabando el programa con END 50000 no sería necesario | + | |
- | ; ejecutarlo manualmente con randomize usr 50000 al ensamblarlo con PASMO. | + | |
</ | </ | ||
- | Este ejemplo proporcionará en pantalla (hasta que se pulse ENTER) | + | Para esto, hemos creado 2 nuevas funciones: |
- | una salida como la siguiente: | + | |
- | {{ cursos:ensamblador: | + | * **Redefinir_Teclas**: Imprime los mensajes y llama a una función para redefinir cada tecla. |
- | | + | * **Redefine_Key**: |
- | Por otra parte, se incluye una rutina PrintBin para mostrar el estado de los diferentes bits del valor del scancode mediante pixeles " | + | Este es el aspecto |
- | pantalla | + | |
- | gráfica binaria de las teclas pulsadas. | + | |
- | Los scancodes asociados a las diferentes teclas son: | + | \\ |
+ | {{ cursos:ensamblador: | ||
+ | \\ | ||
- | ^ 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 " |
- | ^ 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 " |
- | ^ 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: | | + | |
+ | DB $60,$80,$46, | ||
+ | DB $05, | ||
+ | DB $00, | ||
+ | </ | ||
- | + | Los cuales tienen | |
- | 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. | + | |
+ | \\ | ||
+ | {{ : | ||
+ | \\ | ||
\\ | \\ | ||
===== Chequeando las teclas redefinidas ===== | ===== Chequeando las teclas redefinidas ===== | ||
- | | + | |
- | propio (creado a nuestra medida) | + | |
- | almacenar en variables de memoria | + | |
- | valore | + | |
<code z80> | <code z80> | ||
- | tecla_arriba | + | tecla_izq |
- | tecla_abajo | + | tecla_der |
- | tecla_izq | + | tecla_arriba |
- | tecla_der | + | tecla_abajo |
- | tecla_disp | + | tecla_disp |
</ | </ | ||
- | Dichos | + | Como hemos dicho, estos valores podrán ser modificados (o no) por la rutina de redefinición del teclado |
- | del teclado. | + | |
- | Lo único que nos falta para un control total del teclado en nuestro | + | Lo único que nos falta para desarrollar la lectura |
- | 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, | + | |
- | registro A antes de cada llamada, para conocer el estado de las mismas. | + | |
- | | + | |
<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: | ||
; Devuelve: | ; Devuelve: | ||
; CARRY FLAG = 1 y BC = 0 -> Tecla no pulsada | ; CARRY FLAG = 1 y BC = 0 -> Tecla no pulsada | ||
- | ; | + | ;----------------------------------------------------------------------- |
Check_Key: | Check_Key: | ||
- | LD C, A | + | push bc |
+ | ld c, a ; Copia de A | ||
- | AND 7 | + | ; Operaciones para extraer la semifila |
- | INC A | + | ; y la tecla de A (00SSSTTT), para leer |
- | LD B, A | + | ; el puerto y chequear el bit adecuados |
+ | and %00000111 | ||
+ | inc a | ||
+ | ld b, a ; 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, 5 | + | |
- | SUB C | + | |
- | LD C, A ; C = (semifila + 1) | + | |
- | LD A, $FE | + | CKHiFind: |
+ | rrca | ||
+ | djnz CKHiFind | ||
+ | in a, ($fe) ; Leemos la semifila | ||
- | CKHiFind: | + | CKNXKey: |
- | RRCA | + | rra |
- | DJNZ CKHiFind | + | dec c |
+ | jr nz, CKNXKey | ||
+ | pop bc | ||
- | IN A, ($FE) ; Leemos la semifila | + | ret |
- | + | ||
- | CKNXKey: | + | |
- | RRA | + | |
- | DEC C | + | |
- | JR NZ, CKNXKey | + | |
- | + | ||
- | | + | |
</ | </ | ||
- | La forma en que se debe llamar a esta rutina sería la siguiente: | + | La forma más básica |
<code z80> | <code z80> | ||
Comprobar_tecla_izquierda: | Comprobar_tecla_izquierda: | ||
- | LD A, (teclaizq) | + | ld a, (teclaizq) |
- | | + | |
- | JR C, izq_no_pulsada | + | jr c, izq_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) |
- | | + | |
- | JR C, der_no_pulsada | + | jr c, der_no_pulsada |
- | | + | |
- | + | ||
- | ; Repetir para arriba, abajo, disparo, etc. | + | |
- | </ | + | |
- | | + | ; Repetir para arriba, abajo, disparo, etc. |
+ | </ | ||
- | 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, | + | |
- | + | ||
- | Veamos a continuación un ejemplo final que permite modificar el valor | + | |
- | de una variable en memoria (" | + | |
- | 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 | + | |
<code z80> | <code z80> | ||
- | | + | ; Controlando el valor de " |
- | ORG 50000 | + | ORG 50000 |
- | LD A, (valor) | + | call CLS |
- | CALL PrintBin | + | Imprimir_Valor: |
+ | ld a, (valor) | ||
+ | ld b, 0 | ||
+ | ld c, a ; BC = A (B=0, C=A) | ||
+ | call PrintNum | ||
+ | call PrintSpace | ||
- | Bucle_entrada: | + | call Wait_For_No_Key |
- | LD BC, 20000 ; Retardo (bucle 20000 iteraciones) | + | Bucle: |
- | retardo: | + | |
- | DEC BC | + | |
- | LD A, B | + | |
- | OR C | + | |
- | JR NZ, retardo | + | |
Comprobar_tecla_mas: | Comprobar_tecla_mas: | ||
- | LD A, (tecla_mas) | + | ld a, (tecla_mas) |
- | | + | |
- | JR C, mas_no_pulsado | + | |
- | LD A, (valor) | + | jr c, Comprobar_tecla_menos |
- | 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) |
- | | + | |
- | JR C, menos_no_pulsado | + | jr c, Bucle ; Carry = 1, tecla_menos no pulsada |
- | LD A, (valor) | + | ld hl, valor |
- | DEC A | + | |
- | LD (valor), A ; Decrementamos (valor) | + | |
- | JR Print_Valor | + | |
- | menos_no_pulsado: | + | jp Imprimir_Valor |
- | JR Bucle_entrada | + | ; Variables de teclas |
- | ; pulse algo (tecla_mas | + | tecla_mas |
+ | tecla_menos | ||
- | Print_Valor: | + | ; Variable para alojar el valor |
- | LD A, (valor) ; Guardamos en A copia del resultado | + | valor |
- | CALL PrintBin | + | |
- | LD A, (valor) | + | INCLUDE " |
- | CALL PrintHex | + | ;; Nota: Incluir también |
- | JR Bucle_entrada | + | END 50000 |
+ | </ | ||
- | valor DEFB 0 | + | \\ |
- | tecla_mas | + | {{ : |
- | tecla_menos | + | \\ |
+ | 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 "// | ||
+ | \\ | ||
+ | ===== 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 '' | ||
+ | |||
+ | Ya hemos hablado de la importancia de empaquetar las diferentes funciones en rutinas reutilizables, | ||
+ | |||
+ | 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í: | ||
+ | |||
+ | < | ||
+ | ; BITS 4 | ||
+ | ; SIGNIFICADO | ||
+ | </ | ||
+ | |||
+ | Después de leer las 5 teclas redefinidas, | ||
+ | |||
+ | <code z80> | ||
+ | call Leer_Teclado_Empaquetado | ||
+ | ld (estado_teclas), | ||
+ | |||
+ | ; (...) | ||
+ | |||
+ | ; 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 | ||
+ | </ | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | <code z80> | ||
; | ; | ||
- | ; Chequea | + | ; Lee el estado de las teclas definidas en variables y almacena |
- | ; codificado | + | ; dicho estado |
; | ; | ||
- | ; Devuelve: | + | ; BITS 4 3 2 |
- | ; CARRY FLAG = 1 y BC = 0 -> Tecla no pulsada | + | ; SIGNIFICADO |
+ | ; | ||
+ | ; ENTRADA: NADA (usa las variables tecla_*) | ||
+ | ; Se podria modificar para recibir HL = direccion tecla1 | ||
+ | ; SALIDA: | ||
+ | ; MODIFICA: A, HL y CarryFlag | ||
; | ; | ||
- | Check_Key: | + | Leer_Teclado_Empaquetado: |
- | LD C, A | + | push de |
+ | ld d, 0 ; D = 0 | ||
- | AND 7 | + | ld hl, tecla_izq |
- | INC A | + | |
- | LD B, A | + | |
- | 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 | ||
+ | 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 pulsada, ponemos bit 2 a 1. | ||
- | CKHiFind: | + | tecl_der_notpressed: |
- | RRCA | + | inc hl |
- | 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) |
- | JR NZ, CKNXKey | + | 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 | ||
+ | </ | ||
- | ;; Nota: Incluir en el código | + | La rutina simplemente apunta HL a la primera de las teclas y llama a '' |
- | ;; | + | |
- | END 50000 | + | Utilizamos '' |
- | </ | + | |
- | Queda como ejercicio para el lector la modificación del programa para que, | + | A continuación se puede ver un ejemplo |
- | antes de entrar en el bucle principal, lea 2 teclas válidas y diferentes | + | |
- | del teclado para permitir | + | |
- | con respecto a sus valores | + | |
- | \\ | + | <code z80> |
- | ===== Redefinición | + | ; Redefinir teclas y ver el estado |
+ | ORG 33500 | ||
- | En las secciones anteriores hemos visto las rutinas Find_Key y Check_Key para detectar las pulsaciones de teclas | + | call CLS |
+ | call Redefinir_Teclas | ||
+ | call CLS | ||
+ | call Wait_For_No_Key | ||
- | El menú principal deberá | + | ; Bucle del programa, lee nuestras teclas e imprime primero la |
+ | ; " | ||
+ | bucle: | ||
+ | ld de, 0 | ||
+ | call CursorAt | ||
+ | ld de, msg_keys | ||
+ | call PrintString | ||
- | El sistema de redefinición de teclas | + | call Leer_Teclado_Empaquetado |
+ | call PrintBin | ||
- | 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 | + | msg_izq |
- | tecla_abajo | + | msg_der |
- | tecla_izq | + | msg_arriba |
- | tecla_der | + | msg_abajo |
- | tecla_disp | + | msg_disp |
- | </code> | + | msg_keys |
- | B.- Repetir N veces (uno por cada control a redefinir): | + | ; Teclas |
+ | tecla_izq | ||
+ | tecla_der | ||
+ | tecla_arriba | ||
+ | tecla_abajo | ||
+ | tecla_disp | ||
- | * 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, | + | ;;; Insertar aqui el codigo |
+ | ;;; 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 | + | ;-- Incluir libreria |
+ | INCLUDE | ||
- | * Esperar una pulsación de teclado del usuario. | + | END 33500 |
+ | </ | ||
- | * Opcionalmente, | + | \\ |
+ | {{ cursos: | ||
+ | \\ | ||
- | * 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 | + | \\ |
+ | ===== Optimizando | ||
- | Hasta ahora tenemos todos los mecanismos necesarios para crear nuestra propia rutina | + | Hasta este punto hemos creado una serie de rutinas |
- | <code z80> | + | * '' |
- | ; | + | |
- | ; Scancode2Ascii: convierte un scancode | + | |
- | ; IN: D = scancode | + | |
- | ; OUT: A = Codigo ASCII de la tecla | + | |
- | ; | + | |
- | Scancode2Ascii: | + | |
- | push hl | + | * '' |
- | push bc | + | |
- | ld hl,0 | + | * '' |
- | ld bc, TABLA_S2ASCII | + | |
- | add hl, bc ; hl apunta al inicio | + | |
- | ; buscamos en la tabla un max de 40 veces por el codigo | + | * '' |
- | ; le sumamos 40 a HL, leemos | + | |
- | SC2Ascii_1: | + | |
- | ld a, (hl) ; leemos un byte de la tabla | + | |
- | cp " | + | |
- | ; (la tabla habriamos llegado a los ASCIIs) | + | |
- | jr z, SC2Ascii_Exit | + | |
- | inc hl ; incrementamos puntero de HL | + | |
- | cp d ; comparamos si A==D (nuestro scancode) | + | |
- | jr nz, SC2Ascii_1 | + | |
- | SC2Ascii_Found: | + | Nótese que '' |
- | ld bc, 39 ; Sumamos 39(+INC HL=40) | + | |
- | add hl, bc ; de la tabla con los codigos ASCII | + | |
- | ld a, | + | |
- | SC2Ascii_Exit: | + | Por contra, con nuestra aproximación a la lectura del teclado, estamos llamando a '' |
- | pop bc | + | |
- | pop hl | + | |
- | ret | + | |
- | ; 40 scancodes seguidos de sus ASCIIs equivalentes | + | Esto es un tiempo valiosísimo desperdiciado dentro del bucle principal del programa, y que tenemos que repetir en cada iteración del mismo. Cada nueva ejecución del bucle del programa, tenemos que volver a sacar la semifila y la tecla de cada uno de los 5 scancodes (izquierda, derecha, arriba y abajo) dentro de '' |
- | 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 " | + | |
- | </ | + | |
- | La rutina recibe en el registro D el scancode | + | 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 |
- | | + | **En lugar de guardarnos |
- | * 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, " | + | Al principio del curso comentamos que no debemos obsesionarnos con la optimización extrema |
- | A continuación podemos ver un ejemplo que utiliza las rutinas | + | Pero aquellas |
+ | Por eso en este caso, vamos a intentar extraer de las rutinas '' | ||
+ | A continuación podemos ver el programa del ejemplo anterior, pero reescrito de forma que al redefinir las teclas (en '' | ||
+ | |||
+ | \\ | ||
<code z80> | <code z80> | ||
- | ;-------------------------------------------------------------- | + | ; Redefinir teclas y ver el estado |
- | ; Prueba | + | ; Metodo optimizado con la extraccion de semifila (puerto) |
- | ; Recuerda que para compilarla necesitarás añadir | + | ; la redefinicion de las teclas y no durante la lectura |
- | ; Find_Key | + | ; el bucle del juego. |
- | ; del mismo para reducir | + | |
- | ;-------------------------------------------------------------- | + | |
- | ORG 32768 | + | |
- | START: | + | call CLS |
- | | + | call Redefinir_Teclas_SSSTTT |
+ | call Wait_For_No_Key | ||
+ | call CLS | ||
- | chequear_teclas: | + | |
- | CALL Find_Key | + | ; " |
- | JR NZ, chequear_teclas | + | bucle: |
- | INC D | + | ld de, 0 |
- | JR Z, chequear_teclas | + | call CursorAt |
- | DEC D | + | ld de, msg_keys |
+ | call PrintString | ||
- | ; En este punto D es un scancode valido | + | call Leer_Teclado_SSSTTT |
- | call Scancode2Ascii | + | call PrintBin |
- | | + | jr bucle |
- | ; lo imprimimos por pantalla con rst 16. | + | |
- | rst 16 | + | |
- | CALL Wait_For_Keys_Released | + | ; Mensajes del programa |
- | | + | msg_izq |
+ | msg_der | ||
+ | msg_arriba | ||
+ | msg_abajo | ||
+ | msg_disp | ||
+ | msg_keys | ||
+ | ; 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 | ||
+ | p1_tecla_disp | ||
+ | p1_puerto_izq | ||
+ | p1_tecla_izq | ||
+ | p1_puerto_der | ||
+ | p1_tecla_der | ||
+ | p1_puerto_arriba | ||
+ | p1_tecla_arriba | ||
+ | p1_puerto_abajo | ||
+ | p1_tecla_abajo | ||
+ | |||
+ | P1_NUM_TECLAS | ||
+ | |||
+ | ; Estado de las tecla pulsadas | ||
+ | p1_teclas_pulsadas | ||
; | ; | ||
- | ; Esta rutina espera | + | ; 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 | ||
+ | ; el bit (posición) de esa tecla en la respuesta. | ||
+ | ; Ejemplo: ' | ||
+ | ; p1_tecla_izq = 2 (%00000010) | ||
+ | ; Llama a " | ||
+ | ; | ||
+ | ; ENTRADA: Nada | ||
+ | ; SALIDA: | ||
+ | ; 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 de, msg_izq |
- | INC A | + | call PrintString |
- | JR NZ, Wait_For_Keys_Released | + | call Redefine_Key |
- | RET | + | call Scancode_To_Port_Key |
+ | ld hl, 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 | ||
+ | call PrintString | ||
+ | call Redefine_Key | ||
+ | call Scancode_To_Port_Key | ||
+ | ld hl, p1_puerto_der | ||
+ | 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 | ||
+ | call PrintString | ||
+ | call Redefine_Key | ||
+ | call Scancode_To_Port_Key | ||
+ | ld hl, p1_puerto_arriba | ||
+ | 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 | ||
+ | call PrintString | ||
+ | call Redefine_Key | ||
+ | call Scancode_To_Port_Key | ||
+ | ld hl, p1_puerto_abajo | ||
+ | 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 | ||
+ | call PrintString | ||
+ | call Redefine_Key | ||
+ | call Scancode_To_Port_Key | ||
+ | ld hl, p1_puerto_disp | ||
+ | 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 | + | ; Scancode_To_Port_Key: |
+ | ; los valores TTT y SSS, para guardarlos después por separado como | ||
+ | ; PUERTO y TECLA | ||
+ | ; Escrita por Xor_A [Fer]. | ||
+ | ; | ||
+ | ; ENTRADA: A = scancode de teclado (00TTTSSS) | ||
+ | ; SALIDA: | ||
+ | ; E = PUERTO (parte alta) correspondiente a esa SEMIFILA | ||
+ | ; Ejemplo: ' | ||
+ | ; E = 2 (%00000010) | ||
+ | ; | ||
+ | ; MODIFICA: A, DE, E, FLAGS | ||
; | ; | ||
+ | Scancode_To_Port_Key: | ||
+ | ld d, a ; Hacer copia de scancode en D | ||
+ | and %00000111 | ||
+ | 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 | ||
+ | ConvScn_loop1: | ||
+ | rlca ; Rotamos A: Siguiente semifila | ||
+ | dec e | ||
+ | jr nz, ConvScn_loop1 | ||
+ | ConvScn_tecla: | ||
+ | ld e, a ; Salvaguardamos semifila (puerto a leer) en E | ||
+ | ld a, d ; Recuperamos A = 00TTTSSS de nuevo | ||
+ | and %00111000 | ||
+ | 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 | ||
+ | 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 | ||
+ | ; SIGNIFICADO | ||
+ | ; | ||
+ | ; ENTRADA: NADA (usa las variables p1_puerto_* y p1_tecla_*) | ||
+ | ; SALIDA: | ||
+ | ; MODIFICA: A, HL y CarryFlag | ||
+ | ; | ||
+ | Leer_Teclado_SSSTTT: | ||
+ | ld de, P1_NUM_TECLAS*256 | ||
+ | ld hl, teclas_player_1 | ||
+ | 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 | ||
+ | and (hl) ; comparo con la tecla en memoria (carry OFF) | ||
+ | jr z, tecl_sssttt_nopulsada | ||
+ | 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/ | ||
+ | inc hl ; apunto a siguiente puerto (semifila) | ||
+ | dec d ; decrementamos el num. de teclas pendientes por leer | ||
+ | jr nz, tecl_sssttt_loop | ||
+ | ld a, e ; Recuperamos estado final | ||
+ | ld (p1_teclas_pulsadas), | ||
+ | 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 " | ||
+ | |||
+ | END 33500 | ||
- | END 32768 | ||
</ | </ | ||
+ | \\ | ||
- | Una vez en ejecución y tras pulsar múltiples teclas, este es el aspecto | + | * Al empezar el programa, llamamos a '' |
+ | * Se imprime por pantalla el mensaje informativo de cada tecla para el usuario (" | ||
+ | * Se llama a '' | ||
+ | * Usa '' | ||
+ | * Usa '' | ||
+ | * Imprimir por pantalla el ASCII para que el usuario vea qué tecla ha sido pulsada. | ||
+ | * Devuelve el scancode pulsado por el usuario | ||
+ | * Se llama a la nueva rutina '' | ||
+ | * Esta información de **puerto** y **posicion_de_tecla** se guarda en un conjunto de variables '' | ||
+ | \\ | ||
+ | |||
+ | Al acabar la redefinición de teclas, tendremos en estas variables los datos de puerto y tecla para cada una de los " | ||
\\ | \\ | ||
- | {{ cursos:ensamblador:sc2ascii.png |Conversión | + | <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_disp | ||
+ | p1_tecla_disp | ||
+ | p1_puerto_izq | ||
+ | p1_tecla_izq | ||
+ | p1_puerto_der | ||
+ | p1_tecla_der | ||
+ | p1_puerto_arriba | ||
+ | p1_tecla_arriba | ||
+ | p1_puerto_abajo | ||
+ | p1_tecla_abajo | ||
+ | |||
+ | P1_NUM_TECLAS | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | La rutina '' | ||
+ | |||
+ | Ahora, en el bucle principal del programa, usaremos '' | ||
+ | |||
+ | Esta rutina lo que hace es un bucle de N iteraciones (siendo N el valor de '' | ||
+ | |||
+ | \\ | ||
+ | < | ||
+ | ; BITS 4 3 2 | ||
+ | ; SIGNIFICADO | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | 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 a la variable '' | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | \\ | ||
+ | <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 | ||
+ | p1_tecla_pause | ||
+ | p1_puerto_disp2 | ||
+ | p1_tecla_disp2 | ||
+ | p1_puerto_disp | ||
+ | p1_tecla_disp | ||
+ | p1_puerto_izq | ||
+ | p1_tecla_izq | ||
+ | p1_puerto_der | ||
+ | p1_tecla_der | ||
+ | p1_puerto_arriba | ||
+ | p1_tecla_arriba | ||
+ | p1_puerto_abajo | ||
+ | p1_tecla_abajo | ||
+ | |||
+ | P1_NUM_TECLAS | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | Esto dejaría el byte de estado de la siguiente forma: | ||
+ | |||
+ | \\ | ||
+ | < | ||
+ | ; BITS | ||
+ | ; SIGNIFICADO | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | Este conjunto de rutinas es mucho más eficiente para utilizarla en el bucle principal del juego, ya que si examinamos '' | ||
+ | |||
+ | Si quisiéramos tener teclas para el segundo jugador, podríamos replicar las funciones y añadir unas variables '' | ||
\\ | \\ | ||
===== ISSUE 2 vs ISSUE 3 ===== | ===== ISSUE 2 vs ISSUE 3 ===== | ||
- | Una recomendación a la hora de testear | + | Una recomendación a la hora de verificar |
- | Si alguna vez has cargado un snapshot de algún juego en un emulador y has visto que el personaje se movía " | + | Si alguna vez has cargado un snapshot de algún juego en un emulador y has visto que el personaje se movía " |
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 1525: | ||
del " | del " | ||
+ | |< 50% >| | ||
^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ | ^ Bits: ^ D7 ^ D6 ^ D5 ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ | ||
| Teclas: | XX | XX | XX | " | | Teclas: | XX | XX | XX | " | ||
- | Con esta información " | + | Con esta información " |
- | | + | |
| | ||
< | < | ||
- | valor = IN(63486) | + | valor = IN(63486) |
- | SI valor == 253 ENTONCES TECLA_DOS_PULSADA | + | SI: valor == 253 |
+ | ENTONCES: TECLA_DOS_PULSADA | ||
</ | </ | ||
| | ||
- | 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_an, nos proporciona a través de su blog la siguiente prueba de concepto BASIC: |
- | \\ | + | |
+ | \\ | ||
//Una ligera prueba en Spectaculator, | //Una ligera prueba en Spectaculator, | ||
<code basic> | <code basic> | ||
- | 10 PRINT AT 0,0; IN 57342; | + | 10 PRINT AT 0,0; IN 57342; |
20 GOTO 10 | 20 GOTO 10 | ||
</ | </ | ||
- | //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 | + | //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 |
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" | 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" | ||
<code z80> | <code z80> | ||
- | LD A, $DF ; Semifila " | + | ld a, $df ; Semifila " |
- | IN A, ($FE) ; Leemos el puerto | + | in a, ($fe) ; Leemos el puerto |
- | | + | |
- | JR Z, pulsado | + | jr z, pulsado |
</ | </ | ||
+ | |||
+ | Si queremos utilizar la comprobación por valor, o simplemente vamos a saltar con '' | ||
\\ | \\ | ||
- | ===== Curiosidades | + | ===== Curiosidades |
- | 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: | + | |
< | < | ||
- | 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 |
</ | </ | ||
- | 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 |
- | podemos ahorrarnos la sentencia BIT utilizando | + | |
- | 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) | + | |
- | LD A, $7F | + | ld a, $7f |
- | IN A, ($FE) | + | in a, ($fe) |
- | RRA ; pone b0 en el carry flag | + | |
- | JP NC, key_pressed | + | jp nc, key_pressed |
</ | </ | ||
- | 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 |
- | | + | |
- | | + | |
- | | + | El problema: **los microrebotes del teclado**, provocarán el conocido efecto de " |
< | < | ||
- | 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 " | + | Otra cosa es cuando se usa el teclado para " |
esperar a que se pulse una tecla, recoger qué tecla es, almacenarla, | esperar a que se pulse una tecla, recoger qué tecla es, almacenarla, | ||
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, | + | puede detectar pulsaciones incorrectas, |
- | 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 1624: | ||
EsperaSoltar: | EsperaSoltar: | ||
in a,(254) | in a,(254) | ||
- | and 00011111b | + | and %00011111 |
- | cp 00011111b | + | cp %00011111 |
jr nz, | jr nz, | ||
EsperaPulsar: | EsperaPulsar: | ||
in a,(254) | in a,(254) | ||
- | and 00011111b | + | and %00011111 |
- | cp 00011111b | + | cp %00011111 |
jr z, | jr z, | ||
Línea 1135: | Línea 1637: | ||
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, | + | tiempo que dura una trama de microrrebotes, |
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, | + | una pulsación dentro de la trama de microrrebotes, |
- | detecta una no-pulsación, | + | detecta una no-pulsación, |
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, | + | rutina y se lee el teclado directamente, |
- | 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: | EsperaSoltar: | ||
- | in a,(254) | + | in a, (254) |
- | and 00011111b | + | and %00011111 |
- | cp 00011111b | + | cp %00011111 |
- | jr nz, | + | jr nz, EsperaSoltar |
;Hacer pausa AQUI | ;Hacer pausa AQUI | ||
EsperaPulsar: | EsperaPulsar: | ||
- | in a,(254) | + | in a, (254) |
- | and 00011111b | + | and %00011111 |
- | cp 00011111b | + | cp %00011111 |
- | jr z, | + | jr z, EsperaPulsar |
- | ;Hacer pausa AQUI | + | ; Hacer pausa AQUI |
;Se registra la pulsacion... | ;Se registra la pulsacion... | ||
Línea 1181: | Línea 1683: | ||
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, | + | también se generar microrrebotes, |
</ | </ | ||
- | | + | |
+ | El segundo problema se refiere al " | ||
+ | |||
+ | Tal y como nos cuenta Miguel A. Rodríguez Jódar en los foros de Speccy.org: | ||
+ | |||
+ | < | ||
+ | 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 " | ||
+ | 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. | ||
+ | </ | ||
+ | |||
+ | 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 | ||
+ | " | ||
+ | si puede, es CHR$ 14) | ||
+ | 2 DATA " | ||
+ | " | ||
+ | " | ||
+ | 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, | ||
+ | REM Rellenamos la matriz T | ||
+ | 20 DATA " | ||
+ | 30 FOR n=1 TO 7 | ||
+ | 40 READ t$ | ||
+ | 50 PRINT "Elige tecla para "; | ||
+ | 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, | ||
+ | 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 " | ||
+ | 106 IF tecl=32 THEN PRINT " | ||
+ | 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 " | ||
+ | 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, | ||
+ | REM si alguna esta seleccionada, | ||
+ | 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, | ||
+ | REM Si la encontramos, | ||
+ | Marcamos como seleccionada la cuarta tecla del cuadrado, para que | ||
+ | no podamos elegirla | ||
+ | 1020 NEXT j: RETURN | ||
+ | </ | ||
+ | |||
+ | 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: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
- | * {{cursos: | + | * {{cursos: |
+ | * {{cursos: | ||
+ | * {{cursos: | ||
+ | * {{cursos: | ||
+ | * {{cursos: | ||
+ | * {{cursos: | ||
+ | * {{cursos: | ||
+ | \\ | ||
===== Enlaces ===== | ===== Enlaces ===== | ||
Línea 1209: | Línea 1803: | ||
* [[http:// | * [[http:// | ||
* [[http:// | * [[http:// | ||
- | * [[http:// | + | * [[http:// |
* [[http:// | * [[http:// | ||
* [[http:// | * [[http:// | ||
* {{: | * {{: | ||
* [[http:// | * [[http:// | ||
- | * [[http://www.arrakis.es/~ninsesabe/pasmo/|PASMO]] | + | * The Complete Spectrum ROM Disassembly |
+ | \\ | ||
+ | **[ [[.: | ||