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
Servo motor: Use PWM para controlar um servo motor, variando o angulo entre 0 e 180 graus.
Medidor de frequencia: Use Input Capture do timer para medir a frequencia de um sinal externo.
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
- Artigo anterior: GPIO e Perifericos
- Concorrencia em Zig — Conceitos paralelos
Duvidas sobre interrupts e timers? Participe da comunidade Zig Brasil!