Aula 02 - Conjunto de Instruções, Assembly e Linguagem C Embarcada
1. O Repertório de Instruções e o Modelo de Execução RISC
Em microcontroladores de arquitetura RISC (como as famílias Microchip PIC e Atmel AVR), o repertório de instruções é enxuto, regular e ortogonal. No núcleo clássico de linha média (PIC16F), existem apenas 35 instruções de máquina, todas com palavra de tamanho fixo de , divididas estruturalmente em quatro classes canônicas:
2. Modos de Endereçamento de Memória
O modo de endereçamento estabelece como a UCP localiza o operando a ser processado pela ULA:
2.1 Endereçamento Imediato (Literal)
O dado numérico a ser processado está embutido diretamente nos bits da própria instrução (campo de 8 bits). Nenhuma leitura adicional de memória RAM é realizada:
MOVLW 0x55 ; Carrega o valor hexadecimal literal 0x55 no WREG
XORLW 0xFF ; Executa OU-Exclusivo entre o WREG e a máscara literal 0xFF2.2 Endereçamento Direto
A instrução contém nos 7 bits menos significativos o endereço exato do registrador na memória RAM ( a ). O bit de destino determina onde o resultado será armazenado:
MOVF PORTB, W ; Lê o valor presente nos pinos do PORTB e salva em W (d = 0)
MOVWF TRISB ; Grava o conteúdo do WREG no registrador de direção TRISB
INCF CONTADOR, F ; Incrementa em 1 a variável CONTADOR e salva nela mesma (d = 1)2.3 Endereçamento Indireto (Ponteiros por Hardware)
Utiliza um par de registradores de funções especiais: o FSR (File Select Register, endereço ) e o INDF (Indirect File, endereço ):
- O registrador
INDFnão é um registrador físico de silício. - Quando qualquer instrução acessa
INDF, a UCP acessa fisicamente o registrador de memória RAM cujo endereço está contido no registradorFSR. - Essa técnica é a base do hardware para a criação de ponteiros, arrays e buffers circulares em C embarcado:
MOVLW 0x20 ; Aponta para o início da área livre de variáveis GPR (0x20)
MOVWF FSR ; Inicializa o ponteiro FSR com o endereço base
CLRF INDF ; Limpa a posição RAM apontada por FSR (limpa o endereço 0x20!)
INCF FSR, F ; Avança o ponteiro para o próximo endereço (0x21)
CLRF INDF ; Limpa o endereço 0x21!3. Da Linguagem Assembly para a Linguagem C Embarcada
Enquanto o Assembly oferece controle absoluto de cada ciclo de clock e de cada bit de registrador, seu desenvolvimento em projetos complexos é lento, de difícil manutenção e totalmente não-portável entre famílias de microcontroladores diferentes.
A linguagem C Embarcada consolidou-se como o padrão industrial dominante na engenharia eletrônica, oferecendo produtividade, legibilidade e portabilidade de alto nível, mantendo simultaneamente a capacidade de manipulação direta de bits e registradores de hardware de baixo nível.
4. Tipos de Dados Inteiros e a Norma <stdint.h>
Em firmware de missão crítica e controle automotivo (norma MISRA C), o uso de tipos de dados genéricos da linguagem C clássica (int, short, long) é formalmente banido, pois sua largura em bits varia conforme a arquitetura do compilador (um int tem 16 bits no PIC16 e 32 bits no ARM Cortex).
Exige-se a adoção estrita dos tipos inteiros com garantia de largura em bits definidos no padrão ISO C99 (<stdint.h>):
| Tipo Padronizado | Largura | Faixa Numérica | Aplicação Típica em Microcontroladores |
|---|---|---|---|
uint8_t | 8 bits | Registradores de 8 bits (PORTx, TRISx), contadores | |
int8_t | 8 bits com sinal | Leituras de temperatura centrígrada negativa | |
uint16_t | 16 bits | Leituras de conversores ADC de 10 e 12 bits, PR2/TMR1 | |
int16_t | 16 bits com sinal | Sinais de sensores com zero central (giroscópios) | |
uint32_t | 32 bits | Timers acumulados em milissegundos (millis) |
5. O Modificador volatile e sua Importância Física
Em compiladores modernos, o otimizador de código analisa as variáveis e armazena cópias temporárias em registradores de trabalho da CPU para acelerar loops, eliminando leituras redundantes da memória RAM.
No entanto, em sistemas microcontrolados, o valor de um registrador ou variável pode ser alterado assincronamente pelo próprio hardware físico (ex: o registrador PIR1bits.TMR1IF quando o temporizador estoura, ou o pino PORTBbits.RB0 quando um operador pressiona um botão externo) ou por uma Rotina de Serviço de Interrupção (ISR).
// CÓDIGO INCORRETO (Sem volatile) - O compilador infere que 'flag' nunca muda dentro do loop:
uint8_t flag = 0;
void interrupt isr(void) {
if (INTCONbits.INTF) {
flag = 1; // Modificado por hardware na interrupção!
INTCONbits.INTF = 0;
}
}
void main(void) {
while (flag == 0) {
// O otimizador lê 'flag' uma única vez para o registrador WREG
// e gera um LOOP INFINITO: nunca mais relê a memória RAM!
}
aciona_motor();
}Ao declarar a variável ou o ponteiro de registrador com o modificador volatile:
\boxed{\text{volatile uint8_t flag = 0;}}
Instrui-se o compilador de forma mandatória: nunca otimize leituras ou escritas nesta variável; sempre acesse o endereço físico na memória RAM a cada avaliação.
6. Álgebra Bit a Bit (Bitwise Operations) em Firmware
A manipulação cirúrgica de pinos individuais de entrada e saída (GPIO) e de bits de controle de periféricos sem alterar o restante do registrador é a operação mais frequente em engenharia de sistemas embarcados. Ela fundamenta-se nos operadores bit a bit:
7. Estrutura Canônica de um Projeto em C Embarcado
Um firmware profissional para microcontrolador organiza-se no padrão estruturado a seguir:
// 1. Inclusão de Cabeçalhos e Bibliotecas
#include <xc.h> // Definições de hardware dos SFRs do microcontrolador
#include <stdint.h> // Tipos padronizados ISO uint8_t, uint16_t
// 2. Configuração dos Fusíveis de Hardware (Configuration Fuses)
#pragma config FOSC = HS // Oscilador a Cristal de Alta Velocidade (HS)
#pragma config WDTE = OFF // Watchdog Timer desabilitado por software
#pragma config PWRTE = ON // Power-up Timer habilitado para inicialização estável
#pragma config BOREN = ON // Brown-out Reset habilitado (reset por queda de VDD)
#pragma config LVP = OFF // Low-Voltage ICSP desabilitado (libera pino RB3)
#pragma config CPD = OFF // Proteção de código da EEPROM desabilitada
#pragma config CP = OFF // Proteção de código da Flash desabilitada
#define _XTAL_FREQ 20000000UL // Informa ao compilador clock de 20 MHz para rotinas de delay
// 3. Protótipos de Funções e Variáveis Globais Voláteis
void hardware_init(void);
volatile uint8_t contador_pulsos = 0;
// 4. Rotina de Serviço de Interrupção (ISR)
void __interrupt() isr(void) {
if (INTCONbits.INTF) { // Interrupção externa no pino RB0/INT
contador_pulsos++; // Incrementa contador de pulsos físicos
INTCONbits.INTF = 0; // Limpeza mandatória do flag por software!
}
}
// 5. Função Principal (Superloop / Bare-Metal)
void main(void) {
hardware_init(); // Configura GPIOs, Timers e Interrupções
while (1) { // Loop infinito perpétuo do sistema embarcado
if (contador_pulsos >= 100) {
PORTB ^= (1 << 7); // Comuta estado do LED indicador em RB7
contador_pulsos = 0; // Reinicia contagem
}
}
}
void hardware_init(void) {
// Configuração dos Portas I/O
TRISBbits.TRISB0 = 1; // RB0 configurado como Entrada Digital (Botão/Sensor)
TRISBbits.TRISB7 = 0; // RB7 configurado como Saída Digital (LED)
PORTB = 0x00; // Garante saídas desligadas na inicialização
// Configuração do Sistema de Interrupções
OPTION_REGbits.INTEDG = 1; // Interrupção na borda de SUBIDA do pino RB0
INTCONbits.INTE = 1; // Habilita interrupção externa individual de RB0
INTCONbits.GIE = 1; // Habilita a Chave Geral de Interrupções
}8. Diagrama Arquitetural Consolidado
O diagrama técnico vetorial a seguir ilustra a cadeia completa de desenvolvimento de software (toolchain), a transformação dos arquivos de código até a geração do formato Intel HEX e a distribuição física dos segmentos na memória Flash e na memória RAM:
9. Exercícios Resolvidos e Aplicações em Concursos
Exercício 1 (Petrobras - Engenharia de Automação / CESGRANRIO)
Enunciado: Em um sistema embarcado industrial com microcontrolador, o registrador de direção de portas TRISC encontra-se configurado inicialmente com o valor hexadecimal 0x3F (). Deseja-se alterar a configuração de pinos do microcontrolador satisfazendo simultaneamente três requisitos sem modificar o estado dos demais pinos:
- Configurar o pino RC7 como Saída digital (bit 7 de TRISC em 0);
- Configurar o pino RC6 como Entrada digital (bit 6 de TRISC em 1);
- Inverter (toggle) a direção do pino RC0 (bit 0 de TRISC).
Escreva as três instruções equivalentes em linguagem C utilizando estritamente operadores binários bitwise (&, |, ^, ~) e calcule o valor hexadecimal final resultante em TRISC.
Resolução Passo a Passo:
- Configuração Inicial:
- Requisito 1: Forçar bit 7 para 0 (Limpar bit): Utiliza-se operador AND com o inverso da máscara do bit 7: Máscara: .
- Requisito 2: Forçar bit 6 para 1 (Setar bit): Utiliza-se operador OR com a máscara do bit 6: Máscara: .
- Requisito 3: Inverter o bit 0 (Toggle): Utiliza-se operador XOR com a máscara do bit 0: Máscara: .
- Conversão para Hexadecimal Final:
\boxed{\text{Código C: TRISC = (TRISC \& \sim(1 \ll 7)) | (1 \ll 6)) \ \hat{} \ (1 \ll 0); \quad \text{Valor Final: } 0x7E}}
Exercício 2 (Marinha do Brasil - Corpo de Engenheiros)
Enunciado: Em sistemas de tempo real crítico, analise a diferença fundamental entre uma chamada de sub-rotina (CALL / função em C) e o atendimento a uma Interrupção de Hardware (interrupt). Explique detalhadamente:
- Como a UCP realiza o salvamento de contexto (context saving) em cada um dos dois mecanismos.
- Por que o desenvolvedor deve minimizar o tempo despendido dentro de uma rotina ISR.
Resolução Técnica Estruturada:
- Chamada de Sub-rotina (
CALL/ Função):- É um evento síncrono e determinístico: o programador determinou exatamente no código-fonte onde a sub-rotina é chamada;
- O hardware apenas salva o endereço da próxima instrução () na pilha (Stack);
- O compilador sabe de antemão quais registradores serão reutilizados e salva apenas as variáveis locais estritamente necessárias.
- Atendimento a uma Interrupção (
interrupt):- É um evento assíncrono e imprevisível: pode ocorrer a qualquer microssegundo entre quaisquer duas instruções de máquina do programa principal;
- A UCP precisa suspender instantaneamente a tarefa em andamento. Além do , o hardware ou o compilador deve salvar o contexto da máquina (obrigatoriamente o registrador de trabalho
WREG, o registrador de flagsSTATUSe registradores de seleção de bancoBSR/PCLATH); - Se o contexto não for salvo e restaurado antes do retorno com
RETFIE, as operações da rotina de interrupção sobrescreverão os flags de uma operação de cálculo do programa principal que estava no meio da execução, gerando falhas operacionais gravíssimas e erros fantasmas de software.
- Minimização do Tempo de Execução da ISR:
- Durante a execução de uma ISR, a chave geral de interrupções (
GIE) é desabilitada por hardware para evitar interrupções reentrantes descontroladas; - Enquanto a UCP permanece presa na ISR, qualquer outro evento externo de hardware (como recepção de bytes na UART, estouro de temporizadores ou botão de parada de emergência) fica bloqueado e em espera;
- Se a ISR demorar excessivamente (por exemplo, contendo delays bloqueantes como
delay_ms()ou loops lentos de formatação de strings), ocorrerá perda irreversível de dados em buffers seriais e atraso na resposta de proteção de emergência. A regra de ouro da engenharia de firmware estabelece: a ISR deve apenas ler o sensor/dado, setar um flag em memória global e sair imediatamente, deixando o processamento pesado para o loop principal damain().
- Durante a execução de uma ISR, a chave geral de interrupções (