flowchart TB
subgraph CPU["dentro do processador"]
W["W — 8 bits, rascunho"]
PC["PC — endereço da próxima instrução"]
STATUS["STATUS — Z, C, N, OV, DC"]
end
subgraph FLASH["Flash (32 KB)"]
INST["suas instruções:<br/>bsf LATD, 3"]
end
subgraph RAM["RAM (2 KB)"]
GPR["variáveis"]
SFR["SFRs: TRISD, LATD..."]
end
PC --> INST
INST --> W
W <--> GPR
W <--> SFR
Manual de Referência: Assembly para o PIC18F4550
Este apêndice é o seu mapa do assembly do PIC18. Ele começa bem devagar — do “o que é uma instrução” — e termina com as tabelas de consulta rápida que você vai abrir toda vez que esquecer a sintaxe de um mnemônico. A ideia não é decorar tudo, e sim entender o suficiente para ler o assembly que o XC8 gera e escrever os trechos curtos onde assembly compensa. Tudo para o PIC18F4550 no kit ACEPIC PRO V8.2, com o montador pic-as do compilador MPLAB XC8.
Por que aprender assembly se o compilador já faz isso?
Pergunta justa. Eu respondo com três situações reais. Quando você abre o arquivo de listagem (.lst) e vê tblrd*+ acessando um array const, entende o que está acontecendo porque já escreveu tblrd na mão. Quando precisa de uma rotina muito enxuta — uma ISR mínima, um delay de precisão, um protocolo bit-bang — assembly dá o controle fino que C não dá. E quando um programa em C se comporta de um jeito estranho, você abre a janela de desmontagem do MPLAB X e lê o que o processador realmente está fazendo. Saber assembly transforma o compilador de caixa-preta em parceiro transparente.
O modelo mental: quatro caixas
Antes de escrever uma linha, fixe quatro elementos do PIC18F4550. O primeiro é o W (Working register), uma caixa de 8 bits que é o rascunho universal: quase toda conta passa por ele. O segundo é a memória de programa (Flash, 32 KB), onde moram as instruções. O terceiro é a memória de dados (RAM, 2 KB), onde ficam as variáveis. O quarto são os SFRs, que são posições de RAM com nomes especiais (TRISD, LATD, PORTB…) que controlam os periféricos.
Guarde a separação: instrução mora na Flash, dado mora na RAM (é a arquitetura Harvard modificada). O PC (Program Counter) guarda o endereço da próxima instrução e avança sozinho; quando você escreve bra Loop, está mexendo no PC à mão — e é só isso que existe por trás de laços, condicionais e chamadas.
Anatomia de uma instrução
Olhe esta linha com calma:
São três partes. bsf é o mnemônico (Bit Set in File register: ligue um bit). LATD é o registrador sobre o qual ela age. 3 é o bit (o bit 3 é o pino RD3). Repare que não há um operando de banco: o pic-as descobre sozinho que LATD fica no Access Bank, então você não precisa escrever o seletor. Ao executar, o processador pega o byte de LATD, força o bit 3 a 1 sem tocar nos outros sete, e o LED de RD3 acende — tudo em um ciclo, que a 8 MHz dura 500 ns.
A estrutura obrigatória de um programa
Todo arquivo de assembly para o kit segue este molde: inclui as definições, configura os fusíveis, declara variáveis, define os vetores e termina com end. No kit, o bootloader AN1310 mora no topo da Flash (0x7CFC–0x7FFF) e preserva os vetores nativos — por isso seu código começa em 0x0000, como num PIC sem bootloader.
processor 18F4550
#include <xc.inc> ; traz os nomes TRISD, LATD, etc.
config FOSC = HSPLL_HS
config WDT = OFF
config LVP = OFF
config MCLRE = ON
config PBADEN = OFF
config CPUDIV = OSC1_PLL2
psect udata_acs ; variáveis no Access Bank
contador: ds 1 ; reserva 1 byte; 'contador' vira o endereço
psect resetVec,abs
org 0x0000 ; vetor de reset nativo
goto Main
psect ivHigh,abs
org 0x0008 ; interrupção de alta prioridade
retfie 1 ; 1 = restaura do shadow (FAST)
psect ivLow,abs
org 0x0018 ; interrupção de baixa prioridade
retfie 0
psect code ; o linker posiciona este código
Main:
clrf TRISD ; PORTD inteiro como saída
setf LATD ; todos os 8 LEDs acesos
Loop:
bra Loop ; trava aqui para sempre
end ; fim do arquivo (esquecer dá erro!) processor 18F4550
#include <xc.inc>
config FOSC = HSPLL_HS
config WDT = OFF
config LVP = OFF
config MCLRE = ON
config PBADEN = OFF
config CPUDIV = OSC1_PLL2
psect udata_acs
contador: ds 1
psect resetVec,abs
org 0x0000
goto Main
psect ivHigh,abs
org 0x0008
retfie 1
psect ivLow,abs
org 0x0018
retfie 0
psect code
Main:
clrf TRISD
setf LATD
Loop:
bra Loop
endNo pic-as, o linker posiciona sozinho os psects relocáveis (psect code); você só fixa endereços à mão, declarando o psect como absoluto (abs) e usando org, exatamente o que fazemos para os vetores de hardware. Se um dia usar o XC8 em projeto grande, reserve a área do bootloader em Project Properties com ROM ranges: default,-7CFC-7FFF, para o linker não escrever por cima do AN1310. Os vetores do seu programa continuam nos endereços nativos (0x0000, 0x0008, 0x0018).
Por que você quase nunca escreve o seletor de banco
A RAM do PIC18 tem 4 KB de espaço, mas o campo de endereço dentro da instrução só tem 8 bits (256 endereços). A solução foi dividir a RAM em 16 bancos de 256 bytes e usar o BSR (Bank Select Register) para escolher o banco ativo. Trocar de banco a toda hora com movlb seria lento e cheio de bugs. Por isso existe o Access Bank: uma janela de 256 bytes que junta os primeiros 96 bytes de RAM (0x000–0x05F) com os SFRs no topo (0xF60–0xFFF), acessível na hora sem mexer no BSR. O pic-as resolve isso por você: como LATD, TRISD, PORTB e as variáveis que você declara em psect udata_acs vivem no Access Bank, o montador escolhe sozinho essa janela e você simplesmente não escreve nenhum seletor de banco — daí clrf TRISD, e não clrf TRISD, ACCESS. Só quando precisar de uma variável fora do Access Bank é que você acrescenta , b (banked) e seleciona o banco com movlb antes.
Declarando variáveis: psect udata_acs e ds
Em C você escreve unsigned char contador;. Em assembly é a mesma ideia, mais explícita:
ds n (Define Storage) reserva n bytes e associa o rótulo ao primeiro. O linker escolhe os endereços — você nunca os fixa à mão. Precisou de mais de 96 bytes? Declare um psect próprio de RAM (com space=1), use , b (banked) nas instruções e chame movlb k antes. Para começar, fique no Access Bank.
Movendo dados: movlw, movwf, movf
Três setas, cada uma numa direção. movlw k carrega o literal k em W (é a única forma de pôr uma constante no processador). movwf f copia W para o registrador f. movf f, d lê f e manda para W (se d=w) ou de volta para f (se d=f, útil porque atualiza o flag Z).
Cuidado com a base dos números: no pic-as, um literal sem prefixo é decimal. Então movlw 200 carrega duzentos. Para hexadecimal, use o prefixo 0x (movlw 0x55); para binário, use o sufixo B (movlw 01010101B). Marque sempre a base que não for óbvia — escrever movlw 55 achando que carregava 0x55 põe cinquenta e cinco no W em silêncio, e esse engano já custou muitas horas de gente.
Manipulando bits e tomando decisões
Quatro instruções de bit, e elas são o coração do controle de I/O. bsf f, b liga o bit b; bcf f, b desliga; btg f, b inverte. A quarta é diferente: ela não muda nada, ela testa um bit e decide se pula a próxima instrução. btfss (skip if set) pula se o bit for 1; btfsc (skip if clear) pula se for 0. Esse “pula ou não pula” é toda a lógica condicional do PIC.
Quando a condição de pular é verdadeira, btfss/btfsc gastam 2 ciclos (descartam a instrução seguinte); quando é falsa, gastam 1.
Aritmética, lógica e a sutileza do subwf
As instruções de byte operam entre W e um registrador, com o resultado indo para W ou para F conforme você escolher:
Atenção a subwf f, d: a conta é f - W, não W - f (a sigla é “SUBtract W from F”). É fonte recorrente de bug. As lógicas andwf, iorwf, xorwf, comf seguem o mesmo molde e servem para máscaras; e há as versões com literal andlw, iorlw, xorlw, addlw, sublw, uma palavra mais compactas. Para isolar o nibble baixo, por exemplo: movf valor, w seguido de andlw 0x0F.
Laços: a coreografia do decfsz
O padrão idiomático de laço contado é decfsz f, d (Decrement, Skip if Zero): decrementa e pula a próxima se chegou a zero. Junto com um bra que volta, vira um laço:
Subrotinas e a pilha de hardware
Você marca um trecho com rótulo, termina com return e chama com call. O call empilha o endereço de retorno numa pilha de hardware de 31 níveis; o return o desempilha. Não existe PUSH/POP genérico — a pilha só guarda endereços de retorno; variáveis locais vão para a RAM. Cuidado com aninhamento muito profundo: estourados os 31 níveis, o PIC sobrescreve o mais antigo silenciosamente. Recursão profunda, na prática, é proibida.
A instrução movff fs, fd copia de qualquer endereço para qualquer endereço sem passar por W — ótima para copiar entre SFRs, como movff TABLAT, LATD.
STATUS: o boletim do processador
Toda operação aritmética ou lógica atualiza o registrador STATUS com flags: Z (resultado zero), C (carry/transporte), N (negativo, bit 7), OV (estouro com sinal) e DC (transporte do nibble baixo). Você raramente lê STATUS direto; o normal é testar um flag logo depois da operação que o gerou:
A ordem é vital: o flag reflete só a última operação. Se você interpuser outra instrução que mexe em STATUS entre a conta e o teste, testa o flag errado.
Os modos de endereçamento
São quatro maneiras de a instrução achar o operando. O imediato embute a constante na instrução (movlw 0x5A). O direto usa o campo f (clrf TRISD). O relativo ao PC é o dos desvios (bra n soma um deslocamento ao PC). E o indireto, via FSR/INDF, é o mais poderoso: o PIC tem três ponteiros (FSR0/1/2); ao acessar INDF0 você acessa o endereço que está em FSR0. Com os modificadores, você percorre arrays:
POSTINC0 acessa e incrementa FSR0; POSTDEC0 acessa e decrementa; PREINC0 incrementa e acessa; PLUSW0 faz acesso indexado. É exatamente assim que o C implementa array[indice] na RAM.
bra contra goto
bra n é um desvio relativo: deslocamento de 11 bits com sinal, alcance de ±1 KB, 1 palavra de instrução, 2 ciclos. goto k é absoluto: endereço completo, alcança todo o espaço de programa, mas ocupa 2 palavras. Regra prática: bra para desvios locais (que é a maioria) e goto só para saltos longos, como o goto Main no vetor de reset.
Lendo a Flash com tblrd
Dados const (tabelas) ficam na Flash e se leem com a família tblrd, carregando o endereço em TBLPTR e lendo o byte em TABLAT:
Os modificadores são * (não mexe no ponteiro), *+ (pós-incremento, o mais usado para percorrer arrays), *- (pós-decremento) e +* (pré-incremento).
Onde alocar variáveis: o psect udata_acs e os bancos
Três escolhas decidem onde suas variáveis nascem, todas expressas com a diretiva psect. O psect predefinido udata_acs aloca no Access Bank (acesso rápido, sem movlb e sem seletor de banco na instrução) e é a sua escolha padrão. Para mais que os 96 bytes do Access Bank, declare um psect próprio de RAM (com space=1) e deixe o linker posicioná-lo num banco normal — aí use , b (banked) nas instruções e chame movlb k antes, ou endereçamento indireto via FSR, que ignora bancos. Variáveis já inicializadas com um valor não cabem num psect de RAM pura: guarde os valores num psect de dados na Flash e copie-os para a RAM na partida, o que custa ciclos e espaço; em projetos C o runtime do XC8 faz isso por você, mas em assembly puro a cópia é sua — use com parcimônia.
Tabelas de despacho com retlw: um idioma clássico do PIC
Esta é uma das técnicas mais elegantes e características do assembly para PIC, e vale conhecer mesmo que você acabe preferindo arrays na RAM. A ideia é guardar constantes como operandos de instruções retlw (Return with Literal in W) e saltar para a retlw certa somando o índice ao PC. Para acessar o elemento de índice i, você soma 2i ao byte baixo do Program Counter (PCL) com addwf PCL, f, caindo exatamente sobre a retlw desejada, que retorna na hora com o valor em W.
flowchart LR
CALL["call Get_padrao<br/>(W = 2 x indice)"] --> ADD["addwf PCL, f<br/>soma 2i ao PC"]
ADD --> R0["retlw 0x01 (i=0)"]
ADD --> R1["retlw 0x03 (i=1)"]
ADD --> RN["... (demais indices)"]
R0 --> RET["retorna com o valor em W"]
R1 --> RET
RN --> RET
Duas regras inegociáveis aqui. A tabela precisa começar num endereço múltiplo de 256, senão a soma em PCL pode gerar um carry que não se propaga para PCLATH e o salto cai no lugar errado (no pior caso, na área do bootloader); por isso ela vive num psect absoluto com org alinhado. E a tabela só pode ser acessada com call, nunca com bra ou goto — o retlw desempilha o endereço que o call empilhou; sem esse call, ele retorna para um endereço inválido.
Depurando em assembly
Mesmas três técnicas do C, com um detalhe: em assembly não há “variável com nome” para o depurador, então você precisa saber em qual endereço de RAM cada coisa foi alocada (a janela File Registers mostra a RAM toda).
A depuração por LEDs é imediata e barata — escreva direto em LATD em qualquer ponto: movlw 0x55 / movwf LATD / call Delay200ms. Espalhe esses checkpoints e veja até qual padrão o programa chegou. A depuração por USART segue a mesma lógica do C, com SPBRG = 51 para 9600 bps a 8 MHz. E o simulador do MPLAB X deixa executar instrução por instrução (F7 = Step Into) e observar W, STATUS, FSR e a RAM mudarem a cada passo; use Run to Cursor para pular o delay sem rodar milhares de iterações.
Armadilhas que pegam todo mundo
São poucas e previsíveis, então vou contá-las como história para você memorizar. Esquecer o end no fim do arquivo dá um erro críptico de “unexpected end of file” — a última linha é sempre end. Escrever em PORTx em vez de LATx causa o read-modify-write hazard: LEDs vizinhos acendem ou apagam sozinhos; escreva sempre em LATx e leia entradas em PORTx. Esquecer o prefixo 0x num valor hexadecimal: no pic-as movlw 55 carrega cinquenta e cinco (decimal), não 0x55; marque a base. Confundir d=w com d=f (incf contador, w não muda contador, manda o resultado para W) é outro erro mudo. Interpor instruções entre a conta e o teste de flag testa o flag errado. E usar return para sair de um trecho onde se entrou com goto em vez de call faz o programa saltar para um endereço aleatório — subrotina só com call+return.
Referência rápida das instruções
| Instrução | Operação | Ciclos |
|---|---|---|
| movlw k / movwf f / movf f,d | W←k / f←W / W←f (ou f←f) | 1 |
| clrf f / setf f | f←0 / f←0xFF | 1 |
| incf f,d / decf f,d | f±1 | 1 |
| addwf f,d / subwf f,d | W+f / f−W | 1 |
| andwf/iorwf/xorwf/comf f,d | lógicas bit a bit | 1 |
| addlw/sublw/andlw/iorlw/xorlw k | mesmas, com literal em W | 1 |
| bsf/bcf/btg f,b | liga/desliga/inverte bit b | 1 |
| btfss/btfsc f,b | pula próx. se bit =1 / =0 | 1 (2 se pular) |
| decfsz/incfsz f,d | ±1 e pula se zerou | 1 (2/3 se pular) |
| cpfseq/cpfsgt/cpfslt f | compara com W; pula se =,>,< | 1 (2/3 se pular) |
| bra n / goto k | desvio relativo ±1 KB / absoluto | 2 |
| call k / rcall n / return | chamada / chamada relativa / retorno | 2 |
| tblrd* (, +, -, +) | lê Flash para TABLAT | 2 |
Nas instruções de byte acima, o destino d é w (W) ou f (registrador). O seletor de banco normalmente é omitido, porque o pic-as escolhe sozinho o Access Bank; para um registrador fora dele, acrescente , b (banked) e selecione o banco com movlb antes.
Endereços dos SFRs mais usados
| SFR | Endereço | Função |
|---|---|---|
| PORTA–PORTE | 0x080–0x084 | leitura dos pinos |
| LATA–LATE | 0x089–0x08D | escrita nos pinos (LATD=LEDs) |
| TRISA–TRISE | 0x092–0x096 | direção (1=entrada, 0=saída) |
| STATUS | 0xFD8 | flags C, Z, N, OV, DC |
| WREG | 0xFE8 | acumulador W |
| BSR | 0xFE0 | seleção de banco |
| FSR0L/FSR0H | 0xFEA/0xFEB | ponteiro indireto 0 |
| TBLPTRL/H/U | 0xFF6/7/8 | ponteiro de tabela (tblrd) |
| TABLAT | 0xFF5 | byte lido da Flash |
| ADCON1 | 0xFC1 | 0x0F = pinos digitais |
| CMCON | 0xFB4 | 0x07 = comparadores off |
| TXREG/SPBRG/TXSTA/RCSTA | 0xFAD/0xFAF/0xFAC/0xFAB | USART |
Checklist antes de gravar
Passe por aqui antes de cada gravação. processor 18F4550 e #include <xc.inc> no topo e diretivas config corretas (sobretudo LVP = OFF). Vetores em psects absolutos (abs) nos endereços nativos (0x0000, 0x0008, 0x0018) e código principal num psect code. Toda variável declarada com ds antes do uso. Todo laço com saída garantida (um decfsz que zera) e toda subrotina terminando em return. Tabelas com retlw/addwf PCL em psect absoluto alinhado a 256 bytes. O arquivo termina com end. Compilou sem erros nem avisos. Só então grave pelo bootloader.
Para aprofundar
O arquivo xc.inc (na pasta do compilador MPLAB XC8) e os cabeçalhos de dispositivo que ele inclui listam os nomes dos SFRs e os nomes de campo do config. O comportamento binário, os flags e os ciclos de cada instrução estão no PIC18F/PIC18LF Instruction Set Reference; os periféricos, no PIC18F4550 Data Sheet (DS39632E); e todas as diretivas do montador (psect, org, ds, equ, macros), no MPLAB XC8 PIC Assembler User’s Guide.