Written by Legolas Ottobre 17, 2012, 11:04:00 am21491 ViewsRating: 0 (0 Rates)Print
In questo articolo creeremo una prima semplice applicazione per Nintendo DS: mostreremo dei caratteri a schermo e interagiremo con la console attraverso il touch screen e i tasti.
Lo scheletro dell'applicazione
Qui di seguito è riportato il codice necessario per creare una qualsivoglia applicazione per Nintendo DS:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
program prova;
{$mode objfpc}uses
ctypes, nds9;
beginwhiletruedobegin// loop infinito!end;
end.
Il codice è molto semplice, quindi c'è davvero poco da dire, se non che la unit nds9 contiene gli header della libreria libndsfpc. Provando a compilare si otterrà una ROM perfettamente funzionante che mostrerà una bella schermata... nera!
Già, compilare. Il mio consiglio è quello di prendere l'intera directory dell'esempio "hello_world", distribuito con la libreria libndsfpc, copiarla in un'altra posizione (evitate spazi nel percorso!) e utilizzarla come base di partenza per i vostri progetti. Per far funzionare il tutto occorre modificare il file makefile.fpc come segue:
1
2
3
4
5
6
..[default]cpu=armtarget=ndsfpcdir=C:\fpc4nds\..
indicando in fpcdir il percorso del compilatore.
L'ultimo passo è quello di creare uno script batch che servirà per compilare il codice:
Le cose cominciano a farsi più interessanti con la possibilità di stampare dei caratteri sullo schermo. Anche questa operazione richiede poche righe di codice:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
program prova_console;
{$mode objfpc}uses
ctypes, nds9;
var
i: integer;
begin
consoleDemoInit();
printf('Hello World!!!!');
whiletruedobegin
swiWaitForVBlank();
end;
end.
La funzione consoleDemoInit() serve ad inizializzare la console con i caratteri predefiniti. E' possibile anche caricare dei font personalizzati, ma è un argomento che verrà trattato in seguito, quando si sarà affrontata la gestione dei banchi di memoria. Da notare l'uso di swiWaitForVBlank() nel loop infinito, che sincronizza il vertical blank. Per ora basti sapere che, grazie all'utilizzo di questa procedura, è possibile, ad esempio, sincronizzare le chiamate alle procedure di disegno con il refresh dello schermo, evitando così fastidiosi fenomeni di flickering.
La libreria libndsfpc è un binding di una libreria scritta in c, quindi le funzioni di stampa a schermo sono quelle proprie del c. Nel nostro caso stiamo utilizzando printf(), ma ce ne sono altre che vedremo man mano che ne avremo la necessità. Basti sapere che, caratteristica interessante, anteponendo dei codici di controllo alla stringa di testo da stampare, è possibile ad esempio cambiare colore ai caratteri:
Il codice numerico #27 attiva la sequenza di escape, necessaria per indicare che quello che segue è un codice di controllo; [32m indica che da quel punto in poi i caratteri vanno stampati a schermo in verde (codice di colore 32). Nel caso in cui volessimo una tonalità di verde più brillante sarà sufficiente digitare il codice [32;1m. Il codice #10 invece è un semplice "a capo".
I valori accettati per il colore sono riepilogati in tabella:
Codice
Colore
30
nero
31
rosso
32
verde
33
giallo
34
blu
35
magenta
36
ciano
37
bianco
E' anche possibile indicare il punto dello schermo dove si vuole posizionare la stringa di testo:
1
printf(#27 + '[3;2H' + 'Riga 3, colonna 2');
Interazione con i tasti
Il Nintendo DS è equipaggiato da diverse periferiche di input. Oltre ai classici tasti presenti in tutte le console, dispone di uno schermo sensibile al tocco. Vedremo ora come gestire l'input dalla pulsantiera e dal touch screen.
Responsabile della gestione dell'input è un registro a 16 bit, chiamato REG_KEYINPUT, che si trova all'indirizzo di memoria $04000130, che è così dichiarato:
const
REG_KEYINPUT: pcuint16 = pointer($04000130);
...
type
KEYPAD_BITS = cint;
const// Bit values for the keypad buttons.
KEY_A : KEYPAD_BITS = (1shl0); // Keypad A button.
KEY_B : KEYPAD_BITS = (1shl1); // Keypad B button.
KEY_SELECT : KEYPAD_BITS = (1shl2); // Keypad SELECT button.
KEY_START : KEYPAD_BITS = (1shl3); // Keypad START button.
KEY_RIGHT : KEYPAD_BITS = (1shl4); // Keypad RIGHT button.
KEY_LEFT : KEYPAD_BITS = (1shl5); // Keypad LEFT button.
KEY_UP : KEYPAD_BITS = (1shl6); // Keypad UP button.
KEY_DOWN : KEYPAD_BITS = (1shl7); // Keypad DOWN button.
KEY_R : KEYPAD_BITS = (1shl8); // Right shoulder button.
KEY_L : KEYPAD_BITS = (1shl9); // Left shoulder button.
KEY_X : KEYPAD_BITS = (1shl10); // Keypad X button.
KEY_Y : KEYPAD_BITS = (1shl11); // Keypad Y button.
KEY_TOUCH : KEYPAD_BITS = (1shl12); // Touchscreen pendown.
KEY_LID : KEYPAD_BITS = (1shl13); // Lid state.
Lo stato di ogni tasto è associato ad un bit nel registro (1: rilasciato; 0: premuto).
Vediamo un piccolo esempio sull'utilizzo diretto del registro REG_KEYINPUT:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
program keys;
{$mode objfpc}uses
ctypes, nds9;
begin
consoleDemoInit();
whiletruedobeginif (REG_KEYINPUT^ and KEY_A) <> 0then
printf('A Key released')
else
printf('A Key pressed');
swiWaitForVBlank();
consoleClear();
end;
end.
Vediamo il funzionamento dell'esempio linea per linea:
[6] Viene inizializzata la console standard
[7-15] Loop infinito per far girare il programma
[9] Viene controllato lo stato del bit KEY_A (cioè del bit nella posizione 0) nel registro REG_KEYINPUT. Il registro è un puntatore ad una zona di memoria, quindi andrà dereferenziato per accedere al suo contenuto (^). Loperatore logico and restituisce lo stato del bit KEY_A (0 o 1); se non è impostato a 0 (<> 0) il tasto non è premuto e stampiamo a schermo un messaggio ([10]), altrimenti è premuto e stampiamo a schermo un secondo messaggio ([12]).
[13] Aspettiamo il prossimo vertical blank
[14] Cancelliamo il contenuto della console sullo schermo, altrimenti i caratteri verrebbero sovrascritti di volta in volta, sovrapponendosi a quelli scritti in precedenza.
Si può ottenere lo stesso risultato utilizzando delle funzioni di livello più alto, senza andare a scomodare puntatori e bit. Espandiamo un po' l'esempio precedente:
program keys_b;
{$mode objfpc}uses
ctypes, nds9;
var
held: integer;
begin
consoleDemoInit();
whiletruedobegin
scanKeys();
held := keysHeld();
if (held and KEY_A) <> 0then
printf('A Key is pressed'#10)
else
printf('A Key is released'#10);
if (held and KEY_X) <> 0then
printf('X Key is pressed'#10)
else
printf('X Key is released'#10);
if (held and KEY_TOUCH) <> 0then
printf('Touch pad is touched'#10)
else
printf('Touch pad isnot touched'#10);
swiWaitForVBlank();
consoleClear();
end;
end.
[11] La procedura scanKeys() legge lo stato corrente del keypad. Effettuata la chiamata alla procedura una volta per ogni frame, vengono rese disponibili una serie di utili funzioni:
function keysCurrent(): cuint32;
function keysHeld(): cuint32;
function keysDown(): cuint32;
function keysDownRepeat(): cuint32;
procedure keysSetRepeat(setDelay: cuint8; setRepeat: cuint8);
function keysUp(): cuint32;
[12-24] Una volta ottenuto lo stato del keypad, possiamo leggere lo stato di un singolo tasto, effettuando un and tra il valore restituito dalla funzione e il bit occupato dal tasto. Come nell'esempio precedente, stampiamo a schermo dei messaggi indicanti lo stato dei tasti.
Interazione col touch screen
Fino a questo punto le cose sono tanto semplici quanto poco interessanti. Abbiamo visto come gestire i tasti e lo stato del touch screen (tocco/non tocco). Ma se volessimo indicare a schermo le coordinate del punto di tocco? Nel prossimo esempio vedremo appunto come fare. La procedura che si occupa della gestione del touch screen è la seguente:
La procedura touchRead(var data: touchPosition) si occupa di aggiornare il record touchPosition, che possiamo quindi leggere per ottenere le informazioni sul punto di tocco sul touchpad. Le coordinate di un singolo punto su di un piano bidimensionale (come il touch screen) possono essere memorizzate in 2 variabili, ma il record touchPositon è composto da 6 valori. Per quale motivo? Perché memorizza i valori x e y del punto di tocco (px, py), i loro valori non calibrati (rawx, rawy) e il livello di pressione sul touch screen (z1, z2).
Come al solito vediamo un breve esempio sull'utilizzo del touch screen. In esso sono introdotti alcuni concetti più avanzati, che verranno meglio trattati nei prossimi tutorial; per il momento provate comunque seguirli:
program touch_a;
{$mode objfpc}uses
ctypes, nds9;
var
color: integer;
touch: TTouchPosition;
x, y: integer;
begin
videoSetMode(MODE_FB0);
vramSetBankA(VRAM_A_LCD);
lcdMainOnBottom();
whiletruedobegin
touchRead(touch);
color := (touch.px + touch.py) shr4;
for y := 0to192 - 1dofor x := 0to256 - 1do
VRAM_A[y * 256 + x] := integer(RGB15(color, x shr3, y shr3));
swiWaitForVBlank();
end;
end.
[10-12] Nell'esempio utilizzeremo l'accesso diretto ai pixel dello schermo, quindi impostiamo la modalità framebuffer (MODE_FB0) per il video; riserviamo quindi il banco di memoria A per l'utilizzo con lo schermo LCD tramite la procedura vramSetBankA(); per ultimo impostiamo lo schermo inferiore come schermo principale per la nostra piccola applicazione.
[15] All'interno del loop infinito eseguiamo la lettura del touch screen e salviamone il contenuto nella variabile touch.
[16] La variabile color viene utilizzata per memorizzare il valore della componente rossa del colore, ricavandolo dalle coordinate del punto di tocco. Piccola spiegazione: "shr 4" equivale a scrivere "div 2^4". Per ogni componente di un colore RGB15 c'è bisogno di un valore intero nel range 0-31, ma la somma delle coordinate del punto di tocco rientra nel range 0-(191 + 255 = 446). Dividendo questo valore per 2^4=16, otteniamo un valore massimo di 27,875, che è molto vicino al valore massimo che ci serve (31).
[17-19] Ad ogni frame effettuiamo un loop attraverso tutti i pixel dello schermo e assegniamo un colore ad ognuno di essi, passando alla funzione RGB15() il valore che abbiamo memorizzato nella variabile color per la componente rossa; per le componenti verdi e blu del colore eseguiremo invece un calcolo, basandoci sui valori delle coordinate x e y. In questo caso abbiamo bisogno di un valore nel range 0-31, partendo da 0-191 (coordinata y) e 0-255 (coordinata x). Questa volta divideremo per 2^3=8, che è lo stesso che scrivere "shr 3". Anche in questo caso otterremo una buona approssimazione del valore massimo richiesto.
Conclusioni
Non vi rimane che provare a giocare con i comandi appena visti, in attesa del prossimo articolo, in cui proveremo a salvare dei dati su file e a ricaricarli in un secondo momento.
Legolas registered at Italian community of Lazarus and Free Pascal on Ottobre 18, 2011, 11:43:22 am and has posted 366 posts in the boards since then. Last visit was Novembre 03, 2022, 07:43:11 pm.
Questo blog non rappresenta una testata giornalistica poiché viene
aggiornato senza alcuna periodicità. Non può pertanto considerarsi un
prodotto editoriale ai sensi della legge n. 62/2001.
Questo sito utilizza cookie, anche di terze parti, per offriti servizi in linea con le tue preferenze. Chiudendo questo banner, scorrendo questa pagina, cliccando su un link o proseguendo la navigazione in altra maniera, acconsenti all’uso dei cookie.