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
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 é:
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 fonteO 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.
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
ENDAtençã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:
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:
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 FlashOs 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.
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).
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.
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:
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 chamadorA 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 animA 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 é:
Para debug de valores de variáveis, você copia o valor diretamente para LATD antes de qualquer operação crítica:
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'
RETURNuart_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
RETURNEstraté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.
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.