⚡ Aula 04 — Mapeamento de Memória, Registradores de Hardware e Interrupções em C

Navegação Rápida


📌 Sumário da Aula

  1. 🧠 1. Paradigma de Entrada e Saída Mapeada em Memória (MMIO)
  2. 🏗️ 2. A Abstração CMSIS: Estruturas struct de Hardware em C
  3. ⚡ 3. O Controlador de Interrupções Vetorizadas (NVIC)
  4. 🔄 4. Anatomia do Ciclo de Interrupção: Stacking e Latência
  5. 🛡️ 5. Boas Práticas em ISRs: Atomicidade e Seções Críticas
  6. 📊 6. Diagrama de Memory-Mapped I/O e Ciclo de Execução da ISR
  7. 📝 7. Questões de Concursos e Problemas Avançados Resolvidos
  8. 🔗 8. Conexões e Referências Cruzadas

🧠 1. Paradigma de Entrada e Saída Mapeada em Memória (MMIO)

Na arquitetura de computadores, existem dois métodos clássicos para comunicação entre a Unidade Central de Processamento (CPU) e os circuitos periféricos externos:

  1. Entrada e Saída Mapeada por Portas (PMIO - Port-Mapped I/O): Utilizada na arquitetura legada Intel x86, onde os periféricos residem em um espaço de endereçamento separado de , acessados exclusivamente por instruções de máquina especiais (IN e OUT).
  2. Entrada e Saída Mapeada em Memória (MMIO - Memory-Mapped I/O): Padrão universal adotado nas arquiteturas RISC e nos microcontroladores industriais (ARM Cortex-M, RISC-V, ESP32).

No modelo MMIO, não existem instruções especiais para periféricos. Os registradores de controle, status e dados de todos os módulos de hardware (portas GPIO, conversores ADC, temporizadores de alta resolução, interfaces CAN e USART) ocupam posições específicas dentro do mapa linear de endereços de 32 bits ():

flowchart TD
    MemLinear["Espaço Linear de Memória de 32 bits (4 GiB)"] --> Flash["0x08000000: Memória Flash ROM (Código e Vetores)"]
    MemLinear --> SRAM["0x20000000: Memória SRAM Volátil (Variáveis e Pilha)"]
    MemLinear --> Perif["0x40000000: Barramento de Periféricos MMIO (APB/AHB)"]

    Perif --> GPIOA["0x40020000: Porta GPIO A (Controle de Disjuntores)"]
    Perif --> TIM1["0x40010000: Temporizador Avançado PWM (Inversor)"]
    Perif --> ADC1["0x40012000: Conversor ADC 12-bits (Telemedição Fasorial)"]

Uma leitura ou escrita em um registrador de hardware é realizada exatamente como um acesso a qualquer variável comum na memória RAM, utilizando as instruções assembly genéricas LDR (Load Register) e STR (Store Register).


🏗️ 2. A Abstração CMSIS: Estruturas struct de Hardware em C

Manipular registradores através de números hexadecimais brutos e coerções diretas de ponteiros espalhadas pelo código é propenso a erros catastróficos de digitação. Para padronizar a programação de baixo nível em microcontroladores industriais, a ARM desenvolveu o CMSIS (Cortex Microcontroller Software Interface Standard).

O padrão CMSIS mapeia os blocos de registradores de cada periférico através da sobreposição de uma estrutura struct da linguagem C sobre o endereço físico base do barramento.

2.1 Mapeamento Estruturado da Porta GPIO

Considere o periférico GPIO do microcontrolador STM32F4 (ARM Cortex-M4), cujos registradores de 32 bits estão dispostos sequencialmente com deslocamentos (offsets) regulares de 4 bytes:

/* Definição Padrão CMSIS para Bloco de Registradores GPIO */
typedef struct {
    volatile uint32_t MODER;    // Offset 0x00: Modo de operação (Entrada/Saída/Analógico)
    volatile uint32_t OTYPER;   // Offset 0x04: Tipo de saída (Push-pull ou Open-drain)
    volatile uint32_t OSPEEDR;  // Offset 0x08: Velocidade de comutação do slew-rate
    volatile uint32_t PUPDR;    // Offset 0x0C: Resistores de Pull-up / Pull-down
    volatile uint32_t IDR;      // Offset 0x10: Registrador de dados de entrada (Leitura)
    volatile uint32_t ODR;      // Offset 0x14: Registrador de dados de saída (Escrita)
    volatile uint32_t BSRR;     // Offset 0x18: Set/Reset atômico de pinos
    volatile uint32_t LCKR;     // Offset 0x1C: Travamento de configuração de pinos
    volatile uint32_t AFR[2];   // Offset 0x20 e 0x24: Funções alternativas de pinos
} GPIO_TypeDef;

2.2 Associação ao Endereço Físico de Barramento

O arquivo de cabeçalho do fabricante define o endereço base do periférico no barramento AHB e faz a coerção para um ponteiro constante para a estrutura:

#define PERIPH_BASE       0x40000000UL
#define AHB1PERIPH_BASE   (PERIPH_BASE + 0x00020000UL)
#define GPIOA_BASE        (AHB1PERIPH_BASE + 0x0000UL) // 0x40020000
 
#define GPIOA             ((GPIO_TypeDef *) GPIOA_BASE)

No código do engenheiro, acionar a bobina de abertura de um disjuntor conectado ao pino PA5 torna-se uma instrução elegante, legível e otimizada pelo compilador:

// Coloca o pino PA5 em nível alto através do registrador de saída
GPIOA->ODR |= (1U << 5);
 
// Ou com garantia de atomicidade total através do BSRR (Bit Set/Reset Register):
GPIOA->BSRR = (1U << 5); // Seta o bit 5 em 1 ciclo de instrução sem read-modify-write!

⚡ 3. O Controlador de Interrupções Vetorizadas (NVIC)

Nos sistemas de automação de proteção de sistemas de potência, a CPU não pode desperdiçar tempo verificando continuamente em um laço infinito se ocorreu um curto-circuito (Polling).

O NVIC (Nested Vectored Interrupt Controller) é um módulo de hardware integrado diretamente ao núcleo do processador ARM Cortex-M que gerencia dezenas de fontes de interrupção com baixa latência determinística e aninhamento preemptivo por prioridades.

flowchart TD
    Polling["Abordagem por Polling:<br/>while(1) { if (sensor_disparou) ... }<br/>Desperdiça 99% da CPU<br/>Latência imprevisível e alta"]
    Interrupt["Abordagem Orientada a Interrupção (NVIC):<br/>CPU executa cálculos numéricos em segundo plano.<br/>Hardware suspende o código em < 12 ciclos ao detectar falta.<br/>Atendimento imediato e determinístico"]

3.1 A Tabela de Vetores de Interrupção

A Tabela de Vetores reside na memória Flash ROM, iniciando obrigatoriamente no endereço base 0x08000000 (ou 0x00000000 após o remap de boot):

  • A posição 0x00000000 armazena o endereço inicial do ponteiro de pilha (Initial Main Stack Pointer - MSP);
  • A posição 0x00000004 armazena o endereço da rotina de Reset (Reset_Handler);
  • As posições subsequentes contêm os endereços de memória (ponteiros de função) para cada manipulador de interrupção: NMI_Handler, HardFault_Handler, SysTick_Handler, EXTI0_IRQHandler, ADC_IRQHandler, etc.

🔄 4. Anatomia do Ciclo de Interrupção: Stacking e Latência

Quando um sinal elétrico transita na linha de disparo (por exemplo, um relé de sobrecorrente emite um pulso de nível alto no pino conectado à interrupção externa EXTI0), o processador executa mecanicamente uma sequência de ações em hardware:

sequenceDiagram
    autonumber
    participant App as Código Principal (main / Thread Mode)
    participant HW as Núcleo da CPU / Hardware NVIC
    participant Stack as Pilha de Memória (SRAM Stack)
    participant ISR as Rotina de Atendimento EXTI0_IRQHandler

    App->>HW: Executando algoritmo de fluxo de carga
    HW->>HW: Evento Físico Assíncrono detectado na linha EXTI0!
    HW->>Stack: 1. STACKING AUTOMÁTICO (12 ciclos):<br/>Empilha registradores r0-r3, r12, LR, PC, xPSR
    HW->>HW: 2. VECTOR FETCH: Lê endereço da ISR na tabela de vetores
    HW->>ISR: 3. Salta para a função EXTI0_IRQHandler (Handler Mode)
    ISR->>ISR: Executa comando de desarme imediato do disjuntor (< 1 ms)
    ISR->>ISR: Limpa a flag de interrupção pendente: EXTI->PR = bit0
    ISR->>HW: Executa instrução de retorno (BX LR com EXC_RETURN)
    HW->>Stack: 4. UNSTACKING AUTOMÁTICO:<br/>Desempilha os registradores originais
    HW->>App: Retoma a execução exata do fluxo de carga sem corrupção!

4.1 O Mecanismo de Salvamento de Contexto (Stacking)

Em apenas 12 ciclos de clock (o que representa em uma CPU a ), o hardware empilha automaticamente oito registradores da CPU na pilha apontada pelo registrador SP:

  • Registradores de uso geral voláteis: r0, r1, r2, r3;
  • Registrador intra-chamada: r12;
  • Registrador de ligação: LR (Link Register);
  • Contador de programa: PC (Program Counter com o endereço da próxima instrução a ser retomada);
  • Registrador de status do processador: xPSR (contendo as flags aritméticas e lógicas).

Graças a esse empilhamento automático realizado pelos barramentos de hardware, uma rotina de interrupção em C (ISR) pode ser escrita como uma função comum sem argumentos e sem retorno:

void EXTI0_IRQHandler(void) {
    // Código da ISR
}

🛡️ 5. Boas Práticas em ISRs: Atomicidade e Seções Críticas

5.1 As Quatro Leis de Ouro para Rotinas de Interrupção

  1. Breve e Determinística: Uma ISR deve executar apenas as ações estritamente imediatas (como comandar a abertura de um disjuntor ou ler um registrador de dados do ADC) e terminar o mais rápido possível.
  2. Sem Laços Bloqueantes: É expressamente proibido utilizar funções de atraso (delay()), laços while de espera por transmissão externa ou operações pesadas de formatação de strings (como sprintf ou printf serial).
  3. Comunicação por Variáveis volatile: Qualquer variável global modificada dentro da ISR e lida pelo laço principal main() deve ser declarada obrigatoriamente com o qualificador volatile.
  4. Limpeza Mandatória da Flag Pendente: O hardware seta a flag de requisição de interrupção (ex: EXTI->PR). A ISR é estritamente obrigada a limpar essa flag no periférico antes de terminar. Se a flag não for limpa, a CPU reentrará na mesma ISR indefinidamente assim que retornar, travando a execução do programa em um laço infinito de interrupção.

5.2 Condições de Corrida (Race Conditions) e Seções Críticas

Considere uma variável global de 64 bits que acumula o consumo de energia em Watt-hora: volatile uint64_t energia_total_wh = 0;

Em um processador de 32 bits, a leitura ou escrita dessa variável requer duas instruções assembly consecutivas de 32 bits (LDRD ou dois LDR). Se a ISR for disparada exatamente no intervalo entre a leitura da primeira palavra (32 bits inferiores) e a leitura da segunda palavra (32 bits superiores) pelo laço main(), os dados lidos estarão corrompidos (inconsistência temporal).

Para evitar essa condição de corrida, o acesso deve ser encapsulado em uma Seção Crítica Atômica, desabilitando temporariamente as interrupções mascaráveis durante os nanossegundos da cópia:

uint64_t ler_energia_segura(void) {
    uint64_t valor_copia;
    
    // Entra na seção crítica: salva o estado anterior das interrupções e desabilita
    uint32_t primask = __get_PRIMASK();
    __disable_irq();
    
    valor_copia = energia_total_wh; // Cópia atômica garantida sem preempção
    
    // Sai da seção crítica: restaura o estado original das interrupções
    __set_PRIMASK(primask);
    
    return valor_copia;
}

📊 6. Diagrama de Memory-Mapped I/O e Ciclo de Execução da ISR

O diagrama técnico abaixo ilustra a arquitetura do mapeamento MMIO por estruturas em C e a linha temporal do ciclo de interrupção por hardware no NVIC:


📝 7. Questões de Concursos e Problemas Avançados Resolvidos

Questão 1 (Banca CESGRANRIO - Engenheiro de Equipamentos Eletrônica / Petrobras)

Em sistemas microcontrolados baseados na arquitetura ARM Cortex-M, o mecanismo de salvamento de contexto executado automaticamente pelo hardware do processador durante o atendimento a uma solicitação de interrupção externa (processo de stacking) descarrega na pilha de execução do sistema um conjunto específico de registradores internos antes de invocar a Rotina de Serviço de Interrupção (ISR). Assinale a alternativa que relaciona corretamente os registradores empilhados automaticamente pelo hardware nessa arquitetura:

Alternativas: A) Apenas os registradores de uso geral r4, r5, r6, r7 e r8.
B) Os registradores r0, r1, r2, r3, r12, LR, PC e xPSR.
C) Todos os dezesseis registradores de r0 a r15 obrigatoriamente.
D) Apenas o contador de programa PC e o apontador de pilha SP.
E) Os registradores de controle do periférico e a tabela de vetores de interrupção.

Gabarito Comentado:

  • Alternativa correta: B.
  • Justificativa Detalhada: O padrão de arquitetura ARM Cortex-M define formalmente o empilhamento básico de 8 palavras de 32 bits (Basic Stack Frame) realizado inteiramente por hardware pelo NVIC nos primeiros 12 ciclos de clock:
    • Argumentos e retorno da ABI: r0, r1, r2, r3;
    • Registrador de chamada temporária: r12 (IP);
    • Registrador de retorno: LR (carregado com o código mágico EXC_RETURN);
    • Próximo endereço de instrução: PC;
    • Estado das flags aritméticas/lógicas da ALU: xPSR. Os registradores r4 a r11 são considerados preservados pelo chamador (callee-saved) conforme a norma AAPCS (ARM Architecture Procedure Call Standard) e só são empilhados pelo compilador se a própria função ISR decidir utilizá-los internamente.

Questão 2 (Banca FCC - Analista de Sistemas e Automação de Subestações)

Um engenheiro de proteção desenvolve o firmware de um relé digital de sobrecorrente e programa a interrupção externa de um pino de disparo de falta no pino PA0. Após realizar os primeiros testes em bancada, ele observa que, quando o primeiro pulso elétrico de falta é injetado, o relé executa a rotina de desarme, mas entra em travamento permanente, impedindo que qualquer outra tarefa do sistema operacional ou do laço principal volte a ser executada. Ao inspecionar o código da ISR, o engenheiro depara-se com a seguinte implementação:

void EXTI0_IRQHandler(void) {
    acionar_bobina_disjuntor();
    salvar_evento_memoria_flash(); // Rotina lenta que grava na Flash
}

Qual é a falha primária responsável pelo travamento do microcontrolador e qual é a correção mandatória?

Resolução e Análise Crítica:

  1. Identificação da Falha Primária: A função EXTI0_IRQHandler cometeu o erro mortal de não limpar a flag de solicitação de interrupção pendente no registrador do periférico EXTI (EXTI->PR).
  2. Consequência no Hardware: Quando o processador executa o retorno da interrupção (Unstacking), o NVIC verifica que a linha de interrupção correspondente ainda possui a flag de requisição em nível lógico alto (1). O NVIC imediatamente interrompe o processador de novo antes mesmo que ele consiga executar uma única instrução do código principal, criando um laço infinito de interrupção.
  3. Agravante de Projeto: A chamada salvar_evento_memoria_flash() viola o princípio da brevidade determinística em ISRs, pois operações de escrita e apagamento de blocos de memória Flash duram milissegundos e bloqueiam os barramentos de memória interna.
  4. Correção Mandatória em Linguagem C:
void EXTI0_IRQHandler(void) {
    if (EXTI->PR & (1U << 0)) {
        acionar_bobina_disjuntor(); // Ação crítica ultrarrápida (< 1 µs)
        flag_salvar_flash_pendente = 1; // Notifica o loop principal para salvar depois
        EXTI->PR = (1U << 0); // LIMPEZA MANDATÓRIA (em ARM, escreve-se 1 para limpar!)
    }
}

Questão 3 (Problema Prático de Engenharia - Cálculo de Latência de Resposta de Proteção)

Um microcontrolador ARM Cortex-M4 operando com frequência de clock de é utilizado no comando de um disjuntor a vácuo em uma subestação de média tensão (). O sinal elétrico de sobrecorrente é detectado por um comparador de hardware externo ultrarrápido que gera uma borda de subida no pino EXTI. Sabe-se que:

  • O hardware do NVIC leva exatamente 12 ciclos de clock para concluir o stacking e saltar para a primeira instrução da ISR;
  • A ISR executa 15 instruções assembly elementares (cada uma consumindo em média 1 ciclo de clock) para validar o código de falta e escrever 1 no registrador GPIOA->BSRR;
  • A saída física do pino GPIO comanda um circuito de optoacoplador e driver de potência com tempo de atraso de propagação eletrônico de ;
  • O tempo mecânico de deslocamento dos contatos e extinção do arco na câmara a vácuo do disjuntor é de .

Calcule:

  1. O tempo de resposta digital puro do microcontrolador () desde a borda de falta até o chaveamento elétrico no pino do chip em nanossegundos ().
  2. O tempo total decorrido desde a ocorrência da sobrecorrente até a interrupção física definitiva do arco elétrico no disjuntor.

Resolução Passo a Passo:

  1. Cálculo do Período de Clock da CPU ():

  2. Cálculo dos Ciclos de Processamento Digital:

    • Ciclos de Stacking no NVIC:
    • Ciclos de execução das instruções da ISR:
    • Total de ciclos da CPU até a instrução de saída:
  3. Tempo de Resposta Digital do Microcontrolador ():

  4. Tempo Total até a Extinção Física do Arco (): Somam-se todos os tempos em cascata:

Conclusão de Engenharia: A tomada de decisão computacional do microcontrolador através de interrupções de hardware ocorre em meros , representando menos de do tempo total de desarme, o qual é completamente dominado pela inércia mecânica dos contatos do disjuntor de média tensão (, equivalente a da rede de ).


🔗 8. Conexões e Referências Cruzadas

Disciplinas Vinculadas:

Materiais Didáticos Oficiais da Disciplina:

  • Arquivo de Curso: 01 - Bases Para Programação de Sistemas Embarcados.pdf
  • Arquivo de Curso: 04 - Linguagem C Nos Sitemas Embarcados.pdf
  • Arquivo de Apoio: 00 - Plano de Ensino.pdf