Ilcommodore 64
Ilcommodore 64
com
1
INDICE
ARGOMENTO Pagg.
INDIRIZZO LOCAZIONE
ESADECIMALE ETICHETTA DECIMALE DESCRIZIONE
INDIRIZZO LOCAZIONE
ESADECIMALE ETICHETTA DECIMALE DESCRIZIONE
INDIRIZZO LOCAZIONE
ESADECIMALE ETICHETTA DECIMALE DESCRIZIONE
Il SO del Commodore 64
Panoramica sugli aspetti utili o insoliti del sistema operativo del C64
La prima cosa che si deve conoscere di un computer, ai fini della programmazione, è la configurazione globale
della sua mappa di memoria. Nel caso del Commodore 64, il microprocessore 6510, estremamente versatile,
può “vedere” una tra numerose mappe possibili a seconda del modo in cui viene configurato. Infatti il 6510 può
configurare differenti banchi di memoria: la selezione può essere realizzata fisicamente, applicando una
tensione di 5V o di 0V sui piedini 8 e 9 della porta d’espansione, oppure via software modificando il contenuto
dell’indirizzo 1. Entrambe le operazioni modificano radicalmente il modo in cui il 6510 osserva la memoria. La
figura rappresenta la normale mappa di memoria del Commodore 64 ed i 64 Kbyte ai quali il 6510 accede in
condizioni normali. La tabella contiene una minuziosa descrizione della mappa di memoria.
I/O
$000 $1000 $9000 $A000 $B000 $C000 $D000 $E000 $F000 $FFFF
6510
re (sebbene istruzioni POKE e STA, relative agli indirizzi di queste aree, vi filtrino effettivamente). Un’istruzione
PEEK fornisce il contenuto della ROM: la RAM può essere letta solo escludendo, dalla mappa di memoria, la
ROM corrispondente. Si rammenti tuttavia che, qualora si vogliano usare i comandi PEEK e POKE del BASIC,
non si può certo escludere la ROM del BASIC!
9
Come i programmi BASIC necessitano di un’area RAM in cui registrare le proprie variabili, così le routine
dell’interprete e del Kernel ricorrono alla RAM del SO; questa è compresa tra gli indirizzi $0002 e $03FF ma
qui si trova anche lo stack, situato tra le locazioni$0100 e $01FF.
La RAM del SO si trova in pagina zero poiché questa è la parte della mappa di memoria che consente un
accesso più rapido alla RAM, fattore essenziale per l’attività del sistema operativo. Se si desidera abbinare un
programma BASIC ad uno in codice macchina, è necessario conoscere bene il funzionamento di quest’area di
RAM.
Esaminiamo, adesso, due importanti aree della RAM del SO: i puntatori del BASIC ed i vettori del codice
macchina.
eseguita in modo diretto, innalza di due pagine il fondo della memoria portandolo all’indirizzo $2560;
l’istruzione NEW permette di azzerare con grande rapidità tutti i puntatori (contenuti nelle locazioni da $002D a
$0038). L’innalzamento del fondo del BASIC, permette di avere in memoria due programmi BASIC contempo-
raneamente: la procedura prevede il caricamento del primo programma, l’innalzamento del fondo e BASIC e,
infine, il caricamento del secondo programma.
Il seguente intervento sui puntatori, usato con maggior frequenza del precedente, permette di abbassare la
sommità della memoria per fare spazio ad un programma in codice macchina. La linea:
abbassa la sommità della memoria di una pagina. Dopo aver rimosso dall’area di memoria del BASIC un
blocco di RAM, si può star sicuri che il SO non userà quest’area per registrarvi le variabili BASIC. Questa
precauzione mette al sicuro il cosice macchina da accidentali danneggiamenti!
I vettori RAM
TABELLA DEI VETTORI DEL
Un secondo blocco della RAM, di particolare interesse per i CBM 64
programmatori in linguaggio macchina, contiene i vettori RAM ed è Locazioni Vettore
compreso tra le locazioni $0314 e $0333. Un vettore RAM è $0314-$0315 IRQ (Interrupt)
paragonabile ad un comune scambio ferroviario; quando un treno $0316-$0317 BRK (Interrupt)
(o, per analogia, il processore 6510 che esegue il proprio $0318-$0319 NMI (Interrupt)
$031A-$031B Routine OPEN del Kernel
programma) attraversa uno scambio, lo supera senza cambiare $031C-$031D Routine CLOSE del Kernel
direzione di marcia. Tuttavia, in alcuni casi, può essere necessario $031E-$031F Routine CHKIN del Kernel
deviarlo su di una linea laterale, facendogli attraversare una o due $0320-$0321 Routine CHKOUT del Kernel
stazioni secondarie, prima di riportarlo su quella principale. $0322-$0323 Routine CLRCHN del Kernel
Prendiamo ad esempio il vettore IRQ (Interrupt ReQuest). Quando $0324-$0325 Routine CHRin del Kernel
il Commodore 64 funziona normalmente, ogni sessantesimo di $0326-$0327 Routine CHROUT del Kernel
$0328-$0329 Routine STOP del Kernel
secondo viene avviato uno dei temporizzatori del chip di I/O del $032A-$032B Routine GETIN del Kernel
6526 che pone a livello basso la linea IRQ del 6510. Terminata $032C-$032D Routine CLALL del Kernel
l’elaborazione dell’istruzione corrente, il 6510 risponde all’ “abbas- $032E-$032F Definito dall’utente
samento” della linea IRQ, generando una interrupt ed eseguendo $0330-$0331 Routine LOAD del Kernel
$0332-$0333 Routine SAVE del Kernel
10
una routine di servizio che inizia all’indirizzo $FF48. Questa routine, per le IRQ, esamina anche la tastiera
per individuare eventuali pressioni di tasti da parte dell’utente. Una delle prime istruzioni eseguite è:
JMP ($3014)
che provoca un salto indiretto all’indirizzo contenuto nelle locazioni $0314 (byte basso) e $0315 (byte alto) che
si trovano in RAM. In questo modo, modificando il loro contenuto, sarà possibile puntare ad una diversa
sezione del programma. Elaborata questa sezione il microprocessore potrà essere inviato all’indirizzo
registrato in origine nelle due locazioni. Con questo sistema al 6510 viene fatta eseguire, ogni sessantesimo di
secondo, una particolare sezione di codice scritta dall’utente (purché questa non sia troppo lunga, nel qual
caso impedirebbe l’uso di qualsiasi routine Kernel). Una routine così concepita, viene chiamats “cuneo” per il
modo in cui si “incunea” nel normale funzionamento del sistema.
Questo programma “caricatore” in BASIC ed il successivo listato assembly, illustrano come sia possibile inserire una
sezione di programma “cuneo” in una normale routine di gestione degli interrupt. Il “cuneo” in questione visualizza un
orologio in un angolo dello schermo. Poiché l’ora viene aggiornata dal sistema ricorrendo alla normale interrupt generata
ogni sessantesimo di secondo, la visualizzazione dell’orologio avverrà anche durante l’inserimento ed esecuzione, in
contemporanea, di un programma BASIC. Per prima cosa la routine “rimette l’ora” con i tempi iniziali che le vengono
comunicati, poi modifica il contenuto di $0314 e $0315 per puntare alla routine “cuneo” che aggiornerà e visualizzerà
l’orologio ogni volta che verrà generato un IRQ. Come operazione finale, esegue un salto (JMP) alla normale routine di
sistema IRQ il cui indirizzo viene conservato nelle locazioni VETT e VETT+1.
Come alternativa all’introduzione ed all’assemblaggio di questo programma “sorgente”, è possibile inserire il programma
sotto forma di istruzioni DATA che contengono il codice macchina. L’esecuzione del programma “caricatore” in BASIC,
produce la comparsa dell’orologio nell’angolo superiore destro dello schermo. Il “checksum” alla linea 1420 visualizza un
errore qualora uno dei valori nelle DATA non risultasse trascritto giusto.
L’orario iniziale viene selezionato con una serie di istruzioni POKE introducendo le ore, i minuti ed i secondi,
rispettivamente, nelle locazioni 50129, 50130 e 50131. Alla fine del programma di caricamento, le linee da 1480 in poi
avviano l’orologio a partire dall’ora pomeridiana 04:30:00. Per modificare questo orario basta modificare i valori registrati
nelle tre locazioni. SYS 50138 lancia il programma in codice macchina ed avvia l’orologio; questo può essere disattivato
premendo contemporaneamente i tasti RUN/STOP e RESTORE o impartendo il comando SYS 50237 che lancia
un’apposita routine in codice macchina acclusa al programma.
11
Programma di caricamento BASIC
Usare SYS 50237, nel modo diretto o nel programma, per aggiungere o eliminare la routine di “cuneo”.
Listato Assembly
╍ ╍╍╍╍╍ ╍ ╍╍╍╍╍╍ ╍╍
La porta seriale: a questa presa (una DIN a sei poli) si possono collegare le stampanti seriali Commodore
e le unità a dischetti 1541. L’istruzione OPEN si riferisce sempre a questa porta, salvo nel caso in cui si
specifichi il numero del dispositivo 2 (che corrisponde ad un’uscita RS232 sulla porta utente). Con il
numero di dispositivo 8, ad esempio, il comando OPEN 2,8,2,”NOMEFILE” apre un file sull’unità a dischetti
collegata alla porta seriale. Non è consigliare cercare di usare questa porta (né in BASIC né tramite SO)
per periferiche diverse da quelle della Commodore.
In commercio esistono alcune interfacce per convertire i segnali di questa porta da seriali a paralleli IEEE
(spesso impiegate per utilizzare, con il C64, periferiche come l’unità a dischetti 4040 progettata per il PET).
La porta utente: il connettore di questa porta, a 24 linee, è del tipo “a pettine”. Attraverso di esso possono
passare segnali sia paralleli che seriali. Questa porta può essere utilizzata, tra l’altro, per collegare
stampanti che esulino dalla gamma Commodore (per esempio una Epson) trattandola come una periferica
funzionante tramite RS232. Il modo in cui il SO possa pilotare un dispositivo RS232 è spiegato più avanti.
La porta utente può anche essere usata per comunicazioni parallele a 8 bit, ma occorre scrivere (o trovare)
apposite routine perché non presenti nel SO.
Porta di espansione: il connettore di questa porta, anch’esso a pettine, prevede 44 linee e consente di
accedere a tutte le linee di controllo, al bus degli indirizzi ed a quello dei dati del Commodore 64. Questa
porta viene utilizzata per interfacciare cartucce di giochi o cartucce con connessione parallela IEEE,
adatte al collegamento con periferiche della gamma PET; viene anche utilizzata da dispositivi esterni per
acquisire un controllo quasi totale del computer.
14
Gli schemi di connessione di queste tre porte sono riportati qui sotto.
LA PORTA UTENTE
1 2 3 4 5 6 7 8 9 10 11 12 Note:
(*) Oltre ad essere indirizzabili dalle routine del Kernel, queste
▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ linee sono programmabili direttamente dall’utente per operazioni
di I/O.
▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ (1) Usata in entrambi i protocolli XON/XOFF e CTS/RTS.
(2) Usata da entrambi i protocolli, ma a livello alto in
A B C D E F H J K L M N XON/XOFF.
2 GND Massa
5 1
3 SERIAL ATN I/O Segnale di attenzione ai dispositivi
6
4 SERIAL CLK I/O Temporizzatore della porta seriale
2
4 5 SERIAL DATA I/O Linea di trasferimento di singoli bit (dati)
______
6 RESET Linea di reset dell’hardware
3
IL KERNEL IN AZIONE
LDA #$01 ; numero logico del file (lfn)
Il Kernel è una raccolta di routine di I/O, ognuna delle quali LDX #$08 ; numero del dispositivo (disco)
può essere richiamata da programmi scritti in BASIC o in LDY #$00 ; Indirizzo secondario (#$00 attiva la
codice macchina. L’area ROM occupata dal Kernel va da LOAD rilocabile)
$E000 a $FFF ma le routine vengono chiamate tramite una JSR $FFBA ; Chiamata alla routine SLFS del
“tabella di salti” (ossia una sequenza contigua di indirizzi) Kernel (per fissare lfn e indirizzo
situata nella parte alta della memoria. Il vantaggio di un secondario)
accesso tramite tabella di salti è che, indipendentemente LDA #$0A ; Lunghezza del nome del file
dalla versione del Kernel, l’indirizzo di chiamata delle routine (qui 10)
non varia, mentre invece può cambiare il codice macchina LDX PLO ; Parte bassa dell’indirizzo per il
con il quale vengono scritte. nome del file
Per utilizzare le routine del Kernel, servono precise LDY PHI ; Parte alta dell’indirizzo per il nome
informazioni su ciascuna di esse (in particolare occorre del file
sapere quali parametri sono necessari e quali registri JSR $FFBD ; Chiamata alla routine SETNAM del
passare); la fonte migliore è sempre il manuale di Kernel (attribuisci nome del file)
consultazione per il programmatore e fornito dalla stessa LDA #$00 ; Load = #$00 / Verify = #$01
Commodore. Ad esempio: per utilizzare la routine del Kernel LDX DLO ; Parte bassa dell’indirizzo di
per il LOAD dei programmi, occorre un listato come qui destinazione
descritto. Prima di utilizzarlo, i caratteri che compongono il LDY DHI ; Parte alta dell’indirizzo di
nome del file, sul quale operare LOAD, vanno depositati, in destinazione
sequenza, in qualche zona della memoria (qui individuata JSR $FFD5 ; Chiamata alla routine LOAD del
dall’indirizzo PHI/PLO): Kernel
15
LA PORTA D’ESPANSIONE
22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀
▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀
Z Y X W V U T S R P N M L K J H F E D C B A
7 I/O 1 H A14
______
8 GAME J A13
_______
9 EXROM K A12
10 I/O 2 L A11
_____
11 ROML M A10
12 BA N A9
____
13 DMA P A8
14 D7 R A7
15 D6 S A6
16 D5 T A5
17 D4 8 linee dati U A4
18 D3 V A3
19 D2 W A2
20 D1 X A1
21 D0 Y A0
Purché si operi con metoro, l’uso della RS232 tramite le ● Ogni volta che viene aperto (OPEN) un canale seriale
routine del sistema operativo non presenta grossi problemi. RS232, il sistema operativo “scarica” (CLR) il contenuto
Per il suo corretto uso, ci sono diverse considerazioni da dei registri per cui, automaticamente, va perduto il
tener presenti da parte dell’utente (a parte quella di fissare contenuto di tutte le variabili usate da un programma
la velocità di trasferimento in baud, esaminata in seguito): BASIC e le istruzioni GOSUB generano un messaggio
● Il Commodore 64 funziona con livelli di segnale a 0 e +5 d’errore quando si giunge alla rispettiva RETURN.
volt, mentre lo standard RS232 prevede livelli di –12 e +12 Questo succede perché le routine del Kernel assegnano,
volt. Peranto, salvo nei trasferimenti di dati tra apparecchi alla RS232, due buffer di 256 byte nella parte alta della
Commodore, per rendere tali livelli compatibili occorre un memoria, senza tener conto della eventuale presenza di
convertitore. una parte di programma in questa zona, che viene quindi
● I codici ASC del Commodore differiscono dai normali rovinata (per di più tale evento non viene segnalato.
codici ASCII, per cui si rende necessario l’impiego di due ● Se un programma BASIC è lungo oppure contiene
tabelle di conversione (una per la trasmissione ed una per molte assegnazioni di stringhe, prima o poi entra in
la ricezione dei dati). funzione il cosiddetto “garbage collector” (o raccoglitore
16
di rifiuti) che riorganizza l’utilizzazione della memoria. In tal
caso può andar persa parte dei dati ricevuti tramite la
RS232. È quindi consigliabile, durante l’utilizzo della porta, Byte COM
usare programmi BASIC di dimensioni limitate e senza
Bit Funzione
troppe assegnazioni di variabili.
7 6 5 4 3 3 1 0
― ― ― ― X X X 0 Protocollo a 3 linee
Per aprire un canale RS232 dal BASIC si usa il seguente ― ― ― ― X X X 1 Protocollo X
formato: ― ― ― 0 X X X ― Full duplex
― ― ― 1 X X X ― Half duplex
OPEN 2,2,3,CHR$(CTRL)+CHR$(COM) ― ― 0 ― X X X ― Parità ignorata
0 0 1 ― X X X ― Parità dispari
dove CTRL e COM sono, rispettivamente, il byte di 0 1 1 ― X X X ― Parità pari
controllo e di comando che contengono le informazioni 1 0 1 ― X X X ― Invio Mark (senza parità)
necessarie all’apertura del canale. Si noti che CTRL e 1 1 1 ― X X X ― Invio Spazio (senza parità)
COM devono essere due valori alfanumerici (oppure le
PEEK di due locazioni precedentemente riempite) e non Prima di scegliere la velocità di trasferimento, occorre fare
due variabili! Ciascun bit di questi due byte ha un preciso qualche considerazione. In trasmissione la velocità non è
particolarmente critica poiché, in genere, gli apparecchi
significato che viene riportato nelle tabelle
riceventi possono tollerare piccole fluttuazioni (in BASIC si può
Per esempio: per aprire un canale RS232 con un bit di arrivare a 2400 baud per l’invio attraverso la RS232). Si tenga
stop, una lunghezza dati di 7 bit, a 300 baud (byte CTRL = presente, comunque, che la velocità effettiva di trasferimento è
38), parità pari, full duplex e flusso su tre linee (byte COM = sempre leggermente inferiore ai baud selezionati: in parte a
96), si utilizza il comando: causa della presenza dei bit di stop/parità che allungano la
lunghezza totale dei dati.
OPEN 2,2,3,CHR$(38)+CHR$(96) La situazione, però, è alquanto diversa durante la ricezione dei
dati. In questo caso un programma BASIC, all’interno di un
ciclo, riesce a malapena a estrarre un byte dal buffer d’ingresso
Byte CTRL ed a visualizzarlo.
Per migliore l’efficienza della ricezione, occorre, in qualche
Bit Funzione
modo, fermare periodicamente il dispositivo che invia i dati non
7 6 5 4 3 3 1 0
appena il buffer d’ingresso si riempie. In genere la procedura,
― ― ― X 0 0 0 1 50 baud nel caso di un protocollo di ricezione a 3 linee, è:
― ― ― X 0 0 1 0 75 baud 1) Leggere un modesto numero di byte (più sono i baud,
― ― ― X 0 0 1 1 110 baud minore deve essere questo numero), tramite una GET
― ― ― X 0 1 0 0 134,5 baud #2,A$, ed utilizzarli immediatamente oppure depositarli in
― ― ― X 0 1 0 1 150 baud una matrice in attesa di ulteriore elaborazione.
― ― ― X 0 1 1 0 300 baud 2) Bloccare temporaneamente il dispositivo esterno di
― ― ― X 0 1 1 1 1200 baud trasmissione usando una PRINT#2,CHR$(17).
― ― ― X 1 0 0 0 1800 baud 3) Leggere i byte rimanenti, fino ad esaurimento, dal buffer
― ― ― X 1 0 1 0 2400 baud d’ingresso della RS232 ed elaborare tutti i byte letti
― 0 0 X ― ― ― ― dati 8 bit (controllando, nel frattempo, se vengono premuti tasti sulla
― 0 1 X ― ― ― ― dati 7 bit tastiera se si è raggiunta la fine del file (EOF), ecc.).
― 1 0 X ― ― ― ― dati 6 bit 4) Riabilitare il dispositivo di trasmissione usando, stavolta,
― 1 1 X ― ― ― ― dati 5 bit PRINT #2,CHR$(19) e ritornare al punto 1.
0 ― ― X ― ― ― ― 1 bit di stop
1 ― ― X ― ― ― ― 2 bit di stop Una volta aperto un canale per la RS232, i byte vengono
ricevuti o inviati ricorrendo alle semplici PRINT # e GET #
X = senza significato (l’istruzione INPUT # deve essere evitata). Il byte dello stato,
ST, va periodicamente esaminato per verificare la presenza di
errori, segnalati da ST=0 o ST=8. Infine, terminata l’operazione
di ingresso/uscita, il canale va chiuso con una CLOSE.
Attenzione perché anche questa istruzione, con la RS232,
provoca automaticamente lo scarico dei registri.
17
Linee di comunicazione
Caricatore BASIC
Listato Assembly
;***************************************************************** ;
;* * ; BYTE SULLA PORTA
;* CUNEOPAR – PROGRAMMA DI TRASMISSIONE * ;
;* e RICEZIONE PER COMUNICAZIONI * LDA CIA2+1 ; legge un byte
;* PARALLELE A 8 BIT SUL COMMODORE 64 * STA (ZPTEMP),Y ; lo deposita in memoria
;* * INC ZPTEMP
;***************************************************************** BNE TEST1 ; incrementa il puntatore
; INC ZPTEMP+1
CIA2 = $0000 ; indirizzo di base del chip 6526 ;
USCITA = $FF TEST1
INGRESSO = $00 LDA END
OUTSHK = $04 CMP ZPTEMP
INTSHK = $90 LDA END+1 ; verifica se finito
TOGHI = $04 SBC ZPTEMP+1
TOGLO = $FC BCC DONE ; se finito, salta
NMIVETT = $0318 ;
ZPTEMP = $FB ; COMUNICA DI ESSERE PRONTO PER BYTE SUCCESSIVO
* = $C650 ;
INIZIO *=*+2 ; indirizzo iniziale LDA CIA2
FINE *=*+2 ; indirizzo finale AND #TOGLO
MODO *=*+1 ; flag di ingresso/uscita STA CIA2 ; pone PA2 prima bassa, poi alta
VETTORE *=*+2 ; memoria per il vettore NMI ORA #TOGHI
STA CIA2
LDA MODO ; ingresso o uscita ;
BNE OUTDAT ; salta se ingresso ; ESEGUE LA NORMALE ROUTINE NMI
LDA #INGRESSO ;
STA CIA2+3 ; seleziona il DDR (reg. direz. dati)
per l’ingresso NOCOM
LDA INTMSK JMP (VETTORE)
STA CIA2+13 ; disabilita le interrupt di Flag
LDA CIA2+3
ORA #OUTSHK ;
STA CIA2+2 ; seleziona PA2 per l’uscita ; ROUTINE DI SERVIZIO PER L’USCITA DI DATI
LDA CIA2 ;
ORA TOGHI ;
STA CIA2 ; pone alta la linea di NXTOUT
“handshake” PA2 LDA #INTMSK ; verifica ICR
LDA INIZIO BIT CIA2+13 ; interrupt causata dal Flag?
STA ZPTEMP BEQ NOTCOM ; no... una comune NMI
LDA INIZIO+1 ; sposta puntatori in pagina 0 ;
STA ZPTEMP+1 ; BYTE TRASMESSO
; ;
; INIZIALIZZA IL CUNEO D’INGRESSO LDA (ZPTEMP),Y ; estrae un byte dalla memoria
; STA CIA2+1 ; lo trasmette. (PC passa a livello basso
LDA NMIVETT per 1 ciclo)
STA VETTORE ; salva il vecchio vettore NMI INC ZPTEMP
LDA NMIVETT+1 BNE TEST2 ; incrementa il puntatore
STA VETTORE+1 INC ZPTEMP+1
SEI ;
LDA #<NXTIN ; TEST2
STA NMIVETT ; inserisce il cuneo di ingresso-dati LDA END
LDA #>NXTIN ; CMP ZPTEMP
STA NMIVETT+1 ; LDA END+1 ; verifica se finito
CLI SBC ZPTEMP+1
RTS BCC DONE ; se finito, salta
; ;
; INIZIALIZZA IL CUNEO D’USCITA ; ESEGUE LA NORMALE ROUTINE NMI
; ;
OUTDAT JMP (VETTORE)
LDA #USCITA ;
STA CIA2+3 ; seleziona il DDR per l’uscita ;
LDA #INTMSK ; FINITA RIMOZIONE CUNEO
STA CIA2+13 ; disabilita le interrupt di Flag ;
; ;
; DONE
LDA NMIVETT SEI
STA VETTORE LDA VETTORE
LDA NMIVETT+1 ; salva il vecchio vettore NMI STA NMIVETT ; ripristina il valore originale
STA VETTORE+1 LDA VETTORE+1 ; nel vettore NMI
SEI ; STA NMIVETT+1
LDA #<NXTOUT ; CLI
STA NMIVETT ; inserisce il cuneo di uscita-dati JMP (NMIVET)
LDA #>NXTOUT ;
STA NMIVETT+1
CLI ;
RTS
;
:
; ROUTINE DI SERVIZIO PER L’INGRESSO DI DATI
;
;
NXTIN
LDA #INTMSK ; verifica ICR
BIT CIA2+13 ; interrupt causata da Flag?
BEQ NOTCOM ; no... una comune NMI
19
Effetti ottici
Il carattere ‘A’ nel modo di schermo Il carattere ‘A’ nel modo di schermo
“normale” Multicolour
0 0 0 1 1 0 0 0 00 01 10 00
0 0 1 1 1 1 0 0 00 11 11 00
0 1 1 0 0 1 1 0 01 10 01 10
0 1 1 1 1 1 1 0 01 11 11 10
0 1 1 0 0 1 1 0 01 10 01 10
0 1 1 0 0 1 1 0 01 10 01 10
0 1 1 0 0 1 1 0 01 10 01 10
0 0 0 0 0 0 0 0 00 00 00 00
Chiave
Colori coordinati
01 Multicolour 1 Nel modo di schermo “normale” del Commodore 64, ogni bit posto a 1, all’interno degli 8 byte che
definiscono un carattere, viene visualizzato nel colore di primo piano attuale. I bit posti a 0 vengono
10 Multicolour 2 invece visualizzati nel colore di sfondo dello schermo.
Quando viene selezionato il modo Multicolour, i bit non vengono più interpretati singolarmente, ma
11 Colore di primo piano
a coppia. Le quattro combinazioni possibili, di una coppia di bit, rappresentano il colore di primo
00 Colore di sfondo piano, quello di sfondo ed i due “multicolour” supplementari.
Modo Multicolour
Lo schermo in bassa risoluzione ed il modo “bit map” sono descritti a pag 694 e 817 (IL MIO COMPUTER),
pertanto qui vengono esaminati due degli altri modi previsti per la grafica su Commodore 64.
Il modo Multicolour consente di visualizzare 4 colori diversi, anziché solo 2, in un’unica cella di carattere.
L’unico inconveniente è che la risoluzione orizzontale diventa a coppie di pixel e non a pixel singoli. Il modo
grafico Multicolour può essere usato sia in alta che in bassa risoluzione ma, nel primo caso, la determinazione
dei colori dei punti è un po’ diversa. Queste istruzioni BASIC abilitano e disabilitano il modo Multicolour:
Se il modo Multicolour viene usato in bassa risoluzione e se il bit 4 del “nybble” (4 bit o semibyte) del colore
associato vale 1, allora il carattere viene interpretato secondo il modo Multicolour ed i tre bit più bassi ne
definiscono il colore. Ciò significa che i caratteri con “nybble” di colore associato, compresi tra 0 e 7, vengono
visualizzati normalmente, mentre se il codice di colore è compreso tra 8 e 15, il carattere viene visualizzato nel
modo Multicolour. I colori delle coppie di pixel vengono definiti secondo lo schema della tabella:
20
Config. binaria
della coppia Colore Determinato da
di pixel
0 0 Sfondo dello schermo 53281 ($D021) Bit 0-4
0 1 Multicolour #1 53282 ($D022) Bit 0-4
1 0 Multicolour #2 53283 ($D023) Bit 0-4
1 1 Colore di primo piano Bit 0-3 del “nybble” di colore
Agendo sul contenuto degli indirizzi 53282 e 53283 è possibile cambiare istantaneamente il colore di qualsiasi
coppia di pixel Multicolour associati. È da notare che il modo Multicolour produce risultati migliori se usato con
caratteri defini dall’utente poiché le configurazioni di bit traggono vantaggio dal fatto di essere interpretate
come coppie di pixel.
Un altro modo grafico disponibile sul Commodore 64 è il modo Extended colour (a colori estesi) che permette
di controllare il colore di sfondo nei primi 64 caratteri del set. Questo modo, che non può venire usato insieme
al Multicolour, viene abilitato e disabilitato dalle seguenti linee BASIC:
Lo stesso carattere può apparire sullo schermo con un massimo di 4 diversi colori di sfondo, uno dei quali è il
colore di sfondo dello schermo. I caratteri, successivi al sessantaquattresimo, non possono essere visualizzati
nel modo Extended perché i bit 6 e 7, del codice di schermo, vengono usati per controllare indirettamente il
colore del carattere. Il codice di schermo per la ‘A’, per esempio, è 1 mentre quello di una ‘A’ in campo inverso
è 65. Tuttavia un’istruzione POKE 65, se lo schermo è selezionato nel modo Extended colour, non produce
una ‘A’ in campo inverso, bensì una ‘A’ normale con un colore di sfondo definito dal contenuto dell’indirizzo
53282 ($D022). Anche con l’istruzione POKE 129 si ottiene una ‘A? normale ma con un colore di sfondo
definito dal contenuto dell’indirizzo 53283 ($D023). Ta tabella sotto mostra la corrispondenza tra i codici di
schermo ed i registri del colore.
In questo caso FIN può assumere valori compresi tra 0 e 3. In qualsiasi momento l’istruzione PEEK(56576)
AND 3 permette di conoscere il valore corrente di FIN. L’indirizzo in memoria, corrispondente all’inizio della
finestra a 16 Kbyte, può essere calcolato con la formula:
Ai valori registrati in FIN corrispondono i seguenti indirizzi: FIN Indirizzo iniziale della finestra
0 49152 ($C000)
1 32768 ($8000)
2 16384 ($4000)
3 0 ($0000)
All’interno della finestra del VIC-II, il 6510 si aspetta di trovare la memoria video ed un’immagine formata da
caratteri ROM in bassa risoluzione (o valori numerici per l’alta risoluzione se questa è stata selezionata), come
pure le informazioni relative agli sprite. I puntatori, a ciascuno degli otto sprite, si trovano alla fine della
memoria video (quindi devon essere spostati assieme allo schermo).
21
Lo scarto (offset) dell’inizio della memoria video, rispetto alla base dell’attuale finestra del VIC-II, è
controllato dai quattro bit superiori del registro di controllo del VIC-II situato all’indirizzo 53272 ($D018).
Usando questi bit si può collocare lo schermo in uno dei blocchi a 16 Kbyte all’interno della finestra:
In questo caso SO può assumere valori compresi tra 0 e 15 e l’istruzione PEEK(53272) AND 15 permette di
sapere, in qualsiasi momento, tale valore.
L’indirizzo in memoria, corrispondente alla base dello schermo corrente, può essere calcolato con la formula
SC = VFIN+1024*SO
Tuttavia c’è un piccolo problema relativo al movimento dello schermo: la RAM del colore non può essere
spostata. Quindi, per avere uno schermo “alternativo”, è necessario ricorrere ad una piccola routine in codice
macchina capace di trasferire e richiamare la memoria del colore dda un “buffer”. Questa routine è già stata
descritta a pag. 1398 (IL MIO COMPUTER) per il programma degli schermi aggiuntivi sul Commodore 64.
Un secondo fattore da considerare è che, se si desidera visualizzare sul nuovo schermo, è necessario
comunicare al sistema operativo (piuttosto che al chip video) la sua posizione in memoria utilizzando
un’istruzione POKE (oppure STA) per indirizzare 648 ($0288), ossia il puntatore di base della “memoria
video”. Se SC viene calcolato nel modo descritto in precedenza, occorre usare la seguente istruzione BASIC
di indirizzamento:
Per selezionare l’indirizzo di base del set dei caratteri o dello schermo in alta risoluzione, si usano le linee:
In teoria HO può assumere qualsiasi valore compreso tra 0 e 7 ma, in pratica, esistono alcuni fattori che
limitano la scelta. Per FIN uguale a 1 o 3, HO non può avere il valore di 2 o 3 dato che ciò si verifica quando il
VIC-II “vede” l’immagine normale dei caratteri ROM. Inoltre valori elevati di HO pongono la parte superiore
della memoria, di alta risoluzione, fuori dalla portata del VIC-II. L’indirizzo in memoria, corrispondente alla
base dello schermo corrente per la matrice di caratteri e per l’alta risoluzione, viene calcolato come:
MC 0 VFIN + 2048 * HO
(base della finestra + offset), oppure con l’espressione:
Selezionando tali registri è possibile spostare a piacimento l’intera memoria dell’immagine video.
22
Schermi multipli
Suddividere lo schermo
Sul Commodore 64, con qualche accorgimento di programmazione, è possibile suddividere lo schermo in
modo da utilizzare contemporaneamente la grafica sia in alta che in bassa risoluzione. Come spesso accade
nei giochi in commercio, la metà superiore dello schermo raffigura uno scenario in grafica ad alta risoluzione
mentre, nella parte inferiore, vengono visualizzati i messaggi e l’input del giocatore. Normalmente non è
possibile mescolare i due tipi di grafica.
Il programma Spliscreen, presentato più avanti e che illustra molti dei concetti finqui esposti, opera in modo
abbastanza semplice: nella RAM “nascosta” sotto la ROM del BASIC, viene creato uno schermo in alta
risoluzione che viene ininterrottamente visualizzato nel terzo superiore dello schermo. Allo stesso tempo i due
terzi inferiori rimangono nella grafica a bassa risoluzione. Nel primo paragrafo (Il SO del Commodore 64)
abbiamo visto come utilizzare i vettori RAM modificandone l’indirizzo affinché vengano eseguite routine in
codice macchina scritte dall’utente. Per suddividere lo schermo, qui viene operato un intervento, piuttosto
insolito, sul vettore IRQ. La routine abbinata a questo vettore viene chiamata in diverse occasioni, una delle
quali si presenta quando un temporizzatore di uno dei CIA modifica un bit nel registro segnalatore delle
interrupt (IFR, Interrupt Flag Register) che ha per indirizzo 53273 ($D019). Ciò avviene circa ogni
sessantesimo di secondo, provocando l’esecuzione della routine di servizio IRQ. Tra le altre cose, questa
routine effettua una scansione della tastiera per vedere se si sta premendo un tasto. Alcuni bit del registro IFR
vengono posti a 1 anche a seguito di certi eventi controllati dal chip VIC-II; per esempio quando avviene una
collisione tra due sprite o quando il contatore di scansione raggiunge un valore prefissato. Esiste un altro
registro che influenza la linea IRQ del 6510: il registro di abilitazione delle interrupt (IER, interrupt Enable
Register) ed il cui indirizzo è 53274 ($D01A). Se il programmatore modifica i bit di IER, allora i corrispondenti
bit di IFR provocano una chiamata alla routine di IRQ.
23
Ecco i principi del programma Splitscreen:
1) Quando si verifica l’interruzione nella scansione in cima allo schermo, il programma seleziona il modo
“mappa di bit”, predispone un’interruzione di scansione a circa metà dello schermo, e poi esegue un ritorno
da interrupt (RTI).
2) Quando si verifica l’interruzione di scansione a metà schermo, il programma riseleziona la bassa risoluzione
e predispone un’interruzione di scansione all’inizio dello schermo. Di nuovo esegue un ritorno da interrupt
(RTI).
L’oscillatore
L’oscillatore è un circuito elettronico che produce dei segnali ripetitivi. Quando questi segnali vengono
amplificati e passati ad un altoprlante, si ottengono dei suoni o delle note di toni diversi. Negli home computer
ci sono fino a quattro di questi oscillatori: ovviamente più ce ne sono, più note si possono generare
contemporaneamente.
Tre sono i parametri che descrivono i suoni generati: la frequenza, l’inviluppo (in cui è compreso anche il
volume) e la forma d’onda.
La frequenza
Questa è la caratteristica più importante da controllare poiché è proprio la frequenza che determina la nota
generata. Il termine frequenza indica il numero di volte in cui si ripete un segnale nell’intervallo di un secondo
e viene misurata in Hetz (Hz) o cicli al secondo. I suoni percepiti dall’orecchio umano hanno una frequenza
compresa, grosso modo, tra 20 e 10.000 Hz (10 KHz). Anche se le frequenze al di sotto di 20 Hz non sono
percepibili, le possiamo ugualmente utilizzare per modificare le caratteristiche di un suono percepibile. Tale
tecnica si chiama “modulazione” e, attualmente, è possibile applicarla solamente al Commodore 64
(ovviamente nella fascia degli hom computer).
Detto questo, tuttavia non occorre una conoscenza approfondita delle varie frequenze; basta sapere come si
ottengono le note musicali. Con alcuni computer il compito è molto facile: esistono comandi BASIC che
calcolano automaticamente la frequenza di ciascun suono e nin rimane altro che indicare l’intensità del suono
stesso oppure la nota voluta ma, rigorosamente, secondo la notazione anglosassone (in lettere): A, A# B, ecc.
(corrispondenti a LA LA#, SI, ecc.). Purtroppo sul Commodore 64 il sistema è più complesso: occorre utilizzare
una tabella di conversione, nella quale sono indicate le frequenze corrispondenti alla nota voluta, insieme ai
valori che devono essere depositati (con l’impiego dell’istruzione POKE), in una particolare locazione di
memoria, La tabella riportata qui sotto illustra la conversione delle note corrispondenti alla scala di “do
centrale”; questa dovrebbe risultare utile a chi volesse cimentarsi nella generazione di musica in linguaggio
macchina.
C
DO
D
E frequenza della nota, distante un
semitono inferiore, per 1,0594631. Se
si esegue la moltiplicazione 12 volte, si
vedrà che la frequenza originale viene
raddoppiata. In un’ottava musicale ci
261.63 293.66 329.63 349.23 392 440 493.88 523.25 sono 12 semitoni (l’ottava è la distanza
tra due note con lo stesso nome)
pertanto, se raddoppiamo la
frequenza, il suono sale di un’ottava.
La tabella mostra la conversione da
simboli musicali (la scala di ‘do
centrale’) in frequenze audio.
I generatori d’inviluppo
L’inviluppo di un suono è la configurazione delle sue variazioni di volume sonoro: dall’istante in cui l’emissione
inizia fino a quello in cui termina. Inviluppi simili a quelli di una nota di pianoforte e di altri strumenti sono
mostrati nel diagramma.
D
D S
A S R A R
S S
A D A R
Gli inviluppi vengono comunemente suddivisi in 4 parti chiamate, di solito: Attacco, Decadimento, Prolunga-
mento, Rilascio (ADSR). Nel caso del pianoforte, il volume, dopo che il tasto è stato battuto, sale rapidamente
al suo livello più alto (attacco), quindi si abbassa lentamente (decadimento) ad un volume quasi costante
mentre il tasto viene mantenuto abbassato (prolungamento) ed infine cade rapidamente a zero (rilascio) al
momento che il tasto viene risollevato. Si noti che il ‘prolungamento’ è un livello di volume, mentre gli altri tre
sono intervalli di tempo. Uno strumento che permette di controllare tutti i quattro gli aspetti di un inviluppo,
viene chiamato “generatore ADSR”. Il Commodore 64 rientra in questa categoria perché è capace di imitare gli
inviluppi di molti comuni strumenti o di crearne dei nuovi “artificiali”.
Le forme d’onda
Una forma d’onda è, appunto, la “forma” ripetitiva del segnale emessa da un oscillatore e determina il
carattere di un particolare suono. Due strumenti diversi, che suonino note dello stesso tono, non emettono lo
stesso suono perché, in parte, le forme d’onda prodotte non sono uguali. Le più comuni forme d’onda (nel
‘regno’ della musica) sono: l’onda quadra, l’onda triangolare e l’onda “a dente di sega”, come mostrato nella
figura.
La maggior parte degli home computer produce una sola forma d’onda, di solito quella quadra, molto ricca di
armoniche: questo spiega perché il suono generato è stridente. Attualmente il Commodore 64 è il computer
più interessante perché è possibile selezionare a piacimento una delle tre forme d’onda di base su ciascuno
dei tre oscillatori di cui dispone. Le forme d’onda possono venire modificate con filtri che alterano il tono (come
succede negli apparecchi ad alta fedeltà agendo sui controlli bassi/alti). Questo permette di simulare suoni
naturali con maggior fedeltà e di produrre nuovi suoni più stimolanti.
Il rumore
Il rumore è un tipo di suono complesso prodotto da vibrazioni casuali. Immaginiamo qualche suono comune
come la pioggia, il vento ed il tuono. Questi rumori non sono simili tra loro in quanto prodotti da una
combinazione di rumore puro (e quindi casuale) con qualche tono dominante. La maggior parte dei computer,
con capacità di produrre rumore, permette quindi di modulare il rumore stesso o di mixarlo con note pure. La
gamma di effetti possibili varia da “vento fischiante” a violente esplosioni.
Di solito i suoni prodotti dal Commodore 64 vengono inviati direttamente al televisore attraverso l’interfaccia ad
alta frequenza, ma si può anche collegare l’uscita audio ad un sistema hi-fi per ottenere riproduzioni di alta
qualità. Esaminiamo, quindi, i principi coinvolti nella progettazione di un software che permetta di trasformare il
Commodore 4 in una ‘batteria elettronica’. Ovviamente la qualità dei risultati è migliore se viene impiegato un
sistema di riproduzione ad alta fedeltà.
Oltre a produrre sofisticati suoni sotto il controllo del software, il chip SID può ricevere segnali audio generati
da una periferica elettronica (eventualmente dotata di altri chip SID) o da strumenti musicali come chitarre
elettriche. Il segnale ricevuto può essere “mixato” con l’uscita audio del chip SID ed elaborato dai suoi filtri. Le
interfacce di I/O, però, vanno utilizzate con estrema attenzione poiché un errato collegamento delle linee
esterne può danneggiare gravemente il computer. Prima di relizzare qualsiasi collegamento esterno, sarà
meglio consultare sul manuale tecnico i dati relativi ai piedini ed ai livelli di tensione.
Per introdurre l’argomento, vediamo come sia possibile generare un suono musicale periodico, utilizzando una
serie di note pure; in seguito verrà esaminato un metodo alternativo per ottenere lo stesso risultato.
Controllando l’inviluppo di una particolare nota periodica, è possibile, in diverse proporzioni, intodurre nel
suono delle “armoniche”. Ciò significa che si può creare qualsiasi suono periodico intervenendo su di un
28
ristretto numero di controlli, relativi all’inviluppo di un oscillatore, anche noti sotto la sigla ADSR. Sfortunata-
mente il BASIC del Commodore 64 non consente un sofisticato controllo dei suoni che, quindi, vanno
programmati con un massiccio impiego di istruzioni PEEK e POKE. Se alla variabile SID si assegna il valore
54272 (l’indirizzo di base del chip SID), allora gli indirizzi compresi tra SID e SID+28 controlleranno il chip del
suono e, di conseguenza, tutti i suoni generati sul Commodore 64.
Il programma in codice macchina che segue questo capitolo, trasforma il computer in una batteria elettronica a
tre voci controllata da interrupt. Questa routine di “cuneo” può venire controllata dal BASIC ma, indipendente-
mente da questo, continuerà a suonare il ritmo e, quindi, con opportune modifiche, sarà possibile abbinarlo a
qualsiasi programma BASIC per ottenere effetti sonori di sottofondo.
Suoni musicali
I suoni raggiungono l’orecchio umano come vibrazioni periodiche della pressione atmosferica. Il numero di
vibrazioni per secondo è detto “altezza” o “frequenza” del suono. Il limite inferiore dell’udito umano è di circa
15 cicli al secondo (15 Hz); una nota pura con frequenza di 100 Hz, quindi, è appena percettibile, il LA
superiore al DO centrale ha, per convenzione, una frequenza di 440 Hz. Se si raddoppia la frequenza di una
nota, la sua altezza si innalza di un’ottava. Mediamente l’orecchio umano percepisce una gamma di suoni
dell’estensione di circa 10 ottave. Gli oscillatori a tre note del SID, invece, possiedono una gamma di circa 8
ottave, approssimativamente da 0 a 4000 Hz.
Il fisico francese Jean Fourier (1768-1830) per primo osservò che qualsiasi forma d’onda periodica può essere
considerata come composta da una nota pura fondamentale, amalgamata con note le cui frequenze sono
multipli della nota fondamentale: tali note prendono il nome di “armoniche”. Il timbro inconfondibile di un suono
o di una nota dipende, dunque, dalle proporzioni tra le diverse frequenze armoniche che la compongono.
Un’onda sinusoidale pura, che corrisponde ad una nota pura, è essenzialmente un segnale analogico
difficilmente risproducibile con uno strumento digitale in cui sono presenti livelli di tensione di 0 e 5 volt. Per
questo motivo, la tecnica adottata sui micro per ottenere un particolare suono periodico, non prevede la
generazione di frequenze pure e la loro successiva fusione.
Ciascuna delle tre voci generabili dal chip SID può produrre 4 forme d’onda periodiche:
1) Dente di sega: in essa sono presenti tutte le armoniche. L’armonica “ennesima” possiede un’intensità
proporzionale a 1/N.
2) Triangolare: contiene solo le armoniche dispari. Per N dispari, l’ennesima armonica possiede
un’intensità proporzionale a 1/N².
3) Quadra: quest’onda presenta armoniche dispari proporzionali a 1/N. Variando l’ampiezza dell’impulso
si possono generare svariate onde rettangolari, ciascuna composta da un proprio amalgama di
armoniche.
4) Rumore bianco: mistura casuale di frequenze usata per effetti particolari.
Il contenuto armonico di un suono può essere modificato con l’impiego dei filtri. Il chip SID offre tre tipi di filtro:
passa-basso, passa-banda e passa-alto. Il primo, ad esempio, lascia passare tutte le frequenze minori di un
valore prestabilito ed attenua tutte le frequenze superiori ad esso. Grazie a queste funzioni, ed ai controlli
d’inviluppo ADSR, è possibile ottenere qualsiasi suono (anche una simulazione del parlato).
I programmi per la generazione del parlato si possono ottenere in più modi e qui ne viene descritto uno che
abbina una discreta qualità ad un vocabolario illimitato. I suoi elementi di base, o “fonemi”, vengono codificati
in una matrice di valori ADSR. Un programma in codice macchina permette di tradurre i testi ASC (il codice
alternativo del Commodore all’ASCII) in una sequenza di codici di fonema, successivamente inviati al chip SID
per mezzo della matrice ADSR. Tale operazione è meno semplice di quanto possa sembrare poiché le norme,
che regolano la trasposizione di un testo in fonemi, sono piuttosto complesse. La qualità del parlato finale
dipende principalmente dalla qualità di questa sezione del programma. Comunque sia, questa applicazione è
realizzabile sul Commodore 64 ed il mercato offre già numerosi pacchetti commerciali basati su di essa.
Controllo dell’inviluppo
Il diagramma ADSR a pagina seguente, rappresenta la forma d’onda generica di una nota musicale e ne
evidenzia le caratteristiche controllabili con il chip SID. I fattori ADSR sono:
1) Attacco: il tempo di salita di una nota.
2) Caduta: il tempo che una nota impiega a ricadere ad un livello stabile.
3) Persistenza: il volume durante un livello stabile.
4) Rilascio: il tempo necessario affinché il volume di una nota passi dal livello di persistenza a 0.
Le sezioni di Attacco, Caduta e Rilascio di una nota vengono controllate da “nybble” (4 bit) interni ai registri del
chip SID. Quindi ciascuno di questi parametri può assumere solo valori compresi tra 0 e 15 ed il rapporto, tra i
loro valori, deve essere inserito (con POKE) nei registri del SID.
29
Inviluppi indirizzati
AMPIEZZA La qualità di una nota, ossia
INVILUPPO
l’insieme delle caratteristiche
⋂⋂
che permettono di distinguere
ONDA SONORA ad esempio il suono di un
⋂
pianoforte da quello di un
⋂ ⋂⋂⋂⋂⋂
violino, dipende dalla forma o
“profilo” dell’inviluppo. Nella
LIVELLO
DELLA ⋂ sintetizzazione elettronica dei
⋂ ⋂
TEMPO
PERSISTENZA suoni, l’inviluppo si compone
⋃
di quattro fasi diverse:
⋃⋃⋃⋃⋃⋃
e Rilascio (ADSR, da Attack,
Decay, Sustain e Release).
⋃ ⋃
La durata di ciascuna fase
viene programmata inserendo
⋃ (con POKE) particolari valori
nei registri del chip SID.
Questa tecnica consente di
ATTACCO CADUTA sintetizzare i suoni di diversi
RILASCIO strumenti.
Per produrre un suono sul Commodore 64, occorre programmare 6 operazioni diverse :
A questo punto inizia l’emissione del suono, detta anche “apertura della porta”.
Il modo più semplice per programmare l’emissione di una nota sul Commodore 64, è selezionare i valori iniziali
di ADSR e costruire un ciclo FOR che legga (READ) da frasi DATA i byte alto/basso della frequenza. La
30
presenza di valori 0, in queste istruzioni, permette di modificare il ritmo delle diverse voci mantenendo
costante la durata del ritardo.
BATTERIA ELETTRONICA
Il seguente programma usa un “cuneo” IRQ, in codice macchina, che continua avviare il ritmo selezionato, si preme il tasto F1. Il menu sullo schermo visualizza le scelte
a generare il suono di una batteria indipendentemente dal lavoro svolto dal possibili. Poiché l’emissione dei suoni è controllata da interrupt, la combinazione ritmica può
programma BASIC. La sezione in codice macchina esamina, inoltre, il essere modificata sul video mentre il ritmo viene suonato.
contenuto della locazione 197 ($00C5) per individuare quali tasti sono stati Il programma BASIC contiene tutto il codice macchina necessario (sotto forma di frasi DATA)
battuti. Detta locazione di pagina 0, contiene il valore relativo all’ultimo tasto per far funzionare il programma e può essere trascritto ed eseguito senza alcuna modifica. Se
premuto. Al posto di una chiamata al Kernel, può essere utilizzato un si desidera utilizzare il listato in Assembly della parte del programma in codice macchina
programma in codice macchina per la scansione della tastiera, a patto che (vedere alla pagina seguente), occorre assemblarlo e poi eliminare dal programma BASIC le
$00C5 venga esaminato con sufficiente frequenza. linee da 1520 a 1540 e le istruzioni DATA tra linee 1620 e 1940.
Poiché il programma pilota in BASIC non usa le istruzioni GET o INPUT per Dopo aver lanciato il programma, si provi a selezionare la seguente combinazione che genera
svuotare il buffer di tastiera, la routine in codice macchina deve, come ultima un ritmo facilmente riconoscibile:
operazione, azzerare il puntatore nella locazione 198 ($00C6) in cui, sempre
in pagina 0, abitualmente contiene il numero dei tasti battuti ed accumulati nel **** CBM64 RHYTHM GENERATORE ****
buffer della tastiera.
Durante l’esecuzione del programma, ad ogni tamburo vengono assegnati 16 TEMPO = 15
“intervalli” di tempo, visualizzati sullo schermo, durante i quali esso può _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
suonare o restare muto. La scelta del valore 16 facilita la composizione di ritmi X _ _ _ X _ _ X X _ _ _ X _ _ _ -BASS
rock in quattro quarti. Lo sprite del cursore viene quindi spostato sulla griglia
degli intervalli e posizionato sulla cella desiderata; a questo punto basta _ _ X _ _ _ X _ _ _ X _ _ _ X X -SNARE
premere il tasto Invio per attivare o disattivare un intervallo di tempo. Per _ X _ X _ X _ X _ X _ X _ X _ X -BELL
Programma BASIC generatore di ritmo 1490 PRINT TAB(8) ”[M] CURSORE IN BASSO”
1500 RETURN
1000 REM **GENERATORE DI RITMO** 1510 REM * PREPARA COD. MACCHINA & SPRITE *
1010 PRINT CHR$(147): REM RIPULISCE LO SCHERMO 1520 FOR I=49152 TO 49413
1020 GOSUB 1510: REM PREPARA CO. MACCHINA & SPRITE 1530 READ J: C=C+J: POKE I,J: NEXT I
1030 GOSUB 1270: REM PREPARA LO SCHERMO 1540 READ J: IF C<>J THEN PRINT ”DATA ERROR”: END
1040 SYS(49152): REM * INSERISCE IL CUNEO * 1550 FOR I=0 TO 62: READ J: POKE 832+I,J: NEXT I
1050 REM SYS(49175) PER RIMUOVERE IL CUNEO 1560 VIC=53248: X=28: Y=95: B=1024: SID=54272
1060 REM * CICLO PRINCIPALE * 1570 FOR I=0 TO 24 : POKE SID+I,0: NEXT I
1070 P=PEEK(197): REM ULTIMO TASTO BATTUTO 1580 POKE SID+24,15: POKE VIC+21,1: POKE 2040,13
1080 IF p=37 THEN Y=Y-16: SY=SY-1: IF Y<95 THEN Y=95: SY=0 1590 POKE 254,15: POKE 679, 15: POKE VIC+39,1
1090 IF P=36 THEN Y=Y+16: SY=SY+1: IL Y>127 THEN Y=127: SY=2 1600 FOR I=0 TO 50: POKE 50000+I,0: NEXT I
1100 IF P=47 THEN X=X-16: SX=SX-1: IF X<28 THEN X=28: SX=0 1610 RETURN
1110 IF P=44 THEN X=X+16: SX=SX+1: IF X>268 THEN X=268: SX=15 1620 REM ***ISTRUZIONI DATA DEL COD. MACCHINA***
1120 Z=0: IF P=1 THEN Z=1 1630 DATA 120,173,20,3,133,251,173,21,3
1130 PRINT CHR$(19); S$; “TEMPO=”; PEEK(679); CHR$(157); “ “ 1640 DATA 133,252,169,36,141,20,3,169
1140 POKE VIC+16,X/256: POKE VIC, X AND 255 1650 DATA 192,141,21,3,88,96,120,165,251
1150 POKE VIC+1,Y 1660 DATA 141,20,3,165,252,141,21,3,88
1160 REM * CALCOLA LA POSIZIONE DI SCHERMO OM BASE ALLE 1670 DATA 96,32,177,192,165,197,201,3
COORDINATE x, y DELLO SPRITE * 1680 DATA 208,3,32,135,192,165,197,201,4
1170 SC=B+INT((Y-50)/8+1)*40+INT((X-18)/8) 1690 DATA 208,3,32,140,192,165,197,201,5
1180 IF Y=0 THEN ROW=50000: REM TAMBURO BASSO 1700 DATA 208,3,32,155,192,165,197,201,6
1190 IF S=1 THE ROW=50016: REM TAMBURO RULLANTE 1710 DATA 208,3,32,166,192,32,87,192,142
1200 IF SY=2 THEN ROW=50032: REM CAMPANA 1720 DATA 168,2,140,169,2,76,49,234,165
1210 IF Z=1 AND PEEK(SC)=32 THEN POKE SC,24: POKE ROW+SX,1: 1730 DATA 253,208,1,96,198,254,240,1,96
GOTO 1230 1740 DATA 32,149,192,224,16,208,3,32,144
1220 IF Z=1 AND PEEK(SC)=24 THEN POKE SC,32: POKE ROW+SX,0 1750 DATA 192,185,80,195,240,3,32,184
1230 IF P=51 THE GOSUB 1270: FOR I=0 TO 50: POKE 50000+I,0: NEXT 1760 DATA 192,185,96,195,240,3,32,210
1240 IF P=57 THEN POKE 198,0: POKE VIC+21,0: PRINT CHR$(147): END 1770 DATA 192,185,112,195,240,3,32,236
1250 GOTO 1070 1780 DATA 192,200,234,232,96,169,0,133
1260 REM * PREPARA LO SCHERMO * 1790 DATA 253,96,169,1,133,253,162,0,160
1270 PRINT CHR$(19) 1800 DATA 0,96,173,167,2,133,254,96,173
1280 S$=CHR$(17)+CHR$(17)+CHR$(17) 1810 DATA 167,2,201,255,240,3,238,167,2
1290 PRINT TAB(3)” “: REM 38 SPAZI 1820 DATA 96,173,167,2,201,1,240,3,206
1300 PRINT CHR$18)TAB(3)” **** GENERATORE DI RITMO CBM 64 **** “ 1830 DATA167,2,96,174,168,2,172,169,2
1310 PRINT: PRINT: PRINT 1840 DATA 96,169,14,141,6,212,169,32,141
1320 PRINT” - - - - - - - - - - - - - - - - “ 1850 DATA 2,212,169,66,141,4,212,169,3
1330 PRINT” -BASSO” 1860 DATA 141,1,212,169,65,141,4,212,96
1340 PRINT” - - - - - - - - - - - - - - - - “ 1870 DATA 169,7,141,12,212,169,12,141,13
1350 PRINT” -RULLANTE” 1880 DATA 212,169,128,141,11,212,169,65
1360 PRINT” - - - - - - - - - - - - - - - - “ 1890 DATA 141,8,212,169,129,141,11,212
1370 PRINT” -CAMPANA” 1900 DATA 96,169,2,141,19,212,169,13,141
1380 PRINT” - - - - - - - - - - - - - - - - “ 1910 DATA 20,212,169,18,141,18,212,169
1390 PRINT : PRINT TAB(8) ”OPZIONI...... ” 1920 DATA 100,141,15,212,169,17,141,18
1400 PRINT TAB(8) ”[F1] SUONA UN RITMO” 1930 DATA 212,96
1410 PRINT TAB(8) ”[F3] RIDUCE IL TEMPO” 1940 DATA 32038: REM * CHECKSUM *
1420 PRINT TAB(8) ”[F5] AUMENTA IL TEMPO” 1950 REM ***ISTRUZIONI DATA DELO SPRITE (CURSORE)***
1430 PRINT TAB(8) ”[F7] FERMA IL RITMO” 1960 DATA 127,254,0,127,254,0,127,254,0
1440 PRINT TAB(8) ”[CLR] CANCELLA LA GRIGLIA » 1970 DATA 112,14,0,112,14,0,112,14,0,112
1450 PRINT TAB(8) ”[T] FINE DEL PROGRAMMA” 1980 DATA 14,0,112,14,0,112,14,0,112,14
1460 PRINT TAB(8) ”[<] CURSORE A SINISTRA” 1990 DATA 0,112,14,0,127,254,0,127,254,0
1470 PRINT TAB(8) ”[>] CURSORE A DESTRA” 2000 DATA127,254,0,0,0,0,0,0,0,0,0,0,0
1480 PRINT TAB(8) ”[K] CURSORE IN ALTO” 2010 DATA 0,0,0,0,0,0,0,0,0,0,0
31
Listato Assembly JMP $EA31 ; torna al punto d’interruzione
;------------------------------------------------------------------------------------
;++++++++++++++++++++++++++++++++ ;ROUTINE DI VERIFICA
;++++++++++++++++++++++++++++++++ ;------------------------------------------------------------------------------------
;++ ++ REST LDA SUONA ; riceve il valore d’innesco
;++ CODICE SORGENTE ++ BNE INIZIO ; se uguale a 1, salta
;++ PER BATTERIA ELETTRONICA ++ RTS ; restituisce il controllo
;++ ++ ;------------------------------------------------------------------------------------
;++++++++++++++++++++++++++++++++ INIZIA DEC RITARDO ; riduce il ritardo
;++++++++++++++++++++++++++++++++ BEQ AVVIA ; salta se 0
; RTS ; restituisce il controllo
VOL = $D4181; SID volume ;------------------------------------------------------------------------------------
ATT1 = $D405 ; SID attacco voce 1 AVVIA JSR CONT ; passa il controllo a CONT
PERS1 = $D406 ; SID persistenza voce 1 CPX #$10 ; fine del ciclo?
IMPULSO = $D402 ; SID freq. d’impulso voce 1 BNE VERIF ; salta se non 0
ONDA1 = $D404 ; SID forma d’onda voce 1 JSR AZZERA ; passa il controllo a AZZERA
BASSO = $D401 ; SID byte-alto della freq. voce 1 ;------------------------------------------------------------------------------------
ATT2 = $D40C ; SID attacco voce 2 VERIF LDA RIGA1,Y ; riceve l’offset di RIGA1 da Y
PERS2 = $D40D ; SID persistenza voce 2 BEQ SUCC1 ; salta se 0
ONDA2 = $D40B ; SID forma d’onda voce 2 JSR BATT1 ; passa il controllo a BATT1
RULLANTE = $D408 ; SID byte-alto della freq. voce 2 SUCC1 LDA RIGA2,Y ; riceve l’offset di RIGA2 da Y
ATT3 = $D413 ; SID attacco voce 3 BEQ SUCC2 ; salta se 0
PERS3 = $D414 ; SID persistenza voce 3 JSR BATT2 ; passa il controllo a BATT2
ONDA3 = $D412 ; SID forma d’onda voce 3 SUCC2 LDA RIGA3,Y ; riceve l’offset di RIGA3 da Y
CAMPANA = $D40F ; SID byte-alto della freq. voce 3 BEQ SUCC3 ; salta se 0
RIGA1 = $C350 ; deposito per voce 1 JSR BATT3 ; passa il controllo a BATT3
RIGA2 = $C360 ; deposito per voce 2 SUCC3 INY ; incrementa l’offset
RIGA3 = $C370 ; deposito per voce 3 INX ; incrementa il contatore di ciclo
TEMPO = $02A7 ; deposito temporaneo per il ritardo RTS ; restituisce il controllo
XCONT = $02AB ; deposito temporaneo per reg. X ;------------------------------------------------------------------------------------
YCONT = $02A9 ; deposito temporaneo per reg. Y ;SUBROUTINE
BBVETT = $FB ; deposito per byte-basso vettore ;------------------------------------------------------------------------------------
BAVETT = $FC ; deposito per byte-alto vettore FLAG0 LDA #$00 ; registra 0
SUONA = $FD ; innesco per emissione del suono (1=si) STA SUONO ; in suono
RITARDO = $FE ; deposito per ritardo corrente RTS ; restituisce il controllo
TASTO = $C5 ; ultimo tasto premuto ;------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------ FLAG1 LDA #$01 ; registra 1
* = $C000 ; assembla da 49152 (decimale) STA SUONO ; in suono
;------------------------------------------------------------------------------------ AZZERA LDX #$00 ; azzera il reg. X
;PREPARA IL CUNEO LDY #$00 ; azzera il reg. Y
;------------------------------------------------------------------------------------ RTS ; restituisce il controllo
SEI ; disabilita la richiesta d’interruzione ;------------------------------------------------------------------------------------
LDA $0314 ; riceve contenuto del byte-basso del vett. CONT LDA TEMPO ; riceve il valore di TEMPO
STA BBVETT ; memorizza in BBVETT STA RITARDO ; lo registra in RITARDO
LDA $0315 ; riceve contenuto del byte-alto del vett. RTS ; restituisce il controllo
STA BAVETT ; memorizza in BAVETT ;------------------------------------------------------------------------------------
LDA #<CUNEO ; riceve il byte-basso dell’inizio del cuneo SOMMA LDA TEMPO ; riceve il valore di TEMPO
STA $0314 ; memorizza bel byte-basso del vett. IRQ CMP #$FF ; confonta il risultato con 255
LDA #>CUNEO ; riceve byte-alto dell’inizio del cuneo BEQ CONT5 ; salta se “vero”
STA $0315 ; memorizza nel byte-alto del vett. IRQ INC TEMPO ; incrementa tempo
CLI ; riabilita la richiesta d’interruzione CONT5 RTS ; restituisce il controllo
RTS ; restituisce il controllo ;------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------ SOTTR LDA TEMPO ; riceve il valore di TEMPO
;RIMUOVE IL CUNEO CMP #$01 ; confronta il risultato con 1
;------------------------------------------------------------------------------------ BEQ CONT6 ; salta se “vero”
SEI ; disabilita la richiesta d’interruzione DEC TEMPO ; decrementa tempo
LDA BBVETT ; riceve il valore iniziale di BBVETT CONT6 RTS ; restituisce il controllo
STA $0314 ; registra nel byte-basso del vett. IRQ ;------------------------------------------------------------------------------------
LDA BAVETT ; riceve il valore iniziale di BAVETT REG LDX XCONT ; memorizza il valore di XCONT nel reg. X
STA $0315 ; registra nel byte-alto del vett. IRQ LDY YCONT ; memorizza il valore di YCONT nel reg. Y
CLI ; riabilita la richiesta d’interruzione RTS ; restituisce il controllo
RTS ; restituisce il controllo ;------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------ ;ROUTINE DI SUONO DELLA BATTERIA
;CICLO PRINCIPALE ;------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------ BATT1 LDA #$0E ;
CUNEO JSR REG ; passa il controllo a REG STA PERS1 ;
LDA TASTO ; quale tasto è stato premuto? LDA #$20 ;
CMP #$03 ; è il tasto di funzione #1? STA IMPULSO ;
BNE CONT1 ; salta se “non vero” LDA #$42 ; prepara il tamburo
JSR FLAG0 ; passa il controllo a FLAG0 STA ONDA1 ; basso e lo suona
CONT1 LDA TASTO ; quale tasto è stato premuto? LDA #$03 ;
CMP #$04 ; è il tasto di funzione #7? STA BASSO ;
BNE CONT2 ; salta se “non vero” LDA #$41 ;
JSR FLAG1 ; passa il controllo a FLAG1 STA ONDA1 ;
CONT2 LDA TASTO ; quale tasto è stato premuto? RTS ; restituisce il controllo
CMP #$05 ; è il tasto di funzione #3? ;------------------------------------------------------------------------------------
BNE CONT3 ; salta se “non vero” BATT2 LDA #$07 ;
JSR SOMMA ; passa il controllo a SOMMA STA ATT2 ;
CONT3 LDA TASTO ; quale tasto è stato premuto? LDA #$0C ;
CMP #$06 ; è il tasto di funzione #5? STA PERS2 ; prepara il tamburo
BNE CONT4 ; salta se “non vero” LDA #$80 ; rullante e lo suona
JSR SOTTR ; passa il controllo a SOTTR STA ONDA2 ;
CONT4 JSR REST ; passa il controllo a REST LDA #$41 ;
STX XCONT ; memorizza il valore del reg. X STA ONDA2 ;
STY YCONT ; memorizza il valore nel reg. Y RTS ;
32
;------------------------------------------------------------------------------------
BATT3 LDA #$02 ;
STA ATT3 ;
LDA #$0D ;
STA PERS3 ;
LDA #$12 ; prepara la campana
STA ONDA3 ; e la suona
LDA #$64 ;
STA CAMPANA ;
LDA #$11 ;
STA ONDA3 ;
RTS ; restituisce il controllo
.END
Disegni reticolari
PEEK(45)+256*PEEK(46)
I puntatori associati alle variabili, insieme al loro normale contenuto, sono riassunti qui:
Le stringhe, gestire dinamicamente, crescono verso il basso, partendo dal limite superiore della memoria, via
via che vengono definite. Le matrici, invece, vengono conservate sopra la tabella delle variabili così, quando
nel programma viene definita una nuova variabile, il sistema operativo sposta l’intera area delle matrici di tanti
byte quanti ne occorrono per far entrare la nuova variabile.
La conservazione delle stringhe è necessariamente più complessa rispetto alle altre variabili. Infatti le variabili
intere (se non appartengono ad una matrice) e le variabili a virgola mobile, occupano sette byte, ma una
stringa può occuparne fino a 255. Per superare questa complicazione, il sistema operativo conserva, nella
tabella delle variabili, soltanto la lunghezza della stringa ed un puntatore all’indirizzo dove viene conservata in
memoria. Se la stringa è definita nel programma BASIC (ad esempio: A$=”ABCD”) allora il puntatore contiene,
nell’area del programma BASIC, l’indirizzo del primo byte della stringa. Una simile stringa viene chiamata
“statica”. Se e quando il programma altera il contenuto di una stringa, allora questa viene chiamata “dinamica”
e viene conservata nell’area subito sottostante il limite superiore della memoria. Ovviamente l’indirizzo del
puntatore, nella tabella delle variabili, riflette la nuova posizione di tale stringa. Solo così il formato degli
elementi nella tabella rimane costante (sette byte).
33
L’indirizzo di una stringa può essere estratto dalla tabella delle variabili usando un piccolo trucco e poche
istruzioni. Infatti, subito dopo l’uso di una variabile BASIC, le locazioni 71 e 72 (decimali) in pagina zero,
contengono l’indirizzo di tale variabile. Comunque sia, il recupero di questo indirizzo deve essere immediato
poiché il sistema operativo utilizza la locazione 71 per certe operazioni aritmetiche. Il seguente programma
mostra il formato usato per conservare in memoria le variabili ordinarie. La prima routine estrae una stringa
dalla memoria e conserva immediatamente il valore del puntatore in 71 e 72 depositandolo in due locazioni del
buffer per il registratore. In seguito l’indirizzo salvato viene usato per calcolare quello della stringa.
Qualcuno potrebbe pensare che, utilizzando variabili intere (quelle con il simbolo %), si risparmierebbe
memoria ed i calcoli sarebbero più rapidi. Sul Commodore 64 ciò non è vero poiché, per eseguire calcoli sugli
interi, questi vengono prima convertiti in virgola mobile e poi richiamate le routine per il calcolo in virgola
mobile! Pertanto, sebbene le variabili intere occupino solo due byte (salvo quando fanno parte di una matrice),
ad esse vengono assegnati comunque sette byte di memoria e, quelli in eccesso, vengono semplicemente
ignorati nel corso del calcolo. La seguente routine consente di individuare in memoria una variabile intera:
La stessa tecnica di individuazione può essere impiegata per localizzare variabili in virgola mobile. Tuttavia,
per far ciò, esiste un metodo più economico basato sul fatto che, usando DEF FN per definire una funzione
della variabile X, tale variabile viene usata (pur rimanendo inalterata) ad ogni chiamata della funzione.
Questo programma utilizza DEF FN per calcolare l’indirizzo dell’attuale variabile BASIC, sempre ricorrendo
alle locazioni 71 e 72. Poiché X viene usata come variabile per la funzione, si può esser certi che l’indirizzo
risultante è quello del primo byte nella tabella delle variabili. Per assegnare l’indirizzo INDR viene quindi
chiamata la funzione FN. Si noti che, passando uno zero (o un altro valore) attraverso il comando FN, nella
tabella non cambia né il valore di X né il suo indirizzo.
Matrici in memoria
Quando il BASIC esegue un’istruzione DIM, viene riservata memoria per la matrice specificata. Il blocco di
memoria consiste in una “testata” più il numero di byte necessari per la conservazione degli elementi. Il
formato di questi elementi varia a seconda del tipo di variabile fin qui esaminati (vedi diagramma). Se si
intende accedere agli elementi della matrice usando il codice macchina, è molto importante comprendere quali
siano le differenze.
Variabili intere
Byte 0 Byte 1
Variabili stringa
Tipi di variabile
Sul Commodore 64 esistono tre tipi di variabili, ognuna delle quali ha un diverso formato quando conservato in memoria. Le variabili
intere vengono conservate in due byte sotto forma di numeri in complemento a due. I numeri in virgola mobile (o fluttuante)
richiedono sette byte: cinque per la mantissa e due per il segno e per l’esponente. Le varibili stringa vengono conservate, in una
zona diversa della memoria, usando un byte per ciascun carattere della stringa. Però, questi dati, non vengono depositati nella
tabella delle matrici, ma solo tre byte: uno per indicare la lunghezza della stringa e due che forniscono l’indirizzo a 16 bit nel quale
viene memorizzata la stringa.
Inoltre occorre esaminare anche il meccanismo del calcolo in virgola mobile. Quando l’interpre BASIC esegue
calcoli questo tipo di calcolo, eventuali risultati intermedi vengono depositati in due “accumulatori a virgola
mobile” chiamati, di solito, FAC e ARG. Il formato è identico ai numeri in virgola mobile conservati in memoria.
FAC si trova agli indirizzi da $61 a $65 (da 97 na 101 decimale) ed ARG da $69 a $6D (da 105 a 109
decimale). Per semplicità, in seguito, vengono utilizzate solo le routine dell’interprete che scmbiano numeri tra
FAC e la memoria. Le routine dell’interprete BASIC, con le quali familiarizzarsi in questa prima parte, sono:
● MOVFM (indirizzo $BBA2)
Questa routine copia in FAC un numero in virgola mobile che si trova in memoria. Simbolicamente viene
rappresentato con F←M. Per chiamare la routine occorre caricare, nell’accumulatore, il byte-basso
dell’indirizzo per il numero in memoria e, nel registro Y, il byte alto.
● MOVMF (indirizzo $BBD4)
Questa routine deposita il contenuto di FAC in sette byte consecutivi della memoria. Simbolicamente viene
rappresentato con M←F. Per la chiamata occorre caricare nei registri X ed Y, rispettivamente, il byte-basso
e quello alto dell’indirizzo di memoria ove depositare il numero
● FMULT (indirizzo $BA28)
Questa è la routine di moltiplicazione: essa moltiplica il contenuto di FAC per un altro numero che si trova in
memoria, depositando il risultato in FAC. Il primo numero viene caricato in FAC usando MOVFM mentre, il
35
byte-basso quello alto, vanno posti rispettivamente nei registri X ed Y prima di chiamare questa routine. Il
risultato viene depositato in FAC ma può essere trasferito in memoria usando MOVMF.
● FADD (indirizzo $B867)
Questa routine esegue la somma FAC=MEM+FAC. Per chiamarla, presupponendo che in FAC sia già
presente un valore, occorre depositare, nell’accumulatore e nel registro Y[U], rispettivamente il byte-basso e
quello alto di MEM.
● FSUB (indirizzo $B850)
Questa routine esegue la differenza FAC=MEM-FAC. La chiamata si esegue come descritto per FADD.
Queste sono le routine dell’interprete utilizzate nella formulazione della prima parte dei programmi di grafica
reticolare.
Il concetto base, del nostro progetto grafico, è che una figura reticolare può venire definita per mezzo di una
serie di numeri (o nodi) ed una matrice di connessioni. I nodi hanno coordinate X(I), Y(I) e Z(I) dove I varia da
1 a NP (il numero di punti). La matrice delle connessioni è E%(I,J), dove I e J variano entrambi da 1 a NP.
È bene che qualsiasi progetto in codice macchina proceda per piccoli passi, altrimenti il compito di individuare
eventuali errori diventa impossibile. Il progetto è stato, così, suddiviso in tre parti. Nella prima vengono definiti
gli algoritmi e redatta una versione di prova in BASIC che ruota, attorno all’asse Z, un cubo tridimensionale
proiettando i risultati sui piani X ed Y.
L’obiettivo, in seguito, sarà quello di convertire il programma BASIC in codice macchina. Anche questo
avviene gradualmente: per prima viene convertita la subroutine che inizia alla linea 1800. Il risultato è il listato
I-ROTSUB per il quale viene fornito anche il relativo programma di prova.
Nella seconda ed ultima parte dedicata a questo argomento, viene completato il programma in codice
macchina per la rotazione di immagini reticolari e fornito il relativo programma caricatore in BASIC.
36
PROGRAMMA I-ROTSUB 64 LDY YIHI
JSR MOVFM ; FAC=Y(I)
LDA SNLO
Il seguente listato Assembly va assemblato e convertito LDY SNHI
in codice con l’apposito programma di load. JSR FMULT ; FAC=Y(I)*SN
Si assegni al file risultante il nome “[Link]”. LDA #<MEM1
; ++++++++++++++++++++++++ LDY #>MEM1
; ++ I-ROTSUB 64 ++ JSR FSUB ; FAC=MEM1-FAC
; ++ ++ LDX #<MEM1
; ++++++++++++++++++++++++ LDY #>MEM1
; ++ USA MATRICI DEFINITE++ JSR MOVMF ; MEM1=FAC
; ++ DAL BASIC. ESEGUIRE ++ ;
; ++ POKE DEGLI INDIRIZZI ++ ; +++ ESEGUE MEM2=Y(I)*CN+X(I)*SN +++
; ++++++++++++++++++++++++ ;
; LDA YILO
; PLTSUB = $C183 LDY YIHI
; XLO = $C103 JSR MOVFM ; FAC=Y(I)
; XHI = $C104 LDA CSLO
; YLO = $C105 LDY CSHI
; JSR FMULT ; FAC=Y(I)*CS
* = $C544 LDX #<MEM2
; LDY #>MEM2
; +++ VARIABILI ROTSUB +++ JSR MOVMF ; MEM2=Y(I)*CS
; LDA XILO
; VARIABILI CHIAMATE DAL BASIC LDY XIHI
XBASLO *=*+1 ; POKE50500,X(0)LO JSR MOVFM ; FAC=X(I)
XBASHI *=*+1 ; POKE50501,X(0)HI LDA SNLO
YBASLO *=*+1 ; POKE 50502,Y(0)LO LDY SNHI
YBASHI *=*+1 ; POKE 50503,Y(0)HI JSR FMULT ; FAC=X(I)*SN
NP *=*+1 ; POKE50504,NP LDA #<MEM2
; VARIABILI USATE DAL C/M LDY #>MEM2
XILO *=*+1 JSR FADD ; FAC=MEM2+FAC
XIHI *=*+1 LDX #<MEM2
YILO *=*+1 LDY #>MEM2
YIHI *=*+1 JSR MOVMF ; MEM2=FAC
CSLO *=*+1 ;
CSHI *=*+1 ; +++ ESEGUE X(I)=MEM1:Y(I)=MEM2 +++
SNLO *=*+1 ;
SNHI *=*+1 LDA #<MEM1
MEM1 *=*+5 ; var. virgola mobile LDY #>MEM1
MEM2 *=*+5 ; var. virgola mobile JSR MOVFM ; FAC=MEM1
FAC =$0061 LDX XILO
ARG =$0069 LDY XIHI
; JSR MOVMF ; X(I)=FAC
; ROUTINE ARITMETICHE DELL’INTERPRETE LDA #<MEM2
; LDY #>MEM2
FMULT =$BA28 ; FAC=FAC*ARG JSR MOVFM ; FAC=MEM2
FADDT =$b86A ; FAC=FAC+ARG FSUB =$B850 ; LDX YILO
FAC=MEM-FAC LDY YIHI
FADD =$B867 ; FAC=FAC+MEM JSR MOVFM ; Y(I)=FAC
MOVFM =$BBA2 ; FAC=MEM ;
MOVMF =$BBD4 ; MEM=FAC ; +++ VERIFICA DI FINE CICLO +++
; ;
; +++ SALVA I REGISTRI +++ DEC NP
; BEQ EXIT
PHA ;
TXA ; +++ INCREMENTA PUNTATORI ALLE MATRICI +++
PHA ;
TYA LDA #$05
PHA CLC
; ADC XILO
; +++ INIZIALIZZA VARIABILI +++ STA XILO
; BCC XNOHI
LDA XBASLO INC XIHI
STA XILO XNOHI
LDA XBASHI LDA #$05
STA XIHI CLC
LDA YBASLO ADC YILO
STA YILO STA YILO
LDA YBASHI BCC YNOHI
STA YIHI INC YIHI
; YNOHI
; +++ ESEGUE MEM1=X(I)*CS-Y(I)*SN +++ JMP INIZIO
; ;
INIZIO ; +++ RECUPERA REGISTRI DALLO STACK +++
LDA XILO ;
LDY XIHI EXIT PLA
JSR MOVFM ; FAC=X(I) TAY
LDA CSLO PLA
LDY CSHI TAX
JSR FMULT ; FAC=X(I)*CS PLA
LDX #<MEM1 ;
LDY #>MEM1 RTS
JSR MOVMF ; MEM1=X(I)*CS END.
LDA YILO
37
PROGRAMMA IN BASIC PROGRAMMA DI VERIFICA PER I-ROTSUB
PER LA ROTAZIONE DEL CUBO
Il seguente programma in BASIC va trascritto e memorizzato sotto il
1000 REM** CUBO RUOTANTE IN BASIC ** nome “TEST I-ROT”. Si noti che nessuna variabile va definita nella
1010 IF A=0 THEN A=1: LOAD”[Link]”,8,1 sezione che va dalla linea 1880 alla 2010, dove è situata la chiamata
1020 IF A=1 THEN A=2: LOAD”[Link]”,8,1 SYS 50523, altrimenti gli indirizzi di base delle matrici vengono alterati
1030 REM** DIMENSIONA MATRICI ** ed il programma produce risultati imprevedibili.
1040 NP=8: REM NUMERO DI PUNTI
1050 DIM X(NP), Y(NP), Z(NP) 1000 REM** TEST DI I-ROTSUB **
1060 DIM ED(NP,NP) : REM CONNESSIONI DEI CONTORNI 1010 IF A=0 THEN A=1: LOAD”[Link]”,8,1
1070 REM** INIZIALIZZA LE MATRICI ** 1020 IF A=1 THEN A=2: LOAD”[Link]”,8,1
1080 REM—DATI SULLE COORDINATE DEL CUBO 1030 IF A=2 THEN A=3: LOAD”[Link]”,8,1
1090 DATA 75, 75, 75: REM------------------/1 1040 REM** DIMENSIONA MATRICI **
1100 DATA -75, 75, 75: REM 4 PUNTI /2 1050 NP=8: REM NUMERO DI PUNTI
1110 DATA -75,-75, 75: REM SUPERIORI /3 1060 DIM X(NP), Y(NP), Z(NP)
1120 DATA 75,-75, 75: REM------------------/4 1070 DIM ED(NP,NP) : REM CONNESSIONI DEI CONTORNI
1130 DATA 75, 75,-75: REM------------------/5 1080 REM ** INIZIALIZZA LE MATRICI **
1140 DATA -75, 75,-75: REM 4 PUNTI /6 1090 REM—DATI SULLE COORDINATE DEL CUBO
1150 DATA -75,-75,-75: REM INFERIORI /7 1100 DATA 75, 75, 75: REM------------------/1
1160 DATA 75,-75,-75: REM-----------------/8 1110 DATA -75, 75, 75: REM 4 PUNTI /2
1170 REM** RUOTA IL CUBO SU ASSE X DI π/4 1120 DATA -75,-75, 75: REM SUPERIORI /3
1180 FOR I=1 TO NP 1130 DATA 75,-75, 75: REM------------------/4
1190 READ X(I),Y(I),Z(I) 1140 DATA 75, 75,-75: REM------------------/5
1200 Y(I)=Y(I)*COS(π/4)-Z(I)*SIN(π/4) 1150 DATA -75, 75,-75: REM 4 PUNTI /6
1210 Z(I)=Z(I)*COS(π/A)+Y(I)*SIN(π/4) 1160 DATA -75,-75,-75: REM INFERIORI /7
1220 NEXT 1170 DATA 75,-75,-75: REM-----------------/8
1230 REM** RUOTA IL CUBO SU ASSE Z DI π/4 1180 REM** ROTAZIONE ATTORNO ASSE X DI π/4
1240 FOR I=1 TO NP 1190 FOR I=1 TO NP
1250 X(I)=X(I)*COS(π/4)-Y(I)*SIN(π/4) 1200 READ X(I),Y(I),Z(I)
1260 Y(I)=Y(I)*COS(π/4)+X(I)*SIN(π/4) 1210 Y(I)=Y(I)*COS(π/4)-Z(I)*SIN(π/4)
1270 NEXT 1220 Z(I)=Z(I)*COS(π/A)+Y(I)*SIN(π/4)
1280 REM—DATI SU CONNESSIONI TRA BORDI – 1230 NEXT
1290 E(1,2)=1: REM 1 CONNESSO 2 1240 REM** ROTAZIONE ATTORNO ASSE Z DI π/4
1300 E(2,3)=1: E(3,4)=1: E(4,1)=1 1250 FOR I=1 TO NP
1310 E(5,6)=1: REM QUADRATO INFERIORE 1260 X(I)=X(I)*COS(π/4)-Y(I)*SIN(π/4)
1320 E(6,7)=1: E(7,8)=1: E(8,5)=5 1270 Y(I)=Y(I)*COS(π/4)+X(I)*SIN(π/4)
1330 E(5,1)=1: REM BORDI VERTICALI 1280 NEXT
1340 E(6,2)=1: E(7,3)=1: E(8,4)=1 1290 REM—DATI SU CONNESSIONI TRA BORDI –
1350 REM** RENDE E(I,J) SIMMETRICA ** 1300 E(1,2)=1: REM 1 CONNESSO 2
1360 FOR I=1 TO NP: FOR J=1 TO NP 1310 E(2,3)=1: E(3,4)=1: E(4,1)=1
1370 IF E(I,J)=1 THEN E(J,I)=1 1320 E(5,6)=1: REM QUADRATO INFERIORE
1380 NEXT j: NEXT I 1330 E(6,7)=1: E(7,8)=1: E(8,5)=5
1390 REM*********************************************** 1340 E(5,1)=1: REM BORDI VERTICALI
1400 REM** TRACCIA CUBO RUOTANTE **
1350 E(6,2)=1: E(7,3)=1: E(8,4)=1
1410 SA=2*π/45 1360 REM** RENDE E(I,J) SIMMETRICA **
1420 FOR A=π/4 TO π/4+2* π STEP SA 1370 FOR I=1 TO NP: FOR J=1 TO NP
1430 GOSUB 1800: REM RUOTA CON PASSO SA 1380 IF E(I,J)=1 THEN E(J,I)=1
1440 GOSUB 1590: REM INIZ/PULISCE SCHERMO 1390 NEXT j: NEXT I
1450 REM—TRACCIA IL CUBO – 1400 REM***********************************************
1460 FOR I=1 TO NP 1410 REM** TRACCIA CUBO RUOTANTE **
1470 FOR J=1 TO I 1420 SA=2*π/45: CS=COS(SA): SN=SIN(SA)
1480 IF E(I,J)=0 THEN 1510: REM NON CONNESSI 1430 FOR A=0 TO 2*π STEP SA
1490 GOSUB 1630: REM CALCOLA PROIEZIONE 1440 GOSUB 1870: REM RUOTA CON PASSO SA
1500 GOSUB 1670: REM CONGIUNGE PUNTI 1450 GOSUB 1600: REM INIZ/PULISCE SCHERMO
1510 NEXT J: NEXT I 1460 REM—TRACCIA IL CUBO –
1520 REM--------------------------------------------------- 1470 FOR I=1 TO NP
1530 NEXT A: REM ANGOLO SUCCESSIVO 1480 FOR J=1 TO I
1540 REM******************************************** 1490 IF E(I,J)=0 THEN 1520: REM NON CONNESSI
1550 REM** ATTENDE ** 1500 GOSUB 1640: REM CALCOLA PROIEZIONE
1560 GET A$: IF A$=”” THEN 1560 1510 GOSUB 1680: REM CONGIUNGE PUNTI
1570 GOSUB 1760: REM RESET DELLO SCHERMO 1520 NEXT J: NEXT I
1580 END 1530 REM---------------------------------------------------
1590 REM ** PREDISPONE HI-RES ** 1540 NEXT A: REM ANGOLO SUCCESSIVO
1600 POKE 49408,1: POKE 49409,1 1550 REM********************************************
1610 POKE 49410,1: SYS 49422 1560 REM** ATTENDE **
1620 RETURN 1570 GET A$: IF A$=”” THEN 1570
1630 REM** CALCOLA PROIEZIONE IN HI-RES ** 1580 GOSUB 1770: REM RESET DELLO SCHERMO
1640 X1%=X(I)+159: Y1%=199-(Z(I)+100) 1590 END
1650 X2%=X(J)+159: Y2%=199-(Z(J)+100) 1600 REM ** PREDISPONE HI-RES **
1660 RETURN 1610 POKE 49408,1: POKE 49409,1
1670 REM** LINESUB ** 1620 POKE 49410,1: SYS 49422
1680 IF(X1%=X2%) AND (Y1%=Y2%) THEN RETURN 1630 RETURN
1690 MHI=INT(X1%/256): MLO=X1%-256*MHI 1640 REM** CALCOLA PROIEZIONE IN HI-RES **
1700 NHI=INT(X2%/256): NLO=X2%-256*NHI 1650 X1%=X(I)+159: Y1%=199-(Z(I)+100)
1710 POKE 49920,MLO: POKE 49921,MHI 1660 X2%=X(J)+159: Y2%=199-(Z(J)+100)
1720 POKE 49922,NLO: POKE 49923,NHI 1670 RETURN
1730 POKE 49924,Y1%: POKE 49925,Y2% 1680 REM** LINESUB **
1740 SYS 49934 : REM LINESUB 1690 IF(X1%=X2%) AND (Y1%=Y2%) THEN RETURN
1750 RETURN 1700 MHI=INT(X1%/256): MLO=X1%-256*MHI
1760 REM** RESET DELLO SCHERMO ** 1710 NHI=INT(X2%/256): NLO=X2%-256*NHI
1770 POKE 49408,0: SYS 49422 1720 POKE 49920,MLO: POKE 49921,MHI
1780 PRINT CHR$(147) 1730 POKE 49922,NLO: POKE 49923,NHI
1790 RETURN
38
1800 REM** RUOTA IL CUBO SU ASSE Z DI SA ** 1740 POKE 49924,Y1%: POKE 49925,Y2%
1810 FOR I=1 TO NP 1750 SYS 49934 : REM LINESUB
1820 X(I)=X(I)*COS(SA)-Y(I)*SIN(SA) 1760 RETURN
1830 Y(I)=Y(I)*COS(SA)+X(I)*SIN(SA) 1770 REM** RESET DELLO SCHERMO **
1840 NEXT 1780 POKE 49408,0: SYS 49422
1850 RETURN 1790 PRINT CHR$(147)
1800 RETURN
1810 REM** RUOTA IL CUBO SU ASSE Z DI SA **
1820 FOR I=1 TO NP
1830 X(I)=X(I)*CS-Y(I)*SN
1840 Y(I)=Y(I)*CS+X(I)*SN
1850 NEXT
1860 RETURN
1870 REM** RUOTA ATTORNO ASSE Z DI SA
1880 X(1)=X(1): REM X(1) VAR CORRENTE
1890 POKE 50500,PEEK(71). REM X(1) LO
1900 POKE 50501,PEEK(72): REM X(1) HI
1910Y(1)=Y(1): REM Y(1) VAR CORRENTE
1920 POKE 50502,PEEK(71) : REM Y(1) LO
1930 POKE 50503,PEEK(72): REM Y(1) HI
1940 POKE 50504,NP: REM NUMERO DI PUNTI
1950 CS=CS: REM CS VAR CORRENTE
1960 POKE 50509,PEEK(71)
1970 POKE 50510,PEEK(72)
1980 SN=SN: REM SN VAR CORRENTE
1990 POKE 50511,PEEK(71)
2000 POKE 50512,PEEK(72)
2010 SYS 50523
2020 RETURN
Rivoluzione grafica
X1%=X(I)+159: Y1%=199-(Z(I)+100)
X2%=X(J)+159: Y2%=199-(Z(J)+100)
originariamente inseriti nelle righe 1650 e 1660 del programma BASIC, è necessario chiamare delle nuove
routine dell’interprete. In concreto: entrambe le linee sommano, alla variabile X(I) in virgola mobile, il valore
159 (anch’esso in virgola mobile) prima di prenderne la parte intera ed immagazzinarla come variabile intera
X1% in 2 byte. Ecco le routine dell’interprete che fanno il lavoro:
E%(0,0),E%(1,0),E%(2,0)... E%(NP,0)
E%(0,1),E%(1,1),E%(2,1)... E%(NP,1) e così via...
Quindi la matrice si compone di blocchi di memoria, ciascuno lungo 2x(NP+1): ogni blocco corrisponde ai
valori del secondo indice e ciascun elemento occupa due byte (perché la matrice contiene valori interi).
Il nostro obiettivo è “tradurre” nel modo più esatto il programma BASIC nel codice macchina in modo che i cicli
I,J, che esaminano la matrice E%(I,J), presentino il formato:
FOR I=1 TO NP
FOR j=1 TO NP
Per ottenere un codice macchina equivalente al NEXT I, questo complica notevolmente la modifica da
apportare al puntatore, considerando che vanno saltati tutti gli elementi il cui primo indice è 0. Per ottenere la
rotazione della figura, gli elementi di E%(I,J) vanno elaborati nel seguente ordine:
E%(1,1)
E%(1,2) E%(2,2)
E%(1,3) E%(2,3) E%(3,3)
E%(1,4) E%(2,4) E%(3,4) E%(4,4) ecc.
Un rapido calcolo dà l’espressione 2x(NP+1), ossia il valore da sommare al puntatore ogni volta che viene
incrementato I. Con il codice macchina del 6510, la tecnica più conveniente per accedere agli elementi di
E%(I,J) è quella dell’indirizzamento indiretto che viene programmato con le istruzioni:
LDY JINDEX
LDA (ZPTEMP),Y
ZPTEMP è un puntatore a due byte situato in pagina zero, mentre JINDEX viene usato per registrare i valori di
J. ZPTEMP va incrementato dopo ogni aumento di J. Dall’incremento, applicato a ZPTEMP ed al registro Y,
dipende l’aumento di due byte dell’offset necessario per ogni successivo incremento di J. Quindi, come
risultato finale di queste complesse operazioni, adogni iterazione nel ciclo di I ZPTEMP viene incrementato di
(2xNP+1)-(I-1). Il valore (I-1) viene sottratto dalla lunghezza del blocco in quanto ZPTEMP è stato
incrementato (I-1)( volte dal cilco di J appena concluso. L’esecuzione di quest espressione, per il calcolo
dell’offset, porta il vettore ZPTEMP a puntare al byte giusto dopo che I è stato incrementato.
Un’ultima coinsiderazione: sarebbe assai conveniente poter chiamare una routine dell’interprete capace di
localizzare direttamente la variabile E%(I,J). Tale routine esiste ma, sfortunatamente, il suo uso è molto
complesso (in effetti funziona con tutti i possibili tipi di variabile) e l’esecuzione lenta. L’offset, pertanto, va
calcolato nel modo appena descritto, a partire dall’indirizzo di E%(1,1).
Durante la descrizione del progetto qui presentato, vengono fornite interessanti informazioni tecniche (quelle
di cui dispongono i progettisti degli apparecchi elettronici dotati di MIDI) e sono facilmente comprensibili a chi
abbia una certa esperienza di programmazione.
L’HARDWARE STANDARD
ISOLATORE OTTICO +5V
DELLA MIDI PC-900
6 Rd 270 Ω (per PC-900)
D1 1N914
5V
2
Rb 220 Ω 4
NC
V0 ALLA UART
2 1
+5V
5 4 5
RICEVITORE Ra 220 Ω
NC NC
3 1
2
MIDI IN A 4
Rc 220 Ω 5
+5V 1
NC
NC
3
Ra 220 Ω
MIDI THRU
DALLA UART
2
A 4
5
NC NC Note:
3 1 1. L’isolatore ottico, qui considerato, è il PC-900 della
Sharp. Con adeguate modifiche posso essere utilizzati
gli isolatori ottici HP 6N138 od altri.
MIDI OUT 2. Le porte “A” sono realizzate con circuiti integrati o con
transistor.
3. Tutte le resistenze hanno una tolleranza del 5%.
Normalmente l’interfaccia comprende due sezioni: una per la ricezione ed una per la trasmissione dei dati. Ognuna di queste ha un
proprio connettore, rispettivamente etichettato MIDI IN e MIDI OUT. Opzionalmente può trovarsi un terzo connettore, etichettato
MIDI THRU, che si limita ad emettere un’esatta copia dei segnali ricevuti da MIDI IN. Questo risulta utile per facilitare il collega-
mento “in serie” di più apparecchi senza dover aggiungere ulteriori prese MIDI OUT sul trasmettitore.
Prima di tutto occorre individuare la natura dei dati scambiati tramite la MIDI, e questo è reso più semplice se
si considerano i vari strumenti elettronici, nel sistema musicale, collegati come particolari periferiche di un
computer. La distinzione tra alcuni di questi dispositivi può non essere immediatamente evidente: è il caso dei
più moderni sintetizzatori che, sebbene formati da due diversi elementi (la tastiera per l’immissione ed il
45
generatore di suoni per l’emissione), formano un solo strumento. Il collegamento più ovvio, in tal caso, è
quello diretto: premendo un tatso della tastiera si ottiene un suono dal generatore.
Questo sistema è paragonabile ad una sofisticata macchina da scrivere elettronica in cui due componenti
distinte, la tastiera ed il dispositivo di stampa, possono essere collegate in modo da riprodurre immediata-
mente quanto digitato. Tuttavia il collegamento può avvenire secondo un criterio ben diverso e ben noto a chi
usa abitualmente un word processor: la tastiera è collegabile ad un computer che elabora lo scritto prima di
inviarlo alla stampante.
CIRCUITO
DELL’ISOLATORE OTTICO
Una specifica hardware dell’interfaccia MIDI stabilisce che la linea
d’ingresso sia isolata elettricamente dal circuito ricevente per evitare
dannosi ritorni di massa nel sistema che produrrebbero fastidiosi ronzii
alla frequenza di rete. Il problema viene facilmente risolto ricorrendo ad
un dispositivo ciamato “accoppiatore” o “isolatore” ottico che fornisce non
un collegamento elettrico ma, appunto, ottico fra i vari stadi. Come si può
notare nella figura a lato, i due poli dell’ingresso sono collegati ad un
diodo foto-emettitore simile ai LED presenti un po’ dappertutto e che
fanno da ‘spia’ ai dispositivi in funzione (segnalazione di ON). La sua
USCITA emissione di luce è dovuta quando si applica una tensione, con la giusta
INGRESSO
polarità, ai suoi capi. Questo diodo LED, non visibile dall’esterno
dell’integrato che lo contiene, invia i suoi segnali (luminosi) al foto-
transistor, a cui è abbinato, che provvede a regolare il passaggio di
corrente ai suoi capi in base alla quantità di luce ricevuta: il segnale
logico (alto o basso) viene dunque trasferito dall’ingresso all’uscita senza
connessione elettrica.
Le specifiche MIDI stabiliscono, inoltre, che deve essere sufficiente una
corrente di 5mA per accendere il LED e che i tempi di risposta siano di
DIODO FOTO-EMITTENTE FOTO-TRANSISTOR ordine inferiore ai due microsecondi.
+5V
BIT DI D7 D0 BIT DI
START (BPS) D6 D5 D4 D3 D2 D1 (BMS) STOP
0V
320 microsecondi
Nel caso di un sintetizzatore è importante notare che l’interfaccia interna, tra la tastiera ed il generatore di
suoni, opera esclusivamente su segnali digitali. In sostanza, la tastiera agisce la puro e semplice controllo
esattamente come in un word processor la tastiera controlla il dispositivo di stampa.
Anche in un sintetizzatore musicale, dunque, è possibile inserire un computer tra la tastiera ed il generatore di
suoni. La musica, in questo caso, non viene rappresentata da variazioni di pressione nell’aria (come negli stru-
menti tradizionali), bensì da una sequenza di “eventi” codificati tramite la tastiera e, in seguito, inviati al
sistema di riproduzione. La conversione di tali segnali sonori, in effetti, spetta ai circuiti del generatore di suoni
per poi, successivamente, trasmessi ad un amplificatore audio. Di cinseguenza la MIDI introduce, per la prima
volta nella storia della musica, la prospettiva di utilizzare sistemi di controllo svincolati dal concetto di “tastiera
musicale”.
Un buon esempio (se pur rudimentale) di un conosciutissimo sistema di elaborazione musicale, risale a
moltissimi anni fa: la pianola. Questo strumento era uno speciale tipo di pianoforte in grado di suonare brani
musicali codificati sotto forma di fori su di un rullo di carta. Un meccanismo di lettura, attraverso il quale
passava a velocità costante il rullo codificato, provvedeva a convertire il codice, in combinazioni di note
agendo sui rispettivi martelletti del pianoforte. La distanza di ciascun foro, dal margine laterale del foglio di
carta, corrispondeva all’altezza della nota, mentre la distanza, in senso longitudinale, corrispondeva
all’intervallo di tempo tra una nota e l’altra. Ciò consentiva una forma piuttosto elementare di registrazione
musicale ma consentiva apportare tutte le correzioni ed aggiunte desiderabili: basta modificare la posizione di
46
uno o più fori nel rotolo di carta. Questo sistema ha notevoli analogie con le schede perforate usate nella
programmazione dei primi computer.
Un attuale sistema di elaborazione musicale (il termine “music processor” è ormai di uso corrente) può essere
considerato, quindi, una versione rivista ed aggiornata del vecchio metodo per pianola: la memoria del
computer (o quella dei dischi) ha sostituito la carta mentre, un sintetizzatore controllato da un’interfaccia, ha
rimpiazzato il pianoforte. La revisione di un pezzo musicale, oggi, viene fatta direttamente nella memoria del
computer grazie anche all’apposito software. Prima di giungere a tutto questo, comunque, occorre essere a
conoscenza di due cose riguardanti la MIDI: il tipo di trasmissione dai dati impiegato ed il formato di tali dati.
Questi due elementi corrispondono alle specifiche hardware e software dell’interfaccia. Per adesso
consideriamo l’aspetto hardware.
Ritardi di trasmissione
A questo punto conviene esaminare la principale limitazione della MIDI, ossia il ritardo di trasmissione tra i vari
strumenti. In generale, un messaggio di nota attiva/non attiva consiste in tre byte il cui tempo necessario per
l’invio è di 3x320 µs = 0.96 millisecondi (ms). Adesso si consideri un sistema con un sequenziatore
multicanale in cui, ad esempio, occorra inviare 20 messaggi simultanei: l’ultima sequenza viene inviata con un
ritardo di circa 20 ms rispetto alla prima!
In molti sistemi sofisticati, i segnali audio prodotti hanno tempi d’innesco sull’ordine dei 2-3 ms per cui, anche
un orecchio non particolarmente addestrato, può accorgersi di un ritardo di ben 20 ms.
47
Circuiti audio
TRASMISSIONE
TRASMISSIONE
SCORRIMENTO
__
REGISTRO DI
REGISTRO
CONTROLLO
R/W
CS2 ⃝ DATI TRASMISSIONE
BUS DI
PER
DATI
CS1
CS0
RSEL
___
REGISTRO LOGICA IRQ ___
DI STATO IRQ
BUFFER DEI DATI
REGISTRO
DI
D0-D7 CONTROL
LO
SCORRIMENTO
REGISTRO DI
RICEZIONE
REGISTRO RICEZIONE
PER
RICEZIONE DATI
DATI
CLOCK Rx
Internamente il circuito ACIA contiene una serie di registri a 8 bit. La linea d’ingresso per la selezione del
registro (RSEL) permette di accedere ai registri del bus dei dati. Per utilizzare il software di controllo, fornito in
seguito, non è indispensabile comprendere il funzionamento dei registri dell’ACIA. Tuttavia, per coloro che
volessero cimentarsi per conto proprio nella programmazione dell’interfaccia, ecco le informazioni necessarie:
Il “registro di stato” è composto da 8 bit che descrivono lo stato attuale del circuito ACIA. Si può
esaminarne il contenuto con una “lettura” dell’ACIA dopo aver posto la linea RSEL a livello logico 0.
Il “registro di controllo” contiene 8 bit che controllano il funzionamento dell’ACIA. Per accedere al registro
occorre eseguire un’operazione di “scrittura” sull’ACIA con la linea RSEL a livello logico 0.
Il “registro di scorrimento per la trasmissione” (TSR) esegue la conversione da parallelo a seriale,
necessaria per trasmettere un byte di dati. Detto registro riceve un byte dal “registro di trasmissione dei
dati” (TDR) ogni volta che, quest’ultimo, è completato: purché sia conclusa la trasmissione del byte pre-
cedente. Ciò pone a 1 il bit 1 del registro di stato. Il byte, in seguito, viene scaricato dal registro alla velo-
cità indicata dal clock di trasmissione ed è, in questa fase, che ai suoi 8 bit vengono aggiunti i bit di start e
di stop. Il TSR è un registro “interno” e non può essere raggiunto direttamente attraverso il bus dei dati.
Il “registro di trasmissione dei dati” (TDR) è un ‘buffer’ (deposito temporaneo di memoria) interposto tra il
TSR ed il bus dei dati del sistema. Il byte da trasmette viene caricato in questo registro, eseguando una
“scrittura” sull’ACIA, dopo aver posto al valore logico 1 la linea di selezione del registro (RSEL). Tale
operazione azzera il bit 1 del registro di stato. Quindi, per non perdere informazioni, il TDR non deve mai
48
essere caricato se il suo bit di stato è a 0 poiché ciò indica che il byte precedente si trova ancora
all’interno del TDR, in attesa di venire trasmesso.
Il “registro di scorrimento per la ricezione” (RSR) esegue la conversione da seriale a parallelo per la
ricezione dei dati dal sistema MIDI. Dopo aver ricevuto un byte completo, il registro lo trasferisce all’RDR e
pone ad 1 il bit di stato 0. Se il bit conteneva già il valore, l’RSR seleziona anche il bit 5 per indicare che il
byte precedente non era stato letto dalla CPU e che l’informazione è perduta.
Se il byte ricevuto manca dei bit di start e di stop, il registro seleziona il bit di stato 4; ciò può verificarsi
quando la linea d’ingresso seriale è disturbata da un ‘rumore elettrico’.
Come il TSR, anche l’RSR non può essere raggiunto direttamente dal bus.
Il “registro di ricezione dei dati” viene caricato ogni volta che l’RSR riceve un byte completo: quindi
contiene l’ultimo byte ricevuto. Anche in questo caso, per accedere al registro, occorre eseguire una
“lettura” del chip ACIA, ma con la linea RSEL al valore logico 1.
L’operazione azzera il bit di stato 0.
+5V
SCHEMA DEL CIRCUITO DELL’INTERFACCIA MIDI
+5V
MASSA R4
0V SK1
+5V
MIDI OUT
C2 (vista dall’esterno) 1 4
2
12 8 10 3 5
Vcc CS0 CS1 0V IC3e IC3d 0V
D0 22 D0 Txd
CONNETTORE PER IL BUS DEL COMPUTER
D1 21 6 11 10 9 8
D1 R2
D2 20
D2
D3 19
D3
D4 18
D4
D5 17
D5 IC1 +5V
D6 16
D6 +5V
D7 15 R3
D7 C3
A0 11 R1
RSEL 8
CS 9 0V
CS2 6 1 4 MIDI IN
R/W 13
2 Vcc 2
Rxd SK2 2 (vista dall’esterno)
R/W 0v a
3 5
14 4
MASSA Txd IC2 D1
IRQ 7 5 3
IRQ 3 V0 k
Vss CTS DCD Rxc 0V
1 24 23
R5 R6
0V
1 2 3 4 5 6
QZ1
TAGLI PER IL
COMMODORE 64
TAGLI PER IL
MICRO BBC
Programmazione dell’ACIA
Per programmare l’interfaccia MIDI si deve conoscere il funzionamento dei quattro registri del chip ACIA.
Quanto segue si riallaccia alla descrizione già fatta nel capitolo precedente e si riferisce alle funzioni dei bit dei
registri di controllo e di stato. Intervenendo sul registro di controllo, si può programmare il chip ACIA, in modo
che generi un’interruzione per la CPU, qualora vengano selezionati particolari bit di stato all’interno dei circuiti
di ricezione e/o trasmissione dell’ACIA. Quando il bit 5 e 6 del registro di controllo vengono posti,
rispettivamente, ad 1 ed a 0 ed il bit di stato TDR si trova a livello basso, il chip ACIA genera una interrupt di
trasmissione. Se invece vengono posti a 1 il bit 7 del registro di controllo 7 ed il bit 0 del registro di stato,
l’ACIA genera una interrupt di ricezione. Quest’ultima viene annullata con una lettura del registro di ricezione
dei dati (RDR) purché non si trovi a livello alto il bit 5 del registro di stato che viene azzerato leggendo, prima
dell’RDR, lo stesso registro di stato. Anche il bit 2 del registro di stato può generare una interrupt, ma non
viene utilizzata in questo progetto.
Il chip prevede due linee separate per la trasmissione e per la ricezione dei segnali di sincronizzazione (clock)
ma, in genere, vengono collegate fra loro. Il clock che regola la frequenza di baud viene generato dal segnale
di clock in ingresso (che può essere anche diviso per 16 o 32 a seconda del valore dei bit di controllo 0 e 1).
I bit 2, 3 e 4 del registro di controllo fissano, rispettivamente: il numero di bit di stop, il numero di bit nei dati e
stabiliscono se la parità è pari, dispari oppure se non prevista. Per adattare il circuito alla standard MIDI, si
deve porre a 1 i bit 2 e 4 ed azzerare il bit 3 (8 bit di dati, nessuna parità, 1 bit di stop).
I bit 5 e 6 del registro di controllo regolano il funzionamento della trasmissione. Per quanto riguarda il sistema
MIDI, l’azzeramento del bit 6 consente al bit 5 di abilitare le interrupt del trasmettitore.
51
I REGISTRI DELL’ACIA
REGISTRI DI TRASMISSIONE/RICEZIONE DEI DATI:
SELEZIONE DEL REGISTRO = 1
ERRORE DI FORMATO
RICHIESTA D’INTERRUZIONE
D7-D0
22 20 18 16 9 7
21 19 17 15 13
0V
+5V
IRQ
R/W
CS2
LATO
COMPONENTI
LATO DELLE
PISTE
COLLEGARE A 0V
A0 SUL LATO
11 COMPONENTI
Lo schema qui sopra illustra i collegamenti dei piedini del chip ACIA per il Commodore 64. I numeri assegnati ai cavetti saldati ai
connettori corrispondono ai piedini dell’ACIA. Terminato il montaggio, innestare la scheda nella porta d’espansione del Commodore
64 con il lato dei componenti rivolti verso l’alto ed accendere il computer per collaudarne il funzionamento.
2) Se il computer funziona normalmente, collegare le prese MIDI IN e MIDI OUT dell’interfaccia con dei
comuni cavetti hi-fi a 5 piedini e digitare il seguente comando:
POKE 56832,3
che carica il valore 3 nel registro di controllo dell’ACIA e produce un reset generale del sistema.
L’istruzione:
53
POKE 56832,22
carica il valore $16 nel registro di controllo e configura l’ACIA nel seguente modo:
● disabilita le interrupt di trasmissione e di ricezione (dato che il sistema non è ancora in grado di gestirle)
● assegna, alle parole seriali trasmesse e ricevute, una lunghezza di 8 bit (per i dati) più un bit di stop
senza alcuna generazione/verifica della parità
● definisce, come frequenza di baud, l’impulso di sincronizzazione (clock) ai piedini 3 e 4 diviso per 64
(2MHz/64 = 31,25 KHz che è la frequenza stabilita dallo standard del sistema MIDI).
PRINT PEEK(56832)
Il valore letto dovrebbe essere 2. Questo indica che sia il registro di trasmissione dei dati (bit 1 posto a 1)
sia il registro di ricezione dei dati (bit 0 posto a 0) sono vuoti. Poiché il computer non ha ricevuto alcun dato
e le interrupt sono disabilitate, i bit di stato, compresi tra 2 e 7, dovrebbero essere a zero.
POKE 56833,X
dove X è un numero compreso tra 0 e 255. Questa istruzione carica nel registro dei dati un valore da
trasmettere.
4) Il byte viene ricevuto in una frazione del tempo necessario per inserire il cussessivo comando per la lettura
del registro di stato:
PRINT PEEK(56832)
Il nuovo valore dovrebbe essere 3. Il bit 1, azzerato subito dopo l’ultimo comando, viene posto
nuovamente a 1 non appena il registro di trasmissione dei dati risulta disponibile per il byte successivo. Il
bit 0 viene posto a 1 per indicare che il bit è stato ricevuto e può essere letto dal registro dei dati. In questo
caso può verificarsi un errore: se il bit 0 non risulta ad 1, probabilmente il circuito del canale di
trasmissione non è elettricamente chiuso e, quindi, l’ingresso del ricevitore rimane a livello alto ampedendo
la lettura.
Accertata la ricezione di un byte, il seguente comando, per la lettura del registro dei dati, verifica se il byte
ricevuto corrisponde a quello trasmesso:
PRINT PEEK(56833)
Questa operazione di lettura dovrebbe fornire lo stesso valore X trasmesso in precedenza. Conviene
ripetere le fasi 3, 4 e 5 usando valori diversi per X.
54
Regole di composizione
COLLEGAMENTO “A CASCATA”
IN
IN OUT THRU IN THRU IN THRU
OUT
Passa parola...
Il metodo più diffuso, di collegare le varie apparecchiature compatibili con il sistema MIDI, è quello chiamato “in cascata” che si
avvale, in particolare, della presa identificata con MIDI THRU. Questa consente una connessione in cui le linee di trasmissione
sono comuni a tutti gli strumenti; questi ultimi vengono selezionati in base a specifici codici di identificazione.
I messaggi di canale sono composti da un byte di stato il cui valore è compreso tra $80 e $EF, e da uno o due
byte di dati. Nel byte di stato il numero di canale viene codificato nei quattro bit meno significativi (BMS) del
byte, pertanto, il valore $00 corrisponde al canale 1 e $0F corrisponde al canale 16. I rimanenti tre bit
determinano il tipo di messaggio che segue. Una caratteristica speciale dei messaggi di canale è che non
occorre ripetere il byte di stato nel caso in cui questo fosse identico a quello trasmesso nel messaggio
precedente. Pertanto, una volta inviato un byte di stato, questo rimane attivo finché non ne viene indicato un
altro (l’unica eccezione si presenta quando un messaggio di sistema, in tempo reale, interrompe tempora-
neamente lo stato “attuale”).
Questo criterio di funzionamento è particolarmente comodo se si tratta di inviare, consecutivamente, più
messaggi per l’emissione (on) o l’annullamento (off) di note. L’annullamento di una nota, in particolare, si
ottiene specificando una “velocità” pari a zero. La possibilità di mantenere inalterato il byte di stato consente,
dunque, di risparmiare memoria e di velocizzare l’emissione delle note.
L’assegnazione della “voce”, nei sintetizzatori, è il processo che consente di dirigere un messaggio (sia
proveniente dal sistema MIDI che da tastiera) ad uno dei sintetizzatori collegati al sistema. Se, per esempio,
un sintetizzatore polifonico permette la simultanea emissione di sei note, si dice che ha sei voci. Per
controllare la risposta dello strumento, al messaggio inviatogli, sono disponibili svariati messaggi modali MIDI.
Un messaggio modale, comunque, dovrebbe essere ignorato da uno strumento ricevitore che non sia capace
di operare nel modo specificato.
COLLEGAMENTO “A STELLA”
IN
IN OUT 1
OUT 2
IN
OUT 3
OUT 4
UNITÀ CENTRALE
I messaggi di sistema non contengono specifiche di canale nel byte di stato, così il loro invio riguarda tutti gli
strumenti collegati al sistema. I messaggi si dividono in tre categorie: i messaggi comuni, in tempo reale ed
esclusivi.
I messaggi di sistema comuni sono composti da un byte di stato ( che può assumere valori compresi tra
$F1 e $F7) e da 0, 1 o 2 byte di dati.
I messaggi di sistema in tempo reale riguardano tutti i componenti del sistema e sono composti soltanto
da un byte di stato (di valore compreso tra $F8 e $FF) e nessun byte di dati. Questa particolarità ne
permette l’invio in qualsiasi momento, anche interrompendo altri messaggi in corso d’esecuzione.
Principalmente questi messaggi servono per la sincronizzazione di dispositivi quali sintetizzatori di
batteria e sequenziatori. La maggior parte dei normali sintetizzatori, comunque, ignora i messaggi di
questo tipo se non possiedono, al loro interno, un sequenziatore capace di sincronizzarsi con il sistema
MIDI.
I messaggi di sistema esclusivi iniziano con un byte di stato il cui valore è $F0, seguito da un qualsiasi
numero di byte di dati. Il messaggio può terminare con il byte di fine-messaggio $F7 o con un qualsiasi
56
altro byte di stato. Il primo byte di dati è il codice identificativo (ID) del fabbricante: se esso non
corrisponde al codice dell’apparecchio ricevente, quest’ultimo deve ignorare il messaggio.
I messaggi di questo tipo servono per scmbiare informazioni tra strumenti dello stesso genere. I dati
trasmessi, in genere, costituiscono particolari sequenze di programmazione (“patch”) per sintetizzatori.
Questa trasmissione non deve essere confusa con il messaggio di canale $Cx che opera una selezione
tra i programmi patch già residenti nella memoria del sintetizzatore ricevente.
Il contenuto dei messaggi di sistema esclusivi, di solito, compete ai fabbricanti purché sia stato
assegnato loro un codice valido di identificazione.
REGISTRAZIONE
MESSAGGIO “NOTA ON”
STATO ALTEZZA VELOCITÀ
IN OUT
$61 $91 $30 $51 $F0 $37 $81 $30 $40 $FF
INIZIO FINE
Registrazione digitale
Lo schema sopra mostra come viene registrata una nota usando il programma per la registrazione digitale.
Durante questa fase, il sintetizzatore trasmette un messaggio “nota on” (quando viene premuto un tasto) ed un
messaggio “nota off” (quando il tasto viene rilasciato). I messaggi vengono immagazzinati in memoria insieme
all’informazione di pausa che indica l’intervallo di tempo, espresso in unità di 2 ms, tra un evento ed il successivo.
Nell’esempio, l’intervallo tra i messaggi di nota on ed off, viene registrato nei due byte $F0 e $37. Durante la fase
di riproduzione il programma utilizza l’informazione di pausa per ritardare la trasmissione dei messaggi di nota e
riprodurre il brano musicale con la massima “fedeltà”.
Riuscendo a sviluppare un programma capace di registrare, nella memoria del computer in tempo reale, una
melodia suonata su di un sintetizzatore, è possibile combinare alcune caratteristiche delle diverse modalità
d’impiego del computer per interagire con gli strumenti collegati alla MIDI. Successivamente il programma, in
risposta ad opportune istruzioni, ritrasmette la melodia al sintetizzatore che la riproduce tramite la MIDI.
Naturalmente la tecnica di registrazione è digitale.
Sebbene apparentemente simile a quella analogica usata dai registratori a nastro magnetico, la registrazione
digitale, attraverso la MIDI, si basa su principi assai diversi. Quando viene eseguita una registrazione da
nastro, i suoni musicali vengono codificati sotto forma di configurazioni magnetiche sulla superficie del nastro:
la loro posizione sul nastro determina la sequenza di riproduzione dei suoni e le pause tra un suono e l’altro.
Facciamo un esempio: due note registrate con questa tecnica e separate da una pausa di due secondi.
58
Durante tale intervallo il nastro continua a scorrere sotto le testine di registrazione anche se non viene
memorizzato alcun suono. Al momento che il nastro viene “riletto”, la lunghezza del segmento non registrato
determina la pausa tra l’emissione della prima e della seconda nota.
In un sistema a registrazione digitale abbinato al MIDI, le due note corrispondono alla pressione di due tasti,
ciascuno dei quali genera un messaggio MIDI. Una procedura di registrazione in tempo reale deve essere in
grado di permettere al computer di registrare la pausa che intercorre tra la ricezione del primo e del secondo
messaggio MIDI. I due messaggi, seguendo l’esempio della registrazione su nastro, potrebbero essere
depositati in memoria utilizzando una matrice mentre, l’intervallo che li separa, si potrebbe rappresentare
scrivendo valori “zero” in una serie di byte, ciascuno dei quali corrispondente ad una singola unità di tempo.
Questa tecnica di registrazione, in tempo reale dei messaggi MIDI, comporta due risultati importanti: consente
di riprodurre le due note nel momento stesso dell’emissione, conservando il corretto intervallo di tempo, e di
risparmiare notevoli quantità di memoria.
Un metodo alternativo al precedente è quello di registrare l’informazione, relativa alla pausa, scrivendone il
valore direttamente in un byte senza inserire alcun byte nullo. Se per esempio l’intervallo di tempo tra le due
note fosse di 50 unità di tempo, sarebbe sufficiente introdurre nella matrice un byte di pausa contenente il
valore 50 al posto di 50 byte “zero”.
Se da un lato questo sistema consente un apprezzabilissimo risparmio di memoria, dall’altro rende più
difficoltoso prevedere l’occupazione in memoria di una sequenza. Indicativamente, a parità di durata, i brani
musicali, caratterizzati da una rapida successione di note, richiedono un maggior numero di messaggi “on-off”
per le note (e pertanto più byte) che non brani con una minore concentrazione di variazioni tonali. Il frequente
ricorso al modificatore di tonalità genera ulteriori messaggi MIDI che contribuiscono ad accrescere il consumo
di memoria.