C64 Bedtime Coding (ITA) – Stampare sullo schermo (#08)

(Article by Phaze101)

Stampare sullo schermo

 

Importante

Ho dovuto prendere una pausa per motivi di lavoro, non potevo fare diversamente. Come per chiunque altro, anche io ho degli impegni per le quali devo decidere cosa è prioritario e cosa no.

Inoltre, ho voluto mettere insieme una squadra per aiutarmi nella traduzione di questi articoli. Sono rimasto sorpreso dal numero di riposte ricevute a riguardo. Desidero ringraziare tutti quelli che mi hanno risposto e si sono adoperati per aiutarmi

Articoli futuri

Gli articoli stanno diventando più lunghi e complessi. Cercherò di mantenere il ritmo di un articolo a settimana, ma non escludo l’ipotesi di saltarne una occasionalmente. Inoltre, alcuni argomenti richiedono ulteriori ricerche. Non sono una enciclopedia e non conosco ogni singolo aspetto. Tutto ciò può richiedere del tempo.

Articoli lunghi inoltre richiedono più tempo per essere tradotti in italiano. Sono fiducioso che questo aspetto diventerà più semplice con il team che mi supporta.

Windows / Linux / MAC

Questo argomento potrebbe risultare un po’ controverso, ma vi chiedo di mettermi nei miei panni. Desidero insegnare il linguaggio assembly nella maniera più semplice possibile. E’ un requisito fondamentale per andare incontro a chi voglia imparare assembly.  Sfortunatamente, CBM Prg Studio gira solo su Windows. Credo sia l’ambiente di programmazione più semplice per chi sta iniziando. All’inizio ero indeciso tra CBM Prg Studio e Kick Assembler. Kick Assembler gira su tutte le piattaforme, ma non è così immediato come CBM Prg Studio e non dispone di un ambiente di sviluppo. Per cui, alla fine, ho optato per CMB Prg Studio.

Capisco che potrebbero esserci alcuni di voi che usano solo Linux o Mac, e che potrebbero sentirsi esclusi, ma questo non è abbastanza per decidere di non utilizzare CBM Prg Studio. Ho deciso di dare priorità alla semplicità perché so che imparare l’assembly non è facile. Se lo strumento scelto non fosse sufficientemente semplice da usare, la situazione diventerebbe pesante. Scegliere qualcosa come Kick Assembler con tutta la sua complessità non aiuterebbe. Lo vedo bene per un articolo futuro, ma non adesso.

Ancora su riempimento / cancellazione schermo

L’articolo precedente ha trattato la cancellazione e il riempimento dello schermo. In questa sezione approfondirò parzialmente tale argomento. Ora che si è compreso come funziona il metodo più semplice, vorrei mostrarne un altro. Più che il metodo in sé, mi interessa il modo di pensare. In assembly è necessario abituarsi a ragionare in modo differente.

Tutti i metodi precedentemente visti iniziavano dall’angolo in alto a sinistra dello schermo, operando sui 1000 byte di memoria a partire dalla locazione \$0400, fino all’angolo in basso a destra che corrisponde a \$07E7 (1024-2023). Ora, invece di iniziare dall’angolo in alto a sinistra, iniziamo dall’angolo opposto in basso a destra. Questo significa che cominciamo dalla locazione 2023 o \$07E7.

Scriviamo allora il codice e percorriamo al contrario le locazioni a partire da 2023 fino a 1024 per cancellare / riempire lo schermo.  Sarà comunque un riempimento.

Questo metodo ha il pregio di non andare oltre ai 1000 caratteri per cui è ottimo per I giochi, dato che i 24 byte successivi a \$07E7 non sono coinvolti nelle operazioni.

E qui abbiamo la schermata risultante.

Ricordate di digitare SYS 49152 per avviare il programma.

 

Esaminiamo il codice

Linee 52 – 55

Sono le stesse viste per il metodo 3, nell’ Articolo 7. L’unica differenza sta nel fatto che lavoriamo con le locazioni di memoria a partire da \$0700 invece di \$0400. Ovvero, stiamo partendo dalla fine, o – più in dettaglio – dal segmento basso dello schermo (caratteri da 768 a 999).

Linee 57 – 59

Queste dovrebbero essere ovvie, ma ad ogni buon conto le esaminiamo da vicino. Poniamo il registro indice Y al valore \$E7, ovvero 231 in decimale. Questo valore corrisponde all’angolo in basso a destra quando viene sommato all’indirizzo \$0700, ossia \$07E7.
Abbiamo diviso lo schermo in 4 segmenti, per cui percorriamo il ciclo 4 volte, contando all’indietro. Per questo Inizializziamo il registro indice X con 4.

Nel registro A invece mettiamo il nostro carattere di riempimento.

Linea 60

Questa è una linea di codice interessante. Ci tornerò più avanti, ma provate ad eliminarla e vedete cosa accade. Capirete il motivo più avanti.

Linee 62 – 68

Queste linee sono le stesse viste nel metodo 3 dell’articolo 7. L’unica differenza risiede nel fatto che nella linea 66 decrementiamo la locazione di memoria \$FC in Pagina Zero poiché ci stiamo muovendo dal basso verso l’alto dello schermo. Inoltre, decrementiamo Y dato che stiamo riempiendo i caratteri dal basso verso l’alto.

Il vantaggio principale di questo metodo risiede nel fatto che le iterazioni su X e Y sono progettate per fermarsi quando i loro valori sono \$00. Quando X o Y raggiungono lo zero, il flag Zero del registro di stato viene impostato a 1. Questo significa che non c’è necessità di confrontare i loro valori tramite una istruzione CPX o CPY, che normalmente setta il flag Zero se i valori confrontati sono uguali. Tutto va come se avessimo un CPX #\$00 gratis dopo ogni DEX e un CPY gratis dopo ogni DEY. Per cui, quando X o Y raggiungono \$00, l’istruzione BNE (Branch if Not Equal) passerà automaticamente all’istruzione successiva perché il flag Zero sarà già impostato.

Linea 60 (di nuovo)

Quando abbiamo rimosso questa linea dal codice, perché ci siamo ritrovati con una locazione di memoria non riempita? Ricordiamoci, stiamo andando a ritroso in questa procedura. Quando il registro X vale 4, andiamo a decrementare il registro Y nella linea 64 per poi verificare se Y è zero, per cui non riusciremo mai a scrivere in quella locazione che corrisponde alla \$0700. Invece, nelle successive iterazioni dove X è minore o uguale a 3, arriviamo a scrivere nella locazione di schermo quando il registro Y vale 0. Cerca di immaginarlo nella tua testa o fai eseguire questo codice alla CPU, e capirai cosa intendo

La linea 66 decrementa il valore in \$FC a \$06, Y è ancora 0 e X diventa 3. Non appena iteriamo andiamo a scrivere il contenuto del nostro registro A in \$0600 dal momento che Y è 0. Al successivo decremento di Y, Y diventa \$FF oppure 255, quindi andiamo a scrivere il contenuto dell’accumulatore in \$06FF e successivamente continuiamo a decrementare questo registro finché non raggiungiamo \$0601. Quando finalmente arriva a 0, abbiamo già riempito la locazione \$0600, per cui non dobbiamo preoccuparci, e possiamo decrementare di nuovo il valore in \$FC. Spero che tutto questo sia comprensibile. Per riassumere, la subroutine essenzialmente scrive bytes secondo la seguente sequenza di indirizzi:

(prima del loop) \$0700,
(X = 4) \$07E7, \$07E6, …, \$0701,
(X = 3) \$0600, \$06FF, \$06FE, …, \$0601,
(X = 2), \$0500, \$05FF, \$05FE, …, \$0501,
(X = 1), \$0400, \$04FF, \$04FE, …, \$0401

Aggiornamento su riempimento / cancellazione schermo

Il mio caro amico Colin Vella mi ha suggerito una variante del mio codice per riempire / cancellare lo schermo, che potrebbe essere più intuitiva per i principianti. Potrebbe avere ragione. Il suo approccio è interessante, ma per essere sincero, tecnicamente non mi sento molto a mio agio con essi.

La differenza tra il mio codice e l’alternativa suggerita da Colin è lo scambio tra le righe 63 e 64. Funziona, poiché STA non imposta la Zero Flag. Quindi, l’istruzione BNE nella riga 65 si attiva in base al Zero Flag, impostata o cancellata da DEY nella riga 63.

Ciò di cui non mi trovo a mio agio, è che normalmente prima del branching usiamo un’istruzione che imposta una Status Flag, cosa che non è il caso qui. Questo approccio può facilmente portare ad errori nel codice e dovrebbe essere evitato il quanto possibile.

Un’altra modifica importante è che il registro indice Y è inizialmente impostato su \$E8 anziché \$E7, per tenere conto del fatto che nel loop, decrementiamo Y, prima di scrivere il valore in memoria.

Infine, l’ultima modifica è l’eliminazione di STA \$0700, poiché in questa variante la routine scrive nella memoria in modo lineare, partendo da \$07E7 fino a \$0400.

Oltre a ciò, tutto il resto rimane lo stesso.

 

Direttive Assembler

Siamo giunti al punto in cui abbiamo bisogno di ulteriori direttive assembler. Non stiamo parlando di istruzioni della CPU e neppure di quelle che fanno parte del set di istruzioni del 6502, ma riguardano l’assembler. L’assembler dispone di caratteristiche che ci rendono la vita più semplice e questo è lo scopo per cui le direttive assembler sono state pensate.

Direttiva  =

Il simbolo = è usato per definire costanti in un programma assembler. Le costanti sono fisse e non possono essere assegnate ad un altro valore.

Per cui:

ChrOut = \$FFD2

significa che da adesso in poi l’indirizzo esadecimale \$FFD2 può essere richiamato con la stringa ChrOut. Durante la fase di assemblaggio del codice, tutte le occorrenze di “ChrOut” saranno sostituite da \$FF02.

Abbiamo già utilizzato la direttiva “=” in:

  • = \$C000

In assembler,  “*” (asterisco) è un simbolo speciale che si riferisce all’indirizzo di memoria a partire dal quale l’assembler deve inviare il codice macchina. Pertanto, quando diciamo * = \$C000, stiamo indicando all’assembler che deve scrivere il codice che seguirà a partire dall’indirizzo di memoria \$C000.

Low Byte (<), High Byte (>)

Abbiamo ripetuto diverse volte che il 6502 ha bisogno che gli indirizzi a 16bit siano memorizzati nel formato Byte Basso (Low Byte), Byte Alto (High Byte).

Se vogliamo il Byte Basso e il Byte Alto di un indirizzo di memoria, possiamo usare i simboli < e >. Per esempio, supponiamo di avere del testo etichettato come “String” nell’indirizzo di memoria \$8040.

LDA #<String

Se ci riferiamo all’etichetta con il simbolo < davanti, significa “prendi il byte basso dell’etichetta”, che è \$40.

LDA #>String

Se ci riferiamo all’etichetta con il simbolo > davanti, significa “prendi il byte alto dell’etichetta”, che è \$80.

Più (+) e Meno (-)

Possiamo utilizzare operatori aritmetici per definire costanti tramite espressioni matematiche. In ogni caso, queste costanti saranno valutate dall’assembler e i loro valori finali utilizzati nel codice. Per esempio, se abbiamo

ScrStart = \$0400
ScrEnd  =  ScrStart + \$03E7


LDA       #ScrEnd

allora l’assembler sostituirà ScrEnd con \$07E7 e genererà una istruzione per LDA #\$07E7.

Direttive testuali

Stringhe di testo possono essere memorizzato sia come codici PETSCII che CBM, per esempio:

text “a”,”b”     – nota: “ ” sono usati per i caratteri PETSCII

text ‘a’,’b’        – nota: ‘ ‘ sono usati per i codici di visualizzazione su schermo

 

Stampa su schermo

 

In questo articolo non potrò trattare tutti i metodi di stampa dei quali vorrei parlare. Ci limiteremo a quanto disponibile dal Kernal e dalla ROM Basic.

Stampa – metodo 1

Stampare a schermo usando l’assembly è una sfida dato che il Commodore utilizza i codici schermo.

La prima routine Kernal che trattiamo è nota come ChrOut e si trova all’indirizzo \$FFD2. ChrOut stampa i caratteri uno ad uno, per cui sarà necessario un ciclo per stampare una stringa intera. Inoltre, la routine stampa nella posizione corrente del cursore, ovunque esso si trovi. Il carattere da stampare deve essere caricato nell’accumulatore prima di richiamare la routine.

In questo articolo ho modificato la mia routine di ingresso Main. Ho iniziato a organizzare meglio il codice in blocchi. Main è il punto di ingresso del programma ed è da dove inizia l’esecuzione.

Ecco il codice della routine di ingresso Main.

La linea 38 del listato è una chiamata alla routine che cancella lo schermo. E’ poi seguita da una chiamata alla routine che stampa del testo sullo schermo e, come nell’esempio precedente, richiamiamo la routine WaitForSpace per attendere che l’utente prema la barra spaziatrice per poi tornare al Basic.

Dal momento che ormai sarete a vostro agio con la cancellazione dello schermo e l’attesa della barra spaziatrice, ci interessiamo più che altro della routine PrintText.

Di seguito il codice:

Spiegazione

 

Linee 56 e 57

Definiamo qui le nostre costanti. Per poterle usare, assegniamo alla costante ChrOut il valore di \$FFD2 e alla costante ZP il primo indirizzo disponibile della Pagina Zero.

Preferisco definire le costanti quanto più possibile vicino alle routine, ma non è sempre possibile. Dipende dalla routine. Un’altra ottima posizione per le costanti è all’inizio del codice, dopo la dichiarazione del punto di ingresso del programma.

Notiamo che String è una stringa “zero terminated”, cioè termina con un carattere 0.

Linee 62 – 65

Questo codice è simile a quello dell’esempio precedente, tranne che per il fatto che qui utilizziamo le direttive. Carichiamo e registriamo il byte basso dell’indirizzo dove l’etichetta String è definita e facciamo lo stesso per il byte alto. ZP e ZP+1 significano che stiamo usando e memorizzando I valori agli indirizzi Pagina Zero \$FB and \$FC.

Linee 67 – 72

Dopo aver inizializzato il registro Y con 0, nella linea 70 prendiamo il valore memorizzato nell’indirizzo corrispondente al valore che è stato caricato precedentemente negli indirizzi \$FB e \$FC di Pagina Zero, in altre parole l’indirizzo dell’etichetta String.

I dati del testo memorizzati presso l’etichetta String sono una sequenza di caratteri che terminano con 0. Per cui, mentre lavoriamo su questi caratteri, se il valore nell’indirizzo è uno zero sappiamo di aver raggiunto il termine della stringa e possiamo saltare all’etichetta Finish che ci fa ritornare alla routine dopo PrintText. In caso contrario, chiamiamo la routine Kernal per stampare il carattere nel registro A.

Linee 73 – 76

Stampato il carattere, incrementiamo Y per prendere il successivo. Se Y è diverso da 0 ripartiamo da inizio ciclo. Se invece Y è uguale a 0, dobbiamo incrementare il Byte Alto all’indirizzo \$FB poiché la nostra stringa è più lunga di 256 caratteri

La cosa buona di questa routine è che può stampare più di 256 caratteri e – volendo – può anche riempire l’intero schermo.

Come detto, il programma ritorna alla routine Main quando l’ultimo carattere della stringa è uno 0. A questo punto viene invocata la routine WaitForSpace che attende la pressione della barra spazio.

Dopo aver compilato e lanciato il programma, ricordarsi di digitare SYS 49152 per eseguire il codice quando è caricato nell’emulatore.

Di seguito una schermata del programma in esecuzione:

E qui, il listato completo:

Bonus “Breakout”

Poiché nel gruppo FB RetroProgramming Italia si sta svolgendo la gara di programmazione di Breakout, quella che segue è una stringa che disegna la schermata di gioco.

Notare che non ho trattato l’aspetto “colore”, ma dovreste avere un’idea di cosa accadrà.

Inoltre, è utile notare che ogni linea è lunga 40 caratteri e che tutto ciò funzionerà solo se state usando CBM Prg Studio.

Questo è ciò che dovreste vedere:

Metodo di stampa 2

Questa routine si baserà su due routine della ROM.

La prima è chiamata SetCursor e si trova all’indirizzo \$E50C. Questa routine richiede che le si passi il valore X o il numero di riga dello schermo, attraverso il registro X. Allo stesso modo, indichiamo il numero di colonna attraverso il registro Y. Pertanto, i due registri devono essere impostati correttamente prima di chiamare la routine.

La seconda si chiama PrintString, si trova all’indirizzo \$AB1E.  Serve a stampare una stringa non più lunga di 256 caratteri. Prima di invocarla, occorre settare il registro A con il Byte Basso e il registro Y con il Byte Alto dell’indirizzo corrispondente all’etichetta String.

Di seguito un listato della nostra routine che stampa del testo:

Panoramica

Questa routine posiziona il cursore alla riga 12 / colonna 8, per poi stampare una stringa.

Spiegazione

 

Linee 53 – 54

Semplicemente, impostiamo le costanti per le due routine SetCursor e PrintString.

Lines 59 to 61

Queste linee riguardano la parte di posizionamento del cursore. Carichiamo il registro X con il numero di riga 12 e il registro Y con il numero di colonna 8. Quindi invochiamo la routine ROM SetCursor per posizionare il cursore alla riga 12 a alla colonna 8.

Lines 63 to 65

Questa è la parte dove stampiamo la nostra stringa. Carichiamo il Byte Basso della stringa nel registro A e il Byte Alto nel registro Y. Dopodiché invochiamo la routine ROM PrintString per stampare la stringa presso la posizione del cursore precedentemente impostata con SetCursor.

E questo è l’output quando eseguiamo il programma con SYS 49152:

Queste due routine sono facili da usare fintanto che hai solo una stringa da gestire, ma la situazione diventa più difficile quando ne hai di più perché sarà necessario creare una sorta di tabella. Questo è un argomento avanzato che forse tratterò in un articolo futuro

È possibile comunque disegnare un’intera schermata con questo approccio, ad esempio, stampando 4 stringhe di 250 caratteri ognuna. Tuttavia, per qualcosa di più complesso come lo scorrimento del testo in un gioco di avventura, questa modalità diventa improponibile.

Di seguito un listato di tutto il codice:

Prossimo articolo – 18 Maggio o 25 Maggio

È tutto per ora. Questo articolo dovrebbe farvi iniziare a pensare in maniera diversa a come rapportarvi all’assembler del 6502. Credo che per l’assembly dobbiate imparare a pensare in maniera del tutto diversa da come siete abituati a fare con gli altri linguaggi. Questo articolo scalfisce solo la superficie della stampa del testo, ma è abbastanza per iniziare.

Il prossimo articolo potrebbe essere pubblicato la prossima settimana o quella dopo. Dipende da alcuni impegni personali e dal tempo libero.
Al momento, sono un po’ sovraccarico, ma spero di poter continuare a pubblicare con una cadenza settimanale. Come sempre, dobbiamo tener conto della realtà delle cose, non è detto che ci sia sempre un’alternativa.

La buona notizia è che ora con me c’è un team di persone che collaborano al progetto.

Come sempre, se avete domande scrivetemi pure.

Coding is Fun 😊

Phaze 101

Traduzione in italiano di: Arturo Dente

Hanno collaborato a questo numero di C64 Bedtime Coding: Colin Vella, Pinov Vox, David La Monaca/Cercamon, Davide Aldegheri

Italian

Articolo 7

C64 Bedtime Coding (ITA) – Cancellare lo schermo (#07)

Articolo 6
https://sys64738.org/2019/04/c64-bedtime-coding-ita-primi-passi-06/

Articolo 5
https://sys64738.org/2019/04/c64-bedtime-coding-ita-il-set-di-istruzioni/

Articolo 4
https://sys64738.org/2019/03/c64-bedtime-coding-ita-modalita-di-indirizzamento-04/

Articolo 3
https://sys64738.org/2019/03/c64-bedtime-coding-ita-i-registri-della-cpu-03/

Articolo 2
https://sys64738.org/2019/03/c64-bedtime-coding-ita-linguaggio-macchina-02/

Articolo 1
https://sys64738.org/2019/03/c64-bedtime-coding-introduzione-e-basi-01/

 

1 comment

  1. Pingback: C64 Bedtime Coding (ITA) – Print AT (#09) – SYS64738

Have your say