---
title: "Zig para Sistemas Embarcados: IoT e Microcontroladores"
url: "https://ziglang.com.br/tutoriais/zig-embedded-systems/"
markdown_url: "https://ziglang.com.br/tutoriais/zig-embedded-systems.MD"
description: "Guia completo de Zig para sistemas embarcados. Aprenda a programar ARM Cortex-M, RISC-V e AVR com exemplos práticos."
date: "2026-02-20"
author: "Zig Brasil"
---

# Zig para Sistemas Embarcados: IoT e Microcontroladores

Guia completo de Zig para sistemas embarcados. Aprenda a programar ARM Cortex-M, RISC-V e AVR com exemplos práticos.


Sistemas embarcados são um dos domínios onde a **zig lang** mais brilha. A **linguagem Zig** foi projetada desde o início para ser uma alternativa moderna ao C, e em nenhum lugar isso é mais evidente do que na programação de microcontroladores. Sem dependência de standard library, sem alocações ocultas, com cross-compilation embutida e controle total sobre o hardware, Zig é uma escolha natural para embedded development.

## Por Que Zig para Embedded

Programadores de sistemas embarcados tradicionalmente usam C (e às vezes C++) como linguagem principal. Zig oferece vantagens significativas sobre ambas:

- **Sem dependência de standard library**: Zig pode compilar para alvos `freestanding`, sem nenhuma dependência de sistema operacional ou biblioteca padrão.
- **Sem alocações ocultas**: Diferente de C++, Zig não faz alocações de memória implícitas. Toda alocação é explícita e controlada pelo programador, como detalhado no tutorial de [gerenciamento de memória em Zig](/tutoriais/gerenciamento-de-memoria-zig/).
- **[Cross-compilation trivial](/tutoriais/zig-cross-compilation/)**: Compile para ARM, RISC-V, AVR e outros alvos com uma simples flag de compilação.
- **Segurança sem custo**: Verificações de bounds checking, integer overflow e alinhamento de memória podem ser ativadas em debug e removidas em release.
- **Interoperabilidade C perfeita**: Use headers C de fabricantes (HAL, CMSIS) diretamente via `@cImport`.
- **Comptime**: Gere registradores, tabelas de vetores de interrupção e configurações de periféricos em tempo de compilação.

## Alvos Embarcados Suportados

Zig, através do backend LLVM, suporta uma ampla gama de arquiteturas embarcadas:

| Arquitetura | Exemplos de Hardware | Alvo Zig |
|---|---|---|
| ARM Cortex-M0/M0+ | STM32F0, nRF51, RP2040 | `thumb-freestanding` |
| ARM Cortex-M3 | STM32F1, LPC1768 | `thumb-freestanding` |
| ARM Cortex-M4/M4F | STM32F4, nRF52, SAM4 | `thumb-freestanding` |
| ARM Cortex-M7 | STM32H7, STM32F7 | `thumb-freestanding` |
| ARM Cortex-A (Linux) | Raspberry Pi, BeagleBone | `aarch64-linux` |
| RISC-V | ESP32-C3, SiFive, GD32V | `riscv32-freestanding` |
| AVR | Arduino, ATmega328P | `avr-freestanding` |

## Configurando para Bare-Metal

Para programação bare-metal (sem sistema operacional), precisamos usar o target `freestanding` e configurar o linker script corretamente.

### build.zig para ARM Cortex-M4

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.resolveTargetQuery(.{
        .cpu_arch = .thumb,
        .os_tag = .freestanding,
        .abi = .eabi,
        .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 },
    });

    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "firmware",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // Linker script específico do hardware
    exe.setLinkerScript(b.path("stm32f407.ld"));

    // Desabilitar stack protector (não disponível em bare-metal)
    exe.root_module.stack_protector = false;

    b.installArtifact(exe);

    // Step para gerar binário .bin para flash
    const bin = b.addObjCopy(exe.getEmittedBin(), .{
        .format = .bin,
    });
    const install_bin = b.addInstallBinFile(bin.getOutput(), "firmware.bin");

    const flash_step = b.step("bin", "Gerar binário para flash");
    flash_step.dependOn(&install_bin.step);
}
```

### Linker Script

O linker script define o layout da memória do microcontrolador:

```ld
/* stm32f407.ld */
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 1024K
    RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .text :
    {
        KEEP(*(.isr_vector))
        *(.text*)
        *(.rodata*)
    } > FLASH

    .data :
    {
        _sdata = .;
        *(.data*)
        _edata = .;
    } > RAM AT > FLASH

    .bss :
    {
        _sbss = .;
        *(.bss*)
        *(COMMON)
        _ebss = .;
    } > RAM

    _stack_top = ORIGIN(RAM) + LENGTH(RAM);
}
```

## Bare-Metal Hello World: Piscando um LED

O "Hello World" dos sistemas embarcados é piscar um LED. Vamos implementar isso para um STM32F407 (ARM Cortex-M4).

```zig
// src/main.zig

// Registradores do STM32F407 (Memory-Mapped I/O)
const RCC_BASE = 0x40023800;
const GPIOD_BASE = 0x40020C00;

// Registrador de habilitação do clock para GPIO
const RCC_AHB1ENR: *volatile u32 = @ptrFromInt(RCC_BASE + 0x30);

// Registradores do GPIOD
const GPIOD_MODER: *volatile u32 = @ptrFromInt(GPIOD_BASE + 0x00);
const GPIOD_ODR: *volatile u32 = @ptrFromInt(GPIOD_BASE + 0x14);

// Função de delay simples (busy wait)
fn delay(count: u32) void {
    var i: u32 = 0;
    while (i < count) : (i += 1) {
        asm volatile ("nop");
    }
}

// Vetor de reset (entry point)
export fn _reset() callconv(.c) noreturn {
    // 1. Habilitar clock do GPIOD
    RCC_AHB1ENR.* |= (1 << 3); // Bit 3 = GPIODEN

    // 2. Configurar PD12 como saída (LED verde na placa Discovery)
    // MODER12[1:0] = 01 (General purpose output)
    GPIOD_MODER.* &= ~(@as(u32, 0x3) << 24); // Limpar bits
    GPIOD_MODER.* |= (@as(u32, 0x1) << 24); // Setar como saída

    // 3. Loop infinito piscando o LED
    while (true) {
        // Ligar LED (setar bit 12)
        GPIOD_ODR.* |= (1 << 12);
        delay(1_000_000);

        // Desligar LED (limpar bit 12)
        GPIOD_ODR.* &= ~(@as(u32, 1) << 12);
        delay(1_000_000);
    }
}

// Tabela de vetores de interrupção
export const vector_table linksection(".isr_vector") = [_]?*const fn () callconv(.c) void{
    @ptrFromInt(@as(u32, 0x20020000)), // Stack pointer inicial
    @ptrCast(&_reset), // Reset handler
};
```

Para compilar e gravar no microcontrolador:

```bash
# Compilar
zig build -Doptimize=ReleaseSmall

# Gerar binário
zig build bin

# Gravar via OpenOCD
openocd -f board/stm32f4discovery.cfg \
    -c "program zig-out/bin/firmware.bin verify reset exit 0x08000000"

# Ou via st-flash
st-flash write zig-out/bin/firmware.bin 0x08000000
```

## Memory-Mapped I/O em Zig

Em sistemas embarcados, periféricos são acessados através de endereços de memória. Zig oferece ferramentas poderosas para isso.

### Abstração Tipada de Registradores

```zig
fn Register(comptime addr: u32) type {
    return struct {
        const ptr: *volatile u32 = @ptrFromInt(addr);

        pub inline fn read() u32 {
            return ptr.*;
        }

        pub inline fn write(val: u32) void {
            ptr.* = val;
        }

        pub inline fn setBit(comptime bit: u5) void {
            ptr.* |= (@as(u32, 1) << bit);
        }

        pub inline fn clearBit(comptime bit: u5) void {
            ptr.* &= ~(@as(u32, 1) << bit);
        }

        pub inline fn toggleBit(comptime bit: u5) void {
            ptr.* ^= (@as(u32, 1) << bit);
        }

        pub inline fn readBit(comptime bit: u5) bool {
            return (ptr.* & (@as(u32, 1) << bit)) != 0;
        }

        pub inline fn modifyBits(comptime mask: u32, val: u32) void {
            ptr.* = (ptr.* & ~mask) | (val & mask);
        }
    };
}

// Uso: muito mais legível e seguro que manipulação direta
const RCC_AHB1ENR = Register(0x40023830);
const GPIOD_MODER = Register(0x40020C00);
const GPIOD_ODR = Register(0x40020C14);

fn configurarLed() void {
    RCC_AHB1ENR.setBit(3);     // Habilitar clock GPIOD
    GPIOD_MODER.modifyBits(0x03000000, 0x01000000); // PD12 como saída
}

fn ligarLed() void {
    GPIOD_ODR.setBit(12);
}

fn desligarLed() void {
    GPIOD_ODR.clearBit(12);
}
```

### Packed Structs para Registradores

Zig suporta packed structs que mapeiam exatamente para layouts de bits em registradores:

```zig
const GpioModer = packed struct(u32) {
    moder0: u2 = 0,
    moder1: u2 = 0,
    moder2: u2 = 0,
    moder3: u2 = 0,
    moder4: u2 = 0,
    moder5: u2 = 0,
    moder6: u2 = 0,
    moder7: u2 = 0,
    moder8: u2 = 0,
    moder9: u2 = 0,
    moder10: u2 = 0,
    moder11: u2 = 0,
    moder12: u2 = 0,
    moder13: u2 = 0,
    moder14: u2 = 0,
    moder15: u2 = 0,
};

const gpiod_moder: *volatile GpioModer = @ptrFromInt(0x40020C00);

fn configurarPinos() void {
    var moder = gpiod_moder.*;
    moder.moder12 = 0b01; // Saída
    moder.moder13 = 0b01; // Saída
    moder.moder14 = 0b01; // Saída
    moder.moder15 = 0b01; // Saída
    gpiod_moder.* = moder;
}
```

Essa abordagem é muito mais segura e legível do que manipulação manual de bits, e o compilador gera código idêntico ao C equivalente.

## Tratamento de Interrupções

Interrupções são fundamentais em sistemas embarcados. Veja como configurar interrupções em Zig:

```zig
const std = @import("std");

// Handler do SysTick Timer
var tick_counter: u32 = 0;

export fn SysTick_Handler() callconv(.c) void {
    tick_counter +%= 1;
}

// Registradores do SysTick
const SYST_CSR: *volatile u32 = @ptrFromInt(0xE000E010);
const SYST_RVR: *volatile u32 = @ptrFromInt(0xE000E014);

fn configurarSysTick(ticks: u24) void {
    SYST_RVR.* = @as(u32, ticks) - 1;
    SYST_CSR.* = 0x07; // Enable, interrupt, use processor clock
}

fn obterTicks() u32 {
    return @atomicLoad(u32, &tick_counter, .monotonic);
}

fn delayMs(ms: u32) void {
    const inicio = obterTicks();
    while (obterTicks() - inicio < ms) {
        asm volatile ("wfi"); // Wait For Interrupt (economia de energia)
    }
}

// Tabela de vetores expandida
export const vector_table linksection(".isr_vector") = blk: {
    var table = [_]?*const fn () callconv(.c) void{null} ** 256;
    table[0] = @ptrFromInt(@as(u32, 0x20020000)); // Stack pointer
    table[1] = @ptrCast(&_reset);                  // Reset
    table[15] = @ptrCast(&SysTick_Handler);        // SysTick
    break :blk table;
};
```

Note o uso de `@atomicLoad` para ler a variável compartilhada entre a interrupção e o código principal, e `wfi` (Wait For Interrupt) para economizar energia enquanto espera.

## UART: Comunicação Serial

A comunicação serial é essencial para depuração em sistemas embarcados:

```zig
// Registradores USART2 do STM32F407
const USART2_BASE = 0x40004400;
const USART2_SR: *volatile u32 = @ptrFromInt(USART2_BASE + 0x00);
const USART2_DR: *volatile u32 = @ptrFromInt(USART2_BASE + 0x04);
const USART2_BRR: *volatile u32 = @ptrFromInt(USART2_BASE + 0x08);
const USART2_CR1: *volatile u32 = @ptrFromInt(USART2_BASE + 0x0C);

fn uartInit(baud_rate: u32) void {
    // Assumindo clock APB1 de 42 MHz
    const clock = 42_000_000;
    USART2_BRR.* = clock / baud_rate;

    // Habilitar USART: UE | TE | RE
    USART2_CR1.* = (1 << 13) | (1 << 3) | (1 << 2);
}

fn uartEnviarByte(byte: u8) void {
    // Esperar até TXE (transmit data register empty)
    while ((USART2_SR.* & (1 << 7)) == 0) {}
    USART2_DR.* = @as(u32, byte);
}

fn uartEnviarString(str: []const u8) void {
    for (str) |byte| {
        uartEnviarByte(byte);
    }
}

fn uartReceberByte() u8 {
    // Esperar até RXNE (read data register not empty)
    while ((USART2_SR.* & (1 << 5)) == 0) {}
    return @truncate(USART2_DR.*);
}

// Implementar Writer para integrar com std.fmt
const UartWriter = struct {
    pub fn write(_: @This(), bytes: []const u8) error{}!usize {
        for (bytes) |b| {
            uartEnviarByte(b);
        }
        return bytes.len;
    }
};

fn uartPrint(comptime fmt: []const u8, args: anytype) void {
    const writer = UartWriter{};
    std.fmt.format(writer, fmt, args) catch {};
}
```

## Comparação: Zig vs C vs Rust para Embedded

| Aspecto | C | Rust | Zig |
|---|---|---|---|
| Curva de aprendizado | Baixa | Alta (borrow checker) | Média |
| Segurança de memória | Nenhuma | Garantida (compile-time) | Verificações opcionais |
| Alocações ocultas | Possíveis (malloc) | Possíveis (Box, Vec) | Nenhuma |
| Ecossistema embedded | Imenso | Crescendo (Embassy) | Crescendo (MicroZig) |
| Interop com HAL C | Nativa | Via FFI (bindgen) | Via @cImport (direto) |
| Compilação cruzada | Complexa | Melhor que C | Trivial |
| Metaprogramação | Macros do preprocessador | Macros procedurais | Comptime |
| Tamanho do binário | Muito pequeno | Pequeno (com esforço) | Muito pequeno |
| Freestanding | Nativo | Requer `no_std` | Nativo |

### Vantagens do Zig sobre C

- Verificações de overflow, bounds checking e null em debug.
- Sem comportamento indefinido acidental.
- Comptime substitui macros de preprocessador de forma segura.
- Build system integrado, sem necessidade de Make/CMake.

### Vantagens do Zig sobre Rust

- Interoperabilidade C sem overhead (sem bindgen, sem unsafe blocks para FFI).
- Modelo mental mais simples (sem borrow checker, lifetimes ou traits).
- Compilação mais rápida.
- Mais fácil para quem vem de C.

## MicroZig: O Framework Embedded para Zig

MicroZig é o principal framework para desenvolvimento embarcado em Zig. Ele fornece:

- **Abstrações de hardware**: Drivers para GPIO, UART, SPI, I2C, timers e mais.
- **Suporte a múltiplos chips**: STM32, nRF, RP2040, ESP32, GD32, AVR.
- **Build system integrado**: Configuração automática de linker scripts e targets.
- **Modelo de camadas**: HAL (Hardware Abstraction Layer), board support packages e drivers de periféricos.

### Exemplo com MicroZig para RP2040 (Raspberry Pi Pico)

```zig
const microzig = @import("microzig");
const rp2040 = microzig.hal;

const led_pin = rp2040.gpio.num(25); // LED onboard do Pi Pico

pub fn main() !void {
    led_pin.set_function(.sio);
    led_pin.set_direction(.out);

    while (true) {
        led_pin.toggle();
        rp2040.time.sleep_ms(500);
    }
}
```

### Configuração do build.zig com MicroZig

```zig
const std = @import("std");
const microzig = @import("microzig");

pub fn build(b: *std.Build) void {
    const firmware = microzig.addFirmware(b, .{
        .name = "meu-firmware",
        .target = .{ .preferred = .rp2040 },
        .root_source_file = b.path("src/main.zig"),
        .optimize = .ReleaseSmall,
    });

    microzig.installFirmware(b, firmware);
}
```

## GPIO Avançado: Leitura de Sensores

Exemplo de leitura de um sensor digital (botão) e controle de LED:

```zig
// Abstrações usando comptime para configuração de pinos
fn GpioPin(comptime port_base: u32, comptime pin: u5) type {
    const moder: *volatile u32 = @ptrFromInt(port_base + 0x00);
    const idr: *volatile u32 = @ptrFromInt(port_base + 0x10);
    const odr: *volatile u32 = @ptrFromInt(port_base + 0x14);
    const bit_mask: u32 = @as(u32, 1) << pin;

    return struct {
        pub fn configSaida() void {
            var val = moder.*;
            val &= ~(@as(u32, 0x3) << (pin * 2));
            val |= (@as(u32, 0x1) << (pin * 2));
            moder.* = val;
        }

        pub fn configEntrada() void {
            var val = moder.*;
            val &= ~(@as(u32, 0x3) << (pin * 2));
            moder.* = val;
        }

        pub fn ligar() void {
            odr.* |= bit_mask;
        }

        pub fn desligar() void {
            odr.* &= ~bit_mask;
        }

        pub fn toggle() void {
            odr.* ^= bit_mask;
        }

        pub fn ler() bool {
            return (idr.* & bit_mask) != 0;
        }
    };
}

// Definição dos pinos (STM32F407 Discovery)
const LED_VERDE = GpioPin(0x40020C00, 12);  // PD12
const LED_LARANJA = GpioPin(0x40020C00, 13); // PD13
const LED_VERMELHO = GpioPin(0x40020C00, 14); // PD14
const LED_AZUL = GpioPin(0x40020C00, 15);    // PD15
const BOTAO = GpioPin(0x40020000, 0);         // PA0

export fn _reset() callconv(.c) noreturn {
    // Habilitar clocks (GPIOA e GPIOD)
    const RCC_AHB1ENR: *volatile u32 = @ptrFromInt(0x40023830);
    RCC_AHB1ENR.* |= (1 << 0) | (1 << 3);

    // Configurar pinos
    LED_VERDE.configSaida();
    LED_LARANJA.configSaida();
    LED_VERMELHO.configSaida();
    LED_AZUL.configSaida();
    BOTAO.configEntrada();

    while (true) {
        if (BOTAO.ler()) {
            LED_VERDE.ligar();
            LED_LARANJA.ligar();
            LED_VERMELHO.ligar();
            LED_AZUL.ligar();
        } else {
            LED_VERDE.desligar();
            LED_LARANJA.desligar();
            LED_VERMELHO.desligar();
            LED_AZUL.desligar();
        }
    }
}
```

O uso de `comptime` aqui gera código zero-overhead: todas as abstrações são resolvidas em tempo de compilação, resultando em código de máquina idêntico à manipulação direta de registradores em C.

## Dicas para Desenvolvimento Embedded com Zig

1. **Comece com o Raspberry Pi Pico**: O RP2040 tem excelente suporte no MicroZig e é barato.
2. **Use `ReleaseSmall`**: Para firmware, tamanho do binário é mais importante que velocidade.
3. **Aproveite `@cImport`**: Use HAL e CMSIS headers do fabricante diretamente.
4. **Debugging com GDB**: Zig gera informações de debug compatíveis com GDB. Use `arm-none-eabi-gdb` com OpenOCD.
5. **Evite std em freestanding**: A maioria da standard library não está disponível em freestanding. Use `std.mem` e `std.fmt` que funcionam sem allocator.
6. **Teste no desktop primeiro**: Escreva lógica de negócio em módulos testáveis no desktop e depois integre com o hardware.

```bash
# Debug via GDB + OpenOCD
openocd -f board/stm32f4discovery.cfg &
arm-none-eabi-gdb zig-out/bin/firmware
(gdb) target remote :3333
(gdb) monitor reset halt
(gdb) load
(gdb) break main
(gdb) continue
```

## Conclusão

Zig está emergindo como uma alternativa real ao C para sistemas embarcados. Com segurança superior, cross-compilation trivial, metaprogramação via comptime e o ecossistema crescente do MicroZig, Zig oferece um caminho moderno para programação de microcontroladores sem sacrificar o controle de baixo nível que embedded exige. Se você trabalha com IoT, firmware ou qualquer tipo de sistema embarcado, Zig merece sua atenção.

## Leia Também

- [Cross-Compilation com Zig: Compile para Qualquer Plataforma](/tutoriais/zig-cross-compilation)
- [Gerenciamento de Memória em Zig](/tutoriais/gerenciamento-de-memoria-zig)
- [Interoperabilidade C em Zig](/tutoriais/zig-c-interoperabilidade)
