Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa |
cursos:ensamblador:teclado [18-01-2024 08:49] – [Microrebotes y Ghosting en la lectura del teclado] sromero | cursos:ensamblador:teclado [22-01-2024 08:01] (actual) – [Redefinicion de teclas] sromero |
---|
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). | 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). | 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. | 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. | 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. | 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). | 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). |
===== 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. Para leer el estado de cada una de las teclas del teclado (0 = pulsada, 1 = no pulsada), debemos obtener el estado del puerto $FE. | 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? | 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. | La respuesta es: organizando el teclado en filas de teclas y seleccionando qué fila leer mediante el registro B. |
| |
<code basic> | <code basic> |
5 REM Mostrando el estado de la fila 1-5 del teclado ($F7FE) | 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 |
| |
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 | 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 |
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 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. | misma función que con su equivalente ensamblador: consultar el valor contenido en dicho puerto. |
| |
El valor $FE se corresponde con el puerto del teclado, mientras que $F7 se corresponde con 11110111 en binario, donde el cero selecciona la fila concreta del teclado que queremos leer, en este caso, la fila de teclas del 1 al 5. | El valor $fe se corresponde con el puerto del teclado, mientras que $f7 se corresponde con 11110111 en binario, donde el cero selecciona la fila concreta del teclado que queremos leer, en este caso, la fila de teclas del 1 al 5. |
| |
Por defecto, sin pulsar ninguna tecla, los diferentes bits del valor leído en dicho puerto estarán a 1. Cuando pulsamos una tecla, el valor del bit correspondiente a dicha tecla aparecerá como 0, y soltándola volverá a su valor 1 original. | Por defecto, sin pulsar ninguna tecla, los diferentes bits del valor leído en dicho puerto estarán a 1. Cuando pulsamos una tecla, el valor del bit correspondiente a dicha tecla aparecerá como 0, y soltándola volverá a su valor 1 original. |
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 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). | 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 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). |
| |
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". | 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% >| | |< 50% >| |
^ Puerto ^ Teclas ^ | ^ Puerto ^ Teclas ^ |
| 65278d ($FEFE) | de CAPS SHIFT a V | | | 65278d ($fefe) | de CAPS SHIFT a V | |
| 65022d ($FDFE) | de A a G | | | 65022d ($fdfe) | de A a G | |
| 64510d ($FBFE) | de Q a T | | | 64510d ($fbfe) | de Q a T | |
| 63486d ($F7FE) | de 1 a 5 (y JOYSTICK 1) | | | 63486d ($f7fe) | de 1 a 5 (y JOYSTICK 1) | |
| 61438d ($EFFE) | de 6 a 0 (y JOYSTICK 2) | | | 61438d ($effe) | de 6 a 0 (y JOYSTICK 2) | |
| 57342d ($DFFE) | de P a Y | | | 57342d ($dffe) | de P a Y | |
| 49150d ($BFFE) | de ENTER a H | | | 49150d ($bffe) | de ENTER a H | |
| 32766d ($7FFE) | de (space) a B | | | 32766d ($7ffe) | de (space) a B | |
\\ | \\ |
| |
|< 70% >| | |< 70% >| |
^ Puerto ^ Bits: ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ | ^ Puerto ^ Bits: ^ D4 ^ D3 ^ D2 ^ D1 ^ D0 ^ |
| 65278d ($FEFE) | Teclas: | "V" | "C" | "X" | "Z" | CAPS | | | 65278d ($fefe) | Teclas: | "V" | "C" | "X" | "Z" | CAPS | |
| 65022d ($FDFE) | Teclas: | "G" | "F" | "D" | "S" | "A" | | | 65022d ($fdfe) | Teclas: | "G" | "F" | "D" | "S" | "A" | |
| 64510d ($FBFE) | Teclas: | "T" | "R" | "E" | "W" | "Q" | | | 64510d ($fbfe) | Teclas: | "T" | "R" | "E" | "W" | "Q" | |
| 63486d ($F7FE) | Teclas: | "5" | "4" | "3" | "2" | "1" | | | 63486d ($f7fe) | Teclas: | "5" | "4" | "3" | "2" | "1" | |
| 61438d ($EFFE) | Teclas: | "6" | "7" | "8" | "9" | "0" | | | 61438d ($effe) | Teclas: | "6" | "7" | "8" | "9" | "0" | |
| 57342d ($DFFE) | Teclas: | "Y" | "U" | "I" | "O" | "P" | | | 57342d ($dffe) | Teclas: | "Y" | "U" | "I" | "O" | "P" | |
| 49150d ($BFFE) | Teclas: | "H" | "J" | "K" | "L" | ENTER | | | 49150d ($bffe) | Teclas: | "H" | "J" | "K" | "L" | ENTER | |
| 32766d ($7FFE) | 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 ($EFFE) | SINCL1 | LEFT | RIGHT | DOWN | UP | FIRE | | | 61438d ($effe) | SINCL1 | LEFT | RIGHT | DOWN | UP | FIRE | |
| 63486d ($F7FE) | 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. | 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. |
| |
La parte alta es, la ú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: | La parte alta es, la ú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: |
|< 70% >| | |< 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 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. |
| |
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). | 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 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 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). |
| |
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, A ; Testeamos el bit 0 | bit 0, a ; 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 |
De nuevo ensamblamos nuestro programa con ''pasmo <nowiki>--</nowiki>tapbas keyb1.asm keyb1.tap'', y lo cargamos en el Spectrum o en un emulador. | De nuevo ensamblamos nuestro programa con ''pasmo <nowiki>--</nowiki>tapbas keyb1.asm keyb1.tap'', y lo cargamos en el Spectrum o en un emulador. |
| |
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. | 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. |
| |
En ocasiones puede ser más recomendable la utilización de ''IN'' en su formato alternativo. Recordemos que tenemos 2 opcodes para IN, con 2 formas diferentes y equivalentes de especificar el puerto: | En ocasiones puede ser más recomendable la utilización de ''IN'' en su formato alternativo. Recordemos que tenemos 2 opcodes para IN, con 2 formas diferentes y equivalentes de especificar el puerto: |
<code z80> | <code z80> |
; Forma 1 | ; Forma 1 |
LD BC, $FFFE | ld bc, $fffe |
IN A, (C) ; A = Lectura de puerto $FFFE | in a, (c) ; A = Lectura de puerto $fffe |
| |
; Forma 2 | ; Forma 2 |
LD A, $FF | ld a, $ff |
IN A, ($FE) ; A = Lectura de puerto $FFFE | in a, ($fe) ; A = Lectura de puerto $fffe |
</code> | </code> |
| |
| |
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, A ; Testeamos el bit 0 | bit 0, a ; 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 |
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. | 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. |
| |
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. | 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. |
| |
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. | 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. |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Wait_For_Keys_Pressed: | Wait_For_Keys_Pressed: |
XOR A ; A = 0 | xor a ; A = 0 => leer todas las semifilas |
IN A, ($FE) | in a, ($fe) ; Leer del puerto del teclado |
OR 224 | or %11100000 ; Poner a 1 los 3 bits más altos |
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 |
| |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Wait_For_Keys_Released: | Wait_For_Keys_Released: |
XOR A | xor a ; A = 0 => leer todas las semifilas |
IN A, ($FE) | in a, ($fe) ; Leer del puerto del teclado |
OR 224 | or %11100000 ; Poner a 1 los 3 bits más altos |
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 |
</code> | </code> |
| |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Wait_For_Keys_Pressed: | Wait_For_Keys_Pressed: |
PUSH AF | push af |
wait_for_keypressed_loop: | wait_for_keypressed_loop: |
XOR A ; A = 0 | xor a ; A = 0 => leer todas las semifilas |
IN A, ($FE) | in a, ($fe) ; Leer del puerto del teclado |
OR 224 | or %11100000 ; Poner a 1 los 3 bits más altos |
INC A | inc a ; Comprobamos el estado del teclado con: A=A+1. |
JR Z, wait_for_keypressed_loop | ; Si A=0 => ZF = 1 => no hay tecla pulsada |
POP AF | ; Si A!=0 => ZF = 0 => hay alguna tecla pulsada |
RET | jr z, wait_for_keypressed_loop |
| pop af |
| ret |
| |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Wait_For_Keys_Released: | Wait_For_Keys_Released: |
PUSH AF | push af |
wait_for_no_pressedkey_loop: | wait_for_no_pressedkey_loop: |
XOR A | xor a ; A = 0 => leer todas las semifilas |
IN A, ($FE) | in a, ($fe) ; Leer del puerto del teclado |
OR 224 | or %11100000 ; Poner a 1 los 3 bits más altos |
INC A | inc a ; Comprobamos el estado del teclado con: A=A+1. |
JR NZ, wait_for_no_pressedkey_loop | ; Si A=0 => ZF = 1 => no hay tecla pulsada |
POP AF | ; Si A!=0 => ZF = 0 => hay alguna tecla pulsada |
RET | jr nz, wait_for_no_pressedkey_loop |
| pop af |
| ret |
</code> | </code> |
| |
; Lee el estado de O, P, Q, A, ESPACIO. | ; Lee el estado de O, P, Q, A, ESPACIO. |
Leer_Teclado: | Leer_Teclado: |
PUSH AF | push af |
PUSH BC | push bc |
PUSH HL | push hl |
| |
LD HL, estado_tecla_arriba | ld hl, estado_tecla_arriba |
LD (HL), 0 ; marcamos estado_tecla_arriba como no pulsada | ld (hl), 0 ; marcamos estado_tecla_arriba como no pulsada |
| |
LD BC, $FBFE | ld bc, $fbfe |
IN A, (C) | in a, (c) |
BIT 0, A ; Leemos la tecla Q | bit 0, a ; Leemos la tecla Q |
; (podríamos haber usado "AND %00000001") | ; (podríamos haber usado "and %00000001") |
JR NZ, Control_no_up ; No pulsada, saltamos para no cambiar valor 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 | ld (hl), 1 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1 |
Control_no_up: | Control_no_up: |
| |
LD HL, estado_tecla_abajo | ld hl, estado_tecla_abajo |
LD (HL), 0 ; marcamos estado_tecla_abajo como no pulsada | ld (hl), 0 ; marcamos estado_tecla_abajo como no pulsada |
LD BC, $FDFE | ld bc, $fdfe |
IN A, (C) | in a, (c) |
BIT 0, A ; Leemos la tecla A | bit 0, a ; Leemos la tecla A |
JR NZ, Control_no_down ; No pulsada, saltamos para no cambiar valor 0 | 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 | ld (hl), 1 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1 |
Control_no_down: | Control_no_down: |
| |
LD HL, estado_tecla_derecha | ld hl, estado_tecla_derecha |
LD (HL), 0 ; marcamos estado_tecla_derecha como no pulsada | ld (hl), 0 ; marcamos estado_tecla_derecha como no pulsada |
LD BC, $DFFE | ld bc, $dffe |
IN A, (C) | in a, (c) |
BIT 0, A ; Leemos la tecla P (aqui no usar AND) | bit 0, a ; Leemos la tecla P (aqui no usar AND) |
JR NZ, Control_no_right ; No pulsada | jr nz, Control_no_right ; No pulsada |
LD (HL), 1 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1 | 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 | ; O y P en misma fila, no leer puerto otra vez |
LD HL, estado_tecla_izquierda | ld hl, estado_tecla_izquierda |
LD (HL), 0 | ld (hl), 0 |
BIT 1, A ; Tecla O | bit 1, a ; Tecla O |
; (podríamos haber usado "AND %00000010") | ; (podríamos haber usado "and %00000010") |
JR NZ, Control_no_left ; No pulsada, saltamos para no cambiar valor 0 | 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 | ld (hl), 1 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1 |
Control_no_left: | Control_no_left: |
| |
LD HL, estado_tecla_disp | ld hl, estado_tecla_disp |
LD (HL), 0 | ld (hl), 0 |
LD BC, $7FFE | ld bc, $7ffe |
IN A, (C) | in a, (c) |
BIT 0, A ; Tecla Espacio | bit 0, a ; Tecla Espacio |
JR NZ, Control_no_fire ; No pulsada, saltamos para no cambiar valor 0 | 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 | ld (hl), 1 ; Pulsada, ponemos a 1 el bit 0 => (HL) = 1 |
Control_no_fire: | Control_no_fire: |
| |
POP HL | pop hl |
POP BC | pop bc |
POP AF | pop af |
RET | ret |
| |
; Estado de cada tecla. 0 = no pulsada, 1 = pulsada | ; Estado de cada tecla. 0 = no pulsada, 1 = pulsada |
\\ | \\ |
| |
//David Webb// 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. | //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. |
| |
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. | 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. |
; 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. | ; El registro D valdrá 255 ($ff) si no hay ninguna tecla pulsada. |
; | ; |
; Flags: ZF 0: Más de una tecla pulsada | ; Flags: ZF 0: Más de una tecla pulsada |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
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, 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 | 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: ; 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> |
| |
<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 A, D | ld a, d |
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 A, D | ld a, d |
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. |
| |
Bucle_entrada: | Bucle_entrada: |
CALL Wait_For_Key | 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 | jr nz, Pedir_Tecla ; Repetir si la tecla no es valida |
INC D | inc d |
JR Z, Pedir_Tecla ; Repetir si no se pulsa ninguna tecla | jr z, Pedir_Tecla ; Repetir si no se pulsa ninguna tecla |
DEC D | dec d |
| |
LD A, D ; Guardamos en A copia del resultado | ld a, d ; Guardamos en A copia del resultado |
| |
CP $21 ; Comprobamos si A == 21h (enter) | cp $21 ; Comprobamos si A == 21h (enter) |
RET Z ; Si no lo es, repetir | ret z ; Si no lo es, repetir |
| |
CALL PrintHex ; Imprimimos el scancode hex en pantalla | call PrintHex ; Imprimimos el scancode hex en pantalla |
| |
CALL PrintSpace ; Espacio para separar | call PrintSpace ; Espacio para separar |
| |
CALL Wait_For_No_Key ; Esperamos a que el usuario SUELTE la tecla | call Wait_For_No_Key ; Esperamos a que el usuario SUELTE la tecla |
JR Pedir_Tecla ; Repetir hasta que arriba un ENTER | jr Pedir_Tecla ; Repetir hasta que arriba un ENTER |
; ($21 pulsado) haga el RET Z a BASIC | ; ($21 pulsado) haga el ret z a BASIC |
| |
INCLUDE "utils.asm" | INCLUDE "utils.asm" |
Los scancodes asociados a las diferentes teclas son: | Los scancodes asociados a las diferentes teclas son: |
| |
| \\ |
|< 80% >| | |< 80% >| |
^ Teclas: ^ 1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 ^ 9 ^ 0 ^ | ^ Teclas: ^ 1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 ^ 9 ^ 0 ^ |
^ Scancodes: | $24 | $1C | $14 | $0C | $04 | $03 | $0B | $13 | $1B | $23 | | ^ Scancodes: | $24 | $1c | $14 | $0c | $04 | $03 | $0b | $13 | $1b | $23 | |
| |
|< 80% >| | |< 80% >| |
^ Teclas: ^ Q ^ W ^ E ^ R ^ T ^ Y ^ U ^ I ^ O ^ P ^ | ^ Teclas: ^ Q ^ W ^ E ^ R ^ T ^ Y ^ U ^ I ^ O ^ P ^ |
^ Scancodes: | $25 | $1D | $15 | $0D | $05 | $02 | $0A | $12 | $1A | $22 | | ^ Scancodes: | $25 | $1d | $15 | $0d | $05 | $02 | $0a | $12 | $1a | $22 | |
| |
|< 80% >| | |< 80% >| |
^ Teclas: ^ A ^ S ^ D ^ F ^ G ^ H ^ J ^ K ^ L ^ ENTER ^ | ^ Teclas: ^ A ^ S ^ D ^ F ^ G ^ H ^ J ^ K ^ L ^ ENTER ^ |
^ Scancodes: | $26 | $1E | $16 | $0E | $06 | $01 | $09 | $11 | $19 | $21 | | ^ Scancodes: | $26 | $1e | $16 | $0e | $06 | $01 | $09 | $11 | $19 | $21 | |
| |
|< 80% >| | |< 80% >| |
^ Teclas: ^ CAPS ^ Z ^ X ^ C ^ V ^ B ^ N ^ M ^ SYMB ^ SPACE ^ | ^ Teclas: ^ CAPS ^ Z ^ X ^ C ^ V ^ B ^ N ^ M ^ SYMB ^ SPACE ^ |
^ Scancodes: | $27 | $1F | $17 | $0F | $07 | $00 | $08 | $10 | $18 | $20 | | ^ 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. | 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. |
tecla_arriba DEFB $25 | tecla_arriba DEFB $25 |
tecla_abajo DEFB $26 | tecla_abajo DEFB $26 |
tecla_izq DEFB $1A | tecla_izq DEFB $1a |
tecla_der DEFB $22 | tecla_der DEFB $22 |
tecla_disp DEFB $20 | tecla_disp DEFB $20 |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Scancode2Ascii: | Scancode2Ascii: |
PUSH HL | push hl |
PUSH BC | push bc |
| |
LD HL, 0 | ld hl, 0 |
LD BC, TABLA_Scancode2ASCII | ld bc, TABLA_Scancode2ASCII |
ADD HL, BC ; HL apunta al inicio de la tabla | add hl, bc ; HL apunta al inicio de la tabla |
| |
; buscamos en la tabla un max de 40 veces por el codigo | ; buscamos en la tabla un max de 40 veces por el codigo |
; le sumamos 40 a HL, leemos el valor de (HL) y ret A | ; le sumamos 40 a HL, leemos el valor de (HL) y ret A |
SC2Ascii_1: | SC2Ascii_1: |
LD A, (HL) ; leemos un byte de la tabla | ld a, (hl) ; leemos un byte de la tabla |
CP '1' ; Si es '1' (caracter) fin de la rutina (porque | cp '1' ; Si es '1' (caracter) fin de la rutina (porque |
; en la tabla habriamos llegado a los ASCIIs) | ; en la tabla habriamos llegado a los ASCIIs) |
JR Z, SC2Ascii_Exit ; (y es condicion de forzado de salida) | jr z, SC2Ascii_Exit ; (y es condicion de forzado de salida) |
INC HL ; incrementamos puntero de HL | inc hl ; incrementamos puntero de HL |
CP D ; comparamos si A==D (nuestro scancode) | cp d ; comparamos si A==D (nuestro scancode) |
JR NZ, SC2Ascii_1 | jr nz, SC2Ascii_1 |
| |
SC2Ascii_Found: | SC2Ascii_Found: |
LD BC, 39 ; Sumamos 39(+INC HL=40) para ir a la | ld bc, 39 ; Sumamos 39(+inc hl=40) para ir a la |
ADD HL, BC ; seccion de la tabla con el codigo ASCII | add hl, bc ; seccion de la tabla con el codigo ASCII |
LD A, (HL) ; leemos el codigo ASCII de esa tabla | ld a, (hl) ; leemos el codigo ASCII de esa tabla |
| |
SC2Ascii_Exit: | SC2Ascii_Exit: |
POP BC | pop bc |
POP HL | pop hl |
RET | ret |
| |
; 40 scancodes seguidos de sus ASCIIs equivalentes | ; 40 scancodes seguidos de sus ASCIIs equivalentes |
TABLA_Scancode2ASCII: | TABLA_Scancode2ASCII: |
DEFB $24, $1C, $14, $0C, $04, $03, $0B, $13, $1B, $23 | DEFB $24, $1c, $14, $0c, $04, $03, $0b, $13, $1b, $23 |
DEFB $25, $1D, $15, $0D, $05, $02, $0A, $12, $1A, $22 | DEFB $25, $1d, $15, $0d, $05, $02, $0a, $12, $1a, $22 |
DEFB $26, $1E, $16, $0E, $06, $01, $09, $11, $19, $21 | DEFB $26, $1e, $16, $0e, $06, $01, $09, $11, $19, $21 |
DEFB $27, $1F, $17, $0F, $07, $00, $08, $10, $18, $20 | DEFB $27, $1f, $17, $0f, $07, $00, $08, $10, $18, $20 |
DEFB "1234567890QWERTYUIOPASDFGHJKLecZXCVBNMys" | DEFB "1234567890QWERTYUIOPASDFGHJKLecZXCVBNMys" |
</code> | </code> |
ORG 50000 | ORG 50000 |
| |
CALL CLS | call CLS |
| |
START: | START: |
| |
CALL Wait_For_No_Key | call Wait_For_No_Key |
| |
chequear_teclas: | chequear_teclas: |
CALL Find_Key ; Llamamos a la rutina | call Find_Key ; Llamamos a la rutina |
JR NZ, chequear_teclas ; Repetir si la tecla no es válida | jr nz, chequear_teclas ; Repetir si la tecla no es válida |
INC D | inc d |
JR Z, chequear_teclas ; Repetir si no se pulsó ninguna tecla | jr z, chequear_teclas ; Repetir si no se pulsó ninguna tecla |
DEC D | dec d |
| |
; En este punto D es un scancode valido | ; En este punto D es un scancode valido |
CALL Scancode2Ascii | call Scancode2Ascii |
| |
; En este punto A contiene el ASCII del scancode en D | ; En este punto A contiene el ASCII del scancode en D |
; lo imprimimos por pantalla con rst 16. | ; lo imprimimos por pantalla con rst 16. |
RST 16 | rst 16 |
| |
JR START ; vuelta a empezar | jr START ; vuelta a empezar |
| |
INCLUDE "utils.asm" | INCLUDE "utils.asm" |
ORG 33500 | ORG 33500 |
| |
CALL CLS | call CLS |
CALL Redefinir_Teclas | call Redefinir_Teclas |
| |
bucle: | bucle: |
JR bucle | jr bucle |
| |
; Mensajes del programa | ; Mensajes del programa |
| |
; Teclas por defecto si no se redefine: O P Q A SPACE | ; Teclas por defecto si no se redefine: O P Q A SPACE |
tecla_izq DEFB $1A | tecla_izq DEFB $1a |
tecla_der DEFB $22 | tecla_der DEFB $22 |
tecla_arriba DEFB $25 | tecla_arriba DEFB $25 |
Redefinir_Teclas: | Redefinir_Teclas: |
; Los siguientes textos se podrian haber impreso tambien usando un bucle | ; Los siguientes textos se podrian haber impreso tambien usando un bucle |
; con INC DE (avanzar cadena) e INC HL (avanzar tecla a escribir) | ; con inc de (avanzar cadena) e inc hl (avanzar tecla a escribir) |
LD DE, msg_izq | ld de, msg_izq |
CALL PrintString ; Imprimir mensaje "Izquierda?" | call PrintString ; Imprimir mensaje "Izquierda?" |
CALL Redefine_Key ; Esperar pulsacion (e imprimir ASCII) | call Redefine_Key ; Esperar pulsacion (e imprimir ASCII) |
LD HL, tecla_izq ; Apuntamos HL a la variable tecla_izq | ld hl, tecla_izq ; Apuntamos HL a la variable tecla_izq |
LD (HL), A | ld (hl), a |
| |
LD DE, msg_der ; Siguiente mensaje: "Derecha?" | ld de, msg_der ; Siguiente mensaje: "Derecha?" |
CALL PrintString | call PrintString |
CALL Redefine_Key ; Esperar pulsacion (e imprimir ASCII) | call Redefine_Key ; Esperar pulsacion (e imprimir ASCII) |
LD HL, tecla_der | ld hl, tecla_der |
LD (HL), A ; Guardamos tecla pulsada | ld (hl), a ; Guardamos tecla pulsada |
| |
LD DE, msg_arriba ; Repetimos con ARRIBA | ld de, msg_arriba ; Repetimos con ARRIBA |
CALL PrintString | call PrintString |
CALL Redefine_Key | call Redefine_Key |
LD HL, tecla_arriba | ld hl, tecla_arriba |
LD (HL), A | ld (hl), a |
| |
LD DE, msg_abajo ; Repetimos con ABAJO | ld de, msg_abajo ; Repetimos con ABAJO |
CALL PrintString | call PrintString |
CALL Redefine_Key | call Redefine_Key |
LD HL, tecla_abajo | ld hl, tecla_abajo |
LD (HL), A | ld (hl), a |
| |
LD DE, msg_disp ; Repetimos con DISPARO | ld de, msg_disp ; Repetimos con DISPARO |
CALL PrintString | call PrintString |
CALL Redefine_Key | call Redefine_Key |
LD HL, tecla_disp | ld hl, tecla_disp |
LD (HL), A | ld (hl), a |
RET | ret |
| |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Redefine_Key: | Redefine_Key: |
PUSH DE | push de |
PUSH HL | push hl |
CALL Wait_For_No_Key | call Wait_For_No_Key |
| |
wait_for_scan_loop: | wait_for_scan_loop: |
CALL Find_Key | call Find_Key |
JR NZ, wait_for_scan_loop ; Mas de una tecla leida, repetir | jr nz, wait_for_scan_loop ; Mas de una tecla leida, repetir |
LD A, D | ld a, d |
CP $FF ; si A es $FF => ninguna tecla pulsada | cp $ff ; si A es $ff => ninguna tecla pulsada |
JR Z, wait_for_scan_loop ; Repetimos hasta que A != $FF | jr z, wait_for_scan_loop ; Repetimos hasta que A != $ff |
LD H, D ; Nos hacemos copia de D en H | ld h, d ; Nos hacemos copia de D en H |
CALL Scancode2Ascii ; Convertir D (scancode) en A (ASCII) | call Scancode2Ascii ; Convertir D (scancode) en A (ASCII) |
| |
CP 'e' ; ¿Es 'e'? Imprimir "ENTER" | cp 'e' ; ¿Es 'e'? Imprimir "ENTER" |
JR NZ, redef_key_NO_ENTER | jr nz, redef_key_NO_ENTER |
LD DE, redef_key_enter | ld de, redef_key_enter |
CALL PrintString | call PrintString |
JR redef_key_end ; Impreso texto, salimos | jr redef_key_end ; Impreso texto, salimos |
redef_key_NO_ENTER: | redef_key_NO_ENTER: |
| |
CP 's' ; ¿Es 's'? Imprimir "SPACE" | cp 's' ; ¿Es 's'? Imprimir "SPACE" |
JR NZ, redef_key_NO_SPACE | jr nz, redef_key_NO_SPACE |
LD DE, redef_key_space | ld de, redef_key_space |
CALL PrintString | call PrintString |
JR redef_key_end | jr redef_key_end |
redef_key_NO_SPACE: | redef_key_NO_SPACE: |
| |
CP 'c' ; ¿Es 'c'? Imprimir "CS" | cp 'c' ; ¿Es 'c'? Imprimir "CS" |
JR NZ, redef_key_NO_CAPSSHIFT | jr nz, redef_key_NO_CAPSSHIFT |
LD DE, redef_key_cs | ld de, redef_key_cs |
CALL PrintString | call PrintString |
JR redef_key_end | jr redef_key_end |
redef_key_NO_CAPSSHIFT: | redef_key_NO_CAPSSHIFT: |
| |
CP 'y' ; ¿Es 'y'? Imprimir "SS" | cp 'y' ; ¿Es 'y'? Imprimir "SS" |
JR NZ, redef_key_NO_SYMBOLSHIFT | jr nz, redef_key_NO_SYMBOLSHIFT |
LD DE, redef_key_ss | ld de, redef_key_ss |
CALL PrintString | call PrintString |
JR redef_key_end | jr redef_key_end |
redef_key_NO_SYMBOLSHIFT: | redef_key_NO_SYMBOLSHIFT: |
; Si llegamos aqui no era tecla especial. | ; Si llegamos aqui no era tecla especial. |
RST 16 ; Ninguna tecla especial => Print ASCII | rst 16 ; Ninguna tecla especial => Print ASCII |
LD A, D | ld a, d |
| |
redef_key_end: | redef_key_end: |
LD A, H ; Recuperamos scancode | ld a, h ; Recuperamos scancode |
CALL PrintCR ; Imprimir retorno de carro | call PrintCR ; Imprimir retorno de carro |
| |
POP HL | pop hl |
POP DE | pop de |
RET ; Volver con registros preservados | ret ; Volver con registros preservados |
| |
redef_key_enter DB "ENTER", _EOS | redef_key_enter DB "ENTER", _EOS |
\\ | \\ |
| |
| |
| \\ |
| ==== UDGs para las teclas especiales ==== |
| |
| 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. |
| |
| 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: |
| |
| <code z80> |
| 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: |
| |
| \\ |
| {{ :cursos:ensamblador:udgs_teclas.png?106 }} |
| \\ |
| |
\\ | \\ |
| |
<code z80> | <code z80> |
tecla_izq DEFB $1A ; O | tecla_izq DEFB $1a ; O |
tecla_der DEFB $22 ; P | tecla_der DEFB $22 ; P |
tecla_arriba DEFB $25 ; Q | tecla_arriba DEFB $25 ; Q |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Check_Key: | Check_Key: |
PUSH BC | push bc |
LD C, A ; Copia de A | ld c, a ; Copia de A |
| |
; Operaciones para extraer la semifila | ; Operaciones para extraer la semifila |
; y la tecla de A (00SSSTTT), para leer | ; y la tecla de A (00SSSTTT), para leer |
; el puerto y chequear el bit adecuados | ; el puerto y chequear el bit adecuados |
AND 7 | and %00000111 ; Nos quedamos con la tecla (bit) |
INC A | inc a |
LD B, A ; B = 16 - (num. linea direccion) | ld b, a ; B = 16 - (num. linea direccion) |
SRL C | srl c |
SRL C | srl c |
SRL C | srl c |
LD A, 5 | ld a, 5 |
SUB C | sub c |
LD C, A ; C = (semifila + 1) | ld c, a ; C = (semifila + 1) |
| |
LD A, $FE | ld a, $fe |
| |
CKHiFind: ; Calcular el octeto de mayor peso del puerto | CKHiFind: ; Calcular el octeto de mayor peso del puerto |
RRCA | rrca |
DJNZ CKHiFind | djnz CKHiFind |
IN A, ($FE) ; Leemos la semifila | in a, ($fe) ; Leemos la semifila |
| |
CKNXKey: | CKNXKey: |
RRA | rra |
DEC C | dec c |
JR NZ, CKNXKey ; Ponemos el bit de tecla en el CF | jr nz, CKNXKey ; Ponemos el bit de tecla en el CF |
POP BC | pop bc |
| |
RET | ret |
</code> | </code> |
| |
<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) |
| |
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) |
ORG 50000 | ORG 50000 |
| |
CALL CLS | call CLS |
Imprimir_Valor: | Imprimir_Valor: |
LD A, (valor) ; Guardamos en A copia del resultado | ld a, (valor) ; Guardamos en A copia del resultado |
LD B, 0 | ld b, 0 |
LD C, A ; BC = A (B=0, C=A) | ld c, a ; BC = A (B=0, C=A) |
CALL PrintNum ; Imprimimos el valor en pantalla | call PrintNum ; Imprimimos el valor en pantalla |
CALL PrintSpace | call PrintSpace |
| |
CALL Wait_For_No_Key ; Esperamos a que se suelte la tecla | call Wait_For_No_Key ; Esperamos a que se suelte la tecla |
| |
Bucle: | Bucle: |
| |
Comprobar_tecla_mas: | Comprobar_tecla_mas: |
LD A, (tecla_mas) | ld a, (tecla_mas) |
CALL Check_Key | call Check_Key |
| |
JR C, Comprobar_tecla_menos ; Carry = 1, tecla_mas no pulsada | jr c, Comprobar_tecla_menos ; Carry = 1, tecla_mas no pulsada |
| |
LD HL, valor | ld hl, valor |
INC (HL) | inc (hl) |
JR Imprimir_Valor | jr Imprimir_Valor |
| |
Comprobar_tecla_menos: | Comprobar_tecla_menos: |
LD A, (tecla_menos) | ld a, (tecla_menos) |
CALL Check_Key | call Check_Key |
JR C, Bucle ; Carry = 1, tecla_menos no pulsada | jr c, Bucle ; Carry = 1, tecla_menos no pulsada |
| |
LD HL, valor | ld hl, valor |
DEC (HL) | dec (hl) |
| |
JP Imprimir_Valor | jp Imprimir_Valor |
| |
; Variables de teclas | ; Variables de teclas |
| |
<code z80> | <code z80> |
CALL Leer_Teclado_Empaquetado | call Leer_Teclado_Empaquetado |
LD (estado_teclas), a | ld (estado_teclas), a |
| |
; (...) | ; (...) |
| |
; Mas adelante en el programa | ; Mas adelante en el programa |
LD A, (estado_teclas) | ld a, (estado_teclas) |
| |
BIT 4, A | bit 4, a |
CALL NZ, Disparo_Pulsado | call nz, Disparo_Pulsado |
| |
BIT 1, A | bit 1, a |
CALL NZ, Salto_Pulsado | call nz, Salto_Pulsado |
| |
; etc... | ; etc... |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Leer_Teclado_Empaquetado: | Leer_Teclado_Empaquetado: |
PUSH DE | push de |
LD D, 0 ; D = 0 | ld d, 0 ; D = 0 |
| |
LD HL, tecla_izq ; Apuntamos HL a la primera de las teclas (izq) | ld hl, tecla_izq ; Apuntamos HL a la primera de las teclas (izq) |
| |
LD A, (HL) ; leemos su valor | ld a, (hl) ; leemos su valor |
CALL Check_Key | call Check_Key |
JR C, tecl_izq_notpressed | jr c, tecl_izq_notpressed |
SET 3, D ; Si pulsada, ponemos bit 3 a 1. | set 3, d ; Si pulsada, ponemos bit 3 a 1. |
; Usando A podríamos usar "OR %00001000" | ; Usando A podríamos usar "or %00001000" |
tecl_izq_notpressed: | tecl_izq_notpressed: |
INC HL ; INC HL a siguiente tecla en memoria (der) | inc hl ; inc hl a siguiente tecla en memoria (der) |
LD A, (HL) ; leemos su valor | ld a, (hl) ; leemos su valor |
CALL Check_Key | call Check_Key |
JR C, tecl_der_notpressed | jr c, tecl_der_notpressed |
SET 2, D ; Si pulsada, ponemos bit 2 a 1. | set 2, d ; Si pulsada, ponemos bit 2 a 1. |
| |
tecl_der_notpressed: | tecl_der_notpressed: |
INC HL ; apuntamos HL a siguiente tecla (arriba) | inc hl ; apuntamos HL a siguiente tecla (arriba) |
LD A, (HL) ; leemos su valor | ld a, (hl) ; leemos su valor |
CALL Check_Key | call Check_Key |
JR C, tecl_arr_notpressed | jr c, tecl_arr_notpressed |
SET 1, D ; Si pulsada, ponemos bit 1 a 1. | set 1, d ; Si pulsada, ponemos bit 1 a 1. |
| |
tecl_arr_notpressed: | tecl_arr_notpressed: |
INC HL ; apuntamos HL a siguiente tecla (abajo) | inc hl ; apuntamos HL a siguiente tecla (abajo) |
LD A, (HL) ; leemos su valor | ld a, (hl) ; leemos su valor |
CALL Check_Key | call Check_Key |
JR C, tecl_aba_notpressed | jr c, tecl_aba_notpressed |
SET 0, D ; Si pulsada, ponemos bit 0 a 1. | set 0, d ; Si pulsada, ponemos bit 0 a 1. |
| |
tecl_aba_notpressed: | tecl_aba_notpressed: |
INC HL ; apuntamos HL a siguiente tecla (disparo) | inc hl ; apuntamos HL a siguiente tecla (disparo) |
LD A, (HL) ; leemos su valorprimera | ld a, (hl) ; leemos su valorprimera |
CALL Check_Key | call Check_Key |
JR C, tecl_fire_notpressed | jr c, tecl_fire_notpressed |
SET 4, D ; Si pulsada, ponemos bit 4 a 1. | set 4, d ; Si pulsada, ponemos bit 4 a 1. |
| |
tecl_fire_notpressed: | tecl_fire_notpressed: |
; Podriamos añadir codigo para disparo 2 | ; Podriamos añadir codigo para disparo 2 |
LD A, D | ld a, d |
POP DE | pop de |
RET ; Devolvemos en A el estado de las teclas | ret ; Devolvemos en A el estado de las teclas |
</code> | </code> |
| |
La rutina simplemente apunta HL a la primera de las teclas y llama a ''Check_Key'' para leer su estado. Si está a 1, establece a 1 el bit 3 del registro D. Si está a 0, salta a comprobar la siguiente tecla. | La rutina simplemente apunta HL a la primera de las teclas y llama a ''Check_Key'' para leer su estado. Si está a 1, establece a 1 el bit 3 del registro D. Si está a 0, salta a comprobar la siguiente tecla. |
| |
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. | 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. |
| |
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. | 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. |
ORG 33500 | ORG 33500 |
| |
CALL CLS | call CLS |
CALL Redefinir_Teclas | call Redefinir_Teclas |
CALL CLS | call CLS |
CALL Wait_For_No_Key ; Esperar a que no haya teclas pulsadas | call Wait_For_No_Key ; Esperar a que no haya teclas pulsadas |
| |
; Bucle del programa, lee nuestras teclas e imprime primero la | ; Bucle del programa, lee nuestras teclas e imprime primero la |
; "leyenda" de las teclas y luego el byte de estado de teclado: | ; "leyenda" de las teclas y luego el byte de estado de teclado: |
bucle: | bucle: |
LD DE, 0 | ld de, 0 |
CALL CursorAt ; Nos vamos a (0,0) | call CursorAt ; Nos vamos a (0,0) |
LD DE, msg_keys | ld de, msg_keys |
CALL PrintString ; Imprimimos mensaje "*<>^v" | call PrintString ; Imprimimos mensaje "*<>^v" |
| |
CALL Leer_Teclado_Empaquetado | call Leer_Teclado_Empaquetado |
CALL PrintBin ; Imprimimos estado teclas (A) en binario | call PrintBin ; Imprimimos estado teclas (A) en binario |
| |
JR bucle ; Repetir hasta reset | jr bucle ; Repetir hasta reset |
| |
; Mensajes del programa | ; Mensajes del programa |
| |
; Teclas por defecto si no se redefine: O P Q A SPACE | ; Teclas por defecto si no se redefine: O P Q A SPACE |
tecla_izq DEFB $1A | tecla_izq DEFB $1a |
tecla_der DEFB $22 | tecla_der DEFB $22 |
tecla_arriba DEFB $25 | tecla_arriba DEFB $25 |
ORG 33500 | ORG 33500 |
| |
CALL CLS | call CLS |
CALL Redefinir_Teclas_SSSTTT ; Redefinir teclado | call Redefinir_Teclas_SSSTTT ; Redefinir teclado |
CALL Wait_For_No_Key ; Esperar a que no haya teclas pulsadas | call Wait_For_No_Key ; Esperar a que no haya teclas pulsadas |
CALL CLS | call CLS |
| |
; Bucle del programa, lee nuestras teclas e imprime primero la | ; Bucle del programa, lee nuestras teclas e imprime primero la |
; "leyenda" de las teclas y luego el byte de estado de teclado: | ; "leyenda" de las teclas y luego el byte de estado de teclado: |
bucle: | bucle: |
LD DE, 0 | ld de, 0 |
CALL CursorAt ; Nos vamos a (0,0) | call CursorAt ; Nos vamos a (0,0) |
LD DE, msg_keys | ld de, msg_keys |
CALL PrintString ; Imprimimos mensaje "*<>^v" | call PrintString ; Imprimimos mensaje "*<>^v" |
| |
CALL Leer_Teclado_SSSTTT | call Leer_Teclado_SSSTTT |
CALL PrintBin ; Imprimimos estado teclas (A) en binario | call PrintBin ; Imprimimos estado teclas (A) en binario |
| |
JR bucle ; Repetir hasta reset | jr bucle ; Repetir hasta reset |
| |
; Mensajes del programa | ; Mensajes del programa |
; que aparezcan en los bits del byte de estado | ; que aparezcan en los bits del byte de estado |
teclas_player_1: | teclas_player_1: |
p1_puerto_disp DEFB $7F ; Semifila ' ' = puerto $FEFE | p1_puerto_disp DEFB $7f ; Semifila ' ' = puerto $fefe |
p1_tecla_disp DEFB $01 ; Tecla ' ' = bit 1 | p1_tecla_disp DEFB $01 ; Tecla ' ' = bit 1 |
p1_puerto_izq DEFB $DF ; Semifila 'O' = puerto $DFFE | p1_puerto_izq DEFB $df ; Semifila 'O' = puerto $dfFE |
p1_tecla_izq DEFB $02 ; Tecla 'O' = bit 2 | p1_tecla_izq DEFB $02 ; Tecla 'O' = bit 2 |
p1_puerto_der DEFB $DF ; Semifila 'P' = puerto $DFFE | p1_puerto_der DEFB $df ; Semifila 'P' = puerto $dfFE |
p1_tecla_der DEFB $01 ; Tecla 'P' = bit 1 | p1_tecla_der DEFB $01 ; Tecla 'P' = bit 1 |
p1_puerto_arriba DEFB $FB ; Semifila 'Q' = puerto $FBFE | p1_puerto_arriba DEFB $fb ; Semifila 'Q' = puerto $fbFE |
p1_tecla_arriba DEFB $01 ; Tecla 'Q' = bit 1 | p1_tecla_arriba DEFB $01 ; Tecla 'Q' = bit 1 |
p1_puerto_abajo DEFB $FD ; Semifila 'A' = puerto $FDFE | p1_puerto_abajo DEFB $fd ; Semifila 'A' = puerto $fdFE |
p1_tecla_abajo DEFB $01 ; Tecla 'A' = bit 1 | p1_tecla_abajo DEFB $01 ; Tecla 'A' = bit 1 |
| |
; La semifila se guarda como puerto a leer y la tecla como | ; La semifila se guarda como puerto a leer y la tecla como |
; el bit (posición) de esa tecla en la respuesta. | ; el bit (posición) de esa tecla en la respuesta. |
; Ejemplo: 'O' = p1_puerto_izq = $DF | ; Ejemplo: 'O' = p1_puerto_izq = $df |
; p1_tecla_izq = 2 (%00000010) | ; p1_tecla_izq = 2 (%00000010) |
; Llama a "Redefine_Key" para mostrar el mensaje y pedir la tecla. | ; Llama a "Redefine_Key" para mostrar el mensaje y pedir la tecla. |
Redefinir_Teclas_SSSTTT: | Redefinir_Teclas_SSSTTT: |
; Los siguientes textos se podrian haber impreso tambien usando un bucle | ; Los siguientes textos se podrian haber impreso tambien usando un bucle |
; con INC DE (avanzar cadena) e INC HL (avanzar tecla a escribir) | ; con inc de (avanzar cadena) e inc hl (avanzar tecla a escribir) |
LD DE, msg_izq | ld de, msg_izq |
CALL PrintString ; Imprimir mensaje "Izquierda?" | call PrintString ; Imprimir mensaje "Izquierda?" |
CALL Redefine_Key ; Esperar pulsacion (e imprimir ASCII) | call Redefine_Key ; Esperar pulsacion (e imprimir ASCII) |
CALL Scancode_To_Port_Key | call Scancode_To_Port_Key |
LD HL, p1_puerto_izq ; Apuntamos HL a la p1_puerto_izq | ld hl, p1_puerto_izq ; Apuntamos HL a la p1_puerto_izq |
LD (HL), E ; Guardamos la semifila | ld (hl), e ; Guardamos la semifila |
INC HL ; Incrementamos HL => apuntamos a p1_tecla_x | inc hl ; Incrementamos HL => apuntamos a p1_tecla_x |
LD (HL), D ; Guardamos la tecla | ld (hl), d ; Guardamos la tecla |
| |
LD DE, msg_der ; Siguiente mensaje: "Derecha?" | ld de, msg_der ; Siguiente mensaje: "Derecha?" |
CALL PrintString | call PrintString |
CALL Redefine_Key ; Esperar pulsacion (e imprimir ASCII) | call Redefine_Key ; Esperar pulsacion (e imprimir ASCII) |
CALL Scancode_To_Port_Key | call Scancode_To_Port_Key |
LD HL, p1_puerto_der ; Apuntamos HL a la p1_puerto_X | ld hl, p1_puerto_der ; Apuntamos HL a la p1_puerto_X |
LD (HL), E ; Guardamos la semifila | ld (hl), e ; Guardamos la semifila |
INC HL ; Incrementamos HL => apuntamos a p1_tecla_x | inc hl ; Incrementamos HL => apuntamos a p1_tecla_x |
LD (HL), D ; Guardamos la tecla | ld (hl), d ; Guardamos la tecla |
| |
LD DE, msg_arriba ; Repetimos con ARRIBA | ld de, msg_arriba ; Repetimos con ARRIBA |
CALL PrintString | call PrintString |
CALL Redefine_Key | call Redefine_Key |
CALL Scancode_To_Port_Key | call Scancode_To_Port_Key |
LD HL, p1_puerto_arriba ; Apuntamos HL a la p1_puerto_X | ld hl, p1_puerto_arriba ; Apuntamos HL a la p1_puerto_X |
LD (HL), E ; Guardamos la semifila | ld (hl), e ; Guardamos la semifila |
INC HL ; Incrementamos HL => apuntamos a p1_tecla_x | inc hl ; Incrementamos HL => apuntamos a p1_tecla_x |
LD (HL), D ; Guardamos la tecla | ld (hl), d ; Guardamos la tecla |
| |
LD DE, msg_abajo ; Repetimos con ABAJO | ld de, msg_abajo ; Repetimos con ABAJO |
CALL PrintString | call PrintString |
CALL Redefine_Key | call Redefine_Key |
CALL Scancode_To_Port_Key | call Scancode_To_Port_Key |
LD HL, p1_puerto_abajo ; Apuntamos HL a la p1_puerto_X | ld hl, p1_puerto_abajo ; Apuntamos HL a la p1_puerto_X |
LD (HL), E ; Guardamos la semifila | ld (hl), e ; Guardamos la semifila |
INC HL ; Incrementamos HL => apuntamos a p1_tecla_x | inc hl ; Incrementamos HL => apuntamos a p1_tecla_x |
LD (HL), D ; Guardamos la tecla | ld (hl), d ; Guardamos la tecla |
| |
LD DE, msg_disp ; Repetimos con DISPARO | ld de, msg_disp ; Repetimos con DISPARO |
CALL PrintString | call PrintString |
CALL Redefine_Key | call Redefine_Key |
CALL Scancode_To_Port_Key | call Scancode_To_Port_Key |
LD HL, p1_puerto_disp ; Apuntamos HL a la p1_puerto_X | ld hl, p1_puerto_disp ; Apuntamos HL a la p1_puerto_X |
LD (HL), E ; Guardamos la semifila | ld (hl), e ; Guardamos la semifila |
INC HL ; Incrementamos HL => apuntamos a p1_tecla_x | inc hl ; Incrementamos HL => apuntamos a p1_tecla_x |
LD (HL), D ; Guardamos la tecla | ld (hl), d ; Guardamos la tecla |
RET | ret |
| |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
; SALIDA: D = NUMERO de bit de TECLA. | ; SALIDA: D = NUMERO de bit de TECLA. |
; E = PUERTO (parte alta) correspondiente a esa SEMIFILA | ; E = PUERTO (parte alta) correspondiente a esa SEMIFILA |
; Ejemplo: 'O' = D = $DF | ; Ejemplo: 'O' = D = $df |
; E = 2 (%00000010) | ; E = 2 (%00000010) |
; | ; |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Scancode_To_Port_Key: | Scancode_To_Port_Key: |
LD D, A ; Hacer copia de scancode en D | ld d, a ; Hacer copia de scancode en D |
AND $07 ; Eliminar todos los bits menos 00000SSS | and %00000111 ; Eliminar todos los bits menos 00000SSS |
CPL ; Invertir valores (1/0) | cpl ; Invertir valores (1/0) |
LD E, A ; E = contador para bucle | ld e, a ; E = contador para bucle |
LD A, $FE ; Empezamos por primera semifila (determina puerto) | ld a, $fe ; Empezamos por primera semifila (determina puerto) |
JR Z, ConvScn_tecla ; Si Z==1 => es primera semifila | jr z, ConvScn_tecla ; Si Z==1 => es primera semifila |
ConvScn_loop1: | ConvScn_loop1: |
RLCA ; Rotamos A: Siguiente semifila | rlca ; Rotamos A: Siguiente semifila |
DEC E | dec e |
JR NZ, ConvScn_loop1 ; Si Z==0 => no es la semifila correcta | jr nz, ConvScn_loop1 ; Si Z==0 => no es la semifila correcta |
ConvScn_tecla: | ConvScn_tecla: |
LD E, A ; Salvaguardamos semifila (puerto a leer) en E | ld e, a ; Salvaguardamos semifila (puerto a leer) en E |
LD A, D ; Recuperamos A = 00TTTSSS de nuevo | ld a, d ; Recuperamos A = 00TTTSSS de nuevo |
AND $38 ; Eliminamos todos los valores menos 00TTT000 | and %00111000 ; Eliminamos todos los valores menos 00TTT000 |
LD D, $10 ; Inicializamos a la tecla mas interna | ld d, $10 ; Inicializamos a la tecla mas interna |
RET Z ; Si Z==1 => es la tecla interna | ret z ; Si Z==1 => es la tecla interna |
ConvScn_loop2 | ConvScn_loop2 |
RR D ; Rotamos: Siguiente tecla de la semifila | rr d ; Rotamos: Siguiente tecla de la semifila |
SUB 8 | sub 8 |
JR NZ, ConvScn_loop2 ; Si Z==0 => NO ES LA TECLA CORRECTA | jr nz, ConvScn_loop2 ; Si Z==0 => NO ES LA TECLA CORRECTA |
RET | ret |
| |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
;----------------------------------------------------------------------- | ;----------------------------------------------------------------------- |
Leer_Teclado_SSSTTT: | Leer_Teclado_SSSTTT: |
LD DE, P1_NUM_TECLAS*256 ; D = 5 (teclas a leer), E = 0 | 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 hl, teclas_player_1 ; 1a tecla (p1_puerto_X) para bit + alto |
LD C, $FE ; Parte baja puerto teclado | ld c, $fe ; Parte baja puerto teclado |
| |
tecl_sssttt_loop: | tecl_sssttt_loop: |
LD B, (HL) ; cogemos puerto a leer (parte alta) | ld b, (hl) ; cogemos puerto a leer (parte alta) |
INC HL ; pasamos al siguiente byte (p1_tecla_X) | inc hl ; pasamos al siguiente byte (p1_tecla_X) |
IN A, (C) ; leer del puerto de esta tecla | in a, (c) ; leer del puerto de esta tecla |
CPL ; Ahora: 1 teclas pulsadas, 0 no pulsadas | cpl ; Ahora: 1 teclas pulsadas, 0 no pulsadas |
AND $1F ; aislamos las teclas | and %00011111 ; aislamos las teclas |
AND (HL) ; comparo con la tecla en memoria (carry OFF) | and (hl) ; comparo con la tecla en memoria (carry OFF) |
JR Z, tecl_sssttt_nopulsada ; Si Z == 1 => tecla no pulsada | jr z, tecl_sssttt_nopulsada ; Si Z == 1 => tecla no pulsada |
SCF ; Ponemos Carry = 1 para meterlo en A al rotar | scf ; Ponemos Carry = 1 para meterlo en A al rotar |
| |
tecl_sssttt_nopulsada: | tecl_sssttt_nopulsada: |
RL E ; meto el valor de la pulsacion de la tecla | rl e ; meto el valor de la pulsacion de la tecla |
; (carry OFF no pulsada/carry ON pulsada) | ; (carry OFF no pulsada/carry ON pulsada) |
INC HL ; apunto a siguiente puerto (semifila) | inc hl ; apunto a siguiente puerto (semifila) |
DEC D ; decrementamos el num. de teclas pendientes por leer | dec d ; decrementamos el num. de teclas pendientes por leer |
JR NZ, tecl_sssttt_loop ; siempre necesita una pulsacion para salir del bucle | jr nz, tecl_sssttt_loop ; siempre necesita una pulsacion para salir del bucle |
LD A, E ; Recuperamos estado final | ld a, e ; Recuperamos estado final |
LD (p1_teclas_pulsadas), A ; Guardamos en variable el estado de las teclas | ld (p1_teclas_pulsadas), a ; Guardamos en variable el estado de las teclas |
RET ; Devolvemos en A 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 Redefine_Key |
; que aparezcan en los bits | ; que aparezcan en los bits |
teclas_player_1: | teclas_player_1: |
p1_puerto_disp DEFB $7F ; Semifila ' ' = puerto $FEFE | p1_puerto_disp DEFB $7f ; Semifila ' ' = puerto $fefe |
p1_tecla_disp DEFB $01 ; Tecla ' ' = bit 1 | p1_tecla_disp DEFB $01 ; Tecla ' ' = bit 1 |
p1_puerto_izq DEFB $DF ; Semifila 'O' = puerto $DFFE | p1_puerto_izq DEFB $df ; Semifila 'O' = puerto $dfFE |
p1_tecla_izq DEFB $02 ; Tecla 'O' = bit 2 | p1_tecla_izq DEFB $02 ; Tecla 'O' = bit 2 |
p1_puerto_der DEFB $DF ; Semifila 'P' = puerto $DFFE | p1_puerto_der DEFB $df ; Semifila 'P' = puerto $dfFE |
p1_tecla_der DEFB $01 ; Tecla 'P' = bit 1 | p1_tecla_der DEFB $01 ; Tecla 'P' = bit 1 |
p1_puerto_arriba DEFB $FB ; Semifila 'Q' = puerto $FBFE | p1_puerto_arriba DEFB $fb ; Semifila 'Q' = puerto $fbFE |
p1_tecla_arriba DEFB $01 ; Tecla 'Q' = bit 1 | p1_tecla_arriba DEFB $01 ; Tecla 'Q' = bit 1 |
p1_puerto_abajo DEFB $FD ; Semifila 'A' = puerto $FDFE | p1_puerto_abajo DEFB $fd ; Semifila 'A' = puerto $fdFE |
p1_tecla_abajo DEFB $01 ; Tecla 'A' = bit 1 | p1_tecla_abajo DEFB $01 ; Tecla 'A' = bit 1 |
| |
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. | 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. | 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. |
| |
\\ | \\ |
; que aparezcan en los bits | ; que aparezcan en los bits |
teclas_player_1: | teclas_player_1: |
p1_puerto_pause DEFB $FE ; Semifila 'C' = puerto $FEFE | p1_puerto_pause DEFB $fe ; Semifila 'C' = puerto $feFE |
p1_tecla_pause DEFB $08 ; Tecla 'C' = bit 4 | p1_tecla_pause DEFB $08 ; Tecla 'C' = bit 4 |
p1_puerto_disp2 DEFB $FE ; Semifila 'V' = puerto $FEFE | p1_puerto_disp2 DEFB $fe ; Semifila 'V' = puerto $feFE |
p1_tecla_disp2 DEFB $10 ; Tecla 'V' = bit 5 | p1_tecla_disp2 DEFB $10 ; Tecla 'V' = bit 5 |
p1_puerto_disp DEFB $7F ; Semifila ' ' = puerto $FEFE | p1_puerto_disp DEFB $7f ; Semifila ' ' = puerto $fefe |
p1_tecla_disp DEFB $01 ; Tecla ' ' = bit 1 | p1_tecla_disp DEFB $01 ; Tecla ' ' = bit 1 |
p1_puerto_izq DEFB $DF ; Semifila 'O' = puerto $DFFE | p1_puerto_izq DEFB $df ; Semifila 'O' = puerto $dfFE |
p1_tecla_izq DEFB $02 ; Tecla 'O' = bit 2 | p1_tecla_izq DEFB $02 ; Tecla 'O' = bit 2 |
p1_puerto_der DEFB $DF ; Semifila 'P' = puerto $DFFE | p1_puerto_der DEFB $df ; Semifila 'P' = puerto $dfFE |
p1_tecla_der DEFB $01 ; Tecla 'P' = bit 1 | p1_tecla_der DEFB $01 ; Tecla 'P' = bit 1 |
p1_puerto_arriba DEFB $FB ; Semifila 'Q' = puerto $FBFE | p1_puerto_arriba DEFB $fb ; Semifila 'Q' = puerto $fbFE |
p1_tecla_arriba DEFB $01 ; Tecla 'Q' = bit 1 | p1_tecla_arriba DEFB $01 ; Tecla 'Q' = bit 1 |
p1_puerto_abajo DEFB $FD ; Semifila 'A' = puerto $FDFE | p1_puerto_abajo DEFB $fd ; Semifila 'A' = puerto $fdFE |
p1_tecla_abajo DEFB $01 ; Tecla 'A' = bit 1 | p1_tecla_abajo DEFB $01 ; Tecla 'A' = bit 1 |
| |
</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, A ; Testeamos el bit 0 | bit 0, a ; 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. | 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. |
| |
\\ | \\ |
</code> | </code> |
| |
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. | 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. |
| |
<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> |
| |
| |
Bucle: | Bucle: |
EsperaSoltar: XOR A | EsperaSoltar: xor a |
IN A, (254) | in a, (254) |
AND %00011111 | and %00011111 |
CP %00011111 | 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 %00011111 | and %00011111 |
CP %00011111 | cp %00011111 |
JR Z, EsperaPulsar | jr z, EsperaPulsar |
| |
; Hacer pausa AQUI | ; Hacer pausa AQUI |
| |
;Se registra la pulsacion... | ;Se registra la pulsacion... |
JR Bucle | jr Bucle |
| |
El efecto que esto tiene sobre el comportamiento de las lecturas es el siguiente: | El efecto que esto tiene sobre el comportamiento de las lecturas es el siguiente: |
120 LET r(n,1)=f: LET r(n,2)=c: | 120 LET r(n,1)=f: LET r(n,2)=c: |
REM La guardamos en nuestra matriz de teclas seleccionadas. | 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: | 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 | REM Repasamos la lista de teclas seleccionadas hasta el momento para |
actualizar la matriz con las teclas "fantasma" que encontremos | actualizar la matriz con las teclas "fantasma" que encontremos |
900 FOR i=1 TO 8: | 900 FOR i=1 TO 8: |
REM recorremos todas las teclas de la misma columna que nuestra tecla | REM recorremos todas las teclas de la misma columna que nuestra tecla |
910 IF t(i,col,1)=1 THEN GO SUB 1000: | 910 IF t(i,col,1)=1 THEN GO sub 1000: |
REM si alguna esta seleccionada, significa que tenemos dos teclas en | REM si alguna esta seleccionada, significa que tenemos dos teclas en |
una misma columna. Miramos si hay una tercera en la misma fila | una misma columna. Miramos si hay una tercera en la misma fila |