martes, 24 de septiembre de 2024

Monto el taller en Retroplay Las Américas

Me vuelto a caer en los viejos vicios, monto el taller en el evento Retroplay, que será el 5 y 6 de octubre de 2024, de 10 a 21 horas, que se realiza en el Centro Comercial "Las Américas" de Torrente.

Solo puedo ir el sábado 5 de Octubre de 2024 de 10 a 21 horas. Si alguno no puede ir el sábado lo puede dejar el domingo en el stand de RetroJapan, que luego pasaré por la tienda a revisarlo.

El que desee traer cosas para Reparar-Ajustar-Revisar-Modificar debe tener en cuenta esto:

  • Lo hago por afición, no es un negocio y no hay garantías.
  • Solo cosas retro, no me traigas una Wii, XBox 360 o PS3/4 (salvo que quieras hacerme un regalo), para esas cosas hay muchos sitios que las reparan.
  • No dispongo en un evento de todo el material necesario, por tanto si puede dime lo que vas a traer y lo que quieres hacerle a este e-mail y voy preparado.
  • No es sencillo reparar durante el evento, si es una avería al menos intento darte un diagnóstico para que decidas que hacer.
  • Si no da tiempo durante el evento y tu quieres, me lo llevaría y te aviso cuando esté, luego lo recoges en la tienda RetroJapan en Paterna o te lo envío a casa, lo que prefieras.
  • En el evento voy con una tele con cable de antena, herramientas y algún componente, pero no puedo llevar todo lo necesario para cada máquina, por tanto debes traer todo lo necesario para que la pueda poner en marcha, incluyendo:
    • La máquina
    • Su alimentador (hay muchos tipos de conectores diferentes y no puedo tener de todos)
    • Cable de video si es necesario (no el de TV que si llevo uno a los eventos, pero cada máquina usa sus cables de AV o RGB)
    • Si es una consola, un juego y un mando (en casa tengo de muchos sistemas, pero en el evento no puedo llevar todo).
    • Si es un ordenador no es imprescindible pues se puede probar directamente, pero si quieres algo relacionado con la carga debes traer una cinta, y si es con los mandos uno de los que uses.
  • IMPORTANTE: Lo debes traer todo en una caja o una bolsa, en los eventos hay mucha gente, no suele pasar nada, pero así es mas difícil que se pierda algo o que me equivoque de máquina.
  • ES FUNDAMENTAL asegurarte de que me has dato un contacto tuyo, mail o teléfono, si no es muy difícil localizarte, en los eventos hay mucho follón, una vez se fueron sin darme el contacto y no regresaron a recoger el material, por tanto no puede devolvérselo.
  • Cuando me dejes el material rellenaremos una hoja con tus datos y lo que me dejas, pero si puedes trae una hoja con estos datos y ahorramos tiempo de rellenar:
    • Nombre o nik.
    • Mail o teléfono
    • Todo el material que me dejas (por ejemplo Snes, alimentador, mando, juego SuperMario, cable RGB).
    • Lo que debo hacer (si es reparar que le pasa, si es revisar algo que es lo que deseas que revise, si es hacer un MOD cual).
  • Debes intentar traerlo temprano para que me de tiempo, y pasar antes del cierre para ver el estado, si se ha podido hacer te lo llevas, si no se ha podido decides si me lo llevo para seguir en casa o te lo prefieres llevar.
  • Si lo traes el domingo y lo dejas en RetroJapan , acuerdate de que debe ir en una caja o una bolsa, y poner la hoja con tus datos, lo que deseas que haga y el material que dejas.

Sobre el coste es difícil darlo sin saber lo que hay que hacer, y depende mucho de los componentes necesarios, pero suele estar entre 10 y 20 euros. Yo no me dedico a esto, es un hobby, no cobro por dar el presupuesto, cuando vea la máquina te digo lo que cuesta y tu decides, si no te gusta el precio te lo llevas y arreglado.

Si lo necesitas, este es el MAIL DE CONTACTO

martes, 10 de septiembre de 2024

Programación del Sinclair QL (XXVI): Arboles. Manejo de la memoria y sus pruebas

 

Índice de entradas sobre programación del Sinclair QL



El módulo que simula la memoria es reducido, crea unas variables de control, luego una variable de cadena en la que simularé el manejo de la memoria, la inicializa rellenándola con asteriscos, tiene una función para retornar un puntero a un espacio en la "memoria" del tamaño que le pasemos, otra para copiar una cadena a la "memoria" a partir de una posición, y una para presentar la "Memoria" en pantalla.

He rediseñado el sistema ocupando ahora cinco módulos:

  • Arbol_Base: Se ocupa de cargar los módulos del programa y presentar un menú de las pruebas. 
  • Arbol_Utilidades: Funciones comunes a todos los módulos
  • Arbol_Nodos: Funciones de manejo de nodos
  • Arbol_Nodos_Test: Para efectuar pruebas del manejo de nodos
  • Arbol_Memoria: Funciones de manejo de la memoria
  • Arbol_Memoria_Test: Pruebas de manejo de la memoria

Veamos las funciones para el manejo de la memoria:

  • init_mem: inicializa la memoria con su tamaño mínimo
  • init_mem_max(max): inicializa la memoria con un tamaño max, si este es menos del mínimo será el mínimo, definido en 300 bytes. Luego podemos usar las variables globales para ver en minMem el tamaño mínimo, y en maxMem el tamaño definido para la memoria.
  • ver_mem: presenta la memoria en pantalla
  • MemPos$(i): función auxiliar, la memoria comienza en posición cero, pero las cadenas en la posición 1, lo que hace es retornar el byte en la posición i+1
  • GetBytes(bytes): Solicita un hueco de bytes tamaño, y retorna el puntero a donde comienza.
  • SaveMem(base, ValorCadena$): Guarda una cadena en la memoria, a partir de una posición
  • GetMem$(base,lon): Retorna el contenido de la memoria a partir de la posición indicada, en una cadena de longitud indicada.


7000 REMark *************************************************************
7010 REMark * Librería.: Arbol Binario                                  *
7020 REMark * Modulo...: Memoria                                        *
7030 REMark * Contenido: Rutinas de manejo de memoria                   *
7040 REMark * Autor....: javu61, septiembre 2024                        *
7050 REMark *************************************************************
7060 :
7070 REMark --- Inicializa la memoria con su tamaño mínimo por defecto
7080 :
7090 DEFine PROCedure init_mem
7100   init_mem_max(0)
7110 END DEFine
7120 :
7130 REMark --- Inicializa la memoria con el tamaño que le indicamos
7140 :
7150 DEFine PROCedure init_mem_max(max)
7160    LOCal i
7170   :
7180   minMem = 300
7190   maxMem=max
7200   IF (max < minMem) THEN maxMem=minMem
7210   NULL = -1 : REMark Puntero nulo
7220   ptrBase = 0 : REMark Puntero a la base de la memoria
7230   :
7240   REMark --- Crear la memoria e inicializarla
7250   :
7260   DIM Memoria$(maxMem)
7270   PRINT "Inicializando la memoria (";maxMem;" bytes) ";
7280   Memoria$=FILL$("*",maxMem)
7290   :
7300 END DEFine
7310 :
7320 REMark --- Presentar el contenido de la memoria en pantalla
7330 :
7340 DEFine PROCedure ver_mem
7350   LOCal i
7360   :
7370   PRINT " "; : FOR i=0 TO 4 : PRINT i;"/";i+5;" "; : END FOR i
7380   PRINT
7390   PRINT " "; : FOR i=0 TO 4 : PRINT "| "; : END FOR i
7400   PRINT
7410   PRINT " "; : FOR i=0 TO 4 : PRINT "0123456789"; : END FOR i
7420   :
7430   FOR i=0 TO maxMem-1
7440     IF (i=0) OR ((i MOD 50) = 0) THEN
7450       INK 7 : PRINT : PRINT n2c$(i,3);":"; : INK 4
7460     END IF
7470     PRINT MemPos$(i);
7480   END FOR i
7490   INK 7 : PRINT
7500 END DEFine
7510 :
7520 DEFine FuNction MemPos$(i)
7530   RETurn (Memoria$(i+1))
7540 END DEFine
7550 :
7560 REMark --- Solicita un hueco en memoria de x bytes
7570 :
7580 DEFine FuNction GetBytes(bytes)
7590   LOCal base
7600   :
7610   IF (ptrBase + bytes > maxMem) THEN
7620     PRINT "No hay sitio en la memoria"
7630     base = NULL
7640   ELSE
7650     base = ptrBase
7660     ptrBase = ptrBase + bytes
7670   END IF
7680   RETurn base
7690 END DEFine GetBytes
7700 :
7710 REMark --- Guarda una cadena en la memoria
7720 :
7730 DEFine PROCedure Save_Mem(base, ValorCadena$)
7740   LOCal i,p
7750   :
7760   FOR i=1 TO LEN(ValorCadena$)
7770     p=base+i
7780     IF (p < 1) OR (p > maxMem) THEN
7790       PRINT " *** Sobrepasa la memoria "
7800       EXIT i
7810     ELSE
7820       Memoria$(base+i)=ValorCadena$(i)
7830     END IF
7840   END FOR i
7850 END DEFine Save_Mem
7860 :
7870 : REMark --- Recupera una cadena desde la memoria
7880 :
7890 DEFine FuNction GetMem$(base,lon)
7900   LOCal c$,i,p
7910   :
7920   c$=""
7930   FOR i=1 TO lon
7940     p = base + i
7950     IF (p<0 or="" p="">maxMem) THEN
7960       PRINT " +++ Sobrepasa la memoria ";
7970       EXIT i
7980     ELSE
7990       c$=c$ & Memoria$(p)
8000     END IF
8010   END FOR i
8020   RETurn c$
8030 END DEFine GetMem
8040 :

Para ejecutar solo debéis cargar el módulo Arbol_Base y ejecutarlo, se encargará de cargar el resto de módulos y presentar el menú. Los módulos los podéis descargar de este enlace.

En este momento el sistema pasa los test, pero al final del segundo da un error que no tiene nada que ver, y no consigo localizar, a ver si alguno ve donde puede estar el problema.  


jueves, 5 de septiembre de 2024

Programación del Sinclair QL (XXV): Arboles. Pruebas de los nodos


Índice de entradas sobre programación del Sinclair QL



Vamos con el programa para poder probar lo escrito en la entrada anterior. Es muy sencillo, genero en un bucle una cantidad aleatoria de nodos, cada uno con un valor que será una cadena de caracteres aleatorios entre "A" y "Z". Luego pruebo las rutinas de modificar los punteros a los hijos de los nodos, y la de cambiar el contenido del nodo.

Como las rutinas para montar los nodos están en otro fichero, tengo que cargarlo con un MERGE. En las entradas anteriores usaba un módulo base con los MERGE, y si se ejecutaba el módulo más de una vez volvía a ejecutarlos, con la pérdida de tiempo que eso genera.

La solución ha sido añadir un controlador de errores, si detecta que no se ha cargado el módulo hace el MERGE, si ve que se ha cargado no hace nada. De esta forma podemos ejecutarlo varias veces sin pérdidas de tiempo.

  • WHEN ERRor: Es la rutina que se encarga de errores, para activarla se mira en la línea 260 el valor de una variable NODOS, si no existe dará un error -17
  • Prepara el sistema: Prepara la pantalla y llama a la rutina del módulo de nodos para inicializarlo
  • Crea módulos: Crea los nodos con información aleatoria
  • Modifica módulo: Modifica un nodo para añadir puntero a los hijos y cambia el contenido.
  • RndChr: Retorna un caracter aleatorio entre "A" y "Z"


100 REMark *************************************************************
110 REMark * Pruebas para el Manejo de nodos del arbol binario
120 REMark *************************************************************
130 :
140 REMark --- Control de si se ha incluido o no el módulo de nodos
150 :
160 WHEN ERRor
170   IF ERNUM=-17 THEN
180     PRINT "Cargando rutinas de nodos..."
190     MERGE mdv1_nodos
200     NODOS=1
201     RETRY
210   ELSE
220     PRINT "Se ha producido un error ";ERNUM;" en l“nea ";ERLIN
230     STOP
240   END IF
250 END WHEN
260 IF NODOS=0 THEN REMark --- Si da un error es que no ha cargado ---
270 :
280 REMark --- Preparar el sistema
290 :
300 MODE 0 : PAPER 0 : CLS
310 inicializa
320 :
330 REMark --- Generar nodos aleatorios para probar las rutinas
340 :
350 generar = RND(1 TO 10) : PRINT "Generando "& generar & " nodos"
360 FOR n=1 TO generar
370   c$=""
380   IF RND(1 TO 9) > 2 THEN
390     FOR i=0 TO RND(20)
400       c$ = c$ & RndChr$
410     NEXT i
420   END IF
430   n$ = NuevoNodo$(n,c$)
440   PRINT n$
450 NEXT n
460 :
470 PRINT "Finalizados los ";n;" nodos"
480 PRINT
490 PRINT "Cambiando datos de un nodo"
500 PRINT NuevoHijo$(n$,pIzq,56)
510 PRINT NuevoHijo$(n$,pDer,67)
520 PRINT NuevoValor$(n$,"Pruebas de valores")
530 :
540 REMark --- Retorna un caracter aleatório entre A y Z
550 :
560 DEFine FuNction RndChr$
570   RETurn CHR$(RND(CODE("A") TO CODE("Z")))
580 END DEFine aleatorio
590 :

 

El programa fuente lo podéis bajar de aquí.


En la siguiente entrada se añadirá el módulo memoria, añadiendo y eliminando nodos en la misma, por lo que luego mejoraré este programa para poder hacer pruebas de ambas partes, añadiendo un menú para definir cual se desea probar.

Para completar habría que hacer un proceso para compactar la memoria, pero eso requiere cambiar los punteros de los nodos, para lo que es necesario conocer su estructura interna, cosa que el sistema operativo no puede hacer, es responsabilidad de nuestro programa. En los lenguajes modernos con recolectores de basura, como Java o C# intentan hacer esto, con la ventaja de que no usan punteros a memoria, sino que las referencias son a la tabla de variables, con lo cual al cambiar la tabla ya ha cambiado todos los punteros, lo que es mucho mas sencillo que ir buscando en cada objeto a donde apunta y cambiarlo, pero a cambio usan mas memoria para la tabla de variables.


miércoles, 4 de septiembre de 2024

Programación del Sinclair QL (XXIV): Arboles. Estructura de los nodos en Super Basic

 

Índice de entradas sobre programación del Sinclair QL



Hemos visto el tema de las variables y el de los punteros, ahora vamos a hacer algo en Super Basic, pero ya que no es un lenguaje que soporte punteros ni se pueden usar variables compuestas de tipo registro, es un poco más complicado su manejo. Para manejar memoria hay que usar llamadas al Sistema Operativo para reservar memoria y código máquina para manejar los registros y punteros, o en el Toolkit II hay algunas funciones que nos lo facilitan. Como esto se sale de estas entradas que son de programación en SuperBasic, con la idea de que se pueda portar a otros Basic, voy a hacer otra cosas diferente para manejar árboles binarios, aunque no descarto luego hacer algo usando código máquina y llamadas al sistema.

Simularé la memoria usando una cadena de caracteres, usando el hecho de que una cadena tiene n caracteres, y podemos acceder a cualquiera de ellos individualmente, y cada carácter tiene un valor de 8 bits, lo que es muy similar a como se maneja una memoria. Desarrollaré funciones para reservar "memoria" dentro de la cadena, guardar datos en ella, leer cualquier dato usando un puntero a su dirección base, etc.

Nodos

Para crear los nodos del árbol, en entradas anteriores usé varios vectores de enteros, ya que los valores que manejamos lo eran. Ahora usaremos valores de tipo cadena de longitud variable, por lo que ya no nos sirve usar un vector, desperdiciamos demasiada memoria definiendo cadenas de longitud fija que pueden estar casi vacías. En su lugar en C se usaría un registro, que es una variable que se subdivide en varias, comportándose como un todo o como cada variable individual. Veamos como lo definiríamos en C:

struct nombre_registro    //El nombre que deseamos darle
{
     tipodato campo_1;    //Se define como cualquier variable, tipo y nombre
     tipodato campo_2;
     ....
     tipodato campo_N;    //Podemos usar todas las variables necesarias       
};

En el programa usaremos el nombre como el tipo de variable:

nombre_registro Variable_Registro;     //El nombre que deseamos darle a la variable

Y luego llamamos a los valores añadiendo un punto al nombre:

Variable_Registro.campo_n = ....
if (Variable_Registro.campo_n == true) {....

En los árboles binarios, necesitamos estos datos dentro para montar el registro:

  • Nodo_Izquierdo: Puntero al sub-nodo izquierdo de este nodo.
  • Nodo_Derecho: Puntero al sub-nodo derecho de este nodo.
  • Nodo_Padre: Puntero al nodo padre de este nodo.Este dato no es estrictamente necesario, pero simplifica el montaje del árbol.
  • Contenido del nodo: En nuestro caso es una cadena de caracteres, pero puede ser cualquier tipo, como un entero o un vector, o incluso otra estructura de tipo registro.
  • Longitud del contenido: Al ser una cadena de longitud variable, necesitamos sabes su longitud para reservar solo la memoria necesaria.

Funciones para montar registros

Para montar un registro usaré una cadena de caracteres dividida en partes, los punteros serán valores numéricos entre 0 y 99, mientras que el contenido lo limitaremos a entre 0 y 99 caracteres. 

Para almacenar los números usaré cadenas de longitud fija, ajustadas a la izquierda con ceros por la derecha, así será sencillo ver su valor, realmente lo que se debe usar son bytes, con 1 byte podemos almacenar un número entero sin signo entre 0 y 255, con 2 bytes alcanzamos entre 0 y 65.535, esto pueden ser fácilmente dos caracteres, pero muchos no sería visibles ni nos informarían de los valores del puntero, por eso prefiero usar números almacenados en la cadena, aunque solo puedo almacenar entre 0 y 99 tengo la ventaja de que los puedo ver de forma sencilla, y este desarrollo intenta ser instructivo más que efectivo.

Nuestro nodos serán una cadena de caracteres con este formato <NN:NN:NN:NN:cadena> en donde:

  • Inicio del nodo: Un carácter "<"
  • Puntero al nodo padre: Dos caracteres numéricos. No es necesario pero simplifica la navegación por el árbol.
  • Separador: Un carácter ":"
  • Puntero al nodo hijo izquierdo: Dos caracteres numéricos.
  • Separador: Un carácter ":"
  • Puntero al nodo hijo derecho: Dos caracteres numéricos.
  • Separador: Un carácter ":"
  • Longitud del contenido: Dos caracteres numéricos.
  • Separador: Un carácter ":"
  • Contenido: Una cadena de entre 0 y 99 caracteres
  • Fin del registro: Un carácter ">"

Los caracteres de inicio y fin no son necesarios, así como los separadores tampoco lo son, pero los usaré para poder ver los nodos de manera sencilla, y rellenaré la "memoria" con asteriscos, de forma que se vean los huecos libres, y cuando queramos ver la "memoria" lo podemos hacer simplemente imprimiendo la cadena, y veremos un texto de la forma:

<00:01:23:04:HOLA>******************<19:40:00:05;ADIOS><04:25_09:00:>********....

Así creo que es mas sencillo presentar los valores de forma que se vean correctamente, sin necesidad de grandes procesos.

Empecemos por las funciones que montan los nodos, para lo que defino funciones y procedimientos para manejarlas:

  • inicializa: Este procedimiento inicializa las variables que usaremos, en donde:
    • mLon: Longitud de las cadenas para guardar los punteros
    • mNodos: Máximo número de nodos que manejamos
    • nIni, nSep, nFin: inicio, separador y final para montar el nodo
    • pPadre, pIzq, pDer, pLon, pVal: Orden de los campos para el nodo
  • n2c$(n,l): Esta función retorna una cadena cuyo contenido es un valor numérico (n), ajustada a una longitud máxima (l) y rellena con ceros por la izquierda. Si le pasamos por ejemplo (17,3) nos retorna "017"
  • MontarNodo$(p,i,d,c$): Función que retorna un nodo montado con los valores para el padre (p), hijo izquierdao (i), hijo derecho (d) y una cadena de caracteres (c$), si le pasamos por ejemplo (2,12,3,"Hola") nos retorna "<02:12:03:04:Hola>"
  • NuevoNodo$(p,c$): Función que retorna un nuevo nodo sin hijos, con los valores para el padre (p) y una cadena de caracteres (c$), si le pasamos por ejemplo (2,"Hola") nos retorna "<02:00:00:04:Hola>"
  • Posicion(pos): Retorna un número que indica donde comienza en la cadena que representa el nodo uno de los valores del nodo (pos con valores para 0=padre, 1=izquierdo, 2=derecho, 3=longitud del contenido, 4=valor del contenido), si le pasamos por ejemplo (pIzq) nos retorna 5
  • NuevoHijo$(nodo$, pos, valor): Retorna una cadena que representa un nodo (nodo$), en la que cambia el puntero para uno de los hijos (pos donde 1=izquierdo o 2=derecho), con su nuevo valor de puntero (valor), si le pasamos por ejemplo ("<02:00:00:04:Hola>", pIzq,2) nos retorna "<02:02:00:04:Hola>"
  • NuevoValor$(nodo$, NuevoValor$): Retorna una cadena que representa un nodo (nodo$), en la que cambia el valor del contenido (NuevoValor$), si le pasamos por ejemplo ("<02:02:00:04:Hola>", "Adios") nos retorna "<02:02:00:05:Adios>"

5000 REMark *************************************************************
5010 REMark * Librería.: Arbol Binario                                  *
5020 REMark * Programa.: Nodos                                          *
5030 REMark * Contenido: Rutinas de manejo de los nodos                 *
5040 REMark * Autor....: javu61, octubre 2024                           *
5050 REMark *************************************************************
5060 :
5070 REMark --- Inicializar el sistema
5080 :
5090 DEFine PROCedure inicializa
5100   mLon=2
5110   mNodos = 10^mLon - 1
5120   nIni$="<" : nSep$=":" : nFin$=">"
5130   pPadre=0 : pIzq=1 : pDer = 2 : pLon=3 : pVal=4
5140 END DEFine inicializa
5150 :
5160 REMark --- Numero en cadena ajustado a longitud
5170 :
5180 DEFine FuNction n2c$(n,l)
5190   LOCal c$
5200   c$=n
5210   REPeat bucle_n2c
5220     IF LEN(c$)>=l THEN EXIT bucle_n2c
5230     c$ = "0" & c$
5240   END REPeat bucle_n2c
5250   c$= c$(1 TO l)
5260   RETurn c$
5270 END DEFine n2c$
5280 :
5290 REMark --- Montar un nodo
5300 :
5310 DEFine FuNction MontarNodo$(p,i,d,c$)
5320   LOCal n$
5330   n$ = nIni$
5340   n$ = n$ & n2c$(p,mLon) & nSep$
5350   n$ = n$ & n2c$(i,mLon) & nSep$
5360   n$ = n$ & n2c$(d,mLon) & nSep$
5370   n$ = n$ & n2c$(LEN(c$),mLon) & nSep$ & c$
5380   n$ = n$ & nFin$
5390   RETurn n$
5400 END DEFine MontarNodo
5410 :
5420 REMark --- Montar un nodo nuevo sin hijos
5430 :
5440 DEFine FuNction NuevoNodo$(p,c$)
5450   RETurn MontarNodo$(p, 0, 0, c$)
5460 END DEFine NuevoNodo
5470 :
5480 REMark --- Retorna la posición de un elemento en el nodo
5490 :
5500 DEFine FuNction Posicion(pos)
5510   RETurn LEN(nIni$) + (pos * mLon) + (pos * LEN(nSep$)) + 1
5520 END DEFine Posicion
5530 :
5540 REMark --- Añadir un hijo a un nodo
5550 :
5560 DEFine FuNction NuevoHijo$(nodo$, pos, valor)
5570   LOCal p
5580   p = Posicion(pos)
5590   nodo$(p TO p+mLon-1)= n2c$(valor, mLon)
5600   RETurn nodo$
5610 END DEFine NuevoHijo$
5620 :
5630 REMark --- Cambiar el valor de un nodo
5640 :
5650 DEFine FuNction NuevoValor$(nodo$, NuevoValor$)
5660   LOCal p1,p2
5670   p1 = Posicion(pLon)
5680   p2 = Posicion(pVal)
5690   nodo$ = nodo$(1 TO p1-1)
5700   nodo$ = nodo$& n2c$(LEN(NuevoValor$), mLon) & nSep$
5710   nodo$ = nodo$ & NuevoValor$ & nFin$
5720   RETurn nodo$
5730 END DEFine NuevoValor$

 

En la siguiente entrada añadiré un programa para probar que estas funciones trabajen correctamente, y si queréis descargar este fichero lo tenéis aquí.

miércoles, 7 de agosto de 2024

Programación del Sinclair QL (XXIII): Punteros

Índice de entradas sobre programación del Sinclair QL



Tras la entrada sobre las variables, entraremos a explicar lo que son los punteros, ya que la definición habitual es real pero muy poco explicativa: Un puntero es una variable que apunta a otra variable.

En la siguiente entrada usaremos ya los punteros en SuperBasic, que aunque no los soporta de forma nativa, hay maneras de simularlos.

Punteros

Como vimos en la entrada anterior, los compiladores e intérpretes usan una tabla en donde almacenar los datos de las variables que se utilizan en el programa. Las entradas de esta tabla disponen de una serie de datos importantes: nombre, tipo, longitud y posición en memoria donde se almacena (lo que se denomina dirección base de la misma, y su ocupación es la longitud de la misma (normalmente viene dados por el tipo).

Un puntero es una variable de tipo entero, que almacena una dirección de memoria, usualmente la dirección base de otra variable o de una zona de memoria libre. Veamos los dos tipos con ejemplos en  lenguaje C (lo tengo bastantes oxidado, quizá no sea del todo correcta la sintaxis): 


[1] float a=78.65;
[2] float *b=&a;
[3] float *c=malloc(4);
[4] printf('%d %f', b, *b);
[5] printf('%d %f', c, *c);
[6] *c = a;
[7] printf('%f', c);


  • En la línea 1 creamos una variable de tipo decimal que ocupa 4 octetos, y le damos como valor inicial 78'65. 
  • En la línea 2 creamos una variable de tipo puntero, que apuntará a una zona de memoria que contiene una variable de tipo decimal de 4 octetos, y la inicializamos con la dirección de base de la variable a. 
  • En la línea 3 creamos una variable de tipo puntero que apuntará a una variable de tipo decimal, y lo inicializamos a una nueva zona de memoria en donde se reservan 4 octetos.
  • En la línea 4 el sistema imprimirá la dirección de memoria a la que apunta la variable b, seguida por el contenido de la memoria a la que apunta b, en este caso 78'65.
  • En la línea 5 el sistema imprimirá la dirección de memoria a la que apunta la variable c, seguida del contenido de esa dirección de memoria, en este caso indefinido pues no se ha inicializado a ningún valor, puede ser cero o dar un error por no servir el contenido como representación de una variable decimal.
  • En la línea 6 asociamos a la dirección base apuntada por la variable c el contenido de la variable a, sería lo mismo que hacer un c=a si ambas variables fueran float.
  • En la línea 7 se imprime 78'65

Como vemos, si una variable de tipo puntero la usamos por su nombre, obtenemos la dirección de memoria a la que apunta, pero si el nombre lo precedemos por asterisco, obtenemos el contenido de dicha dirección de memoria. Igualmente una variable precedida por & nos retorna al dirección de memoria de dicha variable y no su valor.

Uso de los punteros a variables

Al definirse las funciones, los parámetros que les pasamos son variables locales cuyo valor inicial es que que les pasemos en la llamada, y al finalizar  solo retornan un valor, pero en C definieron un sistema para poder alterar los parámetros  usando los punteros. Vemos un ejemplo:


[1] void incrementar(int i) { i = i + 1; }
[2] void duplicar(int *j) { j = j + 1; }
[3] int a=5; incrementar(a); printf('%c', a);
[4] int b=5; duplicar(&b); printf('%c', b);

  • En la línea 1 creamos una función que recibe un parámetro, que es una variable local a la misma, esto incrementa la variable en una unidad. En la línea 2 definimos una semejante, pero recibe un puntero como parámetro.
  • En la línea 3 definimos una variable entera con valor 5, llamamos a incrementar, al que le pasamos el valor de a que es 5, como en la línea 1 estamos incrementando la variable local no se afecta, y luego se imprime otra vez 5. 
  • En la línea 4 definimos una variable entera con valor 5, llamamos a duplicar, al que le pasamos la dirección de b, como en la línea 2 estamos recibiendo el puntero, al incrementar será la zona de memoria a la que apunte, por tanto al final de la línea imprimirá 6.

De esta manera es sencillo para el programador decidir que variables serán alterada en las funciones y cuales no, protegiendo mejor el programa. En lenguajes modernos sin punteros, se añade una palabra, normalmente var, antes de la variable para indicar que su valor podrá ser alterado, lo que internamente es usar un puntero de forma transparente.

Otro uso habitual es emplear punteros a funciones, de esta manera en una variable podemos guardar una u otra función en relación con los datos que estamos procesando, pudiendo alterar el manejo del programa sin necesidad de complicar mucho la lógica. 

En lenguajes modernos esto es transparente, por ejemplo un objeto es solo un puntero a una posición de memoria que contiene funciones y variables, lo manejamos sin necesidad de saber siquiera lo que es un puntero.

Uso de punteros a memoria

La principal ventaja de los punteros es el manejo de memoria libre de forma no estructurada y de longitud variable, pudiendo crear elementos libremente cuando los necesitemos, a diferencia de los vectores que debemos definir su número de elementos y el tamaño de estos antes de empezar a manejarlos. Esto es muy útil para manejar estructuras de datos, como una lista de cadenas, cuando necesitamos un elemento llamamos a una función que nos retorne una dirección de memoria libre, del tamaño que necesitemos para esa cadena más el puntero al siguiente elemento, guardamos los datos, ajustamos los punteros, y ya tenemos una lista que podemos recorrer, sin saber cuanto ocuparán las cadenas de caracteres ni cuantas usaremos. 

Esto lo hacen los lenguajes modernos de forma transparente, al crear una estructura de tipo lista, que luego podemos recorrer con un FOREACH.

Problemas de los punteros

Tras muchos años manejando punteros, por los problemas que pueden ocasionar su uso, en los lenguajes actuales se ha optado por eliminarlos, aunque el sistema los maneja de forma interna de forma transparente, en nuestro programa no podemos usarlos.

El principal problema de los punteros es que son propensos a usarse de forma errónea provocando errores en el programa o, lo que es peor, en el sistema. Si apunto mal en una lista, puedo crear una circular y no salir nunca del bucle que la recorra, o apuntar mal un elemento y salirnos de la lista, por lo que el programa trabajará con datos erróneos. En un vector no podemos usar mas elementos de los definidos, pero con punteros podemos pasarnos y seguir leyendo elementos incorrectos. Esto se soluciona siendo cuidadosos al programar y probar bien nuestros programas con muchos datos diferentes.

Otro problema es la fragmentación, si creamos y destruimos muchos trozos pequeños de memoria, el sistema no puede reutilizarlos y podemos ocupar toda la memoria posible sin darnos cuenta. Esto tiene difícil solución, habría que compactar la memoria reubicando los registros y los punteros, lo que puede ser largo. Esto es realmente lo que hacen en lenguajes modernos los recolectores de basura de forma automática y transparente, ocultando los punteros a los programadores.

Otro problema es la seguridad, mediante un puntero podemos acceder a cualquier posición de memoria, por tanto podemos leer en otras zonas de memoria que no son específicas de nuestro programa, en sistemas como UNIX que son multi-usuario, podemos leer la memoria asignada a otro usuario y obtener por ejemplo claves de acceso. Hay mecanismos en el sistema operativo para que esto no sea posible, dando errores si accedemos a ubicaciones de memoria que no son del rango que tenemos asignado a nuestro programa.

miércoles, 31 de julio de 2024

Programación del Sinclair QL (XXII): Variables

Ampliado el 04/09/2024 en azul y el 05/09/24 en rojo


Retomo tras varios años la serie de programación de mi querido QL, seguiremos con los árboles, pero antes hablemos de conceptos básicos sobre punteros, necesarios para entender como montar los árboles.

Aunque esta entrada entrá en la serie del QL, realmente es general sobre programación, debes entender estos conceptos ya que son válidos para cualquier lenguaje, y en la siguiente hablaré sobre punteros.

Compiladores e Intérpretes

Escribimos nuestros programas en algún lenguaje, en donde usamos variables como un sistema para almacenar los datos que manipulemos y la lógica que usamos para su manipulación. Para ejecutar el programa podemos usar dos sistemas:

  • En BASIC normalmente se usa un interprete, que va leyendo el programa fuente línea a línea y ejecutándola, esto es bastante simple y necesita poco espacio en memoria.
  • Los compiladores en cambio convierte el programa fuente en otro que reconozca la máquina (normalmente código máquina, pero en algunos como JAVA generan un código intermedio que luego se procesan usando un ejecutor o una máquina virtual). Para ello se usa un programa que va leyendo el programa y ejecutando el proceso en una o varias pasadas, montando una serie de tablas para poder manejar cosas que todavía no se han definido. Esto requiere mas memoria para disponer del compilador, del buffer donde almacenar el programa que vamos leyendo, las tablas auxiliares, etc.

Variables

En ambos casos se necesita reconocer las variables, de forma que siempre que encontremos una podamos usar su valor correcto. Para ello en ambos sistemas se crea una tabla de variables, en donde se almacenan los datos necesarios para manejarlas, lo que se denominan atributos. En esta tabla se incluyen varios atributos importantes:

  • Nombre: El nombre que le damos a una variables para nosotros es su principal atributo, ya que es con el que interactuamos con ellas. Su longitud es muy variable entre lenguajes, en los primeros BASIC constaba de una letra o de una letra y un solo dígito (A, B$, C2, D4 ...), en los TRS-80 su BASIC permitía nombre largos, pero solo consideraba los dos primeros (lo que solo es un poco mejor), y por experiencia personal se puede programar así solo requiere disciplina. En lenguajes antiguos se comenzaron usando hasta 6, luego se aumentó a 8 (bastantes años más tarde, y en las primeras versiones del ABAL, un dialecto del BASIC usado por BULL, se mantenían ilimitados pero solo consideraba los 8 primeros), rápidamente creció el tamaño hasta 256, y hoy día podemos decir que los lenguajes modernos tienen longitud ilimitada. Los nombres deben comenzar por una letra, o por el símbolo de subrayado más una letra. Hay lenguajes que distinguen mayúsculas y minúsculas en los nombre (lo que ocasiona errores si te equivocas, por lo que se recomienda usar siempre mayúsculas o minúsculas).
  • Tipo de Contenido: En los lenguajes antiguos teníamos variables numéricas de tipo entero o de tipo decimal, y variables alfanuméricas, de manera similar a lo que soportan los propios ordenadores internamente, luego se han ido ampliando los tipos:
    • Alfanuméricas: Almacenan caracteres alfabéticos, caracteres numéricos y símbolos especiales (espacio, punto, +, *, &, etc.). En general hay estos tipos:
      • Carácter o char: Un solo caracter
      • Cadena o string: Una lista de caracteres, que puede ser de longitud fija (tienen un máximo que no se puede sobrepasar, hay lenguajes que rellenan con espacios por la derecha y otros que no lo hacen, marcando el final de la cadena), o de longitud variable (la cadena ocupa su longitud al crearla, si se amplia su contenido se amplia la cadena a su vez).
En el QL las variables de cadena se definen añadiendo el símbolo $ al nombre de la variable, y almacenan valores de 8 bits en código ASCII, aunque dependiendo de la ROM usará una configuración regional diferentes para algunos caracteres locales, como la Ñ o la £.
    • Enteras: Un valor numérico sin decimales, son los más rápidos de manejo por lo que debe priorizarse su manejo. Internamente se guardan en binario, usando o bien un bit para el signo o bien usando complementos (esto lo explicaré mas adelante), su representación interna siempre es exacta, pero están limitados en rango por su tamaño, así en 8 bits y usando uno para el signo, disponemos de valores entre -127 a +127 (realmente -128 a 127, cosas del binario, pasará lo mismo con el resto de tamaños), con 16 bits ampliamos a ±32.767, con 32 bits alcanzamos ±2.147.483.647, con 64 bits ±9.223.372.036.854.775.808 (ojo, el punto es el separador de miles que usamos en España, en otros países serían comas). Realmente hay que
El tamaño del entero es el base del procesador, en máquinas antiguas serían 8 bits, hoy día es de 64, y se han definido muchas variantes:
      • Entero: Por defecto del tamaño de registro del procesador (8,16,32,64)
      • Entero sin signo: Si no necesitamos números negativos, con este tipo ampliamos en 1 bit el tamaño, por lo que con 8 bits serían de 0 a 256, con 16 bits de 0 a  65.536, etc.
      • Entero corto y largo: Cuando las máquinas eran de 8 bits no había otra opción, pero al ir ampliando el tamaño a 16, para reducir ocupación en memoria que era un recurso muy limitado siempre, se definieron enteros cortos que ocupaban la mitad, y enteros largos que ocupaban el doble.
      • Carácter: Representa el valor numérico de un carácter alfanumérico en el código ASCII, pero en ciertos lenguajes podemos usarlo como un entero corto de 8 bits (o 16 si es en Unicode), soportando también su uso con o sin signo. 
      • Booleanas: Un entero en donde 0=Falso y 1=Cierto, o más generalmente cero falso y diferente a cero cierto. Ocupan un byte completo para solo usar un bit, lo que en máquinas de 64 bits es mucho espacio desaprovechado.
      • Fecha, hora, fecha y hora: Son valores enteros que guardan realmente el número de días desde una fecha inicial dada, la hora del día en décimas de segundo, o ambos simultáneamente en dos enteros.
      • Punteros: Esto lo veremos luego.
      • De coma fija: Estos son números que tienen una cantidad de decimales predefinida, y su valor se guarda directamente en binario como un entero, redondeando el valor a los decimales que aceptan, ajustando los decimales truncando o añadiendo ceros por la derecha para alcanzar los definidos, y luego eliminando el punto decimal, así serán enteros y de esta manera se pueden sumar directamente entre sí, añadiendo ceros por la derecha si es necesario igualar el número de los decimales entre las variables (esto es muy rápido en los ordenadores).
En el QL las variables enteras se definen añadiendo el símbolo % al nombre de la variable, y almacenan valores en 16 bits, entre -32.768 y +32.767.
    • Decimales: Un valor numérico que admite decimales. Internamente se guarda con una codificación científica, del tipo smEn, donde s es el signo, m la mantisa (se guarda el valor completo como todos sus dígitos sin punto decimal), y n sería el exponente (donde se ubica el signo decimal). Hay varios tipos según su tamaño, lo que cambia es la cantidad de cifras significativas de la mantisa y del exponente.:
      • La codificación normalizada IEEE 754 ocupa 32 bits, denominándose de simple precisión
      • Las de media precisión ocupan 16 bits
      • Las de doble precisión ocupan 64 bits
      • Las de cuádruple precisión o doble precisión largas, con 128 bits
      • Hay hasta las de octuple precisión con 256 bits.
En el QL las variables decimales se definen sin añadir nada a su nombre, y almacenan valores en 16 bits, su valor se presentará como un número si no es excesivamente grande, o con formato científico en caso contrario. 
    • Tipos especiales: Aquí cada lenguaje tiene sus variantes, podemos hablar en general de:
      • Puntero:  que guarda una posición de memoria, es el objetivo de esta entrada
      • Tipo indefinido (void): representa que no usaremos su valor por lo que solo nos interesa su nombre (en C una función se comporta como una variable, ya que dispone de su entrada en la tabla de Variables, las funciones son las que retornan un valor, los procedimientos no las retornan y se definen de tipo void)
      • Cualquier tipo (any): que indica que guardaremos cualquier posible valor en la variable (usar con cuidado ya que ralentiza mucho el programa). Son usuales en JavaScript.
      • Objeto: realmente son punteros, pero que apuntan a una estructura en memoria compleja que dispone de código y datos.
El tipo de contenido representa un tamaño en la memoria. Hay lenguajes fuertemente tipados, que no permiten asignar una variable de un tipo a una de otro tipo, y lenguajes pobremente tipados que hacen conversiones automáticas entre los tipos de las variables (esto por si solo requiere una entrada para ello, ya veremos si algún día la hago). NOTA: Nuestro QL es pobremente tipado, por tanto podemos asignar a una numérica una cadena, e intentará hacer la conversión a partir de su contenido. Si escribís a$="12.45 MAS" : b=a$ : c%=a$ : PRINT a$, b, c% veréis que en pantalla aparece   12.45 MAS       12.45       12

  • Ubicación en memoria: Todos los datos deben estar ubicados en algún lugar de la memoria del ordenador, en general se reserva una zona denominada "pila del programa" o de forma similar, en donde se guardan todas las variables que se utilicen. Para variables simples apuntará directamente a donde está su valor, pero para variables mas complejas apunta al inicio de la zona en que está ubicada. Para crear la entrada para un nuevo valor, se mira la posición de la última entrada y se le suma la ocupación, así tenemos la ubicación de la nueva variable. Si fuera la primera variable y por tanto la tabla está vacía, se rellena con la dirección base de la "pila del programa".
Desde nuestro programa no vemos esa tabla de variables ni sabemos nada de direcciones de memoria, realmente solo nos interesa conocer el VALOR que es el contenido de la variable, realmente unas posiciones concretas en la memoria apuntadas por la tabla anterior. En la mayoría de lenguajes su valor se inicializa al comenzar el programa a un valor por defecto, cero para numéricas (en booleanas cero equivale a false, en fechas el valor sería 0, pero como representa los días que transcurren desde una fecha base preestablecida sería el valor de esa fecha), las cadenas se inicializan como vacías o con espacios según el lenguaje.
 
Hay que tener cuidado con lenguajes donde las variables no se inicializan como C o C++ , tendrán un valor aleatorio según lo que haya en ese momento en la memoria, por lo que se siempre se recomienda darles el valor inicial al definirlas en la forma int a=0; float b=0.0; char Saludo[5]="Hola";
 
En nuestro QL el sistema utiliza tres listas separadas para el manejo de las variables, procedimientos y sus nombres:
  • Por un lado tenemos la lista de nombres de las variables, procedimientos y funciones. La primera vez que las definimos se guarda en esta lista, y a partir de entonces al escribir su nombre el sistema lo ajustará para que coincida con lo que has escrito en cuanto a mayúsculas y minúsculas en las líneas. Esto hace que si queremos cambiar la variable nododerecho por NodoDerecho el sistema no nos deje, pues ya está en la lista.
  • Otra lista contiene los tipos de cada variable, procedimiento o función, para las locales incluye el procedimiento o función en que tienen vigencia, junto al puntero en la memoria donde está ubicado su valor.
  • Una tercera es la que contiene los valores de las variables.

Definición explicita de variables

En BASIC o Python no es necesario definir las variables antes de su uso, son lo que se denomina lenguajes implícitos, conforme avanza el programa se va rellenando la tabla de variables cuando se localiza una nueva, esto puede ocasionar problemas ya que si nos equivocamos en el nombre, el sistema crea una nueva variable sin que nos demos cuenta, lo que provocará un error que habitualmente cuesta localizar.
 
En contra la mayoría de lenguajes son de tipo explícito, obligan a definir una variable antes de poder usarla, algunos como en COBOL separadas del código ejecutable, otros como Pascal C (y los que usan su sintaxis como C++, C#, Java o PHP) se pueden definir en cualquier momento. Al definir las variables, si nos equivocamos en el nombre dará un error por variable no definida.

Hay algunos lenguajes que se pueden configurar por la configuración del entorno de programación o usando una instrucción de pre-procesador en el propio programa para que se comporten de una manera o de la otra.

Variables globales y variables locales

En el BASIC estándar todas las variables son globales, eso significa que las podemos usar en cualquier parte del programa sin problema. Evidentemente en lenguajes donde definimos las variables antes de comenzar el programa en un bloque separado, como COBOL o en el ABAL de Bull, todas las variables son también globales.
 
En lenguajes que soportan funciones o procedimientos, existe la posibilidad de definir variables que solo se verán dentro de ellas, desapareciendo su valor al salir de las mismas (aunque en algunos se conservan los valores, perdiendo entonces parte de su ventaja). Estas son variables locales.

La idea de las locales surgió en ALGOL, donde existen bloques del programa, estos bloques se marcan con una instrucción de inicio y otra de fin, por ejemplo en C se usan las llaves {} mientras en PASCAL se usa begin y end. Dentro de estos bloques también podemos definir variables locales a los mismos.

Una variable local pueden usar el mismo nombre que una global, siendo ambas independientes, en la función se usará la variable local, y fuera de ella se usará la global, pero esto puede dar lugar a errores dentro de nuestras funciones por pensar que estamos usando la global, por eso hay que ser cuidadosos en el nombre de las variables. Para evitar errores es buena costumbre usar una letra antes del nombre, sobre todo en las locales, por ejemplo añadir loc delante.
 
Un uso muy habitual de una variable local en C es para los bucles definiendo las variables índice dentro del propio bloque, o incluso en la definición del mismo, por ejemplo for(int i=1;i<10;i++) { ... } esto es un bucle en donde i tendrá los valores del 1 al 9, y después del mismo desaparece. Veamos un ejemplo un poco mas completo:

(1)    int i=84;
(2)    do {
(3)        int i=6;
(4)        i++;
(5)        printf("%d", i);
(6)    } while (i<10) 
(7)    printf("%d", i);
 
La línea 5 presentará los valores de la variable i que es local al bucle: 6,7,8,9 y luego en la línea 7 presentará la variable global con valor 84.
 
En nuestro querido SuperBASIC se permie definir variables locales en las funciones y procedimientos, usando el comando LOC seguido de las variables que queramos definir. Por ejemplo
 
10 a=1 : b$="A"
20 print "1",a,b$
30 prueba
40 print "3",a,b$
50 Define PROCedure prueba
60     loc a,b$
70     a=5 : b$="U"
80    print "2",a,b$
90 End DEFine 
 
Al ejecutarlo imprimirá:
1   1   A
2   5   U
3   1   A 

En la siguiente entrada abordamos los punteros, de uso muy habitual en C, pero fuente de problemas por lo que se prefiere ocultados en otros lenguajes como Java, que los usan implícitamente sin que los veamos.


lunes, 15 de julio de 2024

Historia de la electricidad (XXIV): La guerra de las corrientes

 Índice de entradas sobre la Historia de la Electricidad


Esta pugna entre las empresas General Electric Company (empresa creada por Edison a partir de la Edison General Electric Company, junto a varios inversionistas, principalmente J.P. Morgan) y la Westinghouse Electric (empresa creada por  George Westinghouse, donde trabajaba Nikola Tesla, hoy propiedad de la Paramount), por hacerse con el control de la distribución de la electricidad en hogares e industrias. Aunque esta guerra fue "perdida" finalmente por Edison, ambas empresas se hicieron con una parte de la distribución de la electricidad en los EE.UU., crecieron mucho, diversificaron su producción (electrodomésticos, telefonía, radio, etc.) y con sus altibajos siguen existiendo hoy día y son conocidas a nivel mundial (en España la Westinghouse por sus electrodomésticos, mientras la GE se conoce más en aparatos industriales).

Corriente continua y corriente alterna

Tras las época de las baterías, los generadores de corriente usados hasta finales del 19 eran dinamos, que generaban corriente continua, ideados por Faraday tras sus estudios sobre la inducción electromagnética. De manera simplificada, en una dinamo se hacer girar unos imanes sobre un bobinado, la variación del campo magnético induce una corriente en la bobina, al hacer que los polos del imán se muevan siempre en la misma zona de espiras, se genera una corriente continua en estas..

Haciendo que los polos del imán cambien en su paso por las espiras, la corriente cambia de sentido, por lo que se genera corriente alterna, pero era complicado hacer un sistema que rotara los imanes, por lo que se recurrió a usar electroimanes en su lugar, pero el rendimiento de estos generadores era muy pobre..

Inicialmente la corriente continua tenia muchas ventajas en el laboratorio, se podía generar de manera muy sencilla una corriente del voltaje deseado cambiando la velocidad de movimiento del motor, y se estudia mucho mejor una corriente continua que si esta varía con el tiempo. Por eso los primeros intentos de producir electricidad para usos prácticos usaban algo que estaba bien desarrollado y estudiado en ese momento. Edison electrificó sus laboratorios con continua, y los iluminó con sus bombillas, por lo que tras vender la idea a J.P. Morgan, gracias a la inversión pudo hacer que su empresa, la Edison General Electric Company (en la que comenzó a trabajar Tesla en Europa y luego en EE.UU., ver entrada anterior), que se dedicaba principalmente a temas de telefonía, pusiera una central eléctrica en NuevaYork y comenzara a electrificar las casas.

Whestinhouse estaba trabajando en el generador de alterna, pero no conseguía mejorar el rendimiento, hasta que decidió probar a generar los electroimanes con la propia electricidad que generaba la máquina, idea en principio desechada por su analogía con el movimiento continuo y sus grandes pérdidas, pero la idea funcionaba, la retroalimentación del sistema permitía generar una corriente alterna con mejor rendimiento que con las dinamos, aunque los generadores eran mucho mas complejos.

Whestinhouse y Edison presentaron ofertas para electrificar la Exposición Universal de Boston, ganando el primero por ser mucho mas económico, lo que le  dió fama y le permitió comenzar a electrificar la iluminación de las calles con bombillas de arco usando su corriente alterna, pero aun así Edison ganó la primera batalla.

Los electrodomésticos y sus enchufes

Edison seguía convencido de que era mejor seguir con sus dinamos, pero como la distribución de la electricidad usando corriente continua tiene limitaciones de distancia, principalmente por la pérdida en forma de calor en los cables, su idea era crea muchas pequeñas centrales generadoras, una por cada barrio, de manera similar a como se hacía en ese momento con las centralitas manuales de los teléfonos. De esta manera se podía reemplazar el sistema de iluminación del momento tanto el de las calles como el de las casas, que eran principalmente las lámparas de gas, muy peligrosas por la posibilidad real de incendios y explosiones, por las bombillas de Edison, mucho mas sencillas, mucho mas económicas y de mayor duración que las bombillas de arco, por lo que en 1882 instaló la primera central de corriente continua en Manhatan. Para poder suministrar sin problemas, usaba una corriente de 100 Voltios, ya que a voltajes mas bajos las pérdidas eran mucho mayores.

Pronto fue muy popular su uso, y creció rápidamente para iluminar todo NuevaYork con sus bombillas, único aparato eléctrico en las casas. Cuando se crearon nuevos Electrodomésticos, estos necesitaban un enchufe, y en casa solo había lámparas, por lo que los primeros usaban un casquillo de bombilla como conexión, uno de los primeros electrodomésticos fueron las maquinillas eléctricas para afeitarse, por lo que para hacerlo debías desenroscar la bombilla y enroscar el enchufe de la maquinilla (hay alguna película de la época en que se ve esto). 

Como se vio que era necesario algo mas sencillo, se crearon los primeros enchufes, el primero fue patentado en 1882 por Thomas Tayler Smith, que estaba conectado también a la rosca del portalámparas. Luego se hizo que el propio portalámparas tuviera el conector para el enchufe y la rosca de la bombilla, por lo que en algunas películas antiguas se ve como la afeitadora está enchufada con la bombilla (si no recuerdo mal esto lo hace Clark Gable en la comedia de  Frank Capra "Sucedió una noche" de 1934, y Cary Grant en la también muy divertida "Luna nueva" de Howard Hawks en 1940, y aunque me falle la memoria, ambas son muy recomendables de ver  igualmente).

Niagra

No me refiero a la película de 1953 de Henry Hathaway, una buena película de cine negro, de las pocas de Marilyn Monroe fuera de la comedia, en cine comenzó con un drama y un breve papel donde no lo hizo mal en Peligrosa juventud de 1947, luego podemos destacar fuera de la comedia La jungla de asfalto de 1950 o la mencionada Niagra de 1953, Rio sin Retorno de 1954, aunque para mi sus mejores actuaciones fueron en Bus Stop y, sobre todo, en su última película completa, Vidas rebeldes (aunque a ratos sobre actúa un poco).

Para mover los generadores se necesita energía, la forma mas sencilla es usar un salto de agua, ya existía en el rio Niagra un canal paralelo a las cataratas, que aprovechaba la diferencia de altura y el gran caudal de agua para unos molinos. Se creó la empresa Niagara Falls Hydraulic Power & Manufacturing Company, la primera en generar energía hidroeléctrica a gran escala. 

undefined
Generadores en Niagra Falls (fuente: Wikipedia)

La corriente alterna tiene la ventaja de que se puede convertir de uno a otro voltaje, usando transformadores, componentes pasivos consistentes en bobinados en paralelo, no usan partes móviles, y apenas necesitan refrigeración (principalmente se usan radiadores de aceite para los de alta tensión).

Westinghouse pudo producir corriente alterna de alto voltaje, que luego se puede llevar a las ciudades por líneas de alta tensión, al llegar a las ciudades convertirla en media tensión, y luego en baja tensión para usarla en los hogares. 

Con la gran cantidad de electricidad que generaba, Westinghouse pudo llevar la electricidad a varias ciudades, llegando hasta a Nueva York a 600km de distancia.


La guerra de las corrientes y la silla eléctrica

Tras establecer ambos las bases de su negocio, cada uno de ellos luchó por hacerse con el mercado, compitiendo Edison-Westinghouse (aunque siempre se dice Edison-Tesla, este solo se ocupaba de la parte práctica de la generación de electricidad).

Edison usaba la ventaja de su fama, mientras que para poner de manifiesto la superioridad de su sistema, Westinghouse usaba su mejor baza, el precio mas bajo al poder generar mucha cantidad de electricidad y llevarla donde lo necesitara, en lugar de depender de montar muchas pequeñas centrales  cercanas. También cobro ventaja en la industria por sus motores eléctricos de corriente alterna (desarrollo de Tesla) que eran mas potentes que los de continua de Edison.

Ambas empresas acabaron enfrentándose en una batalla de relaciones públicas, que los diarios del momento calificaron como "la guerra de las corrientes", por ver quien se hacía con el mercado finalmente. Ocurrieron algunos accidentes en las líneas de alta tensión, lo que Edison magnificó moviendo sus contactos en la prensa, que hablaron a favor de su sistema que enterraba los cables bajo tierra, no en líneas aéreas. Pero Nikola Tesla se expuso a una CA que atravesó su cuerpo sin causarle ningún daño (si estás aislado de tierra, tu cuerpo puede conducirla sin ser afectado), lo que no pudo replicar Edison. 

undefined
Enterrando cables eléctricos de la Edison en Nueva York (fuente: Wikipedia)


Pero este contraatacó con la Silla Eléctrica. Esta fue ideada un empleado suyo llamado Harold P. Brown, que estaba buscando un sistema que reemplazara a la horca que se usaba en ese momento. Cuando una corriente alterna atraviesa un cuerpo, si es suficientemente grande producirá una interferencia en el sistema nervioso pudiendo llegar a la parada cardíaca, en teoría de forma muy rápida y de manera indolora. Para esto era mejor la alterna, pues es mas sencillo general altos voltajes con ella. 
 
Tras pruebas con varios animales, la gran demostración la hizo ante la prensa, donde entre otros animales la aplicó a un elefante de un circo. La prensa lo denominó "electrocución", y se adoptó desde 1889 como reemplazo de la orca en la mitad de los estados de EE.UU., principalmente en la zona este del país. Su primer uso fue en Nueva York en 1890, tuvo un auge, pero tras críticas por su uso se reemplazó gradualmente por la inyección letal, y en 2008 que fue abolido su empleo definitivamente.
 
Aunque Edison movió a la prensa todo lo que pudo a su favor, finalmente tuvo que rendirse a los echos, la alterna era mucho mejor sistema para distribuir la electricidad en hogares e industrias, no solo perdió la guerra, sino que quedó como el creador de la silla eléctrica, aunque no fuera cierto. En 1892 se desvinculó de la electricidad, se fusionó su empresa con la segunda mas grande del momento, la Thomson-Houston, creando la General Electrics.