[DS] DS CODE 1: "Interrumpiendo"

/////////////////////////////////INDICE ///////////////////////
DS CODE 0: "Los primeros pasos"
http://www.elotrolado.net/showthread.php?s=&threadid=560011

DS CODE 1: "Interrumpiendo"
http://www.elotrolado.net/showthread.php?s=&postid=1704954093
////////////////////////////////////////////////////////////////

¿Cuánto tiempo desde el primer tutorial introductorio eh? La verdad es que he andado bastante ocupado (hacerse ingeniero y esas cosas) y además ando liado en un par de proyectos gordos (cuak, cuak dicen los patos). A parte, que tratar de enseñar libnds y a la vez mostrar el hardware es más extenso de lo planeado. Pero bueno, aquí vuelvo al pie del cañón esperando que os guste y que el próximo capítulo lo termine en menos tiempo. Y sin más allá vamos.

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Bienvenidos al primer capítulo de "como dejar mi adicción por palib y entender lo que estoy haciendo". Si, primero. Desde aquí comenzaremos ya a tratar cosas más serias y entraremos de lleno a programar en DS utilizando libnds. Tenía previsto juntar más temas, pero visto la extensión de este, los voy a sacar por separado. Supongo que además será más amenos de leer.


INTERRUPCIONES

Menuda mieeeerda de tema has cogido para empezar, dices mientras miras asqueado. Es comprensible, no suena ameno. Sin embargo las interrupciones serán un elemento que estén presentes en cada uno de nuestros proyectos y que además mucha gente que empezó con versiones anteriores de libnds (dícese con ndslib) o aun antes con gba, todavía no hace un uso de la funcionalidad de libnds para este tema. Veréis la de cosas que podéis hacer sólo con interrupciones y un printf

Las interrupciones podemos clasificarlas en interrupciones hardware e interrupciones sofware (de la bios).


1. INTERRUPCIONES HARDWARE

¿Qué es una interrupción? Pues lo que el nombre dice aproximadamente. Una parte del hardware emite una seña l indicando que sucede algo que puede ser interesante de conocer y por tanto de interrumpir el flujo normal de ejecución de un programa. Cada vez que esto sucede se queda guardado en un registro de peticiones de interrupción y será tarea del programador atenderlas o no. Bien, ¿pero como funciona?. Pues tenemos 5 partes: el registro maestro de interrupciones (IME), el registro de activación de interrupciones (IE) , el registro de petición de interrupciones (IF) ,el gestor de interrupción (Interrupt Handler). y el llamémoslo "lanzador de interrupciones " (Interrupt dispatcher). En libnds encontraremos los registros con el nombre de REG_IME, REG_IE y REG_IF

¿Ves? No son tantas cosas. Vamos a ver que hace cada uno entonces.

-Registro maestro de interrupciones (IME)

El más sencillito de todos. Su función es la de permitir o no permitir que sucedan interrupciones

-Registro de activación de interrupciones (IE)

Este es en el que se indica de que interrupciones queremos ser avisados. Para ello contamos con un registro de 32 bits en el que cada bit activa o desactiva una tipo de interrupción (no todos se usan)

-Registro de petición de interrupciones (IF)

El último registro es igualmente 32 bits (evidentemente tiene que existir una correspondencia con el IE) y en el se pone a 1 el bit del tipo de señal que está solicitando interrumpir la CPU. Volviendo a escribir un 1 en éste registro, el hardware sabrá que la petición ha sido atendida

-Interrupt Handler

El Interrupt Handler es una función que se puede definir en una posición especial de memoria y que permite tratar la interrupción . Cuando se produce una interrupción se entra en un modo especial que llama a una rutina de la BIOS que salta a una dirección de memoria, en la que pondremos el Interrupt Handler.

-Interrupt dispatcher

Libnds utiliza un Interrupt dispatcher como interrupt handler. Es decir, es una función con la ventaja de que el decide a que función de las registradas en una tabla de interrupciones llamar según la interrupción recibida. Para los curiosos y valientes pueden mirarlo en el código fuente de libnds en perfecto ensamblador

Por último comentar un pequeño detalle que se ha ido olvidando por el camino. Anteriormente he dicho que el hardware emite una señal que interesa recoger. Y ahi está la clave. Esas señales deben ser activadas en los registros apropiados para poder ser recogidas (si no es como tirarse a parar un penalti antes de que pite el arbitro. Vamos, que estamos perdiendo el tiempo). Iré comentando cuáles son éstos registros y que bits hay que cambiar para las interrupciones de las que hable.

Buenoooo. ¿Y tengo que ir activando yo y mirando todo eso? Al comienzo se hacía todo de forma manual la mitad de las cosas: activar IME, poner a 1 los bits en el IE de las señales que queríamos leer, añadir un interrupt handler (configurándolo manualmente) y por último comprobar que interrupción se producía y gestionarla. Como vemos no es un proceso que podamos calificar de, digamos, entretenido. No voy a poner a poner un ejemplo de ésto porque considero que con saber un poco la teoría es más que suficiente, ya que teniendo todo el proceso simplificado gracias a libnds es practicamente absurdo realizar todo el proceso uno mismo. Vamos a ello.

-Gestionar interrupciones hardware

Bueno primero vamos a mirar que interrupciones tenemos a nuestra disposición y el parámetro correspondiente en libnds (son las máscaras para los bit del registro IE)

IRQ_VBLANK -> vblank. Se produce cuando se pinta toda la pantalla. Aproximadamente 60 veces por segundo
IRQ_HBLANK -> hblank. Ésta es para cuando pinta una línea horizontal (scanline)
IRQ_VCOUNT -> avisa cuando han pasado tantas scanlines como se ha indicado en el vcount (0 a 262. 0 a 191 está pintando y 192-262 es periodo VBLANK)
IRQ_TIMER0 -> interrupciones para los temporizadores
IRQ_TIMER1
IRQ_TIMER2
IRQ_TIMER3
IRQ_NETWORK -> no está muy claro que hace. Sólo en ARM7
IRQ_DMA0 -> para hacer accesos directos a memoria DMA
IRQ_DMA1
IRQ_DMA2
IRQ_DMA3
IRQ_KEYS -> podemos definirla para una combinación de teclas.
IRQ_CART -> se chiva de que estás sacando el cartucho de gba
IRQ_IPC_SYNC -> sirve para intercambiar información entre el ARM7 y el ARM9. Sólo 4 bits
IRQ_FIFO_EMPTY -> para comunicación entre procesadores también e indica que el FIFO está vacío.
IRQ_FIFO_NOT_EMPTY-> en este caso hace lo contrario, es decir, el FIFO no está vacío
IRQ_CARD -> indica el fin de una transmisión de datos desde la tarjeta de la DS
IRQ_CARD_LINE -> algo sobre la tarjeta de la DS pero ni idea de que exactamente
IRQ_GEOMETRY_FIFO -> indica si la FIFO de comandos 3D está vacía o medio llena. Sólo para ARM9
IRQ_LID -> para la tapa de la DS. Seguro que ya éstas pensando en los sellos de Another Code. Sólo ARM7
IRQ_SPI -> bus SPI. Sólo ARM7
IRQ_WIFI -> para wifi. Sólo ARM7


Esas son todas las interrupciones que podremos decidir recoger o no. Algunas no está muy claro su función (trataré de completar más en un futuro, pero no hay mucho donde rascar) y otras no las usaréis nunca o practicamente nunca, pero al menos ya sabéis las armas con las que contáis. Veamos ahora que funciones nos ofrece libnds para poder activar y desactivarlas:

*void irqInit()

Ésta función debe ser llamada siempre que se quieran usar interrupciones (una vez al principio del código o más si queremos resetear la tabla de interrupciones). Inicializa la tabla de interrupciones y el Interrupt Handler .

*void irqSet(int mask, IntFn handler)

Una vez llamada la función de inicialización utilizaremos ésta otra función para indicar que interrupciones queremos usar y si queremos asignar una función a esa interrupción . El primer parámetro es una de las máscaras de la lista de arriba y el segundo el nombre de la función. La función siempre devolverá void y no tendrá parámetro.

*void irqEnable(IRQ_MASK irq)

Tal y como está diseñada libnds, con el irqSet sólo, no se activa la interrupción porque, extrañamente, el IME, registro maestro de interrupciones como recordaréis, se activa sólo dentro de esta función (es decir que también podemos poner un REG_IME=1 después de los irqSet y estarían igualmente todas activadas). De este modo lo normal es que siempre que hagamos un irqEnable para las interrupciones que hemos indicada en irqSet . Igualmente se pasa como parámetro las máscaras de arriba pudiendo pasar varias con | (or para el que no pille). También se usa el enable si se utiliza la siguiente que voy a comentar, irqDisable.

*void irqDisable(int irq)

Está función sirve para poder deshabilitar una interrupción . La habilitaremos de nuevo con irqEnable. Recibe como parámetro una de las mñascaras de arriba

*void irqClear(IRQ_MASK irq)

Con ésta no sólo deshabilitamos, si no que tambíen la quitamos de la tabla de interrupciones . ¿Adivinas que le pasamos cómo parámetro?

*void irqInitHandler(IntFn handler)

Esta función dudo que alguien la use. Permite pasarle un interrupt handler propio en vez del dispatcher que usa libnds. Si alguien usa una propia, posiblemente ni use ésta función, así que para el caso...

Y ya está. Así de fácil y de sencillo, con esas pocas funciones podremos activar, desactivar, etc todas las interrupciones hardware de la DS. Bueno, no está mal, algo he pillado, pero vamos a ver ¿yo quiero ésto para algo? Sssss, calma, que nos quedan interrupciones por software. Después ejemplos.



2. INTERRUPCIONES SOFTWARE

La idea es básicamente la misma que en las interrupciones hardware. El flujo de ejecución normal se interrumpe y se pasa a otra tarea, sólo que en éste caso nosotros estamos generando la interrupción (llamando a la función directamente) y además la función a la que se llama no tenemos que definirla si no que ya está en la bios (de hecho lo que hacemos es llamar a una función de la bios). La DS tiene muchas funciones de éste tipo y en libnds son fácilmente reconocibles porque comienzan con swi. Debido a la cantidad de ellas y a que no son tan frecuentemente usadas (que poco profesionales somos...) sólo voy a poner la lista para que tengáis una referencia rápida de cuáles tenéis disponibles en cada procesador con sus códigos de llamada (en ensamblador se llamarían de la forma swi código) y una pequeña descripción. .

NDS7 NDS9 Function
00h 00h swiSoftReset -> resetea varios elementos del procesador
03h 03h swiDelay -> para pequeñas esperas
04h 04h swiIntrWait -> espera a que se produzca una interrupción indicada
05h 05h swiWaitForVBlank -> espera a que llegue una interrupción de vblank
06h 06h swiWaitForIRQ -> espera a que llegue cualquier interrupción
07h - swiSleep -> deja dormido el ARM7
08h - swiChangeSoundBias -> cambia el SOUND_BIAS (no preguntéis)
09h 09h swiDivide -> division
0Bh 0Bh swiCopy -> copia datos de una posición a otra. En unidades de 2 o 4 bytes
0Ch 0Ch swiFastCopy -> copia datos de una posición a otra más rápido, pero parece que sólo una parte. En unidades de 4 bytes
0Dh 0Dh swiSqrt -> raiz cuadrada
0Eh 0Eh swiCRC16 -> calcula un CRC de 16 bits
0Fh 0Fh swiIsDebugger -> comprueba si se ustando debugger
10h 10h swiUnpackBits -> para incrementar los bpp de las imágenes??
11h 11h swiDecompressLZSSWram -> descomprime datos con LZSS. Más rápida
12h 12h swiDecompressLZSSVram -> descomprime datos con LZSS. Usa funciones callback (puede leer de más sitios que de memoria)
13h 13h swiDecompressHuffman -> descomprime datos con Huffman. Usa funciones callback
14h 14h swiDecompressRLEWram -> descomprime datos con RLE. Más rápida
15h 15h swiDecompressRLEVram -> descomprime datos con RLE. Usa funciones callback
- 16h swiDecodeDelta8 -> descomprime datos guardados de forma incremental (10-12-15-=10-+1-+3)
- 18h swiDecodeDelta16 -> descomprime datos guardados de forma incremental (10-12-15-=10-+1-+3)
1Ah - swiGetSineTable ->
1Bh - swiGetPitchTable ->
1Ch - swiGetVolumeTable ->
1Dh - GetBootProcs (no definida en libnds) ->
1Fh 1Fh swiSetHaltCR ->



Algunas como véis no os puedo decir mucho o nada (si algún dia puedo completarlo, lo haré). Aquí tenéis un link a la sintaxis de las funciones con sus parámtros y retornos, así como más información sobre como tienen que estar alineados, etc., proveniente de la antigua ndslib (que se ha mantenido de forma invariable en libnds)

http://www.drunkencoders.com/documents/DS/ndslib.htm#_BIOS

Bueno vamos a empezar ya con los ejemplos.



3. EJEMPLOS

EJEMPLO INTERRUPCIONES: VBLANK, HBLANK y VCOUNT

-Elementos

Bien como he comentado ya, para poder recoger una interrupción hardware necesitamos tener algo que recoger. En el caso de estas 3 interrupciones nos encontramos con el registros definido en libnds como REG_DISPSTAT/DISP_SR . Vamos a ver como es éste registro y como cambiar sus bits en libnds (despúes de la explicación pongo el define que pone a 1 el bit)

REG_DISPSTAT/DISP_SR

Bit Expl.
0 V-Blank flag -> indica si estas fuera(0) o dentro(1) del VBLANK . DISP_IN_VBLANK
1 H-Blank flag -> indica si estas fuera(0) o dentro(1) del HBLANK . DISP_IN_HBLANK
2 V-Counter flag -> indica si estas fuera(0) o dentro(1) del VCOUNT . DISP_YTRIGGERED
3 V-Blank IRQ -> activa la interrupción VBLANK. DISP_VBLANK_IRQ
4 H-Blank IRQ -> activa la interrupción HBLANK. DISP_HBLANK_IRQ
5 V-Counter IRQ -> activa la interrupción VCOUNT. DISP_YTRIGGER_IRQ
6 No usado
7-15 V-Count -> aquí indicamos que línea entre 0 y 262 activará la interrupción. Este campo es un poco cabrón puesto que los 8 primeros bits son 8-15 y el último es el 7 es decir 5 no es 101 sino 1010 y 256 sería 000000001


Bueno ya sabemos entonces que bits poner a 1 para poder tener ya nuestras interrupciones a punto.... Peeeero (siempre hay un pero) la gente de libnds piensa en nosotros y sabe que usamos tanto VBLANK y HBLANK que cada vez que hagamos un irqSet de estas 2 interrupciones, internamente libnds ya activa los bits de REG_DISPSTAT/DISP_SR . Luego ésto lo podéis tener en cuenta para no volver a ponerlos a 1 (o si si os apetece), siendo sólo necesario cambiarlos para VCOUNT

Y hablando de VCOUNT resulta que también podemos leer que línea se está pintando (muy útil para cuando se produce un HBLANK saber donde estamos). Para ello está este registro de libnds (el nombre a mala leche)

REG_VCOUNT/DISP_Y

Bit Expl.
0-8 Línea actual (0-262) -> aquí si están los bits en su sitio (lo otro es herencia de gba, que como no les cabían por un lado, tiraron para el otro)
9-15 No usado


-Qué hace

En este ejemplo activaremos las 3 interrupciones . Despues configuraremos como en el primer ejemplo un fondo para texto (ésta vez en la secundaria) y en la principal activaremos el modo framebuffer (pintas los pixels directamente el framebuffer y éste se pinta en pantalla). En cada hblank cambiaremos el color con el que se están pintando los pixels y en el vcount cambiaremos el tono a morado. Este modo no es ni mucho menos recomendable para hacer un gradiente, pero lo importante es ver la funciones de interrupciones en las que se definen todas, asi como las funciones a las que se llama tras la interrupción. Además hay un contador que vemos como se incrementa en cada vblank .

-Código
#include "nds.h"
#include <nds/arm9/console.h>
#include <stdio.h>

int counter=0;
u8 color=0;
u8 color2=0;

void vBlank(){
   ++counter;   //incrementamos el contador cada VBLANK, es decir, 60 veces por segundo
}

void hBlank(){
   color=REG_VCOUNT/DISP_Y>>3;   //cambiamos el color en cada HBLANK (linea horizonal)
}

void vCount(){
   color2=10;    //cuando lleguemos a la linea indicada en REG_DISPSTAT/DISP_SR cambiamos el color
}


void interrupciones(){
   irqInit();  //inicializamos la tabla de interrupciones
   irqSet(IRQ_VBLANK,vBlank);  //decimos que queremos recibir la interrupción por VBLANK y además le asignamos
                        //la función vBlank que será llamada cada vez que se produzca
   irqSet(IRQ_HBLANK,hBlank);  //lo mismo para HBLANK
   irqSet(IRQ_VCOUNT,vCount);  //y también para VCOUNT
   irqEnable(IRQ_MASK(IRQ_VBLANK|IRQ_HBLANK|IRQ_VCOUNT));  //activamos las interrupciones. Como ya he comentado, despúes de hacer el set
         //el único motivo por el que aun no está habilitada es por IME, así que podríamos poner también REG_IME=1;
         //irqEnable(IRQ_VBLANK|IRQ_HBLANK|IRQ_VCOUNT); para devkitpro r19
   REG_DISPSTAT/DISP_SR|=DISP_YTRIGGER_IRQ|(128<<8);   //activamos en este registro la señas de VCOUNT y establecemos un valor para comparar con VCOUNT
      //como HBLANK y VBLANK ya están activados en el set, no ponemos nada más
};         

int main(void) {
   powerON(POWER_ALL);  //a partir de la r19 creo que no es necesario
   lcdMainOnBottom();  //vamos a coger la sana costumbre escoger al principio donde poner la pantalla principal
      
   videoSetMode(MODE_FB0);  //usamos el modo framebuffer para la pantalla principal
   videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE);   //selecciona el modo de video 0 para la pantalla secundaria y activa el fondo 0
   
   vramSetBankA(VRAM_A_LCD);    //en el modo framebuffer mapeamos el banco de este modo
   vramSetBankC(VRAM_C_SUB_BG);    //reserva el banco C para fondos de la pantalla secundaria

   SUB_BG0_CR = BG_MAP_BASE(31);       //configura el registro del fondo 0 para que busque el mapa de tiles en el bloque 31.
   BG_PALETTE_SUB[255] = RGB15(31,31,31);  //ponemos el último color de la paleta de fondos de la pantalla principal a blanco
   consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);   //función básica proporcionada por libnds para poner texto. Sencilla pero con ciertas limitaciones

   interrupciones();       //inicializamos las interrupciones
   u16 *screen=VRAM_A;       //empezamos a pintar el framebuffer con el color
   for(int i = 0; i < 256 * 192; ++i)     //el cambio del framebuffer coincide con los hblank y va cambiando de color en cada línea, y en cierto momento se produce el VCOUNT y se cambia de color
      *screen++ = RGB15(color2,0,color);   
   
   
   while(1) {
      iprintf("\x1b[1;1H%d", counter);  //visualizamos el contador incrementado en los vblank

   }

   return 0;
}


EJEMPLO INTERRUPCIONES: IRQ_VBLANK,IRQ_KEYS Y SWIWAITFORVBLANK

-Elementos

Bueno en éste otro ejemplo vamos a ver una interrupcion hardware (IRQ_KEYS ) y la interrupción software más usada (swiWaitForVBlank) [COLOR=sandybrown]que detiene el bucle hasta que llegue un vblank[/COLOR] . IRQ_KEYS nos permite generar una interrupción cuando se pulse una combinación de botones o un botón dentro de la combinación , según lo configuremos. Como véis no da mucho margen y se usa por ejemplo para salir de modo sleep, hacer algún truco en concreto, un salvado automático o porqué no, una captura de pantalla. El registro para activar la interrupción es REG_KEYCNT

REG_KEYCNT

0 A ->(1) añadir a la combinación de teclas
1 B ->
2 Select ->
3 Start ->
4 Right ->
5 Left ->
6 Up ->
7 Down ->
8 R ->
9 L ->
10-13 No usado ->
14 Key IRQ -> activa la interrupción IRQ_KEYS
15 Modo -> (0) cualquier tecla de la combinación es válida o (1) hay que pulsar todas


-Qué hace

En esta demo haremos uso del vblank para controlar los fps de nuestra aplicación . ¿Cómo? Muy sencillo. Primero haremos uso de nuestra nueva interrupción software swiWaitForVBlank que detiene la ejecución del bucle hasta que haya una interrupción por vblank. De este modo incrementamos una variable en el bucle, siendo su valor en el mejor de los casos igual que el contador incrementado en vblank (es decir que se incrementa 60 veces por segundo). Lo que pasaría si el bucle es muy pesado es que la variable no se actualizaría tantas veces y por tanto al visualizarla tendría un menor valor. En definitiva estamos controlando los fps. Por otra parte aprovechando el contador del vblank, he creado un segundero a modo de temporizdor (ya sabéis otro uso del vblank!!).

La segunda parte corresponde a la interrupción de las teclas. Lo que vamos a hacer es configurar el registro REG_KEYCNT para que cuando pulsemos A+B el temporizador se detenga y cuando volvamos a pulsarlas, se reactive (en emulador va un poco "durillo")

-Código
#include "nds.h"
#include <nds/arm9/console.h>
#include <stdio.h>

int vcounter=0;
int lcounter=0;
int tiempo=0;
bool vblankEnable=true;

void vBlank(){
   ++vcounter;   //incrementamos el contador cada VBLANK, es decir, 60 veces por segundo
   if (vcounter==60){  //cuando vcounter llega a 60 es que ha pasado un segundo
      vcounter=0;
      iprintf("\x1b[1;1HFPS %d", lcounter);   //el valor que tenga lcounter son los frames a los que está llendo la lógica, nuestros adorados fps
      ++tiempo;   //incrementamos nuestro temporizador. Esto sería equivalente a un segundo
      lcounter=0;
   }
}

void keys(){
   if (vblankEnable){
      irqDisable(IRQ_VBLANK);   //desactivamos la interrupción de vblank
      vblankEnable=false;
   }
   else{
      irqEnable(IRQ_VBLANK);   //la volamos a activar
      vblankEnable=true;
   }
   
}

void interrupciones(){
   irqInit();  //inicializamos la tabla de interrupciones
   irqSet(IRQ_VBLANK,vBlank);  //decimos que queremos recibir la interrupción por VBLANK y además le asignamos
        //la función vBlank que será llamada cada vez que se produzca
   irqSet(IRQ_KEYS,keys);
   irqEnable(IRQ_MASK(IRQ_VBLANK|IRQ_KEYS));  //activamos las interrupciones. Como ya he comentado, despúes de hacer el set
      //el único motivo por el que aun no está habilitada es por IME, así que podríamos poner también REG_IME=1;
      //irqEnable(IRQ_VBLANK|IRQ_HBLANK|IRQ_VCOUNT); para devkitpro r19
   REG_KEYCNT|=(3<<14)|3; //con esto activamos la interrupción para las teclas (bit 14) A y B (bit 0 y 1) y decimos además que ambas tienen que
                     //estar presionadas (bit 15)
};         


int main(void) {
   powerON(POWER_ALL);  //a partir de la r19 creo que no es necesario
   lcdMainOnBottom();  //vamos a coger la sana costumbre escoger al principio donde poner la pantalla principal
      
   videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE);   //selecciona el modo de video 0 para la pantalla secundaria y activa el fondo 0
   
   vramSetBankC(VRAM_C_SUB_BG);    //reserva el banco C para fondos de la pantalla secundaria

   SUB_BG0_CR = BG_MAP_BASE(31);       //configura el registro del fondo 0 para que busque el mapa de tiles en el bloque 31.
   BG_PALETTE_SUB[255] = RGB15(31,31,31);  //ponemos el último color de la paleta de fondos de la pantalla principal a blanco
   consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);   //función básica proporcionada por libnds para poner texto. Sencilla pero con ciertas limitaciones

   interrupciones();       //inicializamos las interrupciones

   
   
   while(1) {
      ++lcounter;   //con esto contamos cuantas veces pasa por el bucle
      iprintf("\x1b[2;1HTemporizador %d", tiempo);
      swiWaitForVBlank();  //con esta función detendremos la ejecución del bucle hasta que se produzca una interrupción por vblank
                      //tiene varios fines como sincronizar el bucle con el dibujado, ya que el bucle si no está continuamente
                      //realizándose a la máxima velocidad posible
   }

   return 0;
}




4. COMENTARIOS

Bueno ésta creo que es una buena toma de contacto con las interrupciones y creo también que tenéis todo lo suficiente para poder trastear con ellas. Interrupciones como termporizadore, dma o fifo merecen un capítulo a parte (que quien sabe si podré dedicar, pero espero que si). De todos modos si echáis de menos algun ejemplo en concreto lo pedís.

También aclarar que cuando ponga nombre/nombre2 en los registros o funciones, significa que según la versión de libnds incluida en devkitpro han cambiado el nombre. La primera es la versión más nueva y la siguiente la antigua

5. ANEXO. BOTONES Y TÁCTIL

El anexo de éste capítulo se lo vamos a dedicar a la gestión de botones y de táctil. La verdad es que libnds nos lo pone francamente sencillo. Lo primero que debemos saber es que tanto la táctil como los botones X e Y los recoge el subprocesador, aunque libnds hace de ello un proceso transparente. Estos datos son recogidos por el IPC del que no hace falta que nos preocupemos en este caso.Las demás teclas se quedan guardadas en el registro REG_KEYINPUT en el que los bits correspondientes a las teclas están en el mismo orden que en REG_KEYCNT. Vamos a ver sin más que posibilidades nos oferta libnds para leer botones:


*void scanKeys()

Recoge las nuevas teclas pulsadas del IPC y de KEYINPUT

*uint32 keysHeld(void)

Devuelve las teclas que está pulsadas

*uint32 keysDown(void)

Devuelve las teclas que han sido pulsadas desde este scanKey pero que en el anterior no lo estaban

*uint32 keysDownRepeat(void)

En este caso devuelve las teclas pulsadas según un intervalo de repetición que definimos con la siguiente función

*void keysSetRepeat( u8 setDelay, u8 setRepeat )

Con esto definimos primero cuanto se tarda en detectar una tecla como pulsada y con el segundo parámetro, cada cuanto volver a detectarla como pulsada

*uint32 keysUp(void)

Devuelve las teclas que han dejado de estar presionadas

*touchPosition touchReadXY()

Devuelve la posición de la táctil

Tenemos todo ¿no? Teclas presionadas, teclas que pulsas una vez, con repetición al levantar y la táctil. Vamos a ver ahora con que tenemos que comparar los que nos devuelven estas funciones para comprobar el botón que queramos

KEY_A
KEY_B
KEY_SELECT
KEY_START
KEY_RIGHT
KEY_LEFT
KEY_UP
KEY_DOWN
KEY_R
KEY_L
KEY_X
KEY_Y
KEY_TOUCH -> hay presión sobre la táctil
KEY_LID -> estado de la tapa


EJEMPLO

-Qué hace

En el ejemplo vamos a ir mirando todas estas funciones y mirando cuantás pulsaciones se contabilizan según la función usada. Luego también sacaremos por pantalla la posición del puntero dentro de la táctil y contaremos cuantas veces abrimos y cerramos la tapa de la consola

-Código
#include "nds.h"
#include <nds/arm9/console.h>
#include <nds/arm9/input.h>
#include <stdio.h>


int teclaA=0;
int teclaB=0;
int teclaL=0;
int pantallaC=0;
int pantallaA=0;


void interrupciones(){
   irqInit();  //inicializamos la tabla de interrupciones
   irqSet(IRQ_VBLANK,0);  //esta vez no asigno función para que veáis que no siempre es necesario. La activamos para que podamos
                     //usar swiWaitForVBlank
   irqEnable(IRQ_VBLANK);  //activamos las interrupciones
};         

void logica(){
   scanKeys(); //actualizamos los botones pulsadas
   
   if (keysHeld()) iprintf("\x1b[3;1HAlgun boton esta pulsado          ");   //miramos si alguna tecla está pulsada
   else iprintf("\x1b[3;1HNingun boton esta pulsado");
   
   if (KEY_A & keysDownRepeat()) teclaA++;  //si la tecla A está presionada (mediante repeticiones) se incrementa
   
   if (KEY_B & keysDown()) teclaB++;  //cuenta las veces que se presiona B
   
   if (KEY_LID & keysDown()) pantallaC++;  //miramos si cerramos la táctil
   if (KEY_LID & keysUp()) pantallaA++;  //miramos si cerramos la táctil
   
   if (KEY_L & keysUp()) teclaL++;  //cuenta las veces que se presiona B
   
   if (KEY_TOUCH & keysHeld()) {      //si hay presión sobre la táctil entonces miramos las coordenadas. Para una selección quizás mejor keysDown
      touchPosition touch=touchReadXY();
      iprintf("\x1b[1;1HTactil X %d, Y %d   ",touch.px, touch.py);
   }
   
   iprintf("\x1b[4;1HEl boton A se ha pulsado %d",teclaA);
   iprintf("\x1b[5;1HEl boton B se ha pulsado %d",teclaB);
   iprintf("\x1b[6;1HEl boton L se ha levantado %d",teclaL);
   iprintf("\x1b[8;1HPantalla cerrada %d",pantallaC);
   iprintf("\x1b[9;1HPantalla abierta %d",pantallaA);
   
};


int main(void) {
   powerON(POWER_ALL);  //a partir de la r19 creo que no es necesario
   lcdMainOnBottom();  //vamos a coger la sana costumbre escoger al principio donde poner la pantalla principal
      
   videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE);   //selecciona el modo de video 0 para la pantalla secundaria y activa el fondo 0
   
   vramSetBankC(VRAM_C_SUB_BG);    //reserva el banco C para fondos de la pantalla secundaria

   SUB_BG0_CR = BG_MAP_BASE(31);       //configura el registro del fondo 0 para que busque el mapa de tiles en el bloque 31.
   BG_PALETTE_SUB[255] = RGB15(31,31,31);  //ponemos el último color de la paleta de fondos de la pantalla principal a blanco
   consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);   //función básica proporcionada por libnds para poner texto. Sencilla pero con ciertas limitaciones

   interrupciones();       //inicializamos las interrupciones

   keysSetRepeat(20, 20);  //ponemos la repeticiones de teclas con un retraso de comienzo de 20 y de 20 retrasos por repeticion
   
   while(1) {
      logica();      
      swiWaitForVBlank();  //esperamos el vblank
   }

   return 0;
}


////////////////////////////////////////////////////////
Bueno ya está terminado y maquetado (casi hora y media!!!). Me gustaría que me dijerais que preferís respecto al código si como está con interoducción y luego todo comentado (para tener mejor vista general) o sólo comentar las partas relevantes de forma intercalada y quitando comentarios (o dejando). En los pequeños no importa mucho porque practicamente todo el código es importante. En los más grandes pues no se. Ala ahora a leerlo que si no me enfado.

He subido los ejemplos para las versiones antiguas de libnds y para las nuevas. Si no hay 2 versiones es que es común todo a las 2
Muchas gracias, excelente aporte [fies]
uniespi escribió:y pa q sirve todo eso :p

para programar en nds
PD:muchas gracias por el tutorial
pues esta genial, solo le veo una pega y es que anchea el foro.

por cierto, alguno de tus proyectos podria ser un motor grafico para juegos de coches? [+risas]
Gracias por el curro. Habrá que echarle un vistazo a ver si aprendemos decentemente a hacer algo interesante en nuestras DS ... [ayay] [risita]
Muy bueno, ya estoy deseando que salga el próximo, y que nos muestres ya algo de cómo poner gráficos! muchas gracias, 5 puntitos
Gran trabajo!!!

(Five points)
¡¡¡¡ Otra currada de post !!!!
Muy interesante el tema de las interrupciones, y muy bien explicado y ejemplificado.
Y ahora que me han regalado la DS Lite, ya puedo empezar a hacer cosillas sobre el hardware real xD.
5 estrellitas para el post ;)
Gracias por el tutorial, muy currado!! [toctoc]
Un tutorial muy bueno creo que empezare a trastear haber que sale xD
Muchas gracias por el Post, 5 estrellas rapidamnete ;)

Cuando tenga un poco de tiempo, lo paso todo a aquella web que estaba haciendo recopilando lso tutoriales ;)

Bueno, me lo he leido todo, mientras hacia un esquema/ resumen en unos folios, al final, creo que me encuadernare toda la informacon, porque en PAlib, tenia todo lo poco que tenia muy a mano, pero en LibNDS, hay tanto y en principio tan extraño, que hace falta un buen tocho... ^^

Espero con impaciencia tu proximo tutorial, sea cual sea;



PD: una pregunta, el stylus, lo detecta en forma de interrupcion, es decir, si yo estoy reproduciendo una animacion, en ese momento, todo lo demas, queda parado, y no tengo forma de meterle "if(sylus toca cierto lugar en pantalla) que se detenga la animacion" porque hasta que no acaba de mostrarla , no me pasa por el If, una forma es hacerlo fotograma a fotograma... pero queria saber si he leido mal, o lo de la interrupcion es solo para los botones.


Edito:

A mi tb me gusta tal cual esta, aunque en programas mas largos, si que sobrarian algunos ocmentarios que ya se hayan puesto mucho.... creo
A ver. Los botones no son interrupciones. Si miras el ejemplo del anexo verás que hay una función lógica que se llama continuamente en el bucle.

El tema es que da la casualidad que existe una interrupción para los botones (en este caso SOLO botones, ni táctil, ni tapa) que tiene un uso MUY limitado. La ventaja de la interrupción es que imáginate que quieres meter captura de pantalla de tus juegos. Una opción sería preguntar siempre en el bucle que si pulsas tal tecla, saque la captura. Con la interrupción en cambio, defines la combinación (lo que pasa es que sólo puedes definir una) y luego como en las demás interrupciones pones la función a la que llamarás cuando se produzca. Es MUY posible que los aparatos tipo SC, M3, etc. utilicen ésta interrupción para hacer el reset al menu.
Pues muchas gracias... pensaba que ya no seguirias con esto y me daba cosa preguntarte xDDDD... pero que bueno que sigue. yo me voy a cambiar de carrera me encanta la informatica pero se que es algo mas como algo personal y no es algo de lo que realmente voy a dedicarme el resto de mi vida pero quiero aprender y esto me da mas animos, Gracias!!!

Edito: a mi me gusta tal como esta con comentarios y todo pero como tu quieras hacerlo esta bien.
Olé! Muchas gracias por los tutoriales, están muy completos :D

Sólo tengo un par de problemas, y es que he conseguido compilar el ejemplo del tutorial anterior (el de primeros pasos), y de este tutorial el ejemplo que detecta pulsaciones en la pantalla táctil y de teclas y eso, pero los otros dos ejemplos de este tutorial no consigo compilarlos por los siguientes errores:

c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c: In function 'hBlank':
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c:14: error: 'DISP_Y' undeclared (first use in this function)
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c:14: error: (Each undeclared identifier is reported only once
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c:14: error: for each function it appears in.)
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c: In function 'interrupciones':
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c:28: error: expected expression before 'IRQ_MASK'
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c:31: error: 'DISP_SR' undeclared (first use in this function)
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c: In function 'main':
c:/devkitPro/Proyectos/PruebaNDS/PruebaNDS/arm9/source/arm9.c:51: error: 'for' loop initial declaration used outside C99 mode


He probado en VS2005 y en Programmers Notepad y no hay manera :S Aunque como ya digo los demás tiran sin problemas.

Una última cosa... para empezar a hacer algo más divertido en la DS necesito poner unos 10 bitmaps de 16x16 por toda la pantalla, repitiendo algunos pero todos estáticos. Qué debería mirar para hacer eso? Estaba pensando en copiar directamente en la zona correspondiente del framebuffer la información correspondiente al bitmap que sea, pero puede que sea algo lento aparte de chapucero XD

Gracias de nuevo y sigue así!!
Bien, acabo de ver que en la nueva versión de libnds han cambiado el nombre de esos registros. Yo uso una anterior porque la nueva no estaba muy pulida. Veré como lo hago para que quede claro.

Respecto al error de IRQ_MASK prueba a dejar solo las mascaras (es decir quita IRQ_MASK y los parentesis) y si puedes me dices si va (en la versión que tengo yo con una | sólo no va)

Y para los sprites olvidate del framebuffer. El framebuffer es el modo que realmente veo más inutil usar (para hacer alguna prueba rápida y poco más). Tienes que utilizar el mecanismo hardware que hay para ello, al que le dedicaré un capítulo porque es muy extenso (el tema es que primero tengo que hacer 2 capítulos antes). Ya se que hay ganas de poner imágenes y tal, pero es que no quiero empezar la casa por el tejado.
webez escribió:Respecto al error de IRQ_MASK prueba a dejar solo las mascaras (es decir quita IRQ_MASK y los parentesis) y si puedes me dices si va (en la versión que tengo yo con una | sólo no va)


Sí, quitando IRQ_MASK compila y funciona perfectamente :D

webez escribió:Y para los sprites olvidate del framebuffer. El framebuffer es el modo que realmente veo más inutil usar (para hacer alguna prueba rápida y poco más). Tienes que utilizar el mecanismo hardware que hay para ello, al que le dedicaré un capítulo porque es muy extenso (el tema es que primero tengo que hacer 2 capítulos antes). Ya se que hay ganas de poner imágenes y tal, pero es que no quiero empezar la casa por el tejado.


Entiendo. Esperaré a los siguientes tutoriales, mientras tanto leeré otros códigos y tutoriales que vea por ahí, a ver qué sale [fies]
Para sprites lo mejor sin duda, vete a drunkencoders y en gba (si gba) mira los tutoriales de TONC y el DAY 3. Es de lo mejor que vas a encontrar sobre el tema (aunque faltara todo lo específico de DS). Para ir tirando te vale
[tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo] [tadoramo]
Lástima que mi capacidad no llegue a tanto, primero neccesito ir a algun curso de C para entender mas la programacion!

[bye]&five stars!
Lástima que mi capacidad no llegue a tanto, primero neccesito ir a algun curso de C para entender mas la programacion!

se me ocurre que podriais hacer un curso de C. muy basico para entender mejor las cosas.

mis conocimientos de programación se han quedado obsoletos... :-P
Justo lo que necesitaba para terminar de aprender las interrupciones.

Thx
He cambiado el texto para que refleje esas diferencias (espero no haberme dejado nada)

También he resubido los ejemplos para la versión nueva (utilizan también una función nueva)

Si alguien puede probarlos y decirme si va todo ok, lo agradecería.
Me funciona perfectamente. Muchas gracias :D [beer]
y yo podria por ejemplo poner usar un interruptor para detectar que se ha pulsado un boton (cualquiera) para asi llamar a una funcion X?
En el ejemplo 2 de interrupciones se hace eso mismo (bueno en ese caso 2 botones y a la vez, pero podría haber sido con el bit 15 a 0 uno de los dos en vez de ambos). Lo importante es que tengas claro que sólo hay una interrupción de ese tipo y que sólo se puede definir una configuración de botones para esa interrupción (puedes ir cambiándola).

Es decir, haces un set de la interrupcion de botones y a que función quieres llamar cuando se produzca, y configuras el registro para que se produzca esa interrupción con la combinación de botones que quieras (que puede ser por ejemplo sólo X o X+A o X ó B, etc). Si quisieras cambiar la combinación tendrías que poner nuevos valores al registro, y si quieres que se llamara a otra función, tendrías que hacer un nuevo irqSet.

Como ves es para cosas concretas. Para lo demás gestión de teclas en bucle como dice el anexo.

PD: para lo que tu dices valdría con REG_KEYCNT=0x7FFF(que equivale a poner a 1 todos los bits menos el 15 que tiene que ser 0, mira la definición del registro). Luego podrías mirar el registro REG_KEYINPUT para mirar que tecla se ha pulsado. Ésto ahora que lo pienso debiera comentarlo.
Es que lo digo porque me han comentado que es mas eficiente hacer uso de las interrupciones que estar mirando constantemente si se ha pulsado la tecla X.

Imaginemos por ejemplo que queremos hacer un juego de luchas i tenemos, a parte de los tipicos movimientos basicos de adelante, atras , salto, puño, etc.. las demas tecnicas especiales.

Lo que me han comentado que puedo hacer es usar una interrupcion que salte al yo pulsar cualquier boton y mirar entonces cual es la que he pulsado para asi ahorrarme mucho proceso inutil cuando el tipejo esta quieto...
No se si me explico... programacion orientada a eventos me dijo..
Es una opción. Pero también ten en cuenta que las interrupciones tampoco son "gratis". Imaginate estar presionando todo el rato el botón (venga generar interrupcioens que pueden afectar a otras interrupciones). Personalmente no he oido de nadie que use ésta interrupción.

Además si bien en bucle tienes que comprobar siempre, puedes hacerlo más "ameno" mirando primero si hay alguna tecla pulsada y ya después comprobar tecla a tecla cual es.


PD: Hoy estoy bastante espeso así que a ver. La interrupción de teclas NO sirve para X e Y ni táctil ya que estos estan en el arm7. Es decir, si tu pulsaras X o Y la interrupción no se enteraría. Lo mismo para REG_KEYINPUT. Para ver si están esos botones tendrías que mirar el IPC. Esta interrupción estará más que nada por la gba.

PD2: Cuando me de menos pereza le pego un repaso a eso y lo pongo "más mejor"
Para qque sirve hacer esto??
subire este hilo para todos los que en su ia no lo vieron ;)

¿se sabe algo de la proxima entrega DS code 3 Webez?
Quien sabe.... quizás cuando termine un jueguillo que estoy haciendo
Muy buen tutorial.

Lo que daria yo por saber programar para la DS. Con eso y mi nivel de pixel art podria hacer alguna idea que tengo en mente. Pero con lo cazurro que soy que no se me keda el C pues lo veo dificil.

Molaria un port de algun lenguaje sencillo de programacion estilo DIV2 o Fenix. (Este ultimo si que lo controlo, al menos mucho mas que el C)
32 respuestas