De los autómatas a los sistemas embebidos: historia de la robótica y primeros conceptos de electrónica

Por: Artiko
elixirroboticaiotelectronicahistoriasistemas-embebidosautomatasmicrocontroladoresbeam

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.

CriterioAutómata clásicoRobotSistema embebido
Percibe el entornoNo, o casi nadaSí, es esencialDepende del diseño
ComportamientoFijo, mecánicoProgramableProgramable
Cambiar su conductaRehacer el mecanismoCargar otro programaCargar otro firmware
Partes móvilesSiempreCasi siempreNo necesariamente
EjemploMuñeca karakuri de téBrazo industrial de soldaduraSensor de humedad con ESP32
Aparece haciaSiglo IX en adelante19611974

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:

  1. Una fuente de energía almacenada (un peso o un resorte) separada de la ejecución.
  2. 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.
  3. 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ómataPiezas aproximadasQué haceDetalle relevante
El Dibujante~2.000Traza cuatro dibujos distintosSopla el polvo del lápiz y mueve los ojos
La Música~2.500Toca cinco piezas en un órgano realSigue la partitura con la mirada y simula respiración
El Escritor~6.000Escribe textos de hasta 40 caracteresEl 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áquinaAñosTecnologíaAporte principal
Z11936-1938Placas metálicas y pasadores deslizantesMemoria y unidad de cálculo separadas; coma flotante binaria
Z21939-1940Relés en la unidad aritmética, memoria mecánicaPrueba de que los relés eran más fiables
Z31941Unos 2.600 relés electromagnéticosPrimer computador digital programable y automático que funcionó
Z41945-1950Relés, uso comercial en ETH ZúrichPrimer 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ñoHitoProtagonistas
1925-1928Patentes del transistor de efecto de campo y de una estructura tipo MOSJulius Edgar Lilienfeld
1947Transistor de contacto puntual, en germanioJohn Bardeen y Walter Brattain, Bell Labs
1948Presentación pública del dispositivoBell Labs
1948-1951Transistor de unión bipolar, más robusto y fabricableWilliam Shockley
1954Primer transistor comercial de silicioGordon Teal, Texas Instruments
1958-1959Primeros circuitos integradosJack Kilby (TI) y Robert Noyce (Fairchild)
1959-1960MOSFET funcional, base de la electrónica digital actualMohamed Atalla y Dawon Kahng, Bell Labs
1971Intel 4004, primer microprocesador comercial, ~2.300 transistoresFederico 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:

AspectoSeñal analógicaSeñal digital
Valores posiblesInfinitos dentro de un rango continuoUn conjunto discreto, normalmente dos
Ejemplo físicoSalida de un micrófonoEstado de un pulsador
Efecto del ruidoSe acumula y degrada la informaciónSe descarta mientras no cruce el umbral
Copiar la señalPierde calidad en cada copiaEs exacta
ProcesarlaCon circuitos dedicadosCon un programa
En el microcontroladorEntra por el conversor analógico-digitalEntra 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.

MagnitudSensor típicoPrincipio de funcionamientoSalida habitual
TemperaturaNTC, PT100, DS18B20La resistencia del material cambia con la temperaturaAnalógica o digital por bus
LuzLDR, fotodiodoLa conductividad varía con la luz incidenteAnalógica
DistanciaHC-SR04, sensor infrarrojo, ToFTiempo de vuelo de un pulso ultrasónico o de luzPulso digital o bus
Aceleración e inclinaciónMPU-6050, ADXL345Masa suspendida cuyo desplazamiento cambia una capacitanciaBus I2C o SPI
Humedad relativaDHT22, SHT31La capacitancia de un polímero cambia con la humedadBus digital
PresiónBMP280, celda de cargaDeformación de una membrana o de una galga extensiométricaBus digital o analógica
CorrienteShunt, sensor de efecto HallCaída de voltaje o campo magnético generadoAnalógica
PresenciaPulsador, PIR, sensor magnéticoContacto, radiación infrarroja, campo magnéticoDigital

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 buscadoActuadorNota práctica
Movimiento continuoMotor de corriente continuaNecesita driver; su velocidad se regula con PWM
Movimiento a posiciónServomotorSe comanda con un pulso de ancho variable
Movimiento por pasosMotor paso a pasoPosición precisa sin sensor de realimentación
LuzLEDRequiere resistencia limitadora de corriente
SonidoZumbador, altavozEl zumbador pasivo necesita una señal de frecuencia
CalorResistencia calefactoraConsumo alto, exige etapa de potencia dimensionada
Conmutación de cargas grandesRelé, contactor, triacAísla el circuito de control del de potencia
Información visualPantalla LCD, OLED, matriz de LEDSe 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é.

MagnitudSímboloUnidadEn honor aQué significa en la práctica
Carga eléctricaQculombio (C)Charles-Augustin de CoulombCantidad de electricidad
Voltaje o tensiónVvoltio (V)Alessandro VoltaDiferencia de potencial; el empuje que mueve las cargas
CorrienteIamperio (A)André-Marie AmpèreCantidad de carga que pasa por segundo
ResistenciaRohmio (Ω)Georg Simon OhmOposición al paso de la corriente
PotenciaPvatio (W)James WattEnergía por unidad de tiempo; el calor que hay que disipar
EnergíaEjulio (J)James Prescott JoulePotencia acumulada en el tiempo
CapacitanciaCfaradio (F)Michael FaradayCapacidad de almacenar carga en un campo eléctrico
InductanciaLhenrio (H)Joseph HenryCapacidad de almacenar energía en un campo magnético
Frecuenciafhercio (Hz)Heinrich HertzRepeticiones por segundo de una señal periódica
Densidad de flujo magnéticoBtesla (T)Nikola TeslaIntensidad 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.

CriterioMicrocontrolador (ESP32, STM32, AVR)Placa con Linux (Raspberry Pi, BeagleBone)Computador o servidor
Sistema operativoNinguno o un RTOSLinux completoLinux, Windows, macOS
Memoria RAM típicaDe kilobytes a pocos megabytesCientos de megabytes a gigabytesGigabytes
Consumo típicoMiliamperios; microamperios dormidoCientos de miliamperios a amperiosDecenas de vatios
ArranqueMilisegundosDecenas de segundosDecenas de segundos
Tiempo de respuestaDeterminista, del orden de microsegundosVariable, el planificador puede interrumpirMuy variable
Acceso directo a pinesNativoSí, con menor precisión temporalRequiere hardware externo
Precio orientativoBajoMedioAlto
Corte de energía abruptoTolerable por diseñoRiesgo de corromper el sistema de archivosRiesgo de corrupción
Cuándo convieneControl directo, batería, alto volumenVisión, red, almacenamiento, interfazCoordinación, datos, paneles
Qué corre de ElixirAtomVMNerves o distribución LinuxElixir 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:

  1. El estado vive en una estructura de datos, no en variables globales. Cada evento produce una estructura nueva; no se muta nada.
  2. Las transiciones se expresan con coincidencia de patrones en la cabecera de las funciones. Cada cláusula de evento/2 es una flecha del diagrama de estados.
  3. 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.

ErrorPor qué ocurreCómo se resuelve
Alimentar un motor o un relé directo desde un pin del microcontroladorEl pin se ve como una salida “que da corriente” y no se revisa su límite, que suele ser de unas decenas de miliamperiosPoner siempre una etapa de potencia entre el pin y la carga: transistor, driver integrado o módulo de relé
Conectar un LED sin resistenciaEl LED no limita su propia corriente; con voltaje directo consume hasta destruirseCalcular 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 LinuxAmbos se llaman “placa” y tienen pines, así que parecen intercambiablesVerificar 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 VMuchos módulos populares son de 5 V y muchas placas modernas de 3,3 VUsar 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 desacoplarUn motor arrancando produce una caída de voltaje que reinicia el microcontroladorSeparar las fuentes o desacoplar con condensadores; unir siempre las tierras
Suponer que el sensor entrega el valor exactoToda hoja de datos declara error y deriva, pero es fácil ignorarlo al leer un número en pantallaPromediar varias muestras, filtrar, y calibrar contra una referencia conocida
No unir las tierras entre dos circuitos que se comunicanSin referencia común los niveles lógicos no significan nada y la comunicación falla de forma intermitenteConectar siempre el negativo común entre todos los circuitos que intercambian señales
Programar el lazo de control con una espera bloqueanteEs 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 procesoSe percibe como más simple mantener un único bucleSeparar captura, procesamiento y acción en procesos distintos y supervisarlos; un fallo deja de tumbar el sistema entero
Conmutar el actuador sin histéresisSe compara la lectura contra un umbral único y se olvida el ruidoDefinir una banda alrededor del objetivo, como en el programa de este capítulo
Copiar un esquema sin leer la hoja de datosLos módulos “iguales” suelen tener variantes con distinto voltaje o distinta disposición de pinesVerificar 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íaSe mide el consumo mientras el dispositivo trabaja, no mientras esperaMedir 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.

  1. 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.

  2. 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.

  3. 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?

  4. 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.

  5. Modificar el telar. Toma jacquard.exs y añade una función Jacquard.invertir/1 que reciba una tarjeta y devuelva su negativo, y otra Jacquard.repetir/2 que reciba un mazo y un número de repeticiones y produzca el tejido resultante. Después escribe un mazo propio que dibuje tus iniciales.

  6. Ampliar la muñeca. Al módulo Karakuri, agrega un estado :atascada al que se llegue si se recibe el evento :obstaculo mientras 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.

  7. Endurecer el lazo de control. En lazo_control.exs, haz tres cambios y observa el efecto en la cantidad de conmutaciones: (a) reduce @banda a 0.05 y cuenta las conmutaciones; (b) devuélvela a 0.8 y añade al controlador un promedio móvil de las últimas tres lecturas antes de decidir; (c) mata el proceso Controlador en lugar del Sensor y explica por qué el sistema sobrevive igual con la estrategia :one_for_one.

  8. 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/.