[0] Free Pascal per NDS: Primi passi

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;

begin

  while true do
  begin
    // 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=arm
target=nds
fpcdir=C:\fpc4nds\
..

indicando in fpcdir il percorso del compilatore.

L'ultimo passo è quello di creare uno script batch che servirà per compilare il codice:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@echo off
set path=C:\fpc4nds\bin\i386-win32;%PATH%

fpcmake -r -w -Tnds

make clean distclean OS_TARGET=nds CPU_TARGET=arm BINUTILSPREFIX=arm-none-eabi- PP=ppcrossarm.exe 

make OS_TARGET=nds CPU_TARGET=arm BINUTILSPREFIX=arm-none-eabi- PP=ppcrossarm.exe OPT="@c:\fpc4nds\bin\i386-win32\fpc.nds.cfg -CX -XX -O2" 

pause

modificando, ovviamente, il path, se serve.


Scrivere caratteri sullo schermo

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!!!!'); 

  while true do
  begin
    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:

1
2
printf(#27 + '[32m' + 'Verde!' + #10); 
printf(#27 + '[32;1m' + 'Verde acceso!');

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const
  REG_KEYINPUT: pcuint16 = pointer($04000130);
...
type
  KEYPAD_BITS = cint;
const
  // Bit values for the keypad buttons.
  KEY_A : KEYPAD_BITS = (1 shl 0); // Keypad A button.
  KEY_B : KEYPAD_BITS = (1 shl 1); // Keypad B button.
  KEY_SELECT : KEYPAD_BITS = (1 shl 2); // Keypad SELECT button.
  KEY_START : KEYPAD_BITS = (1 shl 3); // Keypad START button.
  KEY_RIGHT : KEYPAD_BITS = (1 shl 4); // Keypad RIGHT button.
  KEY_LEFT : KEYPAD_BITS = (1 shl 5); // Keypad LEFT button.
  KEY_UP : KEYPAD_BITS = (1 shl 6); // Keypad UP button.
  KEY_DOWN : KEYPAD_BITS = (1 shl 7); // Keypad DOWN button.
  KEY_R : KEYPAD_BITS = (1 shl 8); // Right shoulder button.
  KEY_L : KEYPAD_BITS = (1 shl 9); // Left shoulder button.
  KEY_X : KEYPAD_BITS = (1 shl 10); // Keypad X button.
  KEY_Y : KEYPAD_BITS = (1 shl 11); // Keypad Y button.
  KEY_TOUCH : KEYPAD_BITS = (1 shl 12); // Touchscreen pendown.
  KEY_LID : KEYPAD_BITS = (1 shl 13); // 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();
  while true do
  begin
    if (REG_KEYINPUT^ and KEY_A) <> 0 then
      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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
program keys_b;
{$mode objfpc}
uses
  ctypes, nds9;
var
  held: integer;
begin
  consoleDemoInit();
  while true do
  begin
    scanKeys();
    held := keysHeld();
    if (held and KEY_A) <> 0 then
      printf('A Key is pressed'#10)
    else
      printf('A Key is released'#10);
    if (held and KEY_X) <> 0 then
      printf('X Key is pressed'#10)
    else
      printf('X Key is released'#10);
    if (held and KEY_TOUCH) <> 0 then
      printf('Touch pad is touched'#10)
    else
      printf('Touch pad is not 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:

type
  touchPosition = packed record
  rawx: cuint16;
  rawy: cuint16;
  px: cuint16;
  py: cuint16;
  z1: cuint16;
  z2: cuint16;
end;
PtouchPosition = ^touchPosition;
TtouchPosition = touchPosition;
...
procedure touchRead(var data: touchPosition);

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
  while true do
  begin
    touchRead(touch);
    color := (touch.px + touch.py) shr 4;
    for y := 0 to 192 - 1 do
      for x := 0 to 256 - 1 do
        VRAM_A[y * 256 + x] := integer(RGB15(color, x shr 3, y shr 3));
    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. 

Scarica i sorgenti

Per commenti e chiarimenti: http://www.lazaruspascal.it/index.php?topic=78.0



SMF 2.0.8 | SMF © 2011, Simple Machines
Privacy Policy
SMFAds for Free Forums
TinyPortal © 2005-2012

Go back to article