Articolo pubblicato sul n. 33 di MCmicrocomputer (Edizioni Technimedia Srl - Roma) nel settembre 1984

MCmicrocomputer


Commodore 64
I segreti del disco
Trattamento file col Driver Floppy Disk 1541

di Andrea de Prisco

In tutto l'armamentario Commodore (sezione Home Computer) se c'e' un elemento che fa davvero pena per le sue alte doti di scarsezza qualitativa, questi e' il manuale di istruzioni dell'unita' a floppy disk 1541.  Le inesattezze si contano a decine, e non solo a carattere editoriale. Zeri scambiati per "o", uno per "i", virgole in luogo di puntoevirgole. Un vero pasticcio. Copertina del numero di MCmicrocomputer contenente l'articoloQuanti lettori sono in grado di usare i file relativi? Cercheremo di mettere un po' d'ordine alla faccenda, svelandovi anche qualcosa in piu'.

 SAVE e LOAD

 Prima di parlare di unita' a dischi, diamo uno sguardo a come il Commodore 64 salva e recupera programmi da memoria di massa (disco o nastro che sia). Come tutti ben sappiamo, l'istruzione che ci consente di salvare un programma e':  

SAVE "Nome Programma",Periferica

Periferica e' il numero device: 1 per il Nastro 8 o un numero maggiore per l'unita' a dischi. Fin qui tutto normale. La cosa leggermente piu' interessante e' conoscere cosa il computer fa quando gli si dice "SAVE !".

 Le prime 256 locazioni della memoria del 64 (per intenderci: da PEEK(0) a PEEK(255)) costituiscono la cosiddetta Pagina Zero. E' usata dal microprocessore per conservare le variabili di sistema: un insieme di informazioni necessarie al buon funzionamento di tutta la macchina.

 Fra le tante cose che il processore conserva in pagina zero, alcune indicano: l'inizio della memoria Basic, la fine, dove sono stivate le variabili, gli array, le stringhe, dove termina (qual e' l'ultimo byte occupato dal) programma Basic in memoria. Queste ed altre, permettono al microprocessore di non fare confusione durante l'esecuzione di un programma, stivando sempre in locazioni libere le nuove variabili Basic usate, senza perderne nessuna (sarebbe il colmo ...). In figura 1 e' mostrata la tabella delle variabili di sistema piu' utili.

Ritornando alla nostra SAVE, quando da Basic e' impartito questo comando, il processore non fa altro che trasferire sulla periferica (nastro o disco) tutto quello che e' compreso tra l'inizio programma Basic (questa informazione e' mantenuta nelle locazioni 43 e 44) e la fine programma Basic (locazioni 45 e 46).

All'accensione della macchina, i due puntatori sono (per cosi' dire) azzerati: puntano entrambi alla prima locazione Ram disponibile, la 2049 (la 2048 deve sempre contenere 0). Provando a inserire una qualsiasi linea Basic, possiamo controllare che il puntatore fine programma "avanza". Se digitiamo, dopo aver inserito una linea:

PRINT PEEK(46) * 256 + PEEK(45)

Troveremo un numero maggiore di 2049. Man mano che inseriremo nuove linee, il nostro puntatore di fine continuera' ad avanzare, mantenendo sempre l'informazione della locazione del primo byte libero a ridosso del nostro programma Basic. Variando di mano nostra i due puntatori di inizio e fine, e' possibile salvare anche qualcosa di diverso dal programma basic , o un programma Basic che (sempre per nostra scelta) non inizia a 2049.

Volendo ad esempio salvare su disco il contenuto della Rom dell'interprete Basic, locata tra 40960 e 49151, non dovremo far altro che  posizionare il puntatore di inizio a 40960, quello di fine a 49151 e digitare SAVE "ROM BASIC",8. Per posizionare i due puntatori, poniamo:

 POKE 44,160:POKE 43,0:POKE 46,192:POKE 45,0

 Assieme al programma vero e proprio (anzi, per l'esattezza: a tutto quanto compreso tra i due puntatori di cui sopra) il 64 provvede a memorizzare sulla periferica anche il puntatore di inizio. Se per rileggere il programma useremo un normalissimo LOAD, il programma sara' caricato a partire dalla corrente locazione puntata da 44 e 43 (e' ignorata la locazione di inizio salvata insieme al "malloppo"). Se si usa l'istruzione LOAD nel formato:

 LOAD "Nome Programma",Periferica,1

 il programma verra' posizionato nella stessa zona di memoria dalla quale era stato salvato. Resta inteso che avendo salvato un normalissimo programma Basic, usare il LOAD in uno dei due formati non cambia nulla, sempreche' non sia stato spostato volutamente, il puntatore di inizio.

 Conoscendo come avviene il trasferimento di programmi su periferica, possiamo gia' cominciare a fare qualcosa di piu' contorto: Salvare in un solo colpo un programma Basic e uno o piu' programmi in linguaggio macchina adoperati dal primo con il comando SYS. Non dovrebbe essere molto difficile intuire il trucchetto: una volta digitato il programma Basic, si interroga il puntatore di fine (45 e 46) per conoscere l'ultimo byte occupato dal programma. Di seguito a questo, posizioneremo le nostre routine in linguaggio macchina, avendo l'accortezza, terminata questa operazione, di spostare il puntatore di fine oltre lo spazio occupato dal l.m. Salvando, salveremo tutto, ne' ci saranno problemi con l'uso di variabili: in ogni caso Basic e l.m. conviveranno amichevolmente insieme. Unica limitazione e' data dal fatto che non e' possibile inserire nuove linee Basic senza "pestare" le routine. Quindi inserire il l.m. a pie' del basic solo quando si e' certi che non si dovranno apportare modifice al programma originale.

 I comandi del disco

 Piu' che una semplice periferica, il 1541 e' un vero e proprio computer (con tanto di possibilta' di programmarlo). La sua stessa struttura lo grida a gran voce: Microprocessore 6502, due VIA (processori di I/O) 6522, 16 K Rom contenenti un proprio sistema operativo, 2 K di Ram per inserire mini routine o da usare come buffer. Accoppiato al 64 forma di fatto un sistema multi-processo molto flessibile. Ad esempio, e' possibile mandare in esecuzione un programma, mentre il disco formatta un minifloppy; cosa che non accade per quei computer (leggi Apple e molti dei piu' grandi) che caricano da disco il DOS nella memoria utente, gravando il microprocessore della macchina di gestire anche l'unita' a dischi.

   Fra 64 e disco, si stabiliscono veri e propri contatti verbali. Quando e' richiesto un programma, il 64 chiede al disco di passarglielo. Quest'ultimo, controlla se e' presente sul dischetto (in caso contrario comunica:"capo..., non c'e'"), provvede a cercarlo tra le tracce e quando l'ha trovato inizia a trasferirlo byte dopo byte. Al termine di questa operazione, l'unita' centrale ringrazia e dice: "a risentirci !". Da questo momento il disco resta in religioso silenzio ad aspettare un nuovo ordine di "sua eccellenza".

   Per dialogare con il disco, cosi' come per la stampante e ogni altra periferica intelligente (l'unita' a nastri e' scema !), e' necessario aprire un canale di comunicazione. Per il disco, il canale da aprire e' il n.15 e, l'apertura, avviene con il comando OPEN, specificando un parametro di riferimento da usare per le successive operazioni di PRINT#, il numero della periferica (in genere 8) e 15 che comunica al disco che vogliamo dialogare. Quindi:

 OPEN 1,8,15

 e' la prima operazione da compiere.

 A questo punto, col comando PRINT#1 possiamo spedire al driver i nostri messaggi. Per accogliere eventuali messaggi del disco, sempre dopo aver stabilito la comunicazione, bastera' eseguire da basic un INPUT#1,A,B$,C,D. "A" conterra' il numero del messaggio; "B$" il messaggio vero e proprio; "C" e "D" la traccia e il settore (quando serve) dove si e' verificato l'inconveniente di "B$". Cio' proprio perche' la maggior parte dei possibili messaggi (56 in tutto) riguarda errori. Se nel corso di una operazione la spia rossa (sembra quasi un caso di spionaggio sovietico) inizia a lampeggiare, si e' verificato un errore. Conviene "sentire" il driver per saperne di piu'. I comandi accettati dal disco sono in tutto 6 e si spediscono al driver tramite l'istruzione PRINT#, specificando tra apici il comando e i parametri dello stesso. Senza entrare nei dettagli di tutti, diamo la descrizione di quelli meno digeribili dall'utente col solo ausilio del tremendo manuale di istruzioni.

 Il comando VALIDATE, serve per fare pulizia sul disco di tutte le cose inutili. Per esempio, file aperti e non richiusi, programmi mal salvati (apertura dello sportellino prima del termine dalla operazione) e altro. Il comando NEW, da non confondere col corrispondente gemello Basic, serve per formattare o riformattare un disco. Se infatti questo e' gia' stato formattato precedentemente, non specificando l'identificatore avremo un formattazione della sola directory, con conseguente risparmio di tempo. A proposito di identificatore: non tutti sanno che l'ID e' importantissimo, e deve categoricamente essere diverso per ogni dischetto formattato; pena innumerevoli casini, con tanto di perdita di file, usando piu' dischetti con lo stesso ID, di seguito.

 Altro comando interessante e mal spiegato sul manuale, e' il comando COPY. Serve per due scopi: fare una copia sullo stesso dischetto di un qualsiasi file (PRG o SEQ) gia' presente, o unire piu' file sequenziali in un unico nuovo file, somma dei precedenti. La sintassi e' la seguente (nei due casi):

 PRINT#1,"COPY:NuovoFile=VecchioFile"

 PRINT#1,"COPY:NuovoFile=VecchioFile1,VecchioFile2"

 sapendo che NuovoFile e' il nome che avra' il file copia e che VecchioFile e' il nome di un file gia' presente sul dischetto. Si possono riunire in un unico file fino a 4 file sequenziali gia' esistenti.

 Trattamento dei file

 Una delle possibili traduzioni dall'inglese della parola file, e' cartellina, raccoglitore. Potremmo dire che come termine e' abbastanza azzeccato: un file e' un insieme di dati, opportunamente raccolti.  

Esistono due tipi di file, a struttura sequenziale e ad accesso diretto. Sequenziale vuol dire che possiamo recuperare i dati solo e soltanto nello stesso ordine in cui l'abbiamo inseriti: inoltre lo stesso caricamento dei dati su memoria di massa avviene sequenzialmente, si puo' inserire la decima registrazione solo dopo aver inserito le prime 9.

Usando un file ad accesso diretto, si puo' accedere ad una qualsiasi registrazione dell' archivio, per leggerla o modificarla, semplicemente specificando la posizione o una chiave che identifichi univocamente un determinato dato. Esistono poi altre organizzazioni piu' complesse, quali l'organizzazione ad albero o le cosiddette organizzazioni ISAM (Indexed Sequential Access Metod), che riguardano pero' la gestione di milioni e milioni di dati simultaneamente, quindi non fa al caso nostro (leggi non ce ne frega niente, a malincuore).

Il driver 1541 consente, a livello di hardware, la gestione di file sequenziali e relativi. I file relativi sono ad accesso diretto: relativo vuol dire che e' sufficiente specificare la posizione relativa (all'interno del file) della registrazione cercata.

Per quanto concerne il trattamento di archivi sequenziali, c'e' davvero poco da dire: e' una delle poche cose ben spiegate sul manuale. Qualche consiglio, comunque, non guasta mai. Per prima cosa, e' bene parlare col disco, solo in termini di stringhe e non di interi, reali e stringhe. Prima di spedire un dato in un file, conviene convertirlo in stringa con l'istruzione STR$. Allo stesso modo, in lettura, otterremo stringhe, eventualmente da riconvertire in interi o in reali a seconda del caso.

Come separatore, fra un dato e il successivo, e' bene usare un CHR$(13), corrispondente a un [RETURN]. Tutte le stringhe potranno essere riversate sul disco, separandole con un CHR$(13), ma concatenandole con il punto e virgola. Per intenderci:

PRINT#2,A$+CHR$(13);

inserira' la stringa contenuta in A$, e lasciera' il file gia' pronto a ricevere la successiva stringa, con un comando simile. Per ripescare la registrazione, il comando INPUT#2,A$ non fallira' un colpo: il [RETURN] come separatore funziona molto bene. Viene preso il dato voluto e il file resta pronto per la successiva richiesta con analogo comando. E' possibile inoltre sapere se la registrazione letta e' l'ultima dell'archivio, semplicemente interrogando la variabile ST. Questa, come la TI e la TI$, sono variabili di sistema, e quindi riservate per usi specifici. ST conterra' il numero 64 solo quando avremo letto l'ultimo dato di un file. I programmini 1 e 2 mostrano un esempio di creazione di file sequenziale, e di lettura dello stesso.

 E veniamo ai nostri tanto amati file relativi. Per adoperarli, le operazioni da compiere sono due: la creazione del file e il caricamento dei dati. Quando si crea un archivio relativo, bisogna specificare al driver un nome e la lunghezza massima di ogni registrazione. Per inserire dati nel file, e' sufficiente specificare quale posizione occupera' il dato, nonche' il dato stesso.

 Supponiamo di dover creare un archivio indirizzi, che per semplicita' supporremo formato da tante stringhe contenenti le varie informazioni (nome,cognome,via,citta',telefono) codificate alla maniera sequenziale: campi concatenati, separati da CHR$(13). Diciamo che 200 caratteri per ogni dato dovrebbero bastare. La prima cosa da fare, e' stabilire la comunicazione col disco. A tal scopo, eseguiremo un:

 OPEN 1,8,15

 Per creare il file bastera':

 OPEN 4,8,4,"INDIRIZZI,L,"+CHR$(200)

 il primo 4 e' un parametro di riferimento: serve per riferire a questo file, le successive operazioni di PRINT# . 8 e' l'unita' a dischi; il secondo 4 e' il canale di servizio che il disco adoperera' per ricevere e restituire dati. Fra apici il nome del file e la specifica che si tratta di un archivio relativo ("L"); infine, sotto forma di CHR$, la lunghezza di ogni registrazione. Non appena dato il secondo OPEN, per motivi non troppo chiari, il driver segnala un errore, che la Commodore stessa consiglia di ignorare: la spia inizia a lampeggiare e l'unico modo per farla tacere e' eseguire un INPUT#1,A,B$,C,D per leggere il messaggio del disco. Per inserire una registrazione, bisogna specificare la posizione. Cio' avviene utilizzando il canale di "dialogo" col disco, con il comando PRINT#1. Se ad esempio, vogliamo occupare la quinta posizione, comunicheremo un:

 PRINT#1,"P"CHR$(4)CHR$(5)CHR$(0)

 la "P" indica che stiamo dando la posizione; il 4 in forma di CHR$, specifica il canale adoperato dal nostro file (l'abbiamo specificato al momento della creazione); di seguito la posizione, codificata con due byte, essendo possibili piu' di 256 posizioni diverse. Come per il linguaggio macchina, la posizione effettiva e' data moltiplicando per 256 il secondo valore e sommandoci il primo. Nel nostro caso, 0*256+5 e' appunto la quinta posizione; volendo occupare la 600-sima avremmo dovuto scrivere CHR$(88)CHR$(2), dato che 2*256+88 = 600. Creato il nostro file, possiamo accedere alle singole registrazioni sia per scriverle che per leggerle. Dopo il posizionamento, eseguendo un PRINT#4,A$ salveremo sul disco il valore di A$; eseguendo INPUT#4,A$ preleveremo da disco l'informazione contenuta nel record (registrazione) sulla quale ci siamo posizionati. Per maggior sicurezza, dato che a volte il 1541 fa un po' di confusione, e' bene specificare, prima della lettura, oltre alla posizione relativa al file, anche la posizione relativa al record: quale byte sara' letto per primo (presumibilmente il n.1). La specifica della posizione avviene cosi':

 PRINT#1,"P"CHR$(4)CHR$(5)CHR$(0)CHR$(1)

 supposto che eravamo interessati alla quinta registrazione. Come esempio di gestione archivio relativo, e' stato preparato il programma RELARCH (listato 3). L'accesso avviene specificando una chiave, non la posizione relativa. Per realizzare cio' abbiamo adoperato un file ausiliario di tipo sequenziale denominato INDICE.

 Dando RUN al programma, appare il menu': 4 in tutto le opzioni; creazione, lettura, modifica e ... EXIT per uscire. Anche se solo un esempio, questo programma si presta molto bene per parecchi scopi, forse proprio per la sua eccessiva semplicita'. Quando si crea un archivio, bisogna, per ogni registrazione, specificare una chiave (diversa per ognuna) e il testo (max. 80 car.) da associare alla chiave. Ad esempio, se vogliamo un archivio indirizzi, potremmo usare come chiave il nome e cognome  e come testo tutto il rimanente: via, telefono, citta', etc. Per recuperare una registrazione, da menu' si va in lettura, e alla richiesta CHIAVE, si indica il nominativo voluto. Per tornare al menu' basta rispondere [RETURN] a qualsiali richiesta di chiave. E' possibile inserire al piu' 256 record.

 I comandi U1 e U2.

Il driver 1541 ha anche la possibilita' di leggere e/o modificare un qualsiasi settore del dischetto. Grazie a questo, sono possibili vari trucchetti, alcuni usati anche per proteggere programmi da copie illecite. In questa sede ci occuperemo di come cambiare il nome a un dischetto, senza riformattarlo, ossia senza perdere i programmi presenti. Per leggere un blocco, esiste un apposito comando detto User1 (abbr. "U1"). Tanto per cambiare, anche in questo caso, la prima cosa da fare e' stabilire una comunicazione col disco, quindi:

OPEN 1,8,15

Oltre a questo, abbiamo bisogno di un canale e di un buffer per trasferire i 256 byte di un settore. Per far questo e' sufficiente impartire il comando:

OPEN 4,8,4,"#"

in questo caso, il canale scelto e' il 4 e il buffer allocato, sara' scelto dal 1541, arbitrariamente (a noi, comunque, non importa). Per trasferire il blocco nel buffer, si impartira' il comando:

PRINT#1,"U1:"4;0;traccia;settore

Il 4 indica il canale, lo 0 il n.driver, di seguito si specificano la traccia e il settore interessati. In figura 2, sono riportati il n. di settori per le varie tracce. A questo punto, per accedere ai byte del blocco basta eseguire 256 volte il comando GET#4,A$ tenendo presente che A$="" indica che e' stato letto un CHR$(0). Il programma 4 mostra come e' possibile leggere un blocco qualsiasi. Analogamente si puo' scrivere un blocco, semplicemente caricando il buffer con 256 caratteri e impartendo il comando U2, nello stesso formato di U1.

Per cambiare il nome e l'ID di un dischetto, basta leggere il blocco 0 della traccia 18 (dove stanno memorizzati), apportare le dovute modifiche e riscrivere il blocco, s'intende nella stessa posizione. Il programma 7 fa appunto questo: permette di cambiare nome e ID di un dischetto.

Il disco da L.M.

Chi si occupa almeno un po' di linguaggio macchina, avra' certamente sentito, prima o poi, la necessita' di accedere all'unita' a dischi. Per fare cio' si ricorre alle routine Kernal del sistema operativo. Sono un inseme di routine, direttamente richiamabili dall'utente col comando JSR, che riguardano appunto l'I/O: e' impensabile che qualcuno se le costruisca in proprio, quando sono gia' belle e fatte da mamma Commodore.

Anche da L.M. per accere al disco bisogna aprire canali e file. Per aprire un file, bisogna innanzitutto scrivere in qualche zona di memoria il nome del file, come insieme contiguo di byte, rappresentanti i codici Ascii dei caratteri di cui e' formato il nome. Le routine interessate all'apertura di un file sono tre e si trovano agli indirizzi $FFBA, $FFBD e $FFC0.

La prima, setta i tre parametri di una OPEN: il n. del file, il n. della periferica e l'indirizzo secondario (0 se non bisogna specificarlo). La seconda, serve per specificare dove e' stivato il nome del file e quanto  e' lungo (lunghezza 0 se non c'e'). Se vogliamo da L.M. eseguire un

 OPEN 3,8,3,"ARCHIVIO,S,R"

bastera' dapprima scrivere da qualche parte il nome (es. a $C243) e usare la sequenza:

LDA #$03

LDX #$08

LDY #$03

JSR $FFBA

LDA #$0C

LDX #$43

LDY #$C2

JSR $FFBD

JSR $FFC0

Prima di JSR $FFBA, si fissato in A il numero file, in X il device, in Y l'indirizzo secondario (il canale, nel nostro caso). Prima di JSR $FFBD abbiamo fissato in A la lunghezza del nome ($0C e' 12) e nei registri X e Y la posizione del primo byte del nome. JSR $FFC0 apre, di fatto, il file. Per trasferire o prelevare dati dal disco (sempre un byte alla volta) esistono due apposite routine: $FFD2 e $FFCF entrambe utilizzano l'accumulatore per il trasferimento. Prima di utilizzarle, pero', e' obbligatorio specificare se il file appena aperto e' di input o output. Le routine interessate sono rispettivamente $FFC6 e $FFC9 e prima di invocarle, bisogna mettere in X il numero del file cui si riferiscono (potremmo aver aperto piu' file !).  

Nel caso si abbia a che fare con file sequenziali, per interrogare la variabile ST, si puo' usare la routine $FFB7 che restituisce in A il valore dello status (quando e' 64, l'ultimo carattere del file e' stato letto). Terminate le operazioni di lettura o scrittura, per chiudere canale e file, si utilizzano le routine $FFCF (resetta tutti i canali di input e di output) e $FFC3 (chiude il file specificato nell'accumulatore).  

Leggiamo la directory. 

Abbiamo gia' visto alcuni usi dell' indirizzo secondario, specificato all'atto dell'apertura di un file. Anche i programmi possono essere trattati come dei file, anzi per l'esattezza, sono dei file di tipo programma. Per aprire un file di tipo PRG si usa come indirizzo secondario 0 o 1 a seconda che si voglia leggere o scrivere: c'e' una netta corrispondenza con il LOAD e il SAVE vero e proprio. Di fatto, quando si salva un programma, il 64 apre un file di tipo PRG (con indirizzo secondario 1) e riversa sul disco tutto quanto contenuto fra i due puntatori descritti all'inizio dell'articolo.

Volendo leggere un programma come se fosse un file, la prima operazione da compiere e':  

OPEN 4,8,0,"NomeProgramma"

successivamente, ogni GET#4,A$ restituira' un carattere del programma, ricordando che A$="" e' CHR$(0) e che i primi due byte trasferiti sono il puntatore all'inizio area Basic dalla quale era stato salvato il programma.

Con questo stesso metodo, e' possibile leggere la directory di un dischetto, semplicemente usando il comando:

OPEN 4,8,0,"$"

 e ripetere GET#4,A$ fintantoche' ST e' diverso da 64. Il programma 5, AUTOCATALOG, serve per formattare (se si desidera) un dischetto e inserire su di esso un programma "CATALOG" che semplifica l'operazione di selezione programma da caricare. Una volta inizializzato il minifloppy con questa utility, digitando LOAD "CATALOG",8 e dopo il caricamento RUN, vedremo scorrere sul video la directory. Alla fine un punto interrogativo e il cursore lampeggiante segnala che si e' pronti a procedere: per selezionare un programma, e' sufficiente salire col cursore sul nome prescelto e battere [RETURN]. Per interrompere lo scroll prima del termine (per directory piu' lunghe di una schermata) basta premere la barra spaziatrice: per rivedere il tutto bisogna battere [RETURN] quando il cursore e' accanto al punto interrogativo. Giuro che e' molto facile: provare per credere.


Impaginato originale...


  Clicca per ingrandire...
Clicca per ingrandire... Clicca per ingrandire...
Clicca per ingrandire... Clicca per ingrandire...
Clicca per ingrandire...  

Articolo pubblicato su www.digiTANTO.it - per ulteriori informazioni clicca qui