🛠️ Aula 01 — Arquitetura de Sistemas Embarcados e Cadeia de Compilação Cruzada

Navegação Rápida


📌 Sumário da Aula

  1. 🧠 1. Sistemas Embarcados na Engenharia Elétrica Moderna
  2. ⚙️ 2. Arquiteturas de Computadores: von Neumann vs. Harvard e CISC vs. RISC
  3. 🔄 3. O Paradoxo da Compilação Cruzada (Cross-Compilation)
  4. 🔨 4. As Etapas Formais da Toolchain GNU (CPP, GCC, AS, LD)
  5. 🗺️ 5. Anatomia de Memória, Seções ELF e o Linker Script
  6. 📊 6. Diagrama da Toolchain de Compilação Cruzada
  7. 📝 7. Questões de Concursos e Problemas Avançados Resolvidos
  8. 🔗 8. Conexões e Referências Cruzadas

🧠 1. Sistemas Embarcados na Engenharia Elétrica Moderna

Um Sistema Embarcado (Embedded System) é um sistema computacional microprocessado ou microcontrolado projetado para desempenhar funções dedicadas, específicas e em tempo real dentro de um sistema elétrico, mecânico ou industrial maior.

Diferente de um computador pessoal de uso geral (desktop/servidor), que executa múltiplos aplicativos arbitrários sob demanda do usuário, um sistema embarcado na Engenharia Elétrica opera continuamente com alta confiabilidade, restrições rígidas de consumo energético, espaço físico e determinismo temporal absoluto.

Exemplos canônicos de sistemas embarcados no setor elétrico:

  • Relés Numéricos de Proteção (IEDs - Intelligent Electronic Devices): Executam algoritmos de proteção diferencial de transformadores (função ANSI 87T), sobrecorrente temporizada (51) e distância (21), com janelas de decisão inferiores a ;
  • Inversores Solares Fotovoltaicos e Conversores Eólicos: Processam algoritmos de rastreamento do ponto de máxima potência (MPPT) e modulação por largura de pulso vetorial (SVPWM) com frequências de amostragem de a ;
  • Medidores Eletrônicos Inteligentes (Smart Meters / AMI): Calculam consumo de energia ativa e reativa em 4 quadrantes, distorção harmônica total (THD) e comunicam-se via protocolo celular ou PLC (Power Line Communication);
  • Controladores Lógicos Programáveis (CLPs): Automatizam processos de subestações e acionamentos de motores industriais através de barramentos determinísticos.

⚙️ 2. Arquiteturas de Computadores: von Neumann vs. Harvard e CISC vs. RISC

Os microcontroladores industriais modernos (como a família ARM Cortex-M, Texas Instruments C2000 DSP e Microchip AVR/PIC) fundamentam-se em duas classificações arquiteturais essenciais:

flowchart TD
    subgraph VN["Arquitetura von Neumann"]
        CPU1["CPU"] <--> BarramentoUnico["Barramento Compartilhado Único<br/>(Dados + Instruções)"]
        BarramentoUnico <--> MemoriaUnica["Memória Unificada"]
    end

    subgraph HV["Arquitetura Harvard"]
        CPU2["CPU"] <--> BarrInst["Barramento de Instruções"] <--> Flash["Memória de Programa (Flash)"]
        CPU2 <--> BarrDados["Barramento de Dados"] <--> SRAM["Memória de Dados (RAM)"]
    end

2.1 Von Neumann vs. Harvard

  1. Arquitetura von Neumann: Possui um único espaço de memória e um único conjunto de barramentos compartilhados para instruções de programa e dados. Apresenta o clássico Gargalo de von Neumann (von Neumann Bottleneck): a CPU não pode ler uma instrução e acessar um operando em memória simultaneamente no mesmo ciclo de clock.
  2. Arquitetura Harvard (e Harvard Modificada): Possui memórias fisicamente separadas para instruções (geralmente Flash ROM não volátil) e dados (SRAM volátil), interligadas por barramentos independentes de endereços e dados. A CPU busca a próxima instrução de código (fetch) no barramento de programa no exato momento em que lê ou grava dados numéricos (read/write) no barramento de dados, permitindo a implementação de Pipelines profundos com vazão de 1 instrução por ciclo (). A arquitetura ARM Cortex-M utiliza a variante Harvard Modificada (barramentos I-Code e D-Code conectados a um barramento de sistema compartilhado).

2.2 RISC vs. CISC

  • RISC (Reduced Instruction Set Computer): Conjunto reduzido de instruções ortogonais de tamanho fixo (ex: 32 bits em ARM ou 16 bits em Thumb), arquitetura load-store estrita (operações lógicas e aritméticas só ocorrem entre registradores internos da CPU, nunca diretamente na memória).
  • CISC (Complex Instruction Set Computer): Conjunto abrangente de instruções de tamanho variável com múltiplos modos de endereçamento complexos capazes de operar diretamente sobre operandos na memória (ex: x86).

🔄 3. O Paradoxo da Compilação Cruzada (Cross-Compilation)

Na programação convencional de desktop, a compilação é nativa: o código-fonte C é compilado em uma máquina x86_64 para gerar um arquivo executável que rodará na própria CPU x86_64.

Em sistemas embarcados, surge o desafio da Compilação Cruzada (Cross-Compilation):

  • O microcontrolador de destino (Target) possui recursos severamente restritos (ex: processador ARM Cortex-M4 operando a , com apenas de Flash e de RAM), incapaz de hospedar um compilador complexo, editor de código e analisador sintático;
  • Utiliza-se um computador hospedeiro (Host), tipicamente uma estação de trabalho x86_64 multicore com gigabytes de memória RAM, executando Linux;
  • O compilador roda na arquitetura do Host, mas gera instruções de código de máquina binárias especificamente codificadas para o conjunto de instruções (ISA) do processador Target.
flowchart LR
    Host["Máquina Host (x86_64)<br/>Linux / Ubuntu<br/>arm-none-eabi-gcc"] -->|Gera Binário ELF/HEX| Target["Dispositivo Target (ARM Cortex-M)<br/>Sem Sistema Operacional (Bare-Metal)<br/>ou RTOS (FreeRTOS)"]
    Host -->|Depuração via OpenOCD / SWD| Target

A nomenclatura padronizada das ferramentas GNU para compilação cruzada segue o formato triplo ou quádruplo: Exemplo: arm-none-eabi-gcc:

  • arm: Arquitetura do processador de destino (ARM 32 bits);
  • none: Sem fornecedor específico (genérico);
  • eabi: Embedded Application Binary Interface (execução em metal puro / bare-metal sem sistema operacional Linux);
  • gcc: GNU Compiler Collection.

🔨 4. As Etapas Formais da Toolchain GNU (CPP, GCC, AS, LD)

A transformação de arquivos-fonte C (.c e .h) no binário executável final desdobra-se em quatro fases rigorosamente encadeadas:

flowchart TD
    Src["Código Fonte C (.c / .h)"] --> CPP["1. Pré-processador (cpp)<br/>arm-none-eabi-cpp"]
    CPP -->|Fonte Expandido (.i)| CC1["2. Compilador C (cc1)<br/>arm-none-eabi-gcc -O2"]
    CC1 -->|Código Assembly (.s)| AS["3. Montador (as)<br/>arm-none-eabi-as"]
    AS -->|Código Objeto (.o ELF Relocável)| LD["4. Linker / Ligador (ld)<br/>arm-none-eabi-ld -T script.ld"]
    Libs["Bibliotecas (libc, libm)"] --> LD
    LinkerScript["Linker Script (.ld)"] --> LD
    LD --> ELF["Executável ELF Final (.elf)"]
    ELF --> Objcopy["Utilitário Objcopy"]
    Objcopy --> HEX["Arquivo Intel HEX (.hex) ou Raw BIN (.bin)"]

4.1 Fase 1: Pré-processamento (arm-none-eabi-cpp)

O pré-processador executa diretivas textuais iniciadas por #:

  1. Expansão de arquivos de cabeçalho: #include <stdint.h> copia o conteúdo integral do arquivo para o código;
  2. Substituição de macros literais: #define FREQ_CORTE 60.0 substitui todas as ocorrências no texto;
  3. Compilação condicional: #ifdef DEBUG ... #endif inclui ou remove blocos inteiros de código antes da análise sintática;
  4. Remoção de comentários. Saída: Arquivo C puro expandido com extensão .i.

4.2 Fase 2: Compilação C (arm-none-eabi-gcc)

O compilador realiza a análise léxica, sintática e semântica do código expandido, constrói a Árvore Sintática Abstrata (AST) e gera as rotinas equivalentes na linguagem assembly do alvo. É nessa fase que atuam os níveis de otimização:

  • -O0: Sem otimização (facilita depuração passo a passo com GDB);
  • -O2: Otimização agressiva para velocidade sem inflar o tamanho do código;
  • -Os: Otimização máxima voltada para a redução do tamanho do código binário (size), essencial para microcontroladores com Flash restrita. Saída: Arquivo Assembly humano-legível com extensão .s.

4.3 Fase 3: Montagem (Assembly - arm-none-eabi-as)

O montador traduz os mnemônicos textuais do assembly (ex: LDR r0, [r1, #4], ADDS r2, r0, r1) para os códigos de operação (opcodes) hexadecimais binários específicos da CPU. Saída: Arquivo de Código Objeto Relocável com extensão .o no formato padronizado ELF (Executable and Linkable Format). Este arquivo contém tabelas de símbolos, mas os endereços absolutos de memória física ainda não estão resolvidos.

4.4 Fase 4: Vinculação / Ligação (Linking - arm-none-eabi-ld)

O linker junta todos os arquivos .o do projeto, resolve referências externas entre funções e variáveis globais, incorpora o código de funções chamadas das bibliotecas de sistema (como rotinas trigonométricas de libm.a) e aloca cada byte nos endereços físicos reais especificados pelo Linker Script. Saída: Arquivo ELF executável final com extensão .elf.


🗺️ 5. Anatomia de Memória, Seções ELF e o Linker Script

Em um sistema embarcado bare-metal, o mapa de memória física é fixo pelo fabricante do chip. O compilador particiona o código e os dados em Seções Lógicas Canônicas:

Seção ELFConteúdo ArmazenadoMemória FísicaPersistência / Acesso
.isr_vectorTabela com o ponteiro inicial de Stack e os endereços das rotinas de interrupçãoFlash ROM (início em 0x08000000)Não volátil / Somente leitura
.textInstruções binárias executáveis do programa compiladoFlash ROMNão volátil / Somente leitura
.rodataConstantes literais (const float PI = 3.14159f;, lookup tables de senoide)Flash ROMNão volátil / Somente leitura
.dataVariáveis globais e estáticas inicializadas com valores não nulos (int status = 1;)SRAM (VMA: 0x20000000) / Copiada da Flash (LMA)Volátil / Leitura e Escrita
.bssVariáveis globais e estáticas não inicializadas ou inicializadas com zero (int buffer[100];)SRAMVolátil / Zerada pelo startup
HeapMemória dinâmica alocada em tempo de execução via malloc() e free()SRAM (cresce para cima)Evitada em sistemas críticos
StackVariáveis locais de funções, registros de ativação e contexto salvo de interrupçõesSRAM (cresce para baixo a partir do topo)Volátil / LIFO ultrarrápido

5.1 O Linker Script (.ld)

O Linker Script é o arquivo que define os blocos físicos de memória e atribui cada seção lógica aos endereços de barramento:

/* Exemplo de Linker Script para STM32F4 (ARM Cortex-M4) */
MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
 
SECTIONS
{
  .isr_vector : { *(.isr_vector) } > FLASH
  .text       : { *(.text) }       > FLASH
  .rodata     : { *(.rodata) }     > FLASH
  
  /* .data possui LMA (Load Memory Address) na Flash e VMA (Virtual) na RAM */
  .data : AT(LOADADDR(.rodata) + SIZEOF(.rodata))
  {
    _sdata = .;
    *(.data)
    _edata = .;
  } > RAM
 
  .bss :
  {
    _sbss = .;
    *(.bss)
    _ebss = .;
  } > RAM
}

O código de inicialização do microcontrolador (Startup Code, executado antes da função main()) utiliza os símbolos _sdata, _edata, _sbss e _ebss para:

  1. Copiar os valores iniciais da seção .data da Flash para a RAM;
  2. Preencher toda a área da seção .bss na RAM com zeros absolutos (0x00).

📊 6. Diagrama da Toolchain de Compilação Cruzada

O diagrama técnico abaixo exibe o fluxo completo da toolchain de compilação cruzada partindo da máquina Host x86_64 até o particionamento de memória física no target ARM:


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

Questão 1 (Banca CESPE/CEBRASPE - Perito Criminal Federal / Engenharia Eletrônica)

No desenvolvimento de software embarcado para um módulo registrador de oscilografia de perturbações em subestações elétricas baseado em microcontrolador ARM de 32 bits, um engenheiro observa que as variáveis globais alocadas em seu código C consomem diferentes segmentos de memória. Considere as seguintes declarações de variáveis no escopo global:

  1. const float tensao_base_kv = 230.0f;
  2. uint32_t contador_eventos = 100U;
  3. uint16_t buffer_amostras_adc[2048];

Assinale a opção que indica, correta e respectivamente, em quais seções lógicas da imagem binária compilada (formato ELF) essas variáveis serão alocadas pelo linker script.

Alternativas: A) .data, .bss e .rodata.
B) .rodata, .data e .bss.
C) .text, .rodata e .data.
D) .rodata, .bss e .data.
E) .bss, .data e .text.

Gabarito Comentado:

  • Alternativa correta: B.
  • Justificativa Detalhada:
    1. tensao_base_kv: Declarada com o qualificador const, seu valor é de somente leitura e não pode ser alterado em tempo de execução. O compilador a aloca na seção .rodata (read-only data), que reside permanentemente na memória Flash ROM, poupando a escassa memória SRAM.
    2. contador_eventos: É uma variável global modificável inicializada com um valor explícito não nulo (100U). Ela é alocada na seção .data. Seus valores iniciais residem na Flash e são copiados para a SRAM durante a rotina de inicialização (startup).
    3. buffer_amostras_adc: É um vetor global declarado sem inicializador explícito. Por definição do padrão da linguagem C, variáveis estáticas e globais não inicializadas são alocadas na seção .bss (Block Started by Symbol) e zeradas na memória SRAM antes da invocação da função main().

Questão 2 (Banca FCC - Analista Judiciário / Tecnologia da Informação e Automação)

A compilação de um sistema embarcado com o compilador GCC utilizando a flag de otimização -Os tem como objetivo principal: A) Maximizar o paralelismo de execução em múltiplos núcleos de CPU.
B) Desativar completamente a geração de interrupções de hardware pelo NVIC.
C) Otimizar o código gerado visando primariamente à redução do tamanho do arquivo binário final (size).
D) Substituir todas as chamadas de funções recursivas por laços de repetição infinitos.
E) Converter variáveis de ponto flutuante de precisão dupla em números inteiros de 8 bits.

Gabarito Comentado:

  • Alternativa correta: C.
  • Justificativa: No GNU GCC, as flags de otimização representam compromissos de projeto: -O0 (sem otimização, depuração), -O1 (otimização básica), -O2 (otimização balanceada com foco em velocidade), -O3 (otimização agressiva de velocidade, podendo aumentar o tamanho do código através de desenrolamento de laços / loop unrolling e expansão de funções inline). A flag -Os (Optimize for Size) ativa todas as otimizações de -O2 que não aumentem o tamanho do código, aplicando transformações específicas para minimizar a ocupação em bytes na memória Flash ROM do microcontrolador.

Questão 3 (Problema Prático de Engenharia - Dimensionamento de Memória por Linker Script)

Um firmware de telemetria para religadores automáticos desenvolvido para um microcontrolador com de memória Flash e de memória SRAM apresentou o seguinte relatório de saída gerado pelo comando arm-none-eabi-size:

   text    data     bss     dec     hex filename
 412800    8192   48500  469492   729f4 firmware.elf

Determine:

  1. A ocupação total da memória Flash ROM em bytes e a respectiva porcentagem utilizada da capacidade do chip.
  2. A ocupação total da memória volátil SRAM em bytes e a respectiva porcentagem utilizada.
  3. Avalie se o programa pode ser gravado e executado com segurança no microcontrolador especificado.

Resolução Passo a Passo:

  1. Cálculo da Ocupação da Memória Flash ROM (): A memória Flash armazena o código executável (.text + .rodata) e os valores iniciais de carga da seção .data (LMA):

    Capacidade total da Flash:

    Porcentagem de utilização da Flash:

  2. Cálculo da Ocupação da Memória SRAM (): A memória SRAM aloca as variáveis modificáveis inicializadas (.data) e as não inicializadas (.bss):

    Capacidade total da SRAM:

    Porcentagem de utilização estática da RAM:

  3. Avaliação Crítica de Segurança Operacional:

    • A memória Flash possui margem de folga de ( livres), o que é suficiente para futuras atualizações de firmware.
    • A memória SRAM possui apenas (). Veredito de Engenharia: Alerta Crítico de Risco Operacional. Embora o código caiba estaticamente no microcontrolador, o espaço residual de deve acomodar simultaneamente a Stack (pilha de execução das chamadas de função e empilhamento automático de interrupções de hardware pelo NVIC). Se uma rotina de falta disparar múltiplas interrupções aninhadas ou funções com buffers locais grandes, ocorrerá um Stack Overflow, corrompendo a seção .bss e travando o religador em campo. Recomenda-se reduzir o tamanho de buffers estáticos ou migrar para um modelo de microcontrolador com pelo menos de SRAM.

🔗 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