Interrupts e Timers em Zig: Programacao Event-Driven em Embedded

Interrupcoes sao o mecanismo que permite ao microcontrolador responder a eventos externos e internos sem ficar em polling constante. Combinadas com timers de hardware, elas formam a base da programacao event-driven em sistemas embarcados. Zig oferece controle preciso sobre interrupcoes com a seguranca do seu sistema de tipos.

Este artigo continua a serie Zig para Sistemas Embarcados. Certifique-se de ter lido sobre GPIO e perifericos.

Vetores de Interrupcao em Zig

No ARM Cortex-M, as interrupcoes sao gerenciadas pelo NVIC (Nested Vectored Interrupt Controller) e definidas em uma tabela de vetores na memoria flash:

// Tipo para handlers de interrupcao
const InterruptHandler = *const fn () callconv(.C) void;

// Tabela de vetores para STM32F103
const VectorTable = extern struct {
    stack_pointer: u32,
    reset: InterruptHandler,
    nmi: InterruptHandler = defaultHandler,
    hard_fault: InterruptHandler = hardFaultHandler,
    mem_manage: InterruptHandler = defaultHandler,
    bus_fault: InterruptHandler = defaultHandler,
    usage_fault: InterruptHandler = defaultHandler,
    reserved1: [4]u32 = .{0} ** 4,
    sv_call: InterruptHandler = defaultHandler,
    debug_monitor: InterruptHandler = defaultHandler,
    reserved2: u32 = 0,
    pend_sv: InterruptHandler = defaultHandler,
    systick: InterruptHandler = defaultHandler,

    // Interrupcoes externas (especificas do chip)
    exti0: InterruptHandler = defaultHandler,
    exti1: InterruptHandler = defaultHandler,
    exti2: InterruptHandler = defaultHandler,
    exti3: InterruptHandler = defaultHandler,
    exti4: InterruptHandler = defaultHandler,
    // ... mais interrupcoes
    tim2: InterruptHandler = defaultHandler,
    tim3: InterruptHandler = defaultHandler,
    tim4: InterruptHandler = defaultHandler,
    usart1: InterruptHandler = defaultHandler,
    usart2: InterruptHandler = defaultHandler,
};

fn defaultHandler() callconv(.C) void {
    while (true) {} // Loop infinito para interrupcoes nao tratadas
}

fn hardFaultHandler() callconv(.C) void {
    // Em producao: salvar registradores, enviar via UART, reiniciar
    while (true) {
        asm volatile ("bkpt"); // Breakpoint para debug
    }
}

export const vector_table linksection(".isr_vector") = VectorTable{
    .stack_pointer = 0x20005000,
    .reset = &resetHandler,
    .systick = &systickHandler,
    .tim2 = &timer2Handler,
    .exti0 = &extiHandler,
};

Configurando Interrupcoes Externas

Interrupcoes externas respondem a mudancas em pinos GPIO:

const NVIC_ISER0: *volatile u32 = @ptrFromInt(0xE000E100);
const EXTI_BASE = 0x40010400;
const EXTI_IMR: *volatile u32 = @ptrFromInt(EXTI_BASE + 0x00);
const EXTI_RTSR: *volatile u32 = @ptrFromInt(EXTI_BASE + 0x08);
const EXTI_PR: *volatile u32 = @ptrFromInt(EXTI_BASE + 0x14);

var contador_botao: u32 = 0;
var led_estado: bool = false;

fn configurarExti0() void {
    // Configurar PA0 como input
    // (assumindo GPIO ja configurado)

    // Habilitar interrupcao EXTI0
    EXTI_IMR.* |= 1; // Unmask EXTI0
    EXTI_RTSR.* |= 1; // Rising edge trigger

    // Habilitar no NVIC (EXTI0 = interrupcao 6)
    NVIC_ISER0.* |= (1 << 6);
}

fn extiHandler() callconv(.C) void {
    // Verificar e limpar flag
    if (EXTI_PR.* & 1 != 0) {
        EXTI_PR.* = 1; // Limpar pendencia (write 1 to clear)

        contador_botao += 1;
        led_estado = !led_estado;

        // Toggle LED
        if (led_estado) {
            GPIOC.bsrr = (1 << 13);
        } else {
            GPIOC.bsrr = (1 << (13 + 16));
        }
    }
}

Timers de Hardware

Timers sao extremamente versateis em microcontroladores. Eles podem gerar interrupcoes periodicas, PWM, medir tempo e contar pulsos.

Timer Periodico

const TIM2_BASE = 0x40000000;

const Timer = packed struct {
    cr1: u32,
    cr2: u32,
    smcr: u32,
    dier: u32,
    sr: u32,
    egr: u32,
    ccmr1: u32,
    ccmr2: u32,
    ccer: u32,
    cnt: u32,
    psc: u32,    // Prescaler
    arr: u32,    // Auto-reload (periodo)
    reserved: u32,
    ccr1: u32,   // Capture/Compare 1
    ccr2: u32,
    ccr3: u32,
    ccr4: u32,
};

const TIM2: *volatile Timer = @ptrFromInt(TIM2_BASE);

// Habilitar clock do TIM2
const RCC_APB1ENR: *volatile u32 = @ptrFromInt(0x4002101C);

fn timerInit(frequencia_hz: u32) void {
    const clock: u32 = 72_000_000;

    // Habilitar clock
    RCC_APB1ENR.* |= 1; // TIM2EN

    // Calcular prescaler e periodo
    const prescaler: u32 = 7200 - 1; // Clock/7200 = 10kHz
    const periodo: u32 = (clock / 7200) / frequencia_hz - 1;

    TIM2.psc = prescaler;
    TIM2.arr = periodo;
    TIM2.dier |= 1; // UIE - Update Interrupt Enable
    TIM2.cr1 |= 1; // CEN - Counter Enable

    // Habilitar no NVIC (TIM2 = interrupcao 28)
    const NVIC_ISER0_ptr: *volatile u32 = @ptrFromInt(0xE000E100);
    NVIC_ISER0_ptr.* |= (1 << 28);
}

var tick_counter: u32 = 0;

fn timer2Handler() callconv(.C) void {
    TIM2.sr &= ~@as(u32, 1); // Limpar flag UIF
    tick_counter += 1;
}

PWM (Pulse Width Modulation)

PWM e essencial para controle de motores, LEDs com brilho variavel e geracao de sinais analogicos:

fn pwmInit(canal: u2, frequencia: u32) void {
    const clock: u32 = 72_000_000;
    const prescaler: u32 = 72 - 1; // 1MHz timer clock
    const periodo: u32 = (clock / 72) / frequencia - 1;

    TIM2.psc = prescaler;
    TIM2.arr = periodo;

    // Configurar canal como PWM Mode 1
    switch (canal) {
        0 => {
            TIM2.ccmr1 = (TIM2.ccmr1 & ~@as(u32, 0xFF)) | (0b110 << 4) | (1 << 3);
            TIM2.ccer |= 1; // CC1E
        },
        1 => {
            TIM2.ccmr1 = (TIM2.ccmr1 & ~@as(u32, 0xFF00)) | (0b110 << 12) | (1 << 11);
            TIM2.ccer |= (1 << 4); // CC2E
        },
        else => {},
    }

    TIM2.cr1 |= 1; // Enable
}

fn pwmSetDuty(canal: u2, duty_percent: f32) void {
    const periodo = TIM2.arr;
    const valor: u32 = @intFromFloat(@as(f32, @floatFromInt(periodo)) * duty_percent / 100.0);

    switch (canal) {
        0 => TIM2.ccr1 = valor,
        1 => TIM2.ccr2 = valor,
        2 => TIM2.ccr3 = valor,
        3 => TIM2.ccr4 = valor,
    }
}

// Exemplo: LED com brilho crescente
pub fn breathingLed() void {
    pwmInit(0, 1000); // 1kHz PWM

    var brilho: f32 = 0;
    var crescendo = true;

    while (true) {
        pwmSetDuty(0, brilho);

        if (crescendo) {
            brilho += 0.5;
            if (brilho >= 100) crescendo = false;
        } else {
            brilho -= 0.5;
            if (brilho <= 0) crescendo = true;
        }

        delay(10000);
    }
}

SysTick - Timer do Sistema

O SysTick e um timer de 24 bits presente em todos os Cortex-M, ideal para base de tempo:

const SYSTICK_BASE = 0xE000E010;
const SYST_CSR: *volatile u32 = @ptrFromInt(SYSTICK_BASE);
const SYST_RVR: *volatile u32 = @ptrFromInt(SYSTICK_BASE + 0x04);
const SYST_CVR: *volatile u32 = @ptrFromInt(SYSTICK_BASE + 0x08);

var systick_ms: u32 = 0;

fn systickInit() void {
    const clock: u32 = 72_000_000;
    SYST_RVR.* = clock / 1000 - 1; // 1ms
    SYST_CVR.* = 0;
    SYST_CSR.* = 0b111; // Enable + Interrupt + AHB clock
}

fn systickHandler() callconv(.C) void {
    systick_ms += 1;
}

fn millis() u32 {
    return systick_ms;
}

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

Debounce de Botoes

Botoes fisicos geram multiplos pulsos (bouncing). Debounce por software com timer:

const DebounceState = struct {
    ultimo_estado: bool = false,
    estado_estavel: bool = false,
    ultimo_tempo: u32 = 0,
    tempo_debounce: u32 = 50, // 50ms

    pub fn update(self: *DebounceState, estado_atual: bool, tempo_atual: u32) bool {
        if (estado_atual != self.ultimo_estado) {
            self.ultimo_tempo = tempo_atual;
        }

        if (tempo_atual - self.ultimo_tempo > self.tempo_debounce) {
            if (estado_atual != self.estado_estavel) {
                self.estado_estavel = estado_atual;
                self.ultimo_estado = estado_atual;
                return true; // Estado mudou
            }
        }

        self.ultimo_estado = estado_atual;
        return false; // Sem mudanca
    }
};

var botao_state = DebounceState{};

fn verificarBotao() void {
    const pressionado = lerPino(GPIOA, 0);
    if (botao_state.update(pressionado, millis())) {
        if (botao_state.estado_estavel) {
            // Botao foi pressionado (borda de subida)
            led_estado = !led_estado;
        }
    }
}

Exercicios

  1. Servo motor: Use PWM para controlar um servo motor, variando o angulo entre 0 e 180 graus.

  2. Medidor de frequencia: Use Input Capture do timer para medir a frequencia de um sinal externo.

  3. Relogio com alarme: Implemente um relogio usando SysTick que gere um alarme (buzzer via PWM) em horario configurado via UART.


Proximo Artigo

No artigo final, integramos Zig com RTOS (Real-Time Operating System) para construir aplicacoes embarcadas complexas.

Conteudo Relacionado


Duvidas sobre interrupts e timers? Participe da comunidade Zig Brasil!

Continue aprendendo Zig

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