b Acceso directo a los Microdrives (II) Por R. Zonin y D. Bolla Despues de haber visto el modo para leer los microdrives en modo directo, ahora veremos como escribir directamente. A quien se ha perdido la primera parte, recomendamos que lea el numero 12. Escritura en acceso directo Cuando nos topamos con el problema de la escritura en directo por primera vez, pensamos que seria indispensable recurrrir al lenguaje maquina; resulto ser falso y estamos en grado de presentarles una serie de rutinas todas escritas en Basic, y que son facilmente comprensibles aunque se necesita de un minimo de conocimiento del sistema de E/S del Spectrum, y en particular de las variables del sistema del Interface 1. Todo eso fue comentado y publicado en el numero 12. Sabes ya (y si no lo sabias, te lo decimos ahora) que el ZX 8pectrum, por cada canal abierto crea un mapa, en el cual mete toda la informacion relativa a cada canal, para los canales asociados al microdrive se crea tambien un mapa de la cinta, que contiene cuales son los sectores libres y cuales los que estan ocupados. Ya has notado en este punto donde esta el truco: para escribir "directo" a un sector necesitamos de: a) poder manipular el mapa del micodrive en modo tal que engañe al Spectrum cuando va a escribir los datos, por ejemplo haciendo que parezca libre un sector ocupado; b) saber cuales son los flags (indicadores); literalmente banderas) que muestran si un fichero esta abierto en modo de escritura o lectura; c) saber cual es el numero de sectores en el cual se ha memorizado un bloque de fichero. Vemos aqui como esta organizado el mapa del microdrive y como necesitamos manipularla para obtener eso que queremos. El mapa se compone de 32 bytes que estan a partir de la direccion 23792 (para un solo canal abierto). Cada bit solo corresponde a un sector en cinta: el bit 1 del byte 1 corresponde al primer sector, el bit 2 al segundo, el bit 1 del byte 2 al sector 0, y asi. Si un bit es puesto a 1, el sector correspondiente es ocupado; en caao contrario esta libre. En total se pueden individualizar 32*8=256 sectores. Modificar un fichero Comenzamos examinando el caso mas simple de escritura directa, y es la modificacion de cualquier sector de un fichero. Debemos hacer creer al Spectrum que el sector que queremos modificar sea libre y que sea el unico sector en el cual puede escribir. Para modificar un bloque debemos saber en cual sector se ubica (atencion a no hacer confusion con el numero del sector de cinta donde esta registrado). La informacion que nos sirve esta en la posicion HDNUMB (byte 41 del canal), que indica cual es el sector en el cual se registra el bloque en curso. Una vez notado este dato no es dificil crear una rutina que libera el sector interesado. Esta rutina, que se encuentra en las lineas de 9000 a 9040, recive como dato el numero del sector y POKEa en el byte correspondiente del mapa, el valor deseado. A este punto debemos solamente convertir el fichero da lectura a escritura y viceversa. Nada mas facil, el flag que indica si un fichero esta abierto en lectura o escritura se encuentra en la variable del sistema CHFLAG (byte 24 del canal), para habilitar la escritura basta agregar un POKE 23844+24,255 y para la lectura un POKE 23844+24,254. Se debe tener presente empero que tanto la lectura como la escritura son manejados por la variable CHBYTE (11/12), que apunta al proximo dato a leer o escribir. Eso quiere decir que si hay un fichero abierto en lectura y lo commutamos a escritura, escribira en el fichero a partir del dato apuntado de CHBYTE. Esto no provoca problema alguno en el caso que estamos tratando (modificar un fichero); de hecho la modificacion del fichero se da en 3 fases: lectura directa del sector buscado, escritura en RAM del nuevo dato en lugar del anterior, escritura directa del bloque de datos modificado en el sector de la cinta ocupado por viejos datos. Añadir datos a un fichero Supongamos ahora que no nos baste con sustituir viejos datos oon nuevo datos, y que nuestro objetivo sea añadir datos a un fichero, el metodo a seguir es practicamente el mismo al de la reescritura, solo que es un problema nuevo que afrontar: donde vamos a escribir? cuando modificamos el mapa del microdrive, pusimos todos los sectores a 1 y luego liberamos lo que nos interesa; al hacer eso terminamos peerdiendo de vista cuales eran los sectores realmente libres y cuales los ocupados, para evitar este inconveniente, hay que modificar primero el mapa original y conese fin debemos crear un mapa alternativo, que permita recordar tanto los sectores libres como los ocupados (por el fichero o el programa). De esto se ocupa la rutina que se encuentra en las lineas 8000 a 8040. El mapa alternativo se guarda en una matriz p. Entre parentesis, recordamos que los sectores del microdrive son numerados desde 0 en adelante, mientras las matrices en el Spectrum tienen como indice inferior 1; esto explega los +1 o -1 presentes en las rutinas del programa. La convencion que se usa en este programa y que el primer sector sea el 1: hay que tenerlo presente cuando se lancen por vuestra cuenta. Retomando nuestro fichero, si queremos añadir un bloque de datos, primero cargamos en memoria aquello que en realidad es el ultimo bloque del fichero. Modificamos la variable RECFLG (byte 67) y tras repetir el experimento, nos resulta ser simplemente un identificador de final de fichero, poniendolo a 0, de este modo el bloque que era de fin de fichero se vuelve para el sistema igual a los otros bloques (el valor 2 en RECFLG indica que este bloque es el ultimo del fichero). Y para finalizar rescribiamos el bloque. Veamos con detalle las operaciones requeridas para rescribir un sector: a) modificar el mapa b) pasar de lectura a escritura c) indicar que el bloque ya esta lleno con POKE 23844+13,3 d) digitar PRINT#4;" " A este punto el drive se activara y escribira el bloque de datos en el sector elegido, ahora en memoria habra un bloque vacio; escribe lo que quieras (es decir con PRINT#4;"..."); y cuando ya hayas terminado, POKEas en RECFLG el valor 2, elije en el mapa especial un sector libre y repite el proceso de salvar. Explicacion de las rutinas Como ya notaron el programa presentado esta en buena parte estructurado en subrutinas. Se ha hecho asi con el fin de hacerlo facilmente legible y modificable. 1) Copiado del mapa del microdrive, cubre la labor de copiar el mapa y reproducirlo en una matriz de 256 elementos. El procedimiento no es particularmente complicado; puede ser interesante la parte que hace la convercion de numeros de decimal y binario: para guardarla. 2) Transformacion de lectura a escritura. Aqui tampoco hay nada complicado: se trata de saber cuales son las posiciones correctas y el valor justo a escribir con POKE. Notese que para cada rutina viene definida una variable que apunta segun sea el caso, al mapa del micodrive o al punto de inicio de la informacion relativa al fichero en cuestion (es posible de hecho tener mas ficheros abiertos a la vez con mapas en diversas posiciones de memoria). 3) Trasformacion de escritura a lectura. Y todo como la rutina precedente, cambia solo el valor del POKE. 4) lectura del numero del sector en el cual se registra el bloque de datos que tengamos en memoria. Esto va unido a la variable (s), que solo sirve para que sea una unica convencion de numeracion de sectores. La variable (s) es aquella que es pasada a la rutina que libera el sector que nos interesa. 5) Insercion de EOF (End Of File). Sirve solo en el caso de escribir un sector que es el ultimo del fichero, y no es obligatorio usarla. Pero si no se hiciese, sepan que el ZX Spectrum considerara aquel fichero como no cerrado, con la perdida de eficiencia en ciertas funciones (como por ejemplo DELETE). 6) Cancelacion de EOF. Esta es llamada regularmente en la lectura directa; cuando lee datos del ultimo sector, y llega al final del mismo; si se quiere leer un sector precedente el computador escribira el fichero y lo terminara tambien si se trata de modificar la variable para proseguir. El Spectrum se comporta asi viendo en RECFLG un 2: basta transformar este 2 en un 0 y todo vuelve a funcionar regularmente. 7) escritura en el fichero del bloque en curso en el sector justo. Al preparar el sector pensamos en las otras rutinas. Esta no hace otra cosa que hacer creer al computador que el buffer esta lleno: en consecuencia hace que escriba en el fichero todo eso que esta en el buffer. 8) lectura de un sector. Esta es una rutina que tambien hemos visto ya antes, y que igualmente debe "engañar" al computador, para que crea que ya ha terminado de leer el bloque en curso y que debe pasar al que le sigue. 9) Liberacion de sector. Esta es totalmente nueva y serve para poner a 0 (libre) el bit que indica el sector apuntado por la variable (s). No es tan complicada, basta tener presente como esta organizado el mapa del microdrive y recordar que un sector esta ocupado si el bit correspondiente es 1. 10) escrive 1 en el mapa dei microdrive. Esta es quiza la rutina mas banal: pone a uno todos los bits del mapa del microdrive. Siempre es usada antes que la rutina 9, como una medida cautelar. 11) busqueda. Implementa la parte final de la lectura directa (ver Sinclair C. n.12). Requiere el ingreso del numero del bloque y la posicion del dato, enseguida va y lee el dato mismo. 12) pone a 512 el numero de caracteres insertados. Su fin es el de regularizar el puntero del buffer, que en algunos casos pierde el control a causa de nuestros fallos... 13) Controla la longitud de la cadena a insertar en el buffer. Si la cadena fuese muy larga que superase los limites, sera descartada y aparecera el mensaje "illegal quantity ERROR". Esta rutina sirve aqui para evitar escribir datos a caballo de dos sectores de cinta. Eso si es muy necesario sobre todo porque las rutinas operan sobre un solo sector por vez. No se trata mas que de de una limitacion grave: en el uso normal los registros logicos a escribir son dificilmente mas largos de 512 bytes; pero, hay que tener presente que para la mejor explotación de la memoria de sectores se ha empleado registros logicos cuyas dimensiones son un submultiplo de la dimension del bloque fisico. 14) escribe en el buffer. Primero habiamos dicho que se puede escribir usando simplemente PRINT#4, con el que a su vez teniamos algunos problemas, porque actualiza los oontadores de los datos insertados. Debido a eso incluimos esta rutina, que POKEa la cadena D$ directamente al buffer. El input tramita esta rutina y es la que preferira. 15) escribe en la cinta. Solo una secuencia de GOSUB, y sirve para simplificarle la vida a quien escriba los sub programas incluyendo la de acceso directo: de hecho lleva a cabo auto maticamente todas las operaciones necesarias para escribir fisicamente el buffer en la cinta del microdrive. Ahora ya tenemos toda la informacion necesaria para crear nuestra base de datos, utilizando los microdrive casi como si fueran discos. En la practica, no tendremos limitaciones en cuanto a como debe ser constituida una base de datos, en el sentido de que los datos no estan mas vinculados a la memoria del Spectrum. Tras haber trabajado todo este tiempo con los microdrives, pensamos que el mayor defecto no este en la realizacion o en el funcionaimento sino en el precio de los cartuchos. Ejemplos de como usarlos Los tres programas cortos que invocan a las subrutinas son un primer ejemplo ilustrativo de como se pueden utilizar todas ellas. Todos estos tres se deben ejecutar tras asegurarse de no haber dejado en la memoria nada que lo afecte. Programa Uno El primer programa es el mas simple, y un ejemplo demostrativo y nos guia totalmente. Para comenzar, crea un fichero compuesto de caracteres "#", y nos muestra los primeros 100. Luego nos pide escribir qualquier cosa, para enseguida pulsar ENTER. Si lo que hayamos tipeado nos basta como prueba, pulsa de nuevo ENTER. Si ya no vamos a insertar mas caracteres tipeamos ENTER. El computador escribira en el fichero, a partir del caracter 101 la cadena que hayamos insertado; luego pasa a lectura y nos muestra todo el fichero con las modificaciones que hemos hecho (tratemos de no exagerar con el numero de caracteres para no incurrir en inconvenientes). Programa Dos El segundo programa es fundamentalmente similar al primero, pero seras tu quien escoja donde escribir. Tambien aqui les damos una recomendacion: no tratar de escribir en el ultimo sector generado por el programa (el tercero, en este ejemplo), si aparece el mensaje "illegal quantity ERROR" no preocuparse, solo tenemos que introducir una cadena mas corta. Programa Tres El tercero es el mas complejo y versatil. Prevee tambien la posibilidad de añadir un nuevo sector al fichero, esta funcion es por eso llamada APPEND, una rutina algo oompleja: para comprender el funcionamiento hay que examinar el listado tomando en cuenta las explicaciones que hemos dado a las rutinas. Enseguida, de forma breve como funciona la parte de las lineas 20 a la 70. 20 abre el fichero en lectura; analiza el mapa del microdrive; coge la informacion necesaria. 25 controla si el bloque en curso es un bloque de fin de fichero (recordar la descripcion de las variables de sistema). 26 "pule" todos los bloques del fichero y devuelve el control a la 25 28 fija el numero de bytes del bloque en curso 29 cancela EOF 30 escribe el bloque asi modificado en la cinta 40 reinicia el numero de datos del bloque 50 inserta EOF 55 inicializa una variable ficticia 60 oontrola la matriz que contiene la informacion relativa al mapa y al prlmer sector libre, y lo asigna a la variable de canal adecuada. 70 oontinua hasta cuando es sotisfecha la condicion. Una vez encontrado el sector libre se pasa a la rutina de escritura para insertar los nuevo datos en el nuevo bloque. Conclusiones: Debemos notar que estos ficheros, creados en modo directo, son comunmente leibles serialmente con PRINT INKEY$#4; esto es por que nuestro sistema de gestion directo no altera la estructura fisica del fichero, sino solo el modo de acceder a ella, engañando al sistema operativo. Esperen la tercera parte, en la cual describiremos un ejemplo practico del uso de estas subrutinas: una base de datos que explota plenamente todas las posibilidades del microdrive. Microdrives: rutinas de acceso directo 7997 STOP 7998 REM rutinas de escritura directa con MICRODRIVES 7999 REM (C) 1985 ZONIN & BOLLA para SYSTEMS EDITORIALE 8000 REM 1=ocupado 0=libre 8002 DIM p(256) 8003 LET m=23792 8005 FOR x=0 TO 1 8006 LET d=PEEK (x+m) 8010 FOR c=1 TO 8 8015 LET p(c+(x*8))=d/2<>INT (d/2) 8020 LET d=INT (d/2) 8025 NEXT c 8030 NEXT x 8040 RETURN 8048 8049 8050 REM transf. a escritura 8060 LET can=23844 8070 POKE 24+can,255: RETURN 8077 8078 8079 REM transf. a lectura 8080 LET can=23844 8090 POKE 24+can,254: RETURN 8098 8099 8100 REM lectura del record 8110 LET can=23844 8120 LET s=1+PEEK (can+41) 8122 REM +1 es para la matriz 8130 RETURN 8140 8141 8150 REM ins. EOF 8160 LET can=23844 8165 POKE can+67,2: RETURN 8168 8169 8170 REM cancela EOF 8175 LET can=23844 8180 POKE can+67,0: RETURN 8190 8191 8200 REM goto print file 8205 LET can=23844 8210 POKE can+12,1: POKE can+11,255: PRINT #4;" ";: RETURN 8220 8221 8250 REM goto read file 8255 LET can=23844 8260 POKE can+12,3: LET d$=INKEY$#4: RETURN 8290 8291 9000 REM liberar sectores 9001 REM busqueda var(s) 9005 LET m=23792 9006 LET sm=s-1 9010 LET s1=m+INT (sm/8) 9020 LET s2=255-2^(sm-8*INT (sm/8)) 9030 POKE s1,s2: RETURN 9048 9049 9050 REM generador de 1 9060 LET m=23792 9070 FOR c=m TO m+31 9080 POKE c,255 9090 NEXT c 9095 RETURN 9098 9099 9100 REM buscador de dato 9101 REM N. bloque (b) 9102 REM y N. del dato (d) 9105 LET can=23844 9110 POKE can+13,b-1 9115 GO SUB 8250 9120 POKE can+11,d-256*INT (d/256) 9130 POKE can+12,INT (d/256) 9140 RETURN 9148 9149 9150 REM fija n. datos a 512 9160 LET can=23844 9170 POKE can+69,0: POKE can+70,2 9174 RETURN 9175 9176 9200 REM CONTROLLA Longitud 9210 LET CAN=23844 9220 LET chbyte=(256*PEEK (12+can))+PEEK (11+can) 9240 IF (chbyte+LEN d$)>512 THEN PRINT "illegal quantity ERROR": LET d$="" 9250 RETURN 9298 9299 9300 REM escribe en el buffer 9310 LET can=23844 9320 LET chbyte=(256*PEEK (12+can))+PEEK (11+can) 9330 LET ld=LEN d$ 9340 LET start=can+82+chbyte 9350 FOR c=start+1 TO start+ld 9360 LET d=CODE d$(c-start) 9370 POKE c-1,d 9380 NEXT c 9385 LET sum=chbyte+ld 9390 POKE can+11,sum-256*INT (sum/256) 9395 POKE can+12,INT (sum/256) 9396 RETURN 9398 9399 9400 REM PROC. WRITE 9410 GO SUB 8050: REM escritura 9421 GO SUB 9050: REM gen 1 9430 GO SUB 8100: REM pos. reco 9440 GO SUB 9000: REM lib. rec. 9450 GO SUB 8200: REM go print 9460 GO SUB 8079: REM lectura 9470 RETURN 0>REM *** programa UNO *** 1 LET a$="############################################# #######################################################" 2 OPEN #4;"m";1;"fichero" 3 FOR x=1 TO 15: PRINT #4;a$; 4 NEXT x 5 CLOSE #4 7 8 9 OPEN #4;"m";1;"fichero" 10 FOR x=1 TO 100 15 PRINT INKEY$#4; 20 NEXT x 30 31 60 PRINT ';"ESCRIBA LO QUE QUIERA, LUEGO PULSE ENTER DOS VECES" 70 INPUT "escribe: ";d$: IF d$="" THEN GOTO 80 74 GO SUB 9200 76 GO SUB 9300 78 GOTO 70 80 GO SUB 9400: REM write 90 CLOSE #4 100 OPEN #4;"m";1;"fichero" 110 PRINT INKEY$#4; 120 GOTO 110 0>REM *** programa DOS *** 1 LET a$="############################################## ######################################################" 2 OPEN #4;"m";1;"fichero" 3 FOR x=1 TO 15: PRINT #4;a$; 4 NEXT x 5 CLOSE #4 7 8 9 OPEN #4;"m";1;"fichero" 10 PRINT"Pulsa s cuando quieras parar" 12 PRINT INKEY$#4; 15 IF INKEY$="s" THEN GOTO 60 17 PAUSE 3 20 GOTO 12 30 31 60 PRINT '"escribe lo que quieras y pulsa ENTER dos veces" 70 INPUT "tipea: ";d$: IF d$="" THEN GOTO 80 74 GO SUB 9200 76 GO SUB 9300 78 GOTO 70 80 GO SUB 9400: REM write 90 CLOSE #4 100 GOTO 9 0>REM *** programa TRES *** 1 LET a$="############################################# #####################################################": LET can=23844 2 OPEN #4;"m";1;"fichero" 3 FOR x=1 TO 15: PRINT #4;a$; 4 NEXT x 5 CLOSE #4 7 8 10 INPUT "desea modificar o insertar un nuevo bloque ? (m/i) ";d$ 15 IF d$="m" THEN GOTO 100 20 OPEN #4;"m";1;"fichero": GO SUB 8000: REM mem. sectores libres 25 IF PEEK (can+67)=2 THEN GO TO 27 26 GO SUB 8250: GOTO 25 28 GO SUB 9150: REM sect. n. 29 GO SUB 8170: REM canc. EOF 30 GO SUB 9400: REM write 40 GO SUB 9150: REM sect. n. 50 GO SUB 8150: REM ins. EOF 55 LET X=1 60 IF P(X)=0 THEN POKE (can+41),x: GOTO 160 70 LET x=x+1: GOTO 60 98 99 100 OPEN #4;"m";1;"fichero" 110 PRINT "Pulsa s cuando quieras parar" 120 PRINT INKEY$#4; 125 PAUSE 2 130 IF INKEY$="s" THEN GOTO 160 150 GOTO 120 158 159 160 PRINT ';"escribe lo que quieras y luego pulsa ENTER dos veces" 170 INPUT "tipea: ";d$: IF d$="" THEN GOTO 180 174 GO SUB 9200 176 GO SUB 9300 178 GOTO 170 180 GO SUB 9400: REm write 190 CLOSE #4 200 GOTO 9