Introdução: E aí, dá pra fazer isso?
\nImagine: você está programando para um microcontrolador, mas usando lambdas, corrotinas e construtores type-safe. Parece ficção científica? Pois não é. Desenvolvimento embedded com Kotlin não é brincadeira, é uma ferramenta totalmente funcional que está ganhando força.
\nTradicionalmente, o mundo do "hardware" é o reino do C e do assembly. Depois, o C++ entrou timidamente, mas com ressalvas. E agora, o Kotlin. A linguagem que costumamos associar ao Android e ao backend, de repente, fala a língua dos registradores e interrupções. Como? Através do Kotlin/Native — uma tecnologia que compila seu código diretamente em código de máquina, sem passar pela JVM.
\nNeste artigo, vamos explorar como usar Kotlin para sistemas embedded, escrever código real para piscar um LED (sim, não podia faltar) e ver se vale a pena. Se você está cansado das macros do C e quer uma sintaxe moderna no nível dos registradores, este artigo é para você.
\n\nKotlin/Native: Como funciona por baixo dos panos?
\nA principal pergunta que qualquer engenheiro embedded faz: "Isso não vai ser lento?".
\nO Kotlin/Native usa o LLVM como backend. Isso significa que seu código é compilado em um binário nativo, que pode ser executado em bare-metal ou sob uma RTOS. Sem coleta de lixo em tempo de execução (se você a desativar), sem máquina virtual. Apenas você e o processador.
\nEm termos de desempenho, Kotlin para microcontroladores praticamente não fica atrás do C. Sim, há uma pequena sobrecarga do modelo de objetos, mas ela é compensada pela conveniência e segurança do código.
\n\nO que é necessário para começar?
\n- \n
- Kotlin/Native instalado (via plugin Kotlin Multiplatform ou manualmente) \n
- Cadeia de ferramentas para sua arquitetura (ARM, RISC-V, x86) — geralmente um conjunto de cross-compiladores \n
- Biblioteca para trabalhar com hardware — por exemplo,
kotlinx-c-interopou bindings manuais \n - Paciência — os primeiros passos podem ser estranhos \n
Escrevendo código: Piscando um LED com Kotlin
\nVamos direto ao ponto. Suponha que temos uma placa STM32 (ARM Cortex-M). Precisamos acender um LED no pino PA5.
\nPrimeiro, vamos nos conectar aos registradores via Kotlin/Native interop:
\n\nimport kotlinx.cinterop.*\nimport platform.posix.*\n\n// Endereços dos registradores para STM32F4 (exemplo)\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 // Habilita o clock do GPIOA\n RCC_AHB1ENR.pointed.value = RCC_AHB1ENR.pointed.value or (1u shl 0)\n \n // Configura PA5 como saída (01 nos 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 // Loop infinito: pisca\n while (true) {\n GPIOA_ODR.pointed.value = GPIOA_ODR.pointed.value xor (1u shl 5)\n // Atraso (grosseiro)\n for (i in 0 until 100000) {\n // vazio\n }\n }\n}\n\nParece quase C, mas com o açúcar sintático do Kotlin. Observe o u (números sem sinal) e o trabalho com ponteiros via ptr. Isso não é um bug, é um recurso — assim dizemos explicitamente ao compilador que estamos trabalhando diretamente com a memória.
Dá pra deixar mais bonito?
\nSim! Podemos encapsular o trabalho com registradores em um DSL. Veja como o código fica elegante usando extension functions:
\n\n// Define uma API conveniente\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