Integracao de Zig com RTOS: FreeRTOS, Tasks e Projeto IoT Completo

Quando um projeto embarcado cresce alem de um simples super-loop, um RTOS (Real-Time Operating System) se torna essencial. Ele oferece multitarefa preemptiva, sincronizacao entre tarefas e gerenciamento de recursos — tudo com garantias de tempo real. Neste artigo final da serie, integramos Zig com FreeRTOS e construimos um projeto IoT completo.

Pre-requisito: Conhecimento dos artigos anteriores da serie, especialmente interrupts e timers.

Por Que Usar um RTOS?

Sem RTOS (Super Loop)Com RTOS
Uma tarefa por vezMultiplas tarefas concorrentes
Timing por delaysScheduling preemptivo com prioridades
Dificil escalarModular e escalavel
Compartilhamento manual de recursosMutexes e semaphores
Sem isolamentoStacks separadas por tarefa

Integrando Zig com FreeRTOS

FreeRTOS e escrito em C, o que torna a integracao com Zig natural gracas a interoperabilidade nativa.

Configuracao no build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.resolveTargetQuery(.{
        .cpu_arch = .thumb,
        .os_tag = .freestanding,
        .abi = .none,
        .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,
    });

    // Adicionar sources do FreeRTOS
    exe.addCSourceFiles(.{
        .files = &.{
            "freertos/tasks.c",
            "freertos/queue.c",
            "freertos/list.c",
            "freertos/timers.c",
            "freertos/portable/GCC/ARM_CM4F/port.c",
            "freertos/portable/MemMang/heap_4.c",
        },
    });

    exe.addIncludePath(b.path("freertos/include"));
    exe.addIncludePath(b.path("freertos/portable/GCC/ARM_CM4F"));
    exe.addIncludePath(b.path("config")); // FreeRTOSConfig.h

    exe.linkLibC();
    exe.setLinkerScript(b.path("linker.ld"));

    b.installArtifact(exe);
}

Bindings para FreeRTOS

const c = @cImport({
    @cInclude("FreeRTOS.h");
    @cInclude("task.h");
    @cInclude("queue.h");
    @cInclude("semphr.h");
});

pub const TaskHandle = c.TaskHandle_t;
pub const QueueHandle = c.QueueHandle_t;
pub const SemaphoreHandle = c.SemaphoreHandle_t;

pub fn criarTask(
    funcao: c.TaskFunction_t,
    nome: [*:0]const u8,
    stack_size: u32,
    prioridade: u32,
) !TaskHandle {
    var handle: TaskHandle = undefined;
    const resultado = c.xTaskCreate(
        funcao,
        nome,
        @intCast(stack_size),
        null,
        @intCast(prioridade),
        &handle,
    );
    if (resultado != c.pdPASS) return error.TaskCreationFailed;
    return handle;
}

pub fn iniciarScheduler() void {
    c.vTaskStartScheduler();
    // Nunca retorna se bem-sucedido
    unreachable;
}

pub fn delayTicks(ticks: u32) void {
    c.vTaskDelay(ticks);
}

pub fn delayMs(ms: u32) void {
    c.vTaskDelay(ms / c.portTICK_PERIOD_MS);
}

Criando Tasks em Zig

const std = @import("std");
const rtos = @import("rtos.zig");
const c = @cImport({
    @cInclude("FreeRTOS.h");
    @cInclude("task.h");
});

fn taskLed(_: ?*anyopaque) callconv(.C) void {
    // Configurar GPIO LED
    configurarPino(GPIOC, 13, .output);

    while (true) {
        GPIOC.odr ^= (1 << 13); // Toggle LED
        rtos.delayMs(500);
    }
}

fn taskSensor(_: ?*anyopaque) callconv(.C) void {
    // Inicializar I2C
    i2cInit();

    while (true) {
        const temp = lerTemperatura() catch 0;
        // Processar temperatura...
        _ = temp;
        rtos.delayMs(1000);
    }
}

fn taskUart(_: ?*anyopaque) callconv(.C) void {
    uartInit(115200);

    while (true) {
        uartEnviarString("Sistema rodando\r\n");
        rtos.delayMs(5000);
    }
}

pub fn main() void {
    // Configurar hardware basico
    configurarClocks();

    // Criar tasks com prioridades diferentes
    _ = rtos.criarTask(taskLed, "LED", 128, 1) catch {};
    _ = rtos.criarTask(taskSensor, "SENSOR", 256, 2) catch {};
    _ = rtos.criarTask(taskUart, "UART", 256, 1) catch {};

    // Iniciar scheduler (nunca retorna)
    rtos.iniciarScheduler();
}

Sincronizacao entre Tasks

Queues (Filas)

Queues permitem comunicacao segura entre tasks:

const c = @cImport({
    @cInclude("FreeRTOS.h");
    @cInclude("queue.h");
});

const LeituraSensor = struct {
    tipo: enum(u8) { temperatura, umidade, pressao },
    valor: f32,
    timestamp: u32,
};

var fila_sensores: c.QueueHandle_t = undefined;

fn inicializarFilas() void {
    fila_sensores = c.xQueueCreate(
        10, // Capacidade
        @sizeOf(LeituraSensor),
    );
}

fn taskProdutorSensor(_: ?*anyopaque) callconv(.C) void {
    while (true) {
        const leitura = LeituraSensor{
            .tipo = .temperatura,
            .valor = lerTemperatura() catch 0,
            .timestamp = millis(),
        };

        // Enviar para a fila (espera ate 100ms)
        _ = c.xQueueSend(fila_sensores, &leitura, 100);
        rtos.delayMs(500);
    }
}

fn taskConsumidor(_: ?*anyopaque) callconv(.C) void {
    while (true) {
        var leitura: LeituraSensor = undefined;

        // Receber da fila (bloqueia ate receber)
        if (c.xQueueReceive(fila_sensores, &leitura, c.portMAX_DELAY) == c.pdTRUE) {
            // Processar leitura
            uartEnviarString("Temperatura: ");
            // enviar valor...
        }
    }
}

Semaphores e Mutexes

var mutex_uart: c.SemaphoreHandle_t = undefined;
var sem_dados_prontos: c.SemaphoreHandle_t = undefined;

fn inicializarSync() void {
    mutex_uart = c.xSemaphoreCreateMutex();
    sem_dados_prontos = c.xSemaphoreCreateBinary();
}

fn uartProtegido(mensagem: []const u8) void {
    // Adquirir mutex antes de usar UART
    if (c.xSemaphoreTake(mutex_uart, 1000) == c.pdTRUE) {
        uartEnviarString(mensagem);
        _ = c.xSemaphoreGive(mutex_uart);
    }
}

// Qualquer task pode chamar uartProtegido() com seguranca
fn taskA(_: ?*anyopaque) callconv(.C) void {
    while (true) {
        uartProtegido("Task A executando\r\n");
        rtos.delayMs(1000);
    }
}

fn taskB(_: ?*anyopaque) callconv(.C) void {
    while (true) {
        uartProtegido("Task B executando\r\n");
        rtos.delayMs(1500);
    }
}

Projeto Completo: Sensor IoT

Integrando tudo em um projeto de monitoramento de temperatura com envio por UART:

const std = @import("std");
const rtos = @import("rtos.zig");

// Configuracoes do projeto
const INTERVALO_LEITURA_MS = 2000;
const INTERVALO_ENVIO_MS = 10000;
const MAX_LEITURAS_BUFFER = 20;

const Leitura = struct {
    temperatura: f32,
    umidade: f32,
    timestamp_ms: u32,
};

var buffer_leituras: [MAX_LEITURAS_BUFFER]Leitura = undefined;
var idx_leitura: usize = 0;
var mutex_buffer: c.SemaphoreHandle_t = undefined;

fn taskColeta(_: ?*anyopaque) callconv(.C) void {
    while (true) {
        const temp = lerTemperatura() catch continue;
        const umid = lerUmidade() catch continue;

        if (c.xSemaphoreTake(mutex_buffer, 100) == c.pdTRUE) {
            buffer_leituras[idx_leitura % MAX_LEITURAS_BUFFER] = .{
                .temperatura = temp,
                .umidade = umid,
                .timestamp_ms = millis(),
            };
            idx_leitura += 1;
            _ = c.xSemaphoreGive(mutex_buffer);
        }

        rtos.delayMs(INTERVALO_LEITURA_MS);
    }
}

fn taskEnvio(_: ?*anyopaque) callconv(.C) void {
    while (true) {
        if (c.xSemaphoreTake(mutex_buffer, 100) == c.pdTRUE) {
            // Calcular media
            var soma_temp: f32 = 0;
            var soma_umid: f32 = 0;
            const count = @min(idx_leitura, MAX_LEITURAS_BUFFER);

            for (buffer_leituras[0..count]) |l| {
                soma_temp += l.temperatura;
                soma_umid += l.umidade;
            }

            _ = c.xSemaphoreGive(mutex_buffer);

            if (count > 0) {
                const c_f: f32 = @floatFromInt(count);
                // Enviar dados formatados via UART
                uartProtegido("DADOS:");
                // temperatura media, umidade media, count
            }
        }

        rtos.delayMs(INTERVALO_ENVIO_MS);
    }
}

fn taskWatchdog(_: ?*anyopaque) callconv(.C) void {
    while (true) {
        // Alimentar watchdog do hardware
        feedWatchdog();

        // Verificar saude do sistema
        const free_heap = c.xPortGetFreeHeapSize();
        if (free_heap < 1024) {
            uartProtegido("ALERTA: Pouca memoria!\r\n");
        }

        rtos.delayMs(1000);
    }
}

pub fn main() void {
    configurarClocks();
    i2cInit();
    uartInit(115200);
    inicializarSync();

    uartEnviarString("=== Sensor IoT Zig + FreeRTOS ===\r\n");

    _ = rtos.criarTask(taskColeta, "COLETA", 256, 2) catch {};
    _ = rtos.criarTask(taskEnvio, "ENVIO", 512, 1) catch {};
    _ = rtos.criarTask(taskWatchdog, "WDG", 128, 3) catch {};

    rtos.iniciarScheduler();
}

Conclusao da Serie

Esta serie cobriu os fundamentos de programacao embarcada com Zig:

  1. Setup Embedded — Toolchain e bare metal
  2. GPIO e Perifericos — UART, SPI, I2C
  3. Interrupts e Timers — Event-driven
  4. Integracao com RTOS — FreeRTOS (este artigo)

Proximos Passos


Concluiu a serie? Parabens! Compartilhe seus projetos embedded com Zig na comunidade Zig Brasil!

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.