Introducción: ¿Y se puede hacer así?
\nImagina: estás programando un microcontrolador, pero usando lambdas, corrutinas y constructores type-safe. ¿Suena a ciencia ficción? Pues no. El desarrollo embedded con Kotlin no es una broma, sino una herramienta completamente funcional que está ganando terreno.
\nTradicionalmente, el mundo del "hardware" es el reino de C y ensamblador. Luego llegó tímidamente C++, pero con reservas. Y ahora, Kotlin. El lenguaje que asociamos con Android y backend, de repente habla el idioma de los registros y las interrupciones. ¿Cómo? A través de Kotlin/Native, una tecnología que compila tu código directamente a código máquina, sin pasar por la JVM.
\nEn este artículo, analizaremos cómo usar Kotlin para sistemas embebidos, escribiremos código real para hacer parpadear un LED (sí, no puede faltar) y veremos si vale la pena. Si estás cansado de las macros de C y quieres una sintaxis moderna a nivel de registros, este es tu lugar.
\n\nKotlin/Native: ¿Cómo funciona internamente?
\nLa pregunta principal que surge en cualquier ingeniero embedded: "¿No irá lento?".
\nKotlin/Native utiliza LLVM como backend. Esto significa que tu código se compila en un binario nativo que se puede ejecutar en bare-metal o bajo un RTOS. Sin recolección de basura en tiempo de ejecución (si la desactivas), sin máquina virtual. Solo tú y el procesador.
\nEn términos de rendimiento, Kotlin para microcontroladores prácticamente no se queda atrás frente a C. Sí, hay una pequeña sobrecarga del modelo de objetos, pero se compensa con la comodidad y seguridad del código.
\n\n¿Qué necesitas para empezar?
\n- \n
- Kotlin/Native instalado (a través del plugin Kotlin Multiplatform o manualmente) \n
- Toolchain para tu arquitectura (ARM, RISC-V, x86) — normalmente un conjunto de compiladores cruzados \n
- Una biblioteca para trabajar con el hardware — por ejemplo,
kotlinx-c-interopo bindings manuales \n - Paciencia — los primeros pasos serán inusuales \n
Escribiendo código: Haciendo parpadear un LED en Kotlin
\nVamos al grano. Supongamos que tenemos una placa STM32 (ARM Cortex-M). Queremos encender un LED en el pin PA5.
\nPrimero, nos conectamos a los registros a través de Kotlin/Native interop:
\n\nimport kotlinx.cinterop.*\nimport platform.posix.*\n\n// Direcciones de registros para STM32F4 (ejemplo)\nval RCC_AHB1ENR: UIntPointer = 0x40023830.toUInt().ptr\nval GPIOA_MODER: UIntPointer = 0x40020000.toUInt().ptr\nval GPIOA_ODR: UIntPointer = 0x40020014.toUInt().ptr\n\nfun main() {\n // Habilitamos el reloj de GPIOA\n RCC_AHB1ENR.pointed.value = RCC_AHB1ENR.pointed.value or (1u shl 0)\n \n // Configuramos PA5 como salida (01 en los bits 10-11)\n GPIOA_MODER.pointed.value = GPIOA_MODER.pointed.value and (3u shl 10).inv()\n GPIOA_MODER.pointed.value = GPIOA_MODER.pointed.value or (1u shl 10)\n \n // Bucle infinito: parpadeo\n while (true) {\n GPIOA_ODR.pointed.value = GPIOA_ODR.pointed.value xor (1u shl 5)\n // Retardo (aproximado)\n for (i in 0 until 100000) {\n // vacío\n }\n }\n}\n\nSe parece casi a C, pero con el azúcar sintáctico de Kotlin. Observa la u (números sin signo) y el trabajo con punteros mediante ptr. Esto no es un error, sino una característica: así le indicamos explícitamente al compilador que estamos trabajando directamente con la memoria.
¿Se puede hacer más bonito?
\n¡Sí! Podemos envolver el trabajo con los registros en un DSL. Mira qué elegante se ve el código usando funciones de extensión:
\n\n// Definimos una API cómoda\nfun UIntPointer.setBit(bit: Int) {\n this.pointed.value = this.pointed.value or (1u shl bit)\n}\n\nfun UIntPointer.clearBit(bit: Int) {\n this.pointed.value = this.pointed.value and (1u shl bit).inv()\n}\n\nfun UIntPointer.toggleBit(bit: Int) {\n this.pointed.value = this.point