De los autómatas a los sistemas embebidos: historia de la robótica y primeros conceptos de electrónica
De los autómatas a los sistemas embebidos: historia de la robótica y primeros conceptos de electrónica
Este es el primer capítulo del curso, así que no doy nada por sabido. No necesitas haber tocado un cable, ni haber soldado nunca, ni saber Elixir. Lo único que necesitas es aceptar una idea: todo lo que hoy llamamos robótica, domótica o IoT es la misma máquina repetida con distinta tecnología. Esa máquina captura algo del mundo, decide qué hacer con ese dato y modifica el mundo de vuelta. Un autómata de madera del siglo XVII que sirve té hace exactamente eso. Un termostato inteligente que apaga la calefacción cuando detecta que abriste la ventana hace exactamente eso. Cambia el material, no el esquema.
Si entiendes ese esquema desde el principio, cada componente que veas después —una resistencia, un transistor, un sensor de temperatura, un servomotor, un proceso de la BEAM— va a caer en uno de tres lugares: captura, procesamiento o acción. Y si entiendes de dónde salió cada pieza históricamente, vas a entender por qué está diseñada así y no de otra forma. Un microcontrolador no es arbitrario: es la respuesta a un problema concreto que alguien tenía en 1974.
Este capítulo tiene dos mitades. La primera es histórica y va desde los mecanismos de agua del siglo IX hasta los modelos de lenguaje de los años 2020. No es historia decorativa: cada hito introduce un concepto que vas a usar en el resto del curso —programabilidad, memoria, realimentación, conmutación, integración—. La segunda mitad es el primer contacto con la electrónica y con la razón por la que este curso usa Elixir y no C: qué es una señal, qué es un sensor, qué es un actuador, qué unidades se miden y por qué una máquina virtual pensada para centrales telefónicas resulta útil para controlar hardware.
Qué vas a poder hacer al terminar este capítulo
- Explicar qué diferencia hay entre un autómata, un robot y un sistema embebido, sin confundirlos.
- Reconocer el ciclo capturar → procesar → actuar en cualquier dispositivo que tengas delante.
- Clasificar un componente cualquiera como sensor, procesador o actuador.
- Nombrar las magnitudes eléctricas básicas y sus unidades, con el sentido físico de cada una.
- Distinguir cuándo un problema pide un microcontrolador, cuándo una placa con Linux y cuándo un computador completo.
- Ejecutar tres programas de Elixir que modelan, respectivamente, un telar de tarjetas perforadas, una máquina de estados de autómata y un lazo de control tolerante a fallas.
- Entender qué es la BEAM, qué es Nerves y qué es AtomVM, y en qué capa vive cada uno.
Tres palabras que se confunden todo el tiempo
Antes de la historia, tres definiciones. Las vas a ver mezcladas en artículos y en tiendas, y conviene tenerlas separadas.
Autómata. Una máquina que imita movimientos de seres vivos y ejecuta una secuencia fija de acciones sin intervención humana durante su ciclo. La palabra viene del griego automatos, “que se mueve por sí mismo”. Un autómata clásico no percibe nada: repite. Si le pones un obstáculo delante, choca contra el obstáculo y sigue intentando. Cuando el autómata tiene forma humana se le llama androide.
Robot. La norma ISO 8373 lo define como un mecanismo actuado, programable en dos o más ejes, con cierto grado de autonomía, que se mueve dentro de un entorno para ejecutar tareas. Las dos palabras que hacen la diferencia respecto del autómata son programable y autonomía: el comportamiento no está fundido en el metal, y la máquina ajusta lo que hace según lo que percibe.
Sistema embebido (o incrustado, del inglés embedded). Un computador completo —procesador, memoria, entradas y salidas— dedicado a una función específica dentro de un aparato mayor, normalmente sin teclado ni pantalla de propósito general. El microcontrolador de una lavadora, el de un marcapasos y el de un dron son sistemas embebidos. Un robot casi siempre contiene uno o varios sistemas embebidos, pero un sistema embebido no tiene por qué mover nada: puede limitarse a medir y transmitir.
En una tabla, la diferencia queda más clara.
| Criterio | Autómata clásico | Robot | Sistema embebido |
|---|---|---|---|
| Percibe el entorno | No, o casi nada | Sí, es esencial | Depende del diseño |
| Comportamiento | Fijo, mecánico | Programable | Programable |
| Cambiar su conducta | Rehacer el mecanismo | Cargar otro programa | Cargar otro firmware |
| Partes móviles | Siempre | Casi siempre | No necesariamente |
| Ejemplo | Muñeca karakuri de té | Brazo industrial de soldadura | Sensor de humedad con ESP32 |
| Aparece hacia | Siglo IX en adelante | 1961 | 1974 |
Con eso en la mano, la historia se puede leer como una sola pregunta que se responde por etapas: ¿cómo se le dice a una máquina qué hacer?
La línea que va del mito al mecanismo
La idea de un sirviente artificial es anterior a cualquier tecnología capaz de construirlo. En la tradición judía aparece el golem, una figura de arcilla animada por una fórmula, sin habla ni juicio, que ejecuta órdenes al pie de la letra. La versión más difundida atribuye uno de ellos al rabino Iehudá Loew ben Betzalel, el Maharal de Praga, en el siglo XVI, y cuenta que la criatura terminó fuera de control.
Ese detalle —la orden literal que se cumple sin criterio y produce un desastre— no es folclore inútil. Es exactamente el modo en que falla un programa mal escrito, y es el mismo miedo que reaparece cuatro siglos después en la ciencia ficción y hoy en las discusiones sobre sistemas autónomos. Toda la historia posterior consiste en darle a la máquina cada vez más capacidad de ejecutar órdenes, y en descubrir cada vez con más detalle qué pasa cuando la orden estaba mal formulada.
El reloj mecánico: la primera máquina que impone un ritmo
En la Baja Edad Media aparece en Europa el reloj mecánico de escape. Su función inmediata es medir el tiempo, pero su efecto real es otro: desacopla el ritmo del trabajo del ritmo del sol. Las comunidades monásticas y después las ciudades organizan la jornada por campanadas y no por la posición de la luz.
El reloj es la primera máquina de precisión de uso cotidiano y trae tres ideas que la robótica va a heredar completas:
- Una fuente de energía almacenada (un peso o un resorte) separada de la ejecución.
- Un regulador que libera esa energía en porciones iguales, el escape. Es el antepasado directo del concepto de reloj de sistema que sincroniza un microcontrolador.
- Un tren de engranajes que convierte un movimiento en otros movimientos de distinta velocidad, es decir, una transmisión.
El proceso de miniaturización del reloj —de torre a habitación, de habitación a bolsillo, de bolsillo a muñeca— es además el primer ejemplo documentado de una tendencia que se va a repetir con el transistor: la misma función, cada vez en menos espacio y con menos energía.
Banu Musa y al-Jazari: los primeros catálogos de mecanismos
En el Bagdad del siglo IX, tres hermanos —Abu Ya’far Muhammad, Ahmad y al-Hasan, conocidos como los Banu Musa— compilan el Kitab al-hiyal, el “Libro de los mecanismos ingeniosos”, con alrededor de cien dispositivos. Hay fuentes de agua con chorros que cambian de forma, una lámpara que se apaga sola, instrumentos musicales que tocan solos y recipientes que sirven bebida en secuencia.
La tecnología subyacente es hidráulica y neumática: sifones, válvulas, flotadores y ejes con levas. Lo notable es que varios de esos aparatos incluyen un elemento de realimentación —un flotador que corta el paso del agua cuando el nivel sube— que es conceptualmente lo mismo que hace hoy un lazo de control: medir una variable y actuar sobre ella para mantenerla en un rango.
Tres siglos más tarde, hacia 1206, al-Jazari publica el Libro del conocimiento de los ingeniosos mecanismos, con relojes de agua, máquinas elevadoras y un conjunto de músicos autómatas montados sobre una barca, cuyos movimientos se determinan por clavijas colocadas en un tambor giratorio. Cambiar la posición de las clavijas cambia la música. Eso ya no es solo un mecanismo: es un mecanismo reconfigurable, y por lo tanto el ancestro remoto de la programación.
Karakuri: automatización con madera, resortes y ballenas
Durante el período Edo japonés (1603-1868) florecen los karakuri ningyō, muñecos mecánicos construidos con engranajes de madera, levas, hilos, contrapesos y resortes hechos de barbas de ballena. No usan electricidad, obviamente, y tampoco metal en las piezas críticas.
Los dos más conocidos:
- Chahakobi ningyō, la muñeca que sirve té. Se le coloca una taza en la bandeja y el peso de la taza libera el mecanismo; el muñeco avanza en línea recta hacia el invitado. Cuando el invitado retira la taza, el mecanismo se detiene. Al volver a poner la taza vacía, el muñeco gira ciento ochenta grados y regresa.
- Moji-kaki ningyō, la muñeca escritora, que sostiene un pincel y traza caracteres siguiendo el perfil de un juego de levas.
El detalle importante del chahakobi es el disparo por peso: el muñeco no decide nada, pero su comportamiento depende de un evento del entorno. Es la forma más primitiva de un sensor, un interruptor mecánico de presencia. Y ese ciclo —esperar, avanzar, esperar, girar, volver— es literalmente una máquina de estados finitos, cosa que vamos a implementar en código más adelante en este mismo capítulo.
Los autómatas de Jaquet-Droz: la primera máquina programable de la que se conserva evidencia
Entre 1768 y 1774, el relojero suizo Pierre Jaquet-Droz, su hijo Henri-Louis y su colaborador Jean-Frédéric Leschot construyen tres autómatas que hoy se conservan en el museo de arte e historia de Neuchâtel y que todavía funcionan.
| Autómata | Piezas aproximadas | Qué hace | Detalle relevante |
|---|---|---|---|
| El Dibujante | ~2.000 | Traza cuatro dibujos distintos | Sopla el polvo del lápiz y mueve los ojos |
| La Música | ~2.500 | Toca cinco piezas en un órgano real | Sigue la partitura con la mirada y simula respiración |
| El Escritor | ~6.000 | Escribe textos de hasta 40 caracteres | El texto se cambia recolocando piezas en un disco |
El Escritor es el que interesa aquí. Su mano se mueve en dos ejes gracias a un sistema de levas, y el contenido de lo que escribe está codificado en un disco compuesto por piezas intercambiables. Cambiar el orden de esas piezas cambia el texto sin tocar el resto del mecanismo.
Eso es la separación entre máquina y programa. Es la misma distinción que hay entre un microcontrolador y el firmware que le cargas. Lo que en 1774 era un disco de levas, hoy es una memoria flash; el concepto no cambió.
flowchart LR
subgraph MECANICO["Codificacion mecanica"]
A["Disco de levas<br/>Jaquet-Droz 1774"] --> B["Tarjetas perforadas<br/>Jacquard 1801"]
B --> C["Cinta y tarjetas<br/>Zuse Z1 1938"]
end
subgraph ELECTRICO["Codificacion electrica"]
C --> D["Reles<br/>Zuse Z3 1941"]
D --> E["Transistores<br/>Bell Labs 1947"]
E --> F["ROM de mascara<br/>TMS1000 1974"]
end
subgraph REESCRIBIBLE["Codificacion reescribible"]
F --> G["EPROM y EEPROM<br/>anos 1980"]
G --> H["Flash en microcontrolador<br/>Arduino 2005"]
H --> I["OTA por WiFi<br/>ESP32 y IoT"]
end
style MECANICO fill:#e8e2d0,stroke:#8a7f5c
style ELECTRICO fill:#d6e4f0,stroke:#4a6f95
style REESCRIBIBLE fill:#dcedd7,stroke:#5a8f4a
Ese diagrama es la columna vertebral del capítulo: la historia de la robótica es la historia de cómo se almacena y se modifica el programa. Todo lo demás —motores, sensores, materiales— avanzó en paralelo, pero el salto cualitativo siempre estuvo en el soporte del programa.
El telar de Jacquard: el programa se vuelve datos
Joseph Marie Jacquard presenta en Lyon, hacia 1801, un telar que produce tejidos con patrones arbitrariamente complejos. La máquina se apoya en trabajos previos —Basile Bouchon con cinta perforada en 1725, Jean-Baptiste Falcon con tarjetas en 1728, y Jacques de Vaucanson, el mismo del célebre pato digestor de 1739, con un telar automatizado hacia 1745—, y los integra en algo utilizable en producción.
El principio es simple y vale la pena entenderlo bien porque es la primera aparición del bit en una máquina industrial. Cada hilo de urdimbre está sujeto a una aguja. Frente a las agujas pasa una tarjeta de cartón. Donde la tarjeta tiene un agujero, la aguja lo atraviesa y el hilo se levanta; donde la tarjeta es sólida, la aguja queda bloqueada y el hilo se queda abajo. Se pasa la trama, se avanza a la tarjeta siguiente, y así línea por línea hasta completar el dibujo.
Es decir: agujero = 1, cartón = 0. Una fila de tarjeta es una palabra binaria. Una cadena de tarjetas cosidas es un programa secuencial. Cambiar el diseño del tejido no requiere tocar el telar, solo cambiar el mazo de tarjetas.
Las consecuencias fueron largas. Charles Babbage adopta la idea de las tarjetas para su máquina analítica; Ada Lovelace la describe en 1843 diciendo que la máquina teje patrones algebraicos igual que el telar teje flores; Herman Hollerith aplica tarjetas perforadas al censo estadounidense de 1890 y funda la empresa que después será IBM; y las tarjetas perforadas siguen siendo el medio de entrada dominante en informática hasta bien entrados los años setenta.
Vale la pena que veas el mecanismo funcionando en código antes de seguir. Guarda esto como jacquard.exs y ejecútalo con elixir jacquard.exs.
defmodule Jacquard do
@moduledoc """
Modelo del telar de Jacquard: una lista de tarjetas perforadas se convierte
en una secuencia de decisiones de subir o bajar cada hilo de urdimbre.
Un "o" representa una perforacion (la aguja pasa, el hilo sube).
Un "." representa carton solido (la aguja se bloquea, el hilo baja).
"""
@doc "Convierte una tarjeta escrita como texto en una lista de bits."
def leer_tarjeta(texto) when is_binary(texto) do
texto
|> String.graphemes()
|> Enum.map(fn
"o" -> 1
_ -> 0
end)
end
@doc "Aplica una tarjeta: devuelve que hilos suben y cuales bajan."
def aplicar(bits) do
bits
|> Enum.with_index(1)
|> Enum.map(fn
{1, hilo} -> {hilo, :sube}
{0, hilo} -> {hilo, :baja}
end)
end
@doc "Dibuja una pasada del tejido segun los bits de la tarjeta."
def tejer_linea(bits) do
Enum.map_join(bits, fn
1 -> "#"
0 -> " "
end)
end
@doc "Ejecuta el mazo completo de tarjetas y devuelve el tejido."
def ejecutar(mazo) do
Enum.map(mazo, fn tarjeta ->
tarjeta
|> leer_tarjeta()
|> tejer_linea()
end)
end
end
# Mazo de tarjetas: cada cadena es una tarjeta de 16 columnas.
mazo = [
"..oooooooooooo..",
".o..o......o..o.",
"o...o......o...o",
"o..ooo....ooo..o",
"o...o......o...o",
".o.o.oooooo.o.o.",
"..oooooooooooo..",
"...oo......oo..."
]
IO.puts("Tejido producido por el mazo de #{length(mazo)} tarjetas:\n")
mazo
|> Jacquard.ejecutar()
|> Enum.each(&IO.puts/1)
IO.puts("\nDetalle de la primera tarjeta (hilo por hilo):\n")
mazo
|> List.first()
|> Jacquard.leer_tarjeta()
|> Jacquard.aplicar()
|> Enum.each(fn {hilo, accion} ->
IO.puts(" hilo #{String.pad_leading(Integer.to_string(hilo), 2)} -> #{accion}")
end)
Lo que hace ese programa es literalmente lo que hacía la máquina: convertir una representación física de unos y ceros en una secuencia de acciones mecánicas. Si cambias el mazo, cambias el resultado sin tocar una sola línea del módulo Jacquard. Esa propiedad, en 1801, era la novedad completa.
La palabra “robot” y las ideas que trajo consigo
El término no viene de la ingeniería sino del teatro. En enero de 1921 se estrena en Praga R.U.R. (Rossumovi Univerzální Roboti, “Robots Universales de Rossum”), del escritor checo Karel Čapek. La obra trata de seres artificiales fabricados para trabajar, que terminan rebelándose. Čapek atribuyó públicamente la palabra a su hermano Josef Čapek; la raíz eslava robota significa trabajo forzado, servidumbre.
Conviene notar que los robots de R.U.R. no son metálicos: son orgánicos, más cercanos a lo que hoy llamaríamos organismos sintéticos. La imagen del robot metálico llega después, sobre todo por el cine y por la ilustración de revistas.
En los años cuarenta, Isaac Asimov desplaza el foco desde la rebelión hacia el comportamiento gobernado por reglas. Formula sus tres leyes de la robótica en el relato Runaround (1942) y las consolida en la recopilación Yo, robot (1950). Más allá de su valor narrativo, lo que Asimov introduce es un problema de ingeniería que sigue vigente: cómo se especifica sin ambigüedad el conjunto de restricciones bajo el cual una máquina autónoma puede actuar, y qué ocurre en los casos donde dos restricciones se contradicen. Sus relatos son, casi siempre, la depuración de un conflicto entre reglas.
Hoy la definición operativa que se usa en la industria es la de la Organización Internacional de Normalización, mucho más seca: un mecanismo actuado, programable en dos o más ejes, con un grado de autonomía, que se mueve en su entorno para realizar tareas previstas.
Zuse: cuando el cálculo se automatiza
Mientras la palabra circulaba en el teatro, en Berlín un ingeniero civil llamado Konrad Zuse (1910-1995) trabajaba en otro frente. Su motivación era doméstica: calcular estructuras a mano era lento y propenso a error, y quería que la máquina hiciera la parte repetitiva.
| Máquina | Años | Tecnología | Aporte principal |
|---|---|---|---|
| Z1 | 1936-1938 | Placas metálicas y pasadores deslizantes | Memoria y unidad de cálculo separadas; coma flotante binaria |
| Z2 | 1939-1940 | Relés en la unidad aritmética, memoria mecánica | Prueba de que los relés eran más fiables |
| Z3 | 1941 | Unos 2.600 relés electromagnéticos | Primer computador digital programable y automático que funcionó |
| Z4 | 1945-1950 | Relés, uso comercial en ETH Zúrich | Primer computador vendido y puesto en operación continua |
El Z1 leía sus instrucciones de una cinta perforada —Zuse usó película de 35 mm agujereada— y trabajaba con números binarios en coma flotante, decisión poco común en la época. Su punto débil era mecánico: los pasadores se atascaban.
El Z3, terminado en 1941, sustituye la mecánica por relés electromagnéticos y funciona de manera confiable. Es programable por cinta perforada, y realiza operaciones aritméticas con números de 22 bits.
Zuse trabajó además en Plankalkül, escrito entre 1942 y 1945, un lenguaje de programación con estructuras de datos compuestas, asignaciones y condicionales, que no llegó a implementarse en su época pero que anticipa por más de una década muchas ideas de los lenguajes posteriores.
Y hay un concepto de Zuse que conviene fijar ahora porque es puro cimiento de la electrónica digital: lo que él llamaba su álgebra de conmutación. Un relé, un interruptor y más adelante un transistor tienen dos estados: conduce o no conduce. Con esos dos estados y con combinaciones de dispositivos se pueden construir las operaciones lógicas Y, O y NO; con esas operaciones se construye la suma binaria; con la suma se construye toda la aritmética. Toda la computación descansa sobre la posibilidad física de tener un interruptor rápido y barato. Esa frase explica por qué el transistor, más adelante, cambia todo.
El robot industrial: 1961 y lo que vino después
La robótica industrial nace de una patente. George Devol presenta en 1954 una solicitud sobre “transferencia programada de artículos”, concedida en 1961. Devol se asocia con Joseph Engelberger, y de ahí sale Unimation, la primera empresa de robots del mundo.
En 1961 se instala el primer Unimate en una planta de General Motors en Nueva Jersey. Su trabajo consiste en retirar piezas de fundición a presión —trabajo caliente, pesado y peligroso— y apilarlas. El brazo pesaba alrededor de dos toneladas, se accionaba por hidráulica y guardaba sus posiciones en un tambor magnético. Se le enseñaba llevándolo a mano por las posiciones deseadas y grabando cada una: el método de enseñanza por demostración que todavía se usa.
De ahí en adelante los hitos se encadenan rápido:
- 1968-1969: General Motors publica una especificación para reemplazar los armarios de relés cableados de sus líneas, imposibles de reconfigurar. Bedford Associates, con Dick Morley entre sus ingenieros, responde con el Modicon 084, el primer controlador lógico programable (PLC). Un PLC hace lo que hacían los relés, pero el cableado pasa a ser un programa. Es el mismo salto de Jacquard, aplicado a la fábrica.
- 1969: Victor Scheinman, en la Universidad de Stanford, construye el Stanford Arm, el primer brazo robótico eléctrico controlado por computador y diseñado para ser gobernado por software, no por levas ni por hidráulica.
- 1973: KUKA presenta el Famulus, con seis ejes accionados electromecánicamente. ASEA, hoy ABB, presenta el IRB 6 en 1974, con control por microprocesador.
- 1978: Scheinman diseña el PUMA (Programmable Universal Machine for Assembly), que se convierte en el brazo de referencia de laboratorios durante décadas. El mismo año, Hiroshi Makino, en la Universidad de Yamanashi, presenta la configuración SCARA, optimizada para ensamblaje vertical rápido.
- Años 1980 en adelante: los robots dejan de ser exclusivamente brazos fijos. Aparecen los móviles con navegación, y a partir de los 2000 los cobots, robots colaborativos diseñados para trabajar junto a personas con límites de fuerza y velocidad.
Lo interesante para este curso es el patrón: cada generación traslada más comportamiento desde el hardware hacia el software. El Unimate guardaba posiciones en un tambor; el PLC reemplazó cableado por lógica; el PUMA se programaba en un lenguaje. La misma dirección continúa hoy cuando actualizas el firmware de un dispositivo por WiFi sin abrirlo.
Mecatrónica: la palabra que junta las disciplinas
En 1969, un ingeniero de Yaskawa Electric llamado Tetsuro Mori acuña el término mecatrónica, combinando mecánica y electrónica. La empresa lo registra como marca en los años setenta y más tarde libera su uso, lo que permite que se convierta en el nombre de una disciplina completa.
Hoy la mecatrónica se entiende como la integración de cuatro campos que ya no se pueden diseñar por separado:
flowchart TD
MEC["Mecanica<br/>estructuras, engranajes,<br/>transmisiones, materiales"]
ELE["Electronica<br/>circuitos, potencia,<br/>acondicionamiento de senal"]
INF["Informatica<br/>firmware, algoritmos,<br/>comunicaciones"]
CTL["Teoria de control<br/>realimentacion, PID,<br/>estabilidad"]
SYS(("Sistema<br/>mecatronico"))
MEC --> SYS
ELE --> SYS
INF --> SYS
CTL --> SYS
SYS --> R1["Robot industrial"]
SYS --> R2["Dron"]
SYS --> R3["Impresora 3D"]
SYS --> R4["Vehiculo electrico"]
SYS --> R5["Dispositivo IoT"]
style SYS fill:#2f4858,stroke:#1b2b36,color:#ffffff
El punto de la mecatrónica no es que existan cuatro áreas, sino que una decisión en una de ellas restringe a las otras tres. Si eliges un motor más potente, cambia el consumo, cambia la fuente de alimentación, cambia el disipador, cambia la masa de la estructura y cambia el algoritmo de control porque la inercia es distinta. Este curso va a insistir en eso: no existe “el lado del software” separado del resto.
El transistor: el interruptor que lo hizo todo posible
Volvamos al concepto de Zuse: computar es conmutar. Un relé conmuta, pero es grande, lento —milisegundos—, ruidoso, consume bastante y sus contactos se desgastan. El transistor hace lo mismo sin partes móviles, en nanosegundos, con muy poca energía y ocupando un espacio que hoy se mide en nanómetros.
Un transistor es un dispositivo semiconductor de tres terminales en el que la corriente o el voltaje aplicado a uno de ellos controla el paso de corriente entre los otros dos. Esa frase encierra sus dos usos: como amplificador, cuando una señal pequeña controla una grande de forma proporcional; y como interruptor, cuando se opera en los extremos, completamente abierto o completamente cerrado. La electrónica digital vive enteramente en el segundo uso.
Los hitos principales:
| Año | Hito | Protagonistas |
|---|---|---|
| 1925-1928 | Patentes del transistor de efecto de campo y de una estructura tipo MOS | Julius Edgar Lilienfeld |
| 1947 | Transistor de contacto puntual, en germanio | John Bardeen y Walter Brattain, Bell Labs |
| 1948 | Presentación pública del dispositivo | Bell Labs |
| 1948-1951 | Transistor de unión bipolar, más robusto y fabricable | William Shockley |
| 1954 | Primer transistor comercial de silicio | Gordon Teal, Texas Instruments |
| 1958-1959 | Primeros circuitos integrados | Jack Kilby (TI) y Robert Noyce (Fairchild) |
| 1959-1960 | MOSFET funcional, base de la electrónica digital actual | Mohamed Atalla y Dawon Kahng, Bell Labs |
| 1971 | Intel 4004, primer microprocesador comercial, ~2.300 transistores | Federico Faggin, Ted Hoff, Masatoshi Shima |
Lilienfeld patentó el principio décadas antes de que existiera la tecnología de materiales para fabricarlo; el desarrollo de Bell Labs partió de una investigación sistemática sobre semiconductores. El paso del germanio al silicio fue decisivo por estabilidad térmica y por disponibilidad del material.
En 1965, Gordon Moore observa que la cantidad de componentes por circuito integrado venía duplicándose a intervalos regulares y proyecta que la tendencia continuaría; en 1975 revisa el período a aproximadamente dos años. Esa observación —conocida como ley de Moore, aunque no sea una ley física sino una tendencia económica e industrial— describe con bastante fidelidad medio siglo de fabricación de chips. Para dimensionarlo: un circuito integrado de comienzos de los setenta contenía algunos miles de transistores; los procesadores de los años 2020 superan los diez mil millones.
La consecuencia práctica para ti es directa: la capacidad de cómputo dejó de ser el recurso escaso. Un microcontrolador de tres dólares tiene hoy más memoria y más velocidad que el computador que se usó para muchas misiones históricas. Lo escaso hoy es la energía, el tiempo de respuesta garantizado y la confiabilidad del sistema completo. Ese cambio de escasez es, precisamente, la razón por la que tiene sentido correr una máquina virtual como la BEAM sobre hardware embebido.
El microcontrolador: el computador que vive dentro de las cosas
Un microprocesador es solo la unidad de cálculo: necesita memoria externa, controladores de entrada y salida, y circuitos de apoyo. Un microcontrolador integra todo eso en un solo encapsulado: núcleo, memoria de programa, memoria de datos, temporizadores, conversores y pines de entrada y salida.
El primero de propósito general en un solo chip fue el TMS1000 de Texas Instruments, de 1974. Sus características muestran la escala de la época: núcleo de 4 bits, arquitectura Harvard —memoria de programa y de datos separadas—, alrededor de 1 KB de ROM de programa y 64 registros de 4 bits de memoria de datos.
Tenía una limitación que conviene entender porque explica todo lo que vino después: su ROM era de máscara. El programa se fijaba durante la fabricación del chip, grabado en el proceso litográfico. Para cambiar una línea de código había que fabricar un lote nuevo de chips. Además, el desarrollo requería acceso a un computador central de Texas Instruments y entrada por tarjetas perforadas.
El resultado es que el TMS1000 sirvió muy bien para productos de altísimo volumen y comportamiento fijo —calculadoras, juguetes, electrodomésticos— y muy mal para cualquiera que quisiera experimentar. Las familias que lo desplazaron —Intel 8051, Zilog Z80, MOS 6502, y después los PIC de Microchip y los AVR de Atmel— ganaron porque permitieron borrar y reprogramar: primero con EPROM borrable por luz ultravioleta, después con EEPROM y finalmente con memoria flash.
flowchart LR
subgraph MCU["Microcontrolador tipico"]
CPU["Nucleo CPU"]
FLASH[("Flash<br/>programa")]
RAM[("RAM<br/>datos")]
TIM["Temporizadores<br/>y PWM"]
ADC["Conversor<br/>analogico-digital"]
GPIO["Pines de<br/>entrada y salida"]
COM["UART / I2C / SPI"]
BUS{{"Bus interno"}}
CPU --- BUS
FLASH --- BUS
RAM --- BUS
TIM --- BUS
ADC --- BUS
GPIO --- BUS
COM --- BUS
end
SENS["Sensor<br/>de temperatura"] -->|"voltaje 0 a 3,3 V"| ADC
BOT["Pulsador"] -->|"nivel alto o bajo"| GPIO
TIM -->|"senal PWM"| DRV["Driver de potencia"]
DRV -->|"corriente"| MOT["Motor"]
GPIO -->|"nivel logico"| LED["LED indicador"]
COM <-->|"trama serie"| NET["Modulo WiFi<br/>o placa vecina"]
style MCU fill:#eef2f7,stroke:#4a6f95
Ese diagrama vale la pena mirarlo dos veces, porque es el mapa de casi todo lo que vas a hacer en este curso. Los pines analógicos entran por el conversor; los digitales entran y salen por GPIO; los movimientos suaves salen por PWM; la comunicación con otros dispositivos sale por UART, I2C o SPI. Todo lo demás son variaciones sobre eso.
Después llegan las plataformas que bajaron la barrera de entrada:
- 2005, Arduino: nacido en el Interaction Design Institute de Ivrea, Italia. Su aporte no fue técnico —el microcontrolador AVR ya existía— sino de accesibilidad: placa barata, entorno de desarrollo simple, biblioteca que oculta los registros y comunidad enorme. Convirtió al hardware en algo que se podía aprender sin formación en electrónica.
- 2012, Raspberry Pi: un computador completo con Linux del tamaño de una tarjeta. Cambia la ecuación: ya no programas un chip, administras un sistema operativo con red, sistema de archivos y procesos.
- 2014-2016, ESP8266 y ESP32 de Espressif: microcontroladores con WiFi y Bluetooth integrados a precio de microcontrolador simple. Son la razón principal por la que el IoT doméstico se masificó, y son la plataforma sobre la que corre AtomVM, que verás más adelante en el curso.
Los años noventa: robots en la casa
Dos productos de consumo masivo merecen mención porque introdujeron la electrónica interactiva en millones de hogares.
El Tamagotchi, lanzado por Bandai el 23 de noviembre de 1996 y concebido por Aki Maita, es un llavero con una pantalla LCD monocroma de baja resolución, tres botones y un microcontrolador con muy poca memoria. No tiene partes móviles, pero implementa algo que en electrónica interactiva es central: un modelo de estado que evoluciona con el tiempo real y que responde a eventos del usuario. El aparato pide atención según temporizadores internos y cambia de estado según se le atienda o no.
El Furby, de Tiger Electronics en 1998, va más lejos: incorpora sensores —luz, tacto, sonido, posición— y actuadores, y toda la animación de ojos, párpados, boca y orejas se genera con un solo motor cuyo giro se reparte mediante un árbol de levas y engranajes. Es exactamente el principio karakuri, cuatrocientos años después, con un microcontrolador decidiendo cuándo mover el motor y en qué dirección.
Ese detalle del motor único es una lección de diseño que conviene retener: cuando la energía y el costo son escasos, se agrega complejidad mecánica para ahorrar actuadores. Los Furby además se volvieron objeto de circuit bending, la práctica de modificar circuitos de juguetes electrónicos para alterar su comportamiento, que fue la puerta de entrada al hardware de mucha gente antes de que existieran las placas de desarrollo baratas.
De 2000 a hoy: red, sensores por todos lados y modelos de lenguaje
Los años 2000 traen el acceso a internet como supuesto de diseño. Un dispositivo deja de ser una isla y pasa a ser un nodo: puede reportar, recibir órdenes y actualizarse. De ahí sale el término Internet de las Cosas, acuñado por Kevin Ashton en 1999 en el contexto de identificación por radiofrecuencia, y consolidado cuando el hardware con red se volvió barato.
Los años 2010 traen el teléfono inteligente, y con él la producción en masa de sensores que antes eran caros: acelerómetros, giroscopios, magnetómetros, sensores de proximidad, cámaras, receptores GNSS. La escala del mercado móvil abarató esos componentes hasta hacerlos accesibles para cualquier proyecto pequeño. El teléfono también se convierte en la interfaz por defecto de cualquier dispositivo doméstico, y aparece el patrón de arquitectura que domina hoy: dispositivo pequeño, servicio en la nube, aplicación móvil.
Los años 2020 traen los modelos de lenguaje de gran tamaño y su adopción masiva a partir de finales de 2022. El efecto sobre este campo es doble. Por un lado, cambia cómo se escribe el software que controla el hardware. Por otro, aparecen dispositivos que interpretan instrucciones en lenguaje natural, lo que traslada complejidad desde la interfaz física hacia el modelo. Dos consecuencias que conviene tener presentes en cualquier diseño: dónde quedan alojados los datos capturados y cuánta energía consume la infraestructura que procesa esos datos.
Con eso cerramos la mitad histórica. Ahora bajamos al nivel de los electrones.
Qué estudia la electrónica
La electrónica estudia el diseño y la construcción de circuitos que controlan el movimiento de cargas eléctricas para representar, transmitir y transformar información. Esa última palabra es la que la separa de la electricidad a secas: la electrotecnia se ocupa fundamentalmente de transportar energía; la electrónica se ocupa de transportar y manipular señales.
Una señal es una magnitud física que varía en el tiempo y que lleva información. La temperatura de una habitación es una señal. La posición de un eje es una señal. La presión de un dedo sobre un botón es una señal. La electrónica trabaja convirtiendo todas esas magnitudes a una forma común —normalmente voltaje— para poder operar con ellas.
Hay dos grandes familias de señales, y la diferencia entre ambas ordena todo lo que viene:
| Aspecto | Señal analógica | Señal digital |
|---|---|---|
| Valores posibles | Infinitos dentro de un rango continuo | Un conjunto discreto, normalmente dos |
| Ejemplo físico | Salida de un micrófono | Estado de un pulsador |
| Efecto del ruido | Se acumula y degrada la información | Se descarta mientras no cruce el umbral |
| Copiar la señal | Pierde calidad en cada copia | Es exacta |
| Procesarla | Con circuitos dedicados | Con un programa |
| En el microcontrolador | Entra por el conversor analógico-digital | Entra directo por un pin GPIO |
La razón por la que el mundo se volvió digital está en la tercera fila. Una señal analógica arrastra todo el ruido que recoge; una señal digital solo tiene que decidir si está por encima o por debajo de un umbral, y todo el ruido menor a ese margen desaparece en cada etapa. Eso permite encadenar miles de millones de operaciones sin que el resultado se degrade.
Pero el mundo físico es analógico. La temperatura no viene en unos y ceros. Por eso todo sistema real tiene una frontera donde lo analógico se convierte en digital —el conversor analógico-digital, o ADC— y otra donde lo digital vuelve a lo analógico —un DAC, o más frecuentemente en sistemas pequeños, una señal PWM filtrada—. Esa frontera es donde ocurren la mayoría de los problemas prácticos, y es materia de los capítulos siguientes.
El ciclo capturar, procesar y actuar
Aquí está el esquema que anuncié al principio. Todo sistema electrónico interactivo, sin excepción, se organiza en tres etapas.
flowchart LR
MUNDO[["Mundo fisico<br/>temperatura, luz, fuerza,<br/>posicion, sonido"]]
subgraph CAPTURA["1. Capturar"]
S["Sensor<br/>magnitud fisica a<br/>senal electrica"]
AC["Acondicionamiento<br/>amplificar, filtrar,<br/>proteger"]
ADC["Conversor<br/>analogico-digital"]
end
subgraph PROCESO["2. Procesar"]
FW["Programa<br/>filtrar, comparar,<br/>decidir, registrar"]
MEM[("Estado<br/>y memoria")]
end
subgraph ACCION["3. Actuar"]
SAL["Senal de salida<br/>nivel logico o PWM"]
POT["Etapa de potencia<br/>transistor, driver, rele"]
ACT["Actuador<br/>motor, LED, valvula,<br/>calefactor, pantalla"]
end
MUNDO --> S --> AC --> ADC --> FW
FW <--> MEM
FW --> SAL --> POT --> ACT
ACT -->|"modifica"| MUNDO
MUNDO -.->|"realimentacion:<br/>el sensor vuelve a medir<br/>el efecto de la accion"| S
style CAPTURA fill:#dce9f5,stroke:#4a6f95
style PROCESO fill:#e6dff0,stroke:#6f5a95
style ACCION fill:#dcedd7,stroke:#5a8f4a
La flecha punteada del final es la que convierte un aparato en un sistema de control: el actuador cambia el mundo, el sensor mide ese cambio, y la próxima decisión ya toma en cuenta el efecto de la anterior. Eso se llama realimentación o feedback, y aparece desde el flotador de los Banu Musa hasta el algoritmo PID de un dron. Sin realimentación tienes un autómata; con realimentación tienes control.
Sensores: la entrada
Un sensor convierte una magnitud física en una señal eléctrica. La mayoría lo hace variando una propiedad eléctrica de un material según la magnitud a medir.
| Magnitud | Sensor típico | Principio de funcionamiento | Salida habitual |
|---|---|---|---|
| Temperatura | NTC, PT100, DS18B20 | La resistencia del material cambia con la temperatura | Analógica o digital por bus |
| Luz | LDR, fotodiodo | La conductividad varía con la luz incidente | Analógica |
| Distancia | HC-SR04, sensor infrarrojo, ToF | Tiempo de vuelo de un pulso ultrasónico o de luz | Pulso digital o bus |
| Aceleración e inclinación | MPU-6050, ADXL345 | Masa suspendida cuyo desplazamiento cambia una capacitancia | Bus I2C o SPI |
| Humedad relativa | DHT22, SHT31 | La capacitancia de un polímero cambia con la humedad | Bus digital |
| Presión | BMP280, celda de carga | Deformación de una membrana o de una galga extensiométrica | Bus digital o analógica |
| Corriente | Shunt, sensor de efecto Hall | Caída de voltaje o campo magnético generado | Analógica |
| Presencia | Pulsador, PIR, sensor magnético | Contacto, radiación infrarroja, campo magnético | Digital |
Dos observaciones que ahorran horas de depuración más adelante. Primero: un sensor mide su propio estado, no el del mundo. Un sensor de temperatura pegado a un regulador caliente mide el regulador, no el ambiente. Segundo: ningún sensor es exacto. Todos tienen error de offset, error de ganancia, deriva térmica, ruido y tiempo de respuesta. Un valor leído es siempre una estimación, y el software tiene que tratarlo como tal.
Procesadores: la decisión
En el medio está el elemento que decide. Puede ser de tres tipos:
- Analógico: un circuito que opera directamente sobre la señal continua. Un amplificador operacional que compara dos voltajes y conmuta una salida es un procesador analógico. Responde instantáneamente y no necesita programa, pero su comportamiento está fijado por los componentes.
- Digital no programable: lógica combinacional y secuencial construida con compuertas. Hace exactamente lo que su interconexión determina.
- Digital programable: microcontrolador, microprocesador o FPGA. Su comportamiento está definido por un programa que se puede reemplazar.
Este curso vive casi todo el tiempo en la tercera categoría, pero conviene saber que la primera existe: hay problemas —protección contra sobrecorriente, por ejemplo— donde una solución analógica es más rápida y más confiable que cualquier software.
Actuadores: la salida
Un actuador hace el camino inverso al sensor: convierte energía eléctrica en un efecto físico.
| Efecto buscado | Actuador | Nota práctica |
|---|---|---|
| Movimiento continuo | Motor de corriente continua | Necesita driver; su velocidad se regula con PWM |
| Movimiento a posición | Servomotor | Se comanda con un pulso de ancho variable |
| Movimiento por pasos | Motor paso a paso | Posición precisa sin sensor de realimentación |
| Luz | LED | Requiere resistencia limitadora de corriente |
| Sonido | Zumbador, altavoz | El zumbador pasivo necesita una señal de frecuencia |
| Calor | Resistencia calefactora | Consumo alto, exige etapa de potencia dimensionada |
| Conmutación de cargas grandes | Relé, contactor, triac | Aísla el circuito de control del de potencia |
| Información visual | Pantalla LCD, OLED, matriz de LED | Se comandan por I2C o SPI |
Y aquí va la regla de oro de la que dependen la mayoría de los proyectos que fallan al primer intento: un pin de microcontrolador no alimenta un actuador. Un pin entrega unas pocas decenas de miliamperios como máximo. Un motor pequeño consume cientos. Entre la señal de control y el actuador siempre va una etapa de potencia: un transistor, un driver integrado o un relé. Vamos a volver sobre esto con números concretos en los capítulos de electricidad y de componentes.
Las magnitudes eléctricas y la gente que les dio nombre
Para trabajar con circuitos hay que manejar seis o siete magnitudes. La convención de nombrar las unidades con apellidos de científicos hace que la tabla sirva doble: como referencia técnica y como resumen de quién aportó qué.
| Magnitud | Símbolo | Unidad | En honor a | Qué significa en la práctica |
|---|---|---|---|---|
| Carga eléctrica | Q | culombio (C) | Charles-Augustin de Coulomb | Cantidad de electricidad |
| Voltaje o tensión | V | voltio (V) | Alessandro Volta | Diferencia de potencial; el empuje que mueve las cargas |
| Corriente | I | amperio (A) | André-Marie Ampère | Cantidad de carga que pasa por segundo |
| Resistencia | R | ohmio (Ω) | Georg Simon Ohm | Oposición al paso de la corriente |
| Potencia | P | vatio (W) | James Watt | Energía por unidad de tiempo; el calor que hay que disipar |
| Energía | E | julio (J) | James Prescott Joule | Potencia acumulada en el tiempo |
| Capacitancia | C | faradio (F) | Michael Faraday | Capacidad de almacenar carga en un campo eléctrico |
| Inductancia | L | henrio (H) | Joseph Henry | Capacidad de almacenar energía en un campo magnético |
| Frecuencia | f | hercio (Hz) | Heinrich Hertz | Repeticiones por segundo de una señal periódica |
| Densidad de flujo magnético | B | tesla (T) | Nikola Tesla | Intensidad del campo magnético |
Alessandro Volta construyó en 1800 la pila voltaica, la primera fuente de corriente continua estable, sin la cual nada de lo demás habría sido investigable. André-Marie Ampère formuló la relación entre corriente y magnetismo. Georg Ohm publicó en 1827 la relación entre voltaje, corriente y resistencia, que es la ecuación con la que se resuelve el noventa por ciento de los cálculos de este curso. Michael Faraday describió la inducción electromagnética en 1831, base del motor y del generador. Heinrich Hertz demostró experimentalmente en 1887 la existencia de las ondas electromagnéticas, punto de partida de toda comunicación inalámbrica.
En el capítulo 2 vamos a usar estas magnitudes con números, no solo con nombres.
Microcontrolador, placa con Linux o computador: cómo elegir
Cuando llegues a construir algo real, la primera decisión de arquitectura es dónde corre el programa. Las tres opciones tienen costos y garantías distintas.
| Criterio | Microcontrolador (ESP32, STM32, AVR) | Placa con Linux (Raspberry Pi, BeagleBone) | Computador o servidor |
|---|---|---|---|
| Sistema operativo | Ninguno o un RTOS | Linux completo | Linux, Windows, macOS |
| Memoria RAM típica | De kilobytes a pocos megabytes | Cientos de megabytes a gigabytes | Gigabytes |
| Consumo típico | Miliamperios; microamperios dormido | Cientos de miliamperios a amperios | Decenas de vatios |
| Arranque | Milisegundos | Decenas de segundos | Decenas de segundos |
| Tiempo de respuesta | Determinista, del orden de microsegundos | Variable, el planificador puede interrumpir | Muy variable |
| Acceso directo a pines | Nativo | Sí, con menor precisión temporal | Requiere hardware externo |
| Precio orientativo | Bajo | Medio | Alto |
| Corte de energía abrupto | Tolerable por diseño | Riesgo de corromper el sistema de archivos | Riesgo de corrupción |
| Cuándo conviene | Control directo, batería, alto volumen | Visión, red, almacenamiento, interfaz | Coordinación, datos, paneles |
| Qué corre de Elixir | AtomVM | Nerves o distribución Linux | Elixir y OTP completos |
La arquitectura habitual de un proyecto de IoT combina los tres niveles: microcontroladores en los puntos donde hay que medir y actuar, una placa con Linux como concentrador local, y un servidor que agrega datos y presenta paneles. La pregunta correcta no es cuál es mejor, sino qué responsabilidad va en cada nivel.
Dónde entra Elixir en todo esto
Elixir es un lenguaje funcional que corre sobre la BEAM, la máquina virtual de Erlang. Erlang fue creado en Ericsson en los años ochenta para centrales telefónicas, con tres requisitos que se parecen sospechosamente a los de un sistema robótico: manejar decenas de miles de actividades simultáneas, no detenerse nunca, y seguir funcionando cuando una parte falla.
De esos requisitos salieron cuatro propiedades que valen para el hardware:
Procesos livianos. Un proceso de la BEAM no es un proceso del sistema operativo: pesa unos pocos cientos de bytes al crearse y se pueden tener cientos de miles. Esto permite modelar cada sensor, cada actuador y cada lazo de control como un proceso independiente, sin que la cantidad de dispositivos se convierta en un problema de arquitectura.
Aislamiento. Los procesos no comparten memoria. Se comunican por mensajes. Si uno falla, no corrompe el estado de los demás. En un sistema con hardware —donde un sensor se desconecta, un bus se cuelga y un cable hace mal contacto— esa propiedad es directamente utilizable.
Supervisión. OTP, la biblioteca estándar de Erlang, define árboles de supervisión: procesos cuya única tarea es vigilar a otros y reiniciarlos según una estrategia declarada cuando mueren. La filosofía se resume en let it crash: en vez de programar defensivamente cada caso de error, se deja fallar al proceso afectado y se lo reinicia en un estado conocido. Cuando el que falla es el driver de un sensor intermitente, esa estrategia es exactamente lo que quieres.
Concurrencia con preferencia justa. El planificador de la BEAM reparte tiempo entre procesos de modo que un proceso ocupado no bloquea al resto. Un lazo de control que se atrasa no impide que los demás sigan respondiendo.
En el ecosistema hay dos caminos para llevar Elixir al hardware, y es importante no confundirlos:
- Nerves: construye una imagen mínima de Linux que arranca directo en la BEAM, sin escritorio ni gestor de paquetes, pensada para placas tipo Raspberry Pi o BeagleBone. Tienes Elixir y OTP completos, red, actualizaciones de firmware y acceso a GPIO, I2C y SPI mediante bibliotecas dedicadas.
- AtomVM: una implementación reducida de la máquina virtual de BEAM que corre en microcontroladores como ESP32, STM32 y RP2040, donde no hay Linux ni memoria para la VM completa. Soporta un subconjunto del lenguaje y de OTP, suficiente para procesos, mensajes y supervisión.
flowchart TD
subgraph NIVEL3["Nivel 3 - Servidor"]
PH["Aplicacion Phoenix<br/>panel y API"]
DB[("Base de datos<br/>historicos")]
end
subgraph NIVEL2["Nivel 2 - Placa con Linux"]
NER["Nerves<br/>Elixir y OTP completos"]
SUP["Arbol de supervision"]
BUS["Drivers GPIO / I2C / SPI"]
end
subgraph NIVEL1["Nivel 1 - Microcontrolador"]
AVM["AtomVM sobre ESP32"]
SENS2["Sensores y actuadores<br/>conectados a pines"]
end
SENS2 --> AVM
AVM -->|"MQTT o HTTP<br/>por WiFi"| NER
BUS --> SUP
SUP --> NER
NER -->|"metricas y eventos"| PH
PH --> DB
PH -->|"comandos"| NER
NER -->|"comandos"| AVM
style NIVEL1 fill:#dcedd7,stroke:#5a8f4a
style NIVEL2 fill:#dce9f5,stroke:#4a6f95
style NIVEL3 fill:#e6dff0,stroke:#6f5a95
Nada de esto reemplaza a C ni al ensamblador en los lugares donde hacen falta: si necesitas responder a una interrupción en dos microsegundos con jitter acotado, eso se resuelve en el nivel más bajo. Elixir aporta en la capa donde el problema deja de ser eléctrico y pasa a ser de coordinación: muchos dispositivos, muchos estados, fallas parciales frecuentes y necesidad de seguir operando.
Programa 2: la muñeca del té como máquina de estados
Volvamos al karakuri. El chahakobi ningyō es una máquina de estados finitos implementada en madera. Vale la pena escribirla primero como diagrama y después como código, porque ese par —diagrama y código— es el modo en que se especifica el comportamiento de cualquier sistema embebido.
stateDiagram-v2
[*] --> Reposo
Reposo: En reposo<br/>bandeja vacia, resorte cargado
Avanzando: Avanzando hacia el invitado<br/>resorte se descarga
Esperando: Detenido frente al invitado<br/>bandeja vacia
Girando: Girando 180 grados
Volviendo: Regresando al anfitrion
Reposo --> Avanzando: colocan taza llena<br/>(peso sobre la bandeja)
Avanzando --> Avanzando: paso de leva<br/>(avanza un tramo)
Avanzando --> Esperando: retiran la taza<br/>(se libera el peso)
Esperando --> Girando: colocan taza vacia<br/>(peso sobre la bandeja)
Girando --> Volviendo: giro completado
Volviendo --> Volviendo: paso de leva
Volviendo --> Reposo: llego al origen<br/>o resorte agotado
Avanzando --> Reposo: resorte agotado
Volviendo --> [*]: mecanismo detenido
Fíjate en un detalle: el mecanismo real distingue taza llena de taza vacía solo por el contexto, no por el peso. La máquina no sabe si la taza tiene té; sabe en qué estado estaba antes de que le pusieran peso encima. El estado es memoria, y la memoria es lo que permite que la misma entrada produzca respuestas distintas. Ese es el concepto central de cualquier controlador.
Guarda esto como karakuri.exs y ejecútalo con elixir karakuri.exs.
defmodule Karakuri do
@moduledoc """
Maquina de estados del chahakobi ningyo, la muneca japonesa que sirve te.
El unico sensor es el peso sobre la bandeja; el unico actuador es el
mecanismo de resorte que hace avanzar o girar la figura.
"""
defstruct estado: :reposo, pasos: 0, resorte: 12, bitacora: []
@pasos_por_tramo 3
@doc "Crea la muneca con el resorte cargado."
def nueva(carga \\ 12), do: %__MODULE__{resorte: carga}
@doc """
Aplica un evento del entorno y devuelve la muneca en su nuevo estado.
Eventos posibles: :colocan_taza, :retiran_taza, :tick.
"""
def evento(%__MODULE__{resorte: 0} = m, _cualquiera) do
registrar(%{m | estado: :reposo}, "resorte agotado, la figura se detiene")
end
def evento(%__MODULE__{estado: :reposo} = m, :colocan_taza) do
registrar(%{m | estado: :avanzando}, "peso detectado: se libera el freno y avanza")
end
def evento(%__MODULE__{estado: :avanzando} = m, :tick) do
m = gastar_resorte(m)
if m.pasos + 1 >= @pasos_por_tramo do
registrar(%{m | pasos: 0}, "avanza un tramo completo hacia el invitado")
else
registrar(%{m | pasos: m.pasos + 1}, "avanza un paso de leva")
end
end
def evento(%__MODULE__{estado: :avanzando} = m, :retiran_taza) do
registrar(%{m | estado: :esperando, pasos: 0}, "se libera el peso: la figura frena")
end
def evento(%__MODULE__{estado: :esperando} = m, :colocan_taza) do
registrar(%{m | estado: :girando}, "peso detectado de nuevo: inicia el giro")
end
def evento(%__MODULE__{estado: :girando} = m, :tick) do
m = gastar_resorte(m)
registrar(%{m | estado: :volviendo}, "giro de 180 grados completado")
end
def evento(%__MODULE__{estado: :volviendo, pasos: pasos} = m, :tick)
when pasos + 1 >= @pasos_por_tramo do
m = gastar_resorte(m)
registrar(%{m | estado: :reposo, pasos: 0}, "regresa al anfitrion y queda en reposo")
end
def evento(%__MODULE__{estado: :volviendo} = m, :tick) do
m = gastar_resorte(m)
registrar(%{m | pasos: m.pasos + 1}, "regresa un paso de leva")
end
# Cualquier evento que no corresponda al estado actual se ignora,
# igual que en el mecanismo real: el freno simplemente no cede.
def evento(%__MODULE__{} = m, otro) do
registrar(m, "evento #{inspect(otro)} ignorado en estado #{m.estado}")
end
defp gastar_resorte(%__MODULE__{resorte: r} = m), do: %{m | resorte: max(r - 1, 0)}
defp registrar(%__MODULE__{} = m, texto) do
%{m | bitacora: [{m.estado, m.resorte, texto} | m.bitacora]}
end
@doc "Imprime la bitacora en orden cronologico."
def imprimir(%__MODULE__{bitacora: bitacora}) do
bitacora
|> Enum.reverse()
|> Enum.with_index(1)
|> Enum.each(fn {{estado, resorte, texto}, i} ->
indice = String.pad_leading(Integer.to_string(i), 2)
est = String.pad_trailing(Atom.to_string(estado), 10)
IO.puts(" #{indice}. [#{est}] resorte=#{resorte} #{texto}")
end)
end
end
secuencia = [
:tick,
:colocan_taza,
:tick,
:tick,
:tick,
:retiran_taza,
:tick,
:colocan_taza,
:tick,
:tick,
:tick,
:tick
]
IO.puts("Ciclo completo de la muneca que sirve te:\n")
final =
Enum.reduce(secuencia, Karakuri.nueva(), fn ev, muneca ->
Karakuri.evento(muneca, ev)
end)
Karakuri.imprimir(final)
IO.puts("\nEstado final: #{final.estado}, resorte restante: #{final.resorte}")
Tres cosas para observar en ese código, porque se repiten en todo el curso:
- El estado vive en una estructura de datos, no en variables globales. Cada evento produce una estructura nueva; no se muta nada.
- Las transiciones se expresan con coincidencia de patrones en la cabecera de las funciones. Cada cláusula de
evento/2es una flecha del diagrama de estados. - La última cláusula ignora los eventos que no corresponden al estado actual. Un sistema real recibe todo el tiempo entradas que no vienen al caso, y el diseño tiene que decidir explícitamente qué hacer con ellas.
Programa 3: el ciclo completo con supervisión
Ahora juntemos las dos mitades del capítulo. Este programa implementa el ciclo capturar-procesar-actuar con tres procesos independientes de la BEAM, bajo un árbol de supervisión. El “sensor” está simulado —todavía no tenemos hardware—, pero la estructura es exactamente la que usarás cuando el sensor sea real.
El árbol queda así:
flowchart TD
SUP["Supervisor<br/>estrategia one_for_one"]
ACT["Actuador<br/>GenServer"]
CTL["Controlador<br/>GenServer con histeresis"]
SEN["Sensor<br/>GenServer que mide cada 300 ms"]
SUP --> ACT
SUP --> CTL
SUP --> SEN
SEN -->|"cast :nueva_lectura"| CTL
CTL -->|"call :encender / :apagar"| ACT
SEN -.->|"si muere, el supervisor<br/>lo reinicia sin tocar<br/>a los otros dos"| SUP
style SUP fill:#2f4858,stroke:#1b2b36,color:#ffffff
Guarda esto como lazo_control.exs y ejecútalo con elixir lazo_control.exs.
defmodule Actuador do
@moduledoc """
Representa el elemento que modifica el mundo fisico: un calefactor,
un rele o un motor. Aqui solo imprime, pero la interfaz es la misma
que tendria un driver real de GPIO.
"""
use GenServer
def start_link(_opts) do
GenServer.start_link(__MODULE__, :apagado, name: __MODULE__)
end
def encender, do: GenServer.call(__MODULE__, :encender)
def apagar, do: GenServer.call(__MODULE__, :apagar)
def estado, do: GenServer.call(__MODULE__, :estado)
@impl true
def init(estado_inicial), do: {:ok, estado_inicial}
@impl true
def handle_call(:encender, _from, :apagado) do
IO.puts(" [actuador] calefactor ENCENDIDO")
{:reply, :ok, :encendido}
end
def handle_call(:encender, _from, :encendido) do
{:reply, :sin_cambio, :encendido}
end
def handle_call(:apagar, _from, :encendido) do
IO.puts(" [actuador] calefactor APAGADO")
{:reply, :ok, :apagado}
end
def handle_call(:apagar, _from, :apagado) do
{:reply, :sin_cambio, :apagado}
end
def handle_call(:estado, _from, estado) do
{:reply, estado, estado}
end
end
defmodule Controlador do
@moduledoc """
Etapa de procesamiento: recibe lecturas, aplica histeresis y decide
si el actuador debe encenderse o apagarse.
La histeresis evita que el actuador conmute sin parar cuando la
temperatura queda oscilando justo en el valor objetivo.
"""
use GenServer
@objetivo 21.0
@banda 0.8
def start_link(_opts) do
GenServer.start_link(__MODULE__, %{ultima: nil, conmutaciones: 0}, name: __MODULE__)
end
def nueva_lectura(celsius), do: GenServer.cast(__MODULE__, {:lectura, celsius})
def resumen, do: GenServer.call(__MODULE__, :resumen)
@impl true
def init(estado), do: {:ok, estado}
@impl true
def handle_cast({:lectura, celsius}, estado) do
decision =
cond do
celsius < @objetivo - @banda -> :encender
celsius > @objetivo + @banda -> :apagar
true -> :mantener
end
IO.puts(
" [controlador] lectura=#{:erlang.float_to_binary(celsius, decimals: 2)} C" <>
" objetivo=#{@objetivo} +/- #{@banda} decision=#{decision}"
)
resultado =
case decision do
:encender -> Actuador.encender()
:apagar -> Actuador.apagar()
:mantener -> :sin_cambio
end
conmutaciones =
if resultado == :ok, do: estado.conmutaciones + 1, else: estado.conmutaciones
{:noreply, %{estado | ultima: celsius, conmutaciones: conmutaciones}}
end
@impl true
def handle_call(:resumen, _from, estado) do
{:reply, estado, estado}
end
end
defmodule Sensor do
@moduledoc """
Etapa de captura. Simula un sensor de temperatura que se autoprograma
cada 300 ms. En hardware real, aqui iria una lectura por I2C o una
conversion del ADC en lugar del calculo simulado.
"""
use GenServer
@periodo_ms 300
def start_link(_opts) do
GenServer.start_link(__MODULE__, %{t: 18.0, muestra: 0}, name: __MODULE__)
end
@impl true
def init(estado) do
programar()
{:ok, estado}
end
@impl true
def handle_info(:medir, estado) do
# Modelo simple: si el calefactor esta encendido la temperatura sube,
# si esta apagado baja, y siempre hay algo de ruido de medicion.
delta =
case Actuador.estado() do
:encendido -> 0.7
:apagado -> -0.45
end
ruido = (:rand.uniform() - 0.5) * 0.25
nueva_t = estado.t + delta + ruido
IO.puts("[sensor] muestra ##{estado.muestra + 1}")
Controlador.nueva_lectura(nueva_t)
programar()
{:noreply, %{estado | t: nueva_t, muestra: estado.muestra + 1}}
end
defp programar, do: Process.send_after(self(), :medir, @periodo_ms)
end
# --- Arranque del sistema ---
children = [
{Actuador, []},
{Controlador, []},
{Sensor, []}
]
{:ok, supervisor} = Supervisor.start_link(children, strategy: :one_for_one)
IO.puts("Sistema iniciado. Supervisor: #{inspect(supervisor)}\n")
Process.sleep(3_000)
IO.puts("\n--- Provocando una falla del sensor a proposito ---\n")
pid_antes = Process.whereis(Sensor)
Process.exit(pid_antes, :kill)
Process.sleep(100)
pid_despues = Process.whereis(Sensor)
IO.puts("PID del sensor antes de la falla: #{inspect(pid_antes)}")
IO.puts("PID del sensor despues del reinicio: #{inspect(pid_despues)}")
IO.puts("El actuador conservo su estado: #{Actuador.estado()}\n")
Process.sleep(3_000)
resumen = Controlador.resumen()
IO.puts("\n--- Resumen ---")
IO.puts("Ultima lectura procesada: #{:erlang.float_to_binary(resumen.ultima, decimals: 2)} C")
IO.puts("Conmutaciones del actuador: #{resumen.conmutaciones}")
Al ejecutarlo verás la temperatura subir cuando el calefactor se enciende, bajar cuando se apaga, y al actuador conmutando solo cuando se sale de la banda. Después, cuando matamos el proceso del sensor a propósito, el supervisor lo reinicia con un identificador nuevo y el sistema sigue midiendo, sin que el actuador ni el controlador se enteren.
Ese comportamiento es el argumento central para usar la BEAM en hardware. Un sensor real se desconecta, un bus I2C se cuelga, un cable hace mal contacto. Con esta arquitectura, la falla queda contenida en el proceso que la sufrió.
Vale la pena señalar dos decisiones de diseño del código:
- El PWM y la histeresis. Sin la banda de
@banda, el actuador conmutaría en cada muestra alrededor del objetivo. La histeresis es la solución estándar a ese problema en control por encendido y apagado, y aparece en termostatos reales. - El ruido de medición. Lo agregué a propósito. Un sensor real nunca entrega el mismo valor dos veces seguidas, y un controlador que no lo contemple va a conmutar por ruido en lugar de conmutar por temperatura.
Errores comunes al empezar
Estos son los tropiezos que aparecen una y otra vez en el primer contacto con el área. Vale la pena tenerlos identificados antes de encender nada.
| Error | Por qué ocurre | Cómo se resuelve |
|---|---|---|
| Alimentar un motor o un relé directo desde un pin del microcontrolador | El pin se ve como una salida “que da corriente” y no se revisa su límite, que suele ser de unas decenas de miliamperios | Poner siempre una etapa de potencia entre el pin y la carga: transistor, driver integrado o módulo de relé |
| Conectar un LED sin resistencia | El LED no limita su propia corriente; con voltaje directo consume hasta destruirse | Calcular la resistencia en serie a partir del voltaje de alimentación, la caída del LED y la corriente deseada |
| Confundir un microcontrolador con una placa con Linux | Ambos se llaman “placa” y tienen pines, así que parecen intercambiables | Verificar si el proyecto necesita sistema de archivos, red compleja o multitarea del sistema operativo; si no, alcanza un microcontrolador |
| Mezclar niveles lógicos de 5 V y 3,3 V | Muchos módulos populares son de 5 V y muchas placas modernas de 3,3 V | Usar un convertidor de nivel bidireccional o módulos que declaren tolerancia al nivel del otro |
| Alimentar la lógica y la potencia desde la misma fuente sin desacoplar | Un motor arrancando produce una caída de voltaje que reinicia el microcontrolador | Separar las fuentes o desacoplar con condensadores; unir siempre las tierras |
| Suponer que el sensor entrega el valor exacto | Toda hoja de datos declara error y deriva, pero es fácil ignorarlo al leer un número en pantalla | Promediar varias muestras, filtrar, y calibrar contra una referencia conocida |
| No unir las tierras entre dos circuitos que se comunican | Sin referencia común los niveles lógicos no significan nada y la comunicación falla de forma intermitente | Conectar siempre el negativo común entre todos los circuitos que intercambian señales |
| Programar el lazo de control con una espera bloqueante | Es la primera forma que se le ocurre a cualquiera para “esperar un rato” | Usar temporizadores y mensajes; en Elixir, Process.send_after/3 en lugar de Process.sleep/1 dentro de un proceso que debe seguir atendiendo |
| Meter toda la lógica en un solo proceso | Se percibe como más simple mantener un único bucle | Separar captura, procesamiento y acción en procesos distintos y supervisarlos; un fallo deja de tumbar el sistema entero |
| Conmutar el actuador sin histéresis | Se compara la lectura contra un umbral único y se olvida el ruido | Definir una banda alrededor del objetivo, como en el programa de este capítulo |
| Copiar un esquema sin leer la hoja de datos | Los módulos “iguales” suelen tener variantes con distinto voltaje o distinta disposición de pines | Verificar siempre el modelo exacto del componente que tienes en la mano contra su hoja de datos |
| Ignorar el consumo en reposo en un proyecto a batería | Se mide el consumo mientras el dispositivo trabaja, no mientras espera | Medir el consumo en reposo y usar los modos de bajo consumo del microcontrolador |
Ejercicios propuestos
Los primeros cuatro son de comprensión y no requieren hardware. Los últimos dos son de código y se ejecutan con Elixir instalado; puedes comprobar que lo tienes con elixir --version.
-
Clasificación. Elige cinco aparatos que tengas a mano —por ejemplo un microondas, unos audífonos inalámbricos, un ascensor, un semáforo y una impresora— y para cada uno identifica al menos un sensor, la etapa de procesamiento y al menos un actuador. Indica en cuáles existe realimentación y en cuáles el comportamiento es de lazo abierto.
-
Autómata, robot o embebido. Para esos mismos cinco aparatos, decide cuál de las tres categorías del comienzo del capítulo le corresponde, y justifica en una frase qué criterio de la tabla usaste.
-
La cadena del programa. Reconstruye de memoria el diagrama que va del disco de levas de Jaquet-Droz hasta la actualización por WiFi, y para cada eslabón responde: ¿cuánto costaba cambiar el programa en esa etapa, y quién podía hacerlo?
-
Elección de plataforma. Para cada uno de estos tres proyectos, decide si usarías microcontrolador, placa con Linux o servidor, y justifica con al menos dos criterios de la tabla comparativa: (a) un sensor de humedad de suelo alimentado con una batería que debe durar seis meses; (b) un sistema que detecta objetos con una cámara y clasifica lo que ve; (c) un panel que muestra el consumo eléctrico de veinte casas.
-
Modificar el telar. Toma
jacquard.exsy añade una funciónJacquard.invertir/1que reciba una tarjeta y devuelva su negativo, y otraJacquard.repetir/2que reciba un mazo y un número de repeticiones y produzca el tejido resultante. Después escribe un mazo propio que dibuje tus iniciales. -
Ampliar la muñeca. Al módulo
Karakuri, agrega un estado:atascadaal que se llegue si se recibe el evento:obstaculomientras la figura avanza, y del que solo se salga con el evento:destrabar. Actualiza el diagrama de estados en Mermaid para reflejar la transición nueva, y comprueba en la bitácora que los eventos recibidos mientras está atascada quedan registrados como ignorados. -
Endurecer el lazo de control. En
lazo_control.exs, haz tres cambios y observa el efecto en la cantidad de conmutaciones: (a) reduce@bandaa0.05y cuenta las conmutaciones; (b) devuélvela a0.8y añade al controlador un promedio móvil de las últimas tres lecturas antes de decidir; (c) mata el procesoControladoren lugar delSensory explica por qué el sistema sobrevive igual con la estrategia:one_for_one. -
Investigación corta. Busca la hoja de datos de un sensor de temperatura concreto —por ejemplo el DS18B20 o el SHT31— y anota tres datos: su rango de medición, su exactitud declarada y su tiempo de conversión. Compara ese tiempo de conversión con el período de 300 ms del sensor simulado del capítulo y decide si el período elegido era razonable.
Lo que viene
En este capítulo quedó armado el mapa: de dónde viene la idea de máquina programable, cómo llegó a caber dentro de un chip de pocos milímetros, cómo se organiza cualquier sistema interactivo en las tres etapas de capturar, procesar y actuar, y por qué una máquina virtual diseñada para telefonía resulta cómoda para coordinar hardware que falla. También quedaron nombradas las magnitudes eléctricas, pero solo nombradas.
El capítulo 2 las pone a trabajar con números. Vas a ver qué son realmente el voltaje, la corriente y la resistencia, cómo se relacionan por la ley de Ohm, qué diferencia hay entre corriente continua y alterna, cómo se calcula la potencia que disipa un componente y por qué ese cálculo decide si tu circuito funciona o se quema. Con eso vas a poder dimensionar la resistencia de un LED, elegir una fuente de alimentación y entender qué está midiendo un multímetro cuando le pones las puntas a un circuito. El índice completo del curso está en /tecnologias/elixir-robotics/00-indice/.