Manual de Programação em Assembly para o PIC18F4550

Este manual apresenta o conjunto completo de conhecimentos necessários para escrever, compilar, gravar e depurar programas em linguagem de montagem (assembly) para o PIC18F4550 no kit ACEPIC PRO V8.2. Ao contrário do manual de C, que usa o compilador como intermediário entre você e o processador, aqui não há intermediário: cada linha que você escreve é uma instrução de máquina. Isso exige que você gerencie explicitamente registradores, bancos de memória, modos de endereçamento e a estrutura do programa — o que por sua vez lhe dará uma compreensão precisa de como o processador executa código que, em C, parece simples. Leia este manual integralmente e volte a consultá-lo sempre que tiver dúvidas sobre sintaxe, diretivas ou técnicas de depuração.

Por Que Estudar Assembly Para um Microcontrolador?

A pergunta é legítima: se o compilador C gera assembly automaticamente, para que aprender a escrever assembly na mão? A resposta está em três utilidades práticas que se tornarão evidentes ao longo da disciplina.

A primeira utilidade é a compreensão: quando você lê o arquivo de listagem (.lst) gerado pelo compilador e encontra a instrução TBLRD*+ acessando um array const, você entende o que isso significa porque já escreveu código que usa TBLRD diretamente. Sem esse conhecimento, o assembly gerado pelo compilador é apenas texto opaco. A segunda utilidade é a otimização: certas sequências de código crítico — ISRs que precisam ser mínimas em ciclos, rotinas de delay de precisão, protocolos de comunicação bit-bang — são mais bem escritas diretamente em assembly do que em C. A terceira utilidade é a depuração: saber ler assembly permite que você inspecione o código descompilado na janela de desmontagem do MPLAB X e identifique exatamente onde um comportamento inesperado está ocorrendo.

O Ambiente de Desenvolvimento: MPLAB X e o MPASM

O montador (assembler) integrado ao MPLAB X para dispositivos PIC é o MPASM. Ele converte arquivos de texto com extensão .asm em arquivos de objeto binário (.o) e, junto com o linker MPLINK, gera o arquivo .hex final para gravação. O MPASM é automaticamente selecionado quando você cria um projeto no MPLAB X sem especificar o compilador XC8 — ao escolher um projeto do tipo Standalone Project para o PIC18F4550 e deixar o campo de compilador/montador em branco ou selecionar mpasm, você obtém um projeto de assembly puro.

Criando um Projeto Assembly no MPLAB X

Em File → New Project → Microchip Embedded → Standalone Project, selecione o dispositivo PIC18F4550 e, no campo de ferramenta, selecione No Tool ou Simulator (o mesmo raciocínio do manual C se aplica: a gravação será feita via bootloader, não diretamente pelo IDE). No campo de compilador/montador, selecione MPASM. Ao criar o projeto, adicione um arquivo fonte em Source Files → New → ASM Source File e nomeie-o main.asm.

As Configurações do Processador em MPASM

Em C, as configurações são feitas com #pragma config. Em assembly MPASM, a diretiva equivalente é CONFIG. A sintaxe é:

    CONFIG  campo = valor

Cada CONFIG configura um campo de um dos registradores de configuração do PIC18F4550 e é processado pelo montador no momento da montagem, gerando os bytes correspondentes nas palavras de configuração da memória de programa.

Os campos e valores são exatamente os mesmos do manual de C: FOSC = HS para o cristal de 8 MHz do kit, WDT = OFF, LVP = OFF (obrigatório com o bootloader), MCLRE = ON e PBADEN = OFF. O arquivo de definições p18f4550.inc — que deve ser incluído com #include <p18f4550.inc> — declara os nomes válidos para cada campo e valor.

A Estrutura Obrigatória de Todo Programa Assembly para PIC18F4550

Todo programa assembly para o PIC18F4550 tem uma estrutura mínima que inclui os vetores de interrupção, a inclusão do arquivo de definições do processador e a diretiva END no final. Sem essa estrutura, o programa pode não funcionar corretamente ou nem ser montado.

; ============================================================
; Estrutura mínima para PIC18F4550 no kit ACEPIC PRO V8.2
; Montador: MPASM (integrado ao MPLAB X)
; Clock: cristal externo de 8 MHz (modo HS)
; ============================================================

; --- Inclusão do arquivo de definições do processador ---
; Este arquivo declara os endereços de todos os SFRs (TRISD,
; LATD, PORTB, etc.) e as constantes de campo para CONFIG.
    #include <p18f4550.inc>

; --- Configurações do processador (Configuration Bits) ---
    CONFIG  FOSC    = HS          ; Cristal externo de alta velocidade
    CONFIG  WDT     = OFF         ; Watchdog Timer desativado
    CONFIG  LVP     = OFF         ; Programação em baixa tensão desativada
    CONFIG  MCLRE   = ON          ; Pino MCLR como reset externo
    CONFIG  PBADEN  = OFF         ; PORTB pinos 0-4 como digital
    CONFIG  CPUDIV  = OSC1_PLL2   ; Divisor de clock padrão

; ============================================================
; SEÇÃO DE VARIÁVEIS NA RAM
; UDATA_ACS coloca as variáveis no Access Bank (0x000-0x05F),
; que pode ser acessado com a=0 (ACCESS) sem trocar de banco.
; UDATA coloca em bancos normais, exigindo MOVLB para selecionar.
; ============================================================
    UDATA_ACS
minha_var   RES     1   ; Reserva 1 byte no Access Bank

; ============================================================
; VETORES DO PROCESSADOR
; O PIC18F4550 possui três vetores fixos:
;   0x0000: Reset — executado ao ligar ou ao sair do reset
;   0x0008: Interrupção de alta prioridade
;   0x0018: Interrupção de baixa prioridade
;
; Com o bootloader, esses endereços pertencem ao bootloader.
; Os vetores do usuário ficam em 0x1000, 0x1008 e 0x1018.
; Veja a seção "Bootloader e os Vetores" para os detalhes.
; ============================================================
    ORG     0x0000          ; Endereço do vetor de reset
    GOTO    main            ; Desvia imediatamente para 'main'

    ORG     0x0008          ; Vetor de interrupção alta prioridade
    RETFIE  FAST            ; Retorna da interrupção imediatamente
                            ; (FAST restaura W, STATUS, BSR do stack shadow)

    ORG     0x0018          ; Vetor de interrupção baixa prioridade
    RETFIE                  ; Retorna da interrupção imediatamente

; ============================================================
; CÓDIGO PRINCIPAL — começa após os vetores em 0x0020
; ============================================================
    ORG     0x0020
main:
    ; Código de inicialização aqui

loop:
    BRA     loop    ; Laço infinito

    END             ; Diretiva obrigatória: fim do arquivo fonte
    #include <p18f4550.inc>

    CONFIG FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2

    UDATA_ACS
v   RES  1

    ORG  0x0000 & GOTO main
    ORG  0x0008 & RETFIE FAST
    ORG  0x0018 & RETFIE

    ORG  0x0020
main:
loop: BRA loop
    END

O Bootloader e os Vetores de Interrupção em Assembly

Assim como no desenvolvimento em C, o código assembly deve ser posicionado a partir do endereço 0x001000 quando se usa o bootloader do kit ACEPIC PRO V8.2. A diferença em relação ao C é que em assembly você controla explicitamente os endereços via diretiva ORG, o que torna a configuração mais direta — mas também exige atenção redobrada.

block-beta
    columns 1
    block:flash["Flash — 32 KB"]:1
        A["0x000000 – 0x000FFF\nBootloader (gravado de fábrica)\nContém os vetores originais de reset/int"]
        B["0x001000\nORG 0x001000 — GOTO main\n(Vetor de Reset do Usuário)"]
        C["0x001008\nORG 0x001008 — GOTO isr_alta\n(Vetor de Interrupção Alta do Usuário)"]
        D["0x001018\nORG 0x001018 — GOTO isr_baixa\n(Vetor de Interrupção Baixa do Usuário)"]
        E["0x001020 e acima\nCódigo do usuário (main, subrotinas, tabelas)"]
    end

A estrutura correta do arquivo assembly para uso com o bootloader é a seguinte:

; Programa assembly para PIC18F4550 COM bootloader
; Todos os ORG devem ser >= 0x001000

    #include <p18f4550.inc>

    CONFIG  FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2

; --------------- Variáveis na RAM (endereços físicos, não afetados pelo bootloader) ---------------
    UDATA_ACS               ; RAM Access Bank começa em 0x000 (não é Flash, não conflita)
contador    RES     1
temp        RES     1

; --------------- Vetor de Reset do Usuário (deve ser em 0x1000 com bootloader) ---------------
    ORG     0x1000
    GOTO    main            ; O bootloader desvia para cá ao entrar no programa do usuário

; --------------- Vetor de Interrupção Alta do Usuário ---------------
    ORG     0x1008
    RETFIE  FAST            ; Sem ISR: retorna imediatamente

; --------------- Vetor de Interrupção Baixa do Usuário ---------------
    ORG     0x1018
    RETFIE

; --------------- Início do código do usuário ---------------
    ORG     0x1020
main:
    CLRF    TRISD,  ACCESS  ; PORTD como saída
    CLRF    LATD,   ACCESS  ; LEDs apagados

loop:
    SETF    LATD,   ACCESS  ; Todos LEDs acesos
    BRA     loop

    END
    #include <p18f4550.inc>
    CONFIG FOSC=HS,WDT=OFF,LVP=OFF,MCLRE=ON,PBADEN=OFF,CPUDIV=OSC1_PLL2

    UDATA_ACS
cnt RES 1

    ORG 0x1000 & GOTO main
    ORG 0x1008 & RETFIE FAST
    ORG 0x1018 & RETFIE

    ORG 0x1020
main:
    CLRF TRISD,ACCESS
    CLRF LATD,ACCESS
loop: SETF LATD,ACCESS & BRA loop
    END

Atenção: em um projeto assembly no MPLAB X, o linker não realiza a realocação automática de código como faz para projetos C. O endereço de início do programa é determinado exclusivamente pelas diretivas ORG que você escreve. Se você usar ORG 0x0000 em vez de ORG 0x1000, o montador gerará um .hex que começa em 0x000000, e o software de gravação via bootloader sobrescreverá o bootloader ao gravar. Use sempre ORG 0x1000 para o vetor de reset em projetos com bootloader.

Organização da Memória de Dados e Diretivas de Alocação

UDATA_ACS — Access Bank (0x000–0x05F)

O Access Bank é uma área especial de 96 bytes de RAM cujo acesso não requer a instrução MOVLB de troca de banco. Todas as instruções do PIC18 com o operando a = 0 (ou com a palavra-chave ACCESS) acessam diretamente essa região. O compilador C tenta alocar variáveis frequentes no Access Bank; em assembly, você faz isso explicitamente usando a diretiva UDATA_ACS:

    UDATA_ACS        ; Diretiva: variáveis a seguir vão para o Access Bank
minha_var   RES 1   ; Reserva 1 byte; o label 'minha_var' receberá o endereço alocado
outro       RES 2   ; Reserva 2 bytes consecutivos

O montador aloca automaticamente os endereços dentro do Access Bank. Você não precisa (e não deve) especificar endereços fixos — o linker garante que não haja colisão com os SFRs, que também estão mapeados no Access Bank na faixa 0x060–0x0FF.

UDATA — Bancos de RAM Numerados (0x100–0xEFF)

Para variáveis que excedem os 96 bytes do Access Bank, use a diretiva UDATA. Ela aloca variáveis nos bancos de RAM normais (banco 1 em diante). Para acessar essas variáveis, você precisa selecionar o banco correto com MOVLB k antes de qualquer instrução que use a = 1 (modo banqueado), ou carregar o endereço em FSR e usar INDF (endereçamento indireto), que funciona com o endereço completo de 12 bits independentemente de banco:

    UDATA           ; Aloca no banco 1 ou superior (determinado pelo linker)
buffer      RES  16 ; Reserva 16 bytes para um buffer
tabela_ram  RES   8 ; Reserva 8 bytes para uma tabela na RAM

IDATA — Dados Inicializados

A diretiva IDATA declara variáveis que são inicializadas com valores específicos ao iniciar o programa. O MPASM gera código de inicialização que copia esses valores da Flash para a RAM na inicialização. Use com moderação, pois o código de cópia consome ciclos de startup e espaço de Flash.

Mapa de Memória de Dados Relevante

block-beta
    columns 1
    block:ram["Espaço de Endereçamento de Dados"]:1
        A["0x000 – 0x05F (96 bytes)\nAccess Bank — RAM de usuário\nAcesso rápido sem MOVLB"]
        B["0x060 – 0x0FF (160 bytes)\nAccess Bank — SFRs\n(TRISD, LATD, PORTB, ADCON1, etc.)"]
        C["0x100 – 0x1FF (256 bytes)\nBanco 1 — RAM de usuário normal"]
        D["0x200 – 0x2FF\nBanco 2 — RAM de usuário normal"]
        E["... (bancos 3 a 14) ..."]
    end

As Categorias de Instruções do PIC18 em Detalhe

A ISA (Instruction Set Architecture) do PIC18F4550 contém 75 instruções organizadas em quatro categorias principais. Entender a qual categoria cada instrução pertence ajuda a lembrar sua sintaxe e efeitos colaterais.

Instruções de Operação sobre Bytes

Estas instruções operam sobre um byte inteiro de um registrador de arquivo (File Register, f). O resultado pode ser escrito de volta no próprio registrador (d = 1, destino F) ou em W (d = 0, destino W). O campo a seleciona Access Bank (a = 0) ou banco selecionado por BSR (a = 1).

Instrução Operação Ciclos Exemplo Efeito
MOVF f,d,a d ← f 1 MOVF PORTD,W,ACCESS W ← PORTD
MOVWF f,a f ← W 1 MOVWF LATD,ACCESS LATD ← W
CLRF f,a f ← 0 1 CLRF TRISD,ACCESS TRISD ← 0x00
SETF f,a f ← 0xFF 1 SETF LATD,ACCESS LATD ← 0xFF
INCF f,d,a d ← f + 1 1 INCF cnt,F,ACCESS cnt++
DECF f,d,a d ← f − 1 1 DECF cnt,F,ACCESS cnt–
ADDWF f,d,a d ← W + f 1 ADDWF indice,W,ACCESS W ← W + indice
SUBWF f,d,a d ← f − W 1 SUBWF f,W,ACCESS W ← f − W
ANDWF f,d,a d ← W AND f 1 ANDWF PORTB,W,ACCESS W ← W & PORTB
IORWF f,d,a d ← W OR f 1 IORWF LATD,F,ACCESS LATD
XORWF f,d,a d ← W XOR f 1 XORWF LATD,F,ACCESS LATD ^= W
COMF f,d,a d ← NOT f 1 COMF LATD,F,ACCESS LATD = ~LATD
SWAPF f,d,a troca nibbles de f 1 SWAPF temp,F,ACCESS nibbles trocados
RLCF f,d,a rotaciona f para esquerda por C 1 RLCF LATD,F,ACCESS rotação via carry
RRCF f,d,a rotaciona f para direita por C 1 RRCF LATD,F,ACCESS rotação via carry

Instruções de Operação sobre Bits

Estas instruções manipulam um único bit de um registrador de arquivo. O campo b (bits 9-7 da palavra de instrução) especifica qual dos 8 bits é afetado. São as instruções mais eficientes para controle de I/O, pois realizam leitura-modificação-escrita em 1 ciclo.

Instrução Operação Ciclos Exemplo Efeito
BSF f,b,a bit b de f ← 1 1 BSF LATD,3,ACCESS Acende LED em RD3
BCF f,b,a bit b de f ← 0 1 BCF LATD,3,ACCESS Apaga LED em RD3
BTG f,b,a inverte bit b de f 1 BTG LATD,0,ACCESS Toggle LED em RD0
BTFSS f,b,a pula próx. se bit = 1 1/2 BTFSS PORTB,0,ACCESS Pula se RB0 = 1
BTFSC f,b,a pula próx. se bit = 0 1/2 BTFSC PORTB,0,ACCESS Pula se RB0 = 0

O comportamento de “pular ou não pular” das instruções BTFSS e BTFSC merece um parágrafo. Quando a condição é verdadeira (deve pular), a instrução consome 2 ciclos — o extra é necessário para descartar o pipeline da instrução seguinte. Quando a condição é falsa (não pula), consome apenas 1 ciclo. Esse comportamento é a base de toda a lógica condicional em assembly para PIC:

    ; Estrutura equivalente a: if (PORTBbits.RB0 == 0) { ação_pressionado } else { ação_solto }
    BTFSC   PORTB, 0, ACCESS   ; Pula próx. instrução se RB0 = 0 (botão pressionado)
    BRA     solto              ; RB0 = 1 (botão solto): desvia para o ramo "else"
pressionado:
    SETF    LATD, ACCESS       ; Ação para botão pressionado
    BRA     fim_if
solto:
    CLRF    LATD, ACCESS       ; Ação para botão solto
fim_if:

Instruções com Literais

Estas instruções operam sobre o registrador W usando um valor imediato (literal) embutido na palavra de instrução. Elas são a principal forma de carregar constantes no processador.

Instrução Operação Ciclos Exemplo Efeito
MOVLW k W ← k 1 MOVLW 0x5A W ← 90
ADDLW k W ← W + k 1 ADDLW .5 W ← W + 5
SUBLW k W ← k − W 1 SUBLW .10 W ← 10 − W
ANDLW k W ← W AND k 1 ANDLW 0x0F W ← W & 0x0F (nibble baixo)
IORLW k W ← W OR k 1 IORLW 0x80 W ← W
XORLW k W ← W XOR k 1 XORLW 0xFF W ← ~W
MOVLB k BSR ← k 1 MOVLB .1 Seleciona banco 1
LFSR f,k FSR f ← k 2 LFSR FSR0, buffer FSR0 aponta para buffer
RETLW k W ← k; RETURN 2 RETLW 0x3F Retorna com W = 0x3F

A notação dos literais no MPASM pode causar confusão no início. Por padrão, o MPASM interpreta todos os literais como hexadecimal. Para especificar um valor decimal, use o prefixo . (ponto): .200 é o decimal 200 (= 0xC8 em hexadecimal). Para especificar binário explicitamente, use o prefixo B'...': B'10110100'. Para hexadecimal, tanto 0xAB quanto H'AB' são aceitos. Use . para contadores e quantidades (.200, .10), e 0x para valores de registrador e máscaras (0x0F, 0xFF).

Instruções de Controle de Fluxo

Estas instruções modificam o Program Counter (PC), desviando a execução para outros endereços. São a base de laços, chamadas de subrotina e desvios condicionais.

Instrução Operação Ciclos Alcance Uso
BRA n PC ← PC + 2 + 2n 2 ±1 KB Desvio incondicional relativo (mais rápido)
GOTO k PC ← k 2 2 MB Desvio incondicional absoluto (alcance total)
CALL k,s Empilha PC; PC ← k 2 2 MB Chamada de subrotina
RCALL n Empilha PC; PC ← PC+2n 2 ±1 KB Chamada relativa (mais compacta)
RETURN s PC ← desempilha 2 Retorna de subrotina
RETFIE s PC ← desempilha; GIE ← 1 2 Retorna de ISR
RETLW k W ← k; PC ← desempilha 2 Retorna com valor em W
DECFSZ f,d,a d←f−1; pula se d=0 1/3 Decrementa e pula se zero
INCFSZ f,d,a d←f+1; pula se d=0 1/3 Incrementa e pula se zero
CPFSEQ f,a pula se f = W 1/2 Compara com W; pula se igual
CPFSGT f,a pula se f > W 1/2 Compara com W; pula se maior
CPFSLT f,a pula se f < W 1/2 Compara com W; pula se menor

A diferença entre BRA e GOTO é sutil mas importante. BRA (Branch Relative) codifica um deslocamento de 11 bits com sinal em relação ao PC atual, o que cobre uma janela de ±1 KB em torno da instrução. Essa codificação ocupa apenas 1 palavra de instrução (16 bits) e executa em 2 ciclos. GOTO (Go To) codifica um endereço absoluto de 20 bits, cobrindo todo o espaço de 1 MB de programa, mas ocupa 2 palavras de instrução (32 bits) e também executa em 2 ciclos. Em geral, use BRA para desvios locais dentro da mesma função (onde o destino está próximo) e GOTO apenas para desvios de longa distância — como o GOTO main no vetor de reset.

Instruções de Controle do Processador

Este grupo inclui as instruções TBLRD (Table Read) para leitura da memória de programa (Flash), TBLWT (Table Write) para escrita, NOP (No Operation), SLEEP, RESET e algumas outras. De particular interesse é a família TBLRD:

; Leitura de um byte da Flash usando TBLPTR e TABLAT
; Suponha que 'tabela_flash' está em 0x1100 na memória de programa

    ; Carrega o endereço da tabela no ponteiro de tabela (21 bits)
    MOVLW   UPPER(tabela_flash)  ; byte mais alto do endereço (bits 20-16)
    MOVWF   TBLPTRU, ACCESS
    MOVLW   HIGH(tabela_flash)   ; byte intermediário do endereço (bits 15-8)
    MOVWF   TBLPTRH, ACCESS
    MOVLW   LOW(tabela_flash)    ; byte menos significativo do endereço (bits 7-0)
    MOVWF   TBLPTRL, ACCESS

    TBLRD*                       ; Lê o byte apontado por TBLPTR → TABLAT
                                 ; TBLPTR não é modificado (sem incremento)

    MOVFF   TABLAT, LATD         ; LATD ← byte lido da Flash

Os modificadores de TBLRD são: * (sem incremento/decremento), *+ (pós-incremento: TBLPTR++ após leitura), *- (pós-decremento) e +* (pré-incremento: TBLPTR++ antes da leitura). O modo *+ é o mais útil para percorrer arrays na Flash sequencialmente, pois avança o ponteiro automaticamente.

Os Modos de Endereçamento em Profundidade

Esta seção é central para entender como o PIC18 acessa seus dados. Cada instrução usa um ou mais modos de endereçamento para localizar seus operandos.

Endereçamento Imediato

O operando é uma constante embutida diretamente na palavra de instrução. As instruções da família MOVLW, ADDLW, ANDLW, etc. usam esse modo. O valor constante ocupa 8 bits da palavra de instrução de 16 bits.

    MOVLW   0x5A    ; W ← 0x5A. O valor 0x5A está codificado na instrução.
    ADDLW   .3      ; W ← W + 3. O literal 3 está na instrução.

Endereçamento Direto (por Registrador de Arquivo)

O operando é um registrador de arquivo especificado pelo campo f (8 bits) na instrução, combinado com o bit a que seleciona o banco. Com a = 0 (ACCESS), o campo f endereça diretamente os 256 bytes do Access Bank (0x000–0x0FF). Com a = 1, o campo f endereça o banco selecionado pelo BSR (Bank Select Register).

    CLRF    TRISD, ACCESS   ; Zera TRISD. TRISD está no Access Bank em 0x092.
                            ; O campo 'f' da instrução contém 0x92; a=0.
    MOVWF   LATD,  ACCESS   ; LATD ← W. LATD está em 0x08C no Access Bank.

Endereçamento Indireto via FSR/INDF

O PIC18F4550 possui três pares de registradores FSR/INDF: FSR0/INDF0, FSR1/INDF1 e FSR2/INDF2. Um FSR (File Select Register) é um ponteiro de 12 bits que contém um endereço de memória de dados. O INDF correspondente é um registrador “virtual” — quando você lê ou escreve INDF0, está lendo ou escrevendo no endereço contido em FSR0, não no próprio INDF0 (que não existe fisicamente).

Modificadores disponíveis para INDF:

    LFSR    FSR0, minha_var     ; FSR0 ← endereço de minha_var

    MOVF    INDF0,  W, ACCESS   ; W ← RAM[FSR0]  (sem modificar FSR0)
    MOVF    POSTINC0, W, ACCESS ; W ← RAM[FSR0]; FSR0++  (acessa e incrementa)
    MOVF    POSTDEC0, W, ACCESS ; W ← RAM[FSR0]; FSR0--  (acessa e decrementa)
    MOVF    PREINC0,  W, ACCESS ; FSR0++; W ← RAM[FSR0]  (incrementa e acessa)
    MOVF    PLUSW0,   W, ACCESS ; W ← RAM[FSR0 + W]       (acesso indexado)

O modificador PLUSW0 é particularmente poderoso: ele realiza endereçamento indexado, somando o valor de W ao endereço base em FSR0. Isso é exatamente o que o compilador C usa para implementar array[indice] quando o array está na RAM.

; Acesso a array[indice] na RAM — equivale a: LATD = array[indice]
    LFSR    FSR0, array         ; FSR0 ← endereço do início do array
    MOVF    indice, W, ACCESS   ; W ← indice
    MOVF    PLUSW0, W, ACCESS   ; W ← RAM[FSR0 + indice] = array[indice]
    MOVWF   LATD, ACCESS        ; LATD ← array[indice]

Endereçamento Relativo ao PC (para Desvios)

A instrução BRA n codifica um deslocamento de 11 bits com sinal n, e o endereço efetivo é calculado como PC_{efetivo} = PC_{atual} + 2 + 2n (o fator 2 extra é porque o PC já avançou 2 bytes ao executar BRA, e o deslocamento é em palavras de 2 bytes). No MPASM, você usa rótulos (labels) em vez de calcular os deslocamentos manualmente:

loop:
    BTG     LATD, 0, ACCESS     ; Toggle do bit 0 de LATD
    CALL    delay_500ms         ; Chama subrotina de atraso
    BRA     loop                ; Retorna ao rótulo 'loop'
                                ; O montador calcula o deslocamento automaticamente

Subrotinas e a Pilha de Hardware

O PIC18F4550 possui uma pilha de hardware com 31 níveis de profundidade. Cada CALL empurra o endereço de retorno na pilha; cada RETURN ou RETFIE o retira. Não existe instrução genérica de PUSH/POP na ISA do PIC18 — a pilha só é usada para endereços de retorno. Variáveis locais de subrotinas devem ser implementadas em RAM (usando variáveis declaradas com RES) ou, se a profundidade de aninhamento for pequena, armazenando W no Access Bank antes da chamada.

; Subrotina de atraso de aproximadamente 500 ms a 8 MHz
; Usa três laços aninhados com DECFSZ.
;
; Análise de ciclos:
;   Laço interno  (cnt3 = 200): cada iteração = 3 ciclos (DECFSZ + BRA ou DECFSZ pula)
;     → 199 × (1 + 2) + 1 × (2 + 0) = 597 + 2 = 599 ciclos por execução de cnt3
;   Laço médio    (cnt2 = 167): cada iteração = MOVLW+MOVWF (2) + 599 + DECFSZ (1 ou 2) + BRA (2)
;     → 167 × (2 + 599 + 1 + 2) − última_iteração_BRA ≈ 100.568 ciclos
;   Laço externo  (cnt1 = 10): 10 × (2 + 100.568 + 1 + 2) ≈ 1.005.730 ciclos ≈ 502 ms

    UDATA_ACS
cnt1    RES     1       ; Contador externo
cnt2    RES     1       ; Contador médio
cnt3    RES     1       ; Contador interno

; [...] (vetores e main omitidos por brevidade)

delay_500ms:
    MOVLW   .10             ; Carrega valor inicial do laço externo
    MOVWF   cnt1, ACCESS    ; cnt1 ← 10
laco_externo:
    MOVLW   .167            ; Carrega valor inicial do laço médio
    MOVWF   cnt2, ACCESS    ; cnt2 ← 167
laco_medio:
    MOVLW   .200            ; Carrega valor inicial do laço interno
    MOVWF   cnt3, ACCESS    ; cnt3 ← 200
laco_interno:
    DECFSZ  cnt3, F, ACCESS ; cnt3--; se cnt3 = 0, pula a próxima instrução
    BRA     laco_interno    ; Não chegou a zero: repete
    ; cnt3 chegou a zero: sai do laço interno
    DECFSZ  cnt2, F, ACCESS ; cnt2--; se cnt2 = 0, pula
    BRA     laco_medio      ; Não chegou a zero: repete laço médio
    ; cnt2 chegou a zero: sai do laço médio
    DECFSZ  cnt1, F, ACCESS ; cnt1--; se cnt1 = 0, pula
    BRA     laco_externo    ; Não chegou a zero: repete laço externo
    ; cnt1 chegou a zero: terminou todos os laços
    RETURN                  ; Retorna ao chamador
    UDATA_ACS
c1 RES 1 & c2 RES 1 & c3 RES 1

delay_500ms:
    MOVLW .10  & MOVWF c1,ACCESS
d1: MOVLW .167 & MOVWF c2,ACCESS
d2: MOVLW .200 & MOVWF c3,ACCESS
d3: DECFSZ c3,F,ACCESS & BRA d3
    DECFSZ c2,F,ACCESS & BRA d2
    DECFSZ c1,F,ACCESS & BRA d1
    RETURN

A Instrução MOVFF — Mover de Qualquer Lugar para Qualquer Lugar

MOVFF fs, fd copia o byte no endereço fs (fonte) para o endereço fd (destino), usando endereços completos de 12 bits. Ela é única na ISA do PIC18 por não usar W como intermediário e por aceitar qualquer combinação de SFRs e endereços de RAM. É especialmente útil para copiar entre SFRs: MOVFF TABLAT, LATD copia o byte lido da Flash diretamente para LATD sem passar por W, preservando o valor atual de W.

Técnicas Avançadas: Tabelas com RETLW e ADDWF PCL

Uma das técnicas mais elegantes e características do assembly para PIC (especialmente para as versões mais antigas, mas ainda válida e instrutiva no PIC18) é a tabela de despacho por RETLW. A ideia é armazenar constantes diretamente como operandos de instruções RETLW na memória de programa. Para acessar o elemento de índice i, o programa soma 2i ao Program Counter Low (PCL), desviando a execução para a instrução RETLW desejada, que retorna imediatamente com o valor em W.

flowchart LR
    CODIGO["Código principal\n(carrega índice em W,\ncalcula W = 2 × índice,\nchama get_padrao)"] --> CALL["CALL get_padrao"]
    CALL --> TABELA["get_padrao:\nADDWF PCL, F  → soma 2i ao PC"]
    TABELA --> R0["RETLW 0x01 (índice 0)"]
    TABELA --> R1["RETLW 0x03 (índice 1)"]
    TABELA --> R2["RETLW 0x07 (índice 2)"]
    TABELA --> RN["... (demais índices)"]
    R0 --> W_RETORNO["W = valor retornado\n→ usável diretamente"]
    R1 --> W_RETORNO
    R2 --> W_RETORNO
    RN --> W_RETORNO

; Tabela de padrões de LED com técnica ADDWF PCL / RETLW
; OBRIGATÓRIO: a tabela deve estar em um endereço múltiplo de 256 bytes
; para que a soma não cause carry de PCL para PCLATH.

    ORG     0x1100      ; Endereço alinhado em 256 bytes (0x1100 mod 256 = 0)
get_padrao:
    ; Quando ADDWF PCL executa, PC já aponta para a instrução seguinte
    ; (dois bytes à frente), que é o primeiro RETLW. Somando 2*i, caímos
    ; exatamente sobre o RETLW do índice i.
    ADDWF   PCL, F, ACCESS   ; PC ← PC + W (W deve conter 2 × índice)
    RETLW   0x01        ; índice 0: 1 LED
    RETLW   0x03        ; índice 1: 2 LEDs
    RETLW   0x07        ; índice 2: 3 LEDs
    RETLW   0x0F        ; índice 3: 4 LEDs
    RETLW   0x1F        ; índice 4: 5 LEDs
    RETLW   0x3F        ; índice 5: 6 LEDs
    RETLW   0x7F        ; índice 6: 7 LEDs
    RETLW   0xFF        ; índice 7: todos os 8 LEDs

    ORG     0x1020      ; Código principal (após os vetores)
    UDATA_ACS
indice  RES     1

; main já definiu TRISD = 0, assumindo que está acima
laco_animacao:
    ; Para chamar get_padrao com índice i:
    ;   1. Carrega i em W
    ;   2. Soma W consigo mesmo para obter 2i (ADDWF WREG, W equivale a W + W)
    ;   3. Chama get_padrao — retorna com o padrão em W
    ;   4. Copia W para LATD

    MOVF    indice, W, ACCESS   ; W ← indice atual
    ADDWF   WREG, W             ; W ← W + W = 2 × indice
    CALL    get_padrao          ; W retorna com o padrão do índice

    MOVWF   LATD, ACCESS        ; LATD ← padrão

    CALL    delay_150ms         ; Aguarda 150 ms

    INCF    indice, F, ACCESS   ; indice++
    MOVLW   .8
    CPFSEQ  indice, ACCESS      ; Pula se indice = 8
    BRA     laco_animacao       ; Não chegou a 8: repete
    CLRF    indice, ACCESS      ; Reinicia índice para 0
    BRA     laco_animacao
    ORG 0x1100
get_p:
    ADDWF PCL,F,ACCESS
    RETLW 0x01 & RETLW 0x03 & RETLW 0x07 & RETLW 0x0F
    RETLW 0x1F & RETLW 0x3F & RETLW 0x7F & RETLW 0xFF

    UDATA_ACS
idx RES 1

anim:
    MOVF idx,W,ACCESS & ADDWF WREG,W & CALL get_p
    MOVWF LATD,ACCESS & CALL dly150
    INCF idx,F,ACCESS & MOVLW .8 & CPFSEQ idx,ACCESS & BRA anim
    CLRF idx,ACCESS & BRA anim

A restrição de alinhamento em 256 bytes é fundamental. Quando ADDWF PCL, F executa, ele soma W ao byte baixo do Program Counter, que é PCL. Se a tabela começa em 0x1100 e W = 12 (para índice 6), o novo PCL = 0x00 + 12 = 0x0C, resultando em PC = 0x110C, que é exatamente o endereço do RETLW para o índice 6. Se a tabela estivesse em 0x10F0 (não alinhada) e W = 12, o novo PCL = 0xF0 + 12 = 0xFC, sem carry — mas se W = 14 para índice 7: 0xF0 + 14 = 0x104, com carry que não se propaga para PCLATH automaticamente, resultando em PC = 0x1004 (endereço errado, na área do bootloader!). Portanto, o alinhamento é indispensável.

Depuração em Tempo Real no Kit ACEPIC PRO V8.2

Por Que a Depuração é Mais Desafiadora em Assembly

Em assembly, não existe o conceito de “variável com nome” para o depurador. O MPLAB X Simulator mostra os registradores (W, STATUS, BSR, FSR0, etc.) e os endereços de RAM, mas para saber o que é o “contador” ou o “indice” você precisa saber em qual endereço de RAM eles foram alocados. A janela File Registers do simulador mostra o conteúdo de toda a RAM, e você pode adicionar endereços específicos à janela Watch usando o endereço físico ou o símbolo do rótulo se o depurador conseguir resolvê-lo.

Depuração com o Simulador do MPLAB X

Com o projeto assembly configurado para Simulator, você pode executar o programa instrução por instrução (F7 = Step Into), definir breakpoints em linhas específicas do código e observar como os registradores mudam a cada instrução. A janela Registers mostra todos os SFRs relevantes; a janela File Registers mostra a RAM completa; a janela Program Memory mostra o código assembly gerado e o endereço de cada instrução.

Para depurar a subrotina de atraso, o simulador é especialmente útil: você pode usar a função Run → Run to Cursor para pular até o retorno da subrotina sem executar milhares de iterações de laço, e então inspecionar o estado após o atraso.

Janela do Simulador Caminho no MPLAB X O que mostra
Registers Window → Target Memory Views → SFRs Todos os SFRs: W, STATUS, BSR, TRIS, LAT, PORT…
File Registers Window → Target Memory Views → File Registers Conteúdo de toda a RAM de dados
Program Memory Window → Target Memory Views → Program Memory Código assembly montado com endereços
Watch Window → Debugging → Watches Variáveis específicas por nome ou endereço
Disassembly Window → Debugging → Disassembly Listing Código assembly com endereços e valores hexadecimais

Depuração por LEDs em Assembly

A técnica de depuração por LEDs funciona da mesma forma que em C, mas com a vantagem de que em assembly você pode escrever diretamente em LATD em qualquer ponto do código sem sobrecarga de chamada de função. O padrão de debug por LED em assembly é:

; Ponto de verificação: chegou aqui, exibe padrão 0x55
    MOVLW   0x55
    MOVWF   LATD, ACCESS
    CALL    delay_200ms     ; Aguarda para que o padrão seja visível
    CLRF    LATD, ACCESS    ; Apaga LEDs para indicar que continuou
    CALL    delay_200ms

Para debug de valores de variáveis, você copia o valor diretamente para LATD antes de qualquer operação crítica:

; Antes de uma operação: exibe o valor de 'resultado' nos LEDs
    MOVFF   resultado, LATD   ; LATD ← resultado (sem passar por W)
    CALL    delay_500ms       ; Aguarda 500 ms para que o padrão seja visível

Depuração Via USART em Assembly

A transmissão de dados pelo módulo USART em assembly segue a mesma lógica do código C, mas cada operação que em C é uma linha de código pode exigir várias instruções em assembly. O esqueleto a seguir inicializa a USART e transmite um byte:

; Inicialização da USART para 9600 bps com F_OSC = 8 MHz
; SPBRG = (8000000 / (16 × 9600)) - 1 ≈ 51
uart_init:
    BSF     TRISC, 7, ACCESS    ; RC7 = RX: entrada
    BCF     TRISC, 6, ACCESS    ; RC6 = TX: saída

    MOVLW   .51
    MOVWF   SPBRG, ACCESS       ; Baud rate ≈ 9600 bps

    MOVLW   0x24                ; SYNC=0, BRGH=1, TXEN=1, TX9=0
    MOVWF   TXSTA, ACCESS

    MOVLW   0x90                ; SPEN=1, CREN=1, RX9=0
    MOVWF   RCSTA, ACCESS

    BCF     BAUDCON, 3, ACCESS  ; BRG16 = 0: SPBRG de 8 bits
    RETURN

; Envia um byte (em W) pela USART
; Aguarda o buffer de transmissão estar vazio antes de enviar.
uart_send_byte:
    MOVWF   TXREG, ACCESS       ; Inicia transmissão (TXREG ← W)
    ; Aguarda TRMT = 1 (Transmit Shift Register vazio)
uart_wait:
    BTFSS   TXSTA, 1, ACCESS    ; Pula se TRMT = 1 (transmissão completa)
    BRA     uart_wait           ; Ainda transmitindo: aguarda
    RETURN

; Envia o valor de 'resultado' como dois dígitos hexadecimais
; Exemplo: se resultado = 0xB7, transmite "B7\r\n"
uart_send_hex_byte:
    MOVF    resultado, W, ACCESS ; W ← resultado

    ; Envia nibble alto
    SWAPF   WREG, W              ; W ← nibble alto no nibble baixo
    ANDLW   0x0F                 ; Isola nibble baixo
    ADDLW   .48                  ; Converte para ASCII: 0→'0', 1→'1', ...
    BTFSC   STATUS, 0, ACCESS    ; Testa se houve carry (valor > '9' = > '0'+9)
    ADDLW   .7                   ; Corrige para 'A'–'F': 10+'0'+7 = 'A'
    CALL    uart_send_byte

    ; Envia nibble baixo
    MOVF    resultado, W, ACCESS
    ANDLW   0x0F
    ADDLW   .48
    BTFSC   STATUS, 0, ACCESS
    ADDLW   .7
    CALL    uart_send_byte

    ; Envia CR + LF
    MOVLW   0x0D & CALL uart_send_byte   ; '\r'
    MOVLW   0x0A & CALL uart_send_byte   ; '\n'
    RETURN
uart_init:
    BSF TRISC,7,ACCESS & BCF TRISC,6,ACCESS
    MOVLW .51 & MOVWF SPBRG,ACCESS
    MOVLW 0x24 & MOVWF TXSTA,ACCESS
    MOVLW 0x90 & MOVWF RCSTA,ACCESS
    BCF BAUDCON,3,ACCESS
    RETURN

uart_tx:                                   ; W = byte a enviar
    MOVWF TXREG,ACCESS
utx: BTFSS TXSTA,1,ACCESS & BRA utx & RETURN

nibble_to_ascii:                           ; W = nibble (0-15) → W = ASCII
    ANDLW 0x0F & ADDLW .48
    BTFSC STATUS,0,ACCESS & ADDLW .7
    RETURN

Estratégia de Depuração em Assembly: Ponto de Verificação Sistemático

A metodologia mais eficaz para depurar programas assembly é inserir pontos de verificação (checkpoints) em posições estratégicas do código, que geram um sinal visível (LED ou UART) ao ser alcançados. Ao executar o programa e observar até qual checkpoint foi alcançado antes do comportamento inesperado, você isola a seção problemática.

flowchart TD
    INICIO["Início do programa"] --> CP1["Checkpoint 1\n(padrão 0x01 nos LEDs)"]
    CP1 --> INIT_HW["Inicialização de Hardware\n(TRIS, LAT, ADCON1)"]
    INIT_HW --> CP2["Checkpoint 2\n(padrão 0x03 nos LEDs)"]
    CP2 --> INIT_UART["Inicialização da USART\n(se usada)"]
    INIT_UART --> CP3["Checkpoint 3\n(padrão 0x07 nos LEDs)"]
    CP3 --> LOOP["Laço Principal"]
    LOOP --> CP4["Checkpoint de laço\n(toggle bit 7 a cada iteração)"]
    CP4 --> LOOP
    style CP1 fill:#e8f5e9,stroke:#388e3c
    style CP2 fill:#e8f5e9,stroke:#388e3c
    style CP3 fill:#e8f5e9,stroke:#388e3c
    style CP4 fill:#fff3e0,stroke:#f57c00

Um programa parou com apenas o Checkpoint 1 ativo (padrão 0x01 nos LEDs e nenhum outro)? Significa que falhou entre o CP1 e o CP2 — examine a inicialização de hardware. O padrão 0x03 aparece mas 0x07 não? A falha está na inicialização da USART. Essa técnica permite localizar o problema em poucos testes.

Armadilhas Comuns em Assembly para PIC18

A Armadilha do Banco de Registradores

Usar a = 1 (banqueado) em vez de a = 0 (ACCESS) para um SFR que está no Access Bank resulta em acesso ao endereço errado. Os SFRs como TRISD, LATD e PORTB estão todos no Access Bank (endereços 0x060–0x0FF), e a = 0 (ACCESS) funciona sempre para eles. Por segurança, use sempre ACCESS para SFRs.

; ERRADO: usa banco selecionado por BSR
    MOVLB   .0          ; BSR ← 0 (banco 0)
    CLRF    TRISD, BANKED ; Acessa banco 0 no endereço 0x092 — correto APENAS no banco 0

; CORRETO: Access Bank sempre funciona para SFRs
    CLRF    TRISD, ACCESS ; Acessa diretamente 0x092 no Access Bank

A Armadilha do Overflow em PCL

Como descrito na seção de tabelas com RETLW, ADDWF PCL, F funciona apenas quando a tabela está alinhada em 256 bytes e o índice máximo não causa carry de PCL para PCLATH. Sempre use ORG com endereço múltiplo de 256 para tabelas RETLW.

A Armadilha do STATUS Flags

Muitas instruções alteram os flags do registrador STATUS (carry C, zero Z, negative N, overflow OV, half-carry DC). Se você precisa testar o resultado de uma instrução com BTFSC STATUS, Z, ACCESS (por exemplo), certifique-se de que nenhuma outra instrução entre a operação e o teste de flag modifica o STATUS. Instruções como MOVWF, MOVF com d = W e as instruções de bit (BSF, BCF, BTG) não alteram flags, mas INCF, DECF, ADDWF e SUBWF alteram.

A Armadilha do Endereço de Retorno Errado

O RETLW dentro de uma tabela ADDWF PCL retorna para o chamador que fez o CALL get_padrao. Se você usar BRA para entrar em get_padrao em vez de CALL, o RETLW tentará retornar para um endereço inválido (o que foi empurrado pelo CALL anterior, não o correto). Tabelas com RETLW devem ser sempre acessadas via CALL.

Checklist de Projeto Assembly: Antes de Gravar no Kit

Antes de qualquer gravação via bootloader, percorra este checklist. Cada item representa uma fonte comum de erros em projetos assembly.

Primeiro, verifique se #include <p18f4550.inc> está presente no início do arquivo e se as diretivas CONFIG estão corretas, especialmente LVP=OFF. Segundo, confirme que o vetor de reset usa ORG 0x1000 (não ORG 0x0000), e que os vetores de interrupção alta e baixa estão em ORG 0x1008 e ORG 0x1018 respectivamente. Terceiro, certifique-se de que o código do usuário começa com ORG 0x1020 ou posterior. Quarto, verifique que todas as variáveis usadas na seção UDATA_ACS ou UDATA estão declaradas com RES antes de qualquer uso. Quinto, confirme que todos os laços têm uma condição de saída — um DECFSZ que eventualmente zerará o contador — e que a subrotina termina com RETURN. Sexto, verifique que tabelas com RETLW e ADDWF PCL estão em endereços alinhados a 256 bytes. Sétimo, confirme que o arquivo termina com a diretiva END. Oitavo, compile com Build → Build All e corrija todos os erros e avisos do montador antes de gravar.

Mapa de Endereços dos SFRs Mais Usados

SFR Endereço Função Observação
PORTA 0x080 Leitura dos pinos de PORTA Use ADCON1 = 0x0F para modo digital
PORTB 0x081 Leitura dos pinos de PORTB Use PBADEN=OFF e ADCON1=0x0F
PORTC 0x082 Leitura dos pinos de PORTC RC6=TX, RC7=RX
PORTD 0x083 Leitura dos pinos de PORTD Totalmente digital
PORTE 0x084 Leitura dos pinos de PORTE
LATA 0x089 Escrita nos pinos de PORTA
LATB 0x08A Escrita nos pinos de PORTB
LATC 0x08B Escrita nos pinos de PORTC
LATD 0x08C Escrita nos pinos de PORTD LEDs no kit
LATE 0x08D Escrita nos pinos de PORTE
TRISA 0x092 Direção de PORTA 1=entrada, 0=saída
TRISB 0x093 Direção de PORTB
TRISC 0x094 Direção de PORTC
TRISD 0x095 Direção de PORTD
TRISE 0x096 Direção de PORTE
STATUS 0xFD8 Flags C, Z, N, OV, DC
WREG 0xFE8 Acumulador W
BSR 0xFE0 Bank Select Register Selecionado por MOVLB
FSR0L 0xFEA FSR0 byte baixo
FSR0H 0xFEB FSR0 byte alto
FSR1L 0xFE2 FSR1 byte baixo
FSR2L 0xFDA FSR2 byte baixo
TBLPTRL 0xFF6 Ponteiro de tabela — byte baixo Para TBLRD
TBLPTRH 0xFF7 Ponteiro de tabela — byte médio
TBLPTRU 0xFF8 Ponteiro de tabela — byte alto
TABLAT 0xFF5 Registrador de latch de tabela Resultado de TBLRD
ADCON1 0xFC1 Configuração do ADC 0x0F = todos digitais
CMCON 0xFB4 Configuração dos comparadores 0x07 = desativado
INTCON2 0xFF1 Controle de interrupção 2 bit 7 RBPU: pull-ups PORTB
TXREG 0FAD Registrador de TX da USART
SPBRG 0xFAF Divisor de baud rate
TXSTA 0xFAC Status/controle TX
RCSTA 0xFAB Status/controle RX

Para Aprofundamento

O arquivo de definições p18f4550.inc — localizado em <pasta de instalação do MPASM> — declara todos os endereços dos SFRs e os nomes dos bits usados nas diretivas CONFIG. Abrir esse arquivo e examinar as declarações dos registradores que você está usando é uma das melhores formas de aprender os nomes corretos. O PIC18F4550 Data Sheet (DS39632E) contém a referência completa de todos os SFRs e sua descrição bit a bit, e o PIC18F/PIC18LF Instruction Set Reference Manual (DS33014) descreve em detalhe o formato binário e o comportamento de cada instrução, incluindo quais flags do STATUS são afetados e em quantos ciclos cada instrução executa em diferentes condições. O MPASM User’s Guide (DS33014K) documenta todas as diretivas do montador — ORG, UDATA, IDATA, RES, EQU, SET, e as macros do pré-processador #include, #define, #ifdef, #macro/#endm.