Módulo 3: Exercícios Práticos de Programação em Assembly para o PIC18F4550
Estes cinco exercícios repetem a mesma sequência de desafios dos exercícios em C, mas agora você escreverá diretamente na linguagem de montagem do PIC18F4550. A diferença é fundamental: em C, o compilador toma inúmeras decisões por você — quais instruções usar, como endereçar variáveis, como montar o laço de atraso. Em assembly, cada uma dessas decisões é sua. Isso não significa que seja mais difícil; significa que o que você escreve é exatamente o que o processador executa, sem intermediários. Ao concluir estes exercícios, você terá a experiência de escrever código que conversa diretamente com o hardware, e compreenderá com muito mais precisão por que o compilador faz as escolhas que faz quando você usa C.
Todo o código deve ser criado como arquivo .asm em um projeto MPLAB X configurado para o PIC18F4550. O assembler utilizado é o MPASM (integrado ao MPLAB X). Cada arquivo assembly começa com a inclusão do arquivo de definições do processador e com as diretivas de configuração dos fusíveis, já fornecidas em cada esqueleto. Complete apenas as partes marcadas com ; SEU CÓDIGO AQUI. A saída do assembler é um arquivo .hex idêntico ao gerado pelo compilador C — não há diferença entre eles do ponto de vista do processador.
Estrutura obrigatória de todo programa assembly para PIC18F4550
Todo programa assembly para o PIC18F4550 deve declarar explicitamente os vetores de reset e de interrupção antes do código principal. O vetor de reset fica em 0x0000 e contém um desvio incondicional para a função main. Os vetores de interrupção de alta e baixa prioridade ficam em 0x0008 e 0x0018 respectivamente; como nenhum exercício usa interrupções, esses vetores recebem apenas um RETFIE que retorna imediatamente caso sejam acionados por acidente. O código do usuário começa a partir de 0x0020, após os vetores.
Exercício 1 — Configuração de E/S: CLRF, SETF e Endereçamento Direto
Em C, basta escrever TRISD = 0x00; LATD = 0xFF; e o compilador gera as instruções de máquina necessárias. Em assembly, você precisa saber exatamente quais instruções existem e como elas movem dados. Neste exercício, você descobrirá que existem instruções mais diretas do que a sequência MOVLW + MOVWF para os casos especiais de escrever 0x00 e 0xFF em um registrador.
Seu objetivo é o mesmo do Exercício 1 em C: configurar todos os pinos de PORTD como saída e acender todos os oito LEDs simultaneamente. O estado deve ser mantido indefinidamente no laço loop.
; ex1_leds_acesos.asm
; Acende todos os LEDs de PORTD - PIC18F4550 - ACEPIC PRO V8.2
#include <p18f4550.inc>
CONFIG FOSC = HS
CONFIG WDT = OFF
CONFIG LVP = OFF
; --------------- Vetores do processador ---------------
ORG 0x0000
GOTO main
ORG 0x0008
RETFIE FAST
ORG 0x0018
RETFIE
; --------------- Código principal ---------------
ORG 0x0020
main:
; SEU CÓDIGO AQUI:
; 1. Configure todos os bits de TRISD como 0 (saída).
; Dica: existe uma instrução que limpa todos os bits
; de um registrador de uma só vez, sem usar W.
; 2. Sete todos os bits de LATD como 1 (todos os LEDs acesos).
; Dica: existe uma instrução que seta todos os bits
; de um registrador de uma só vez, sem usar W.
loop:
BRA loop ; Laço infinito
ENDQuestão de reflexão: a instrução que você usou para escrever 0x00 em TRISD e a que escreveu 0xFF em LATD pertencem a qual categoria da ISA do PIC18? Quantos ciclos de clock cada uma consome, e como isso se compara com a sequência MOVLW k seguida de MOVWF f? Por que o compilador XC8 prefere essas instruções especializadas em vez da sequência genérica?
Exercício 2 — Piscar LED: Subrotina de Atraso e Instruções de Bit
Em C, __delay_ms(500) é uma função de biblioteca que gera automaticamente o laço de espera correto. Em assembly, você precisa implementar esse atraso do zero, calculando quantas iterações de laço são necessárias para consumir o tempo desejado. Este exercício ensina ao mesmo tempo duas coisas: como as instruções de bit BSF, BCF e BTG funcionam na prática, e como um laço de espera por software é estruturado usando DECFSZ e BRA.
O PIC18F4550 com oscilador interno de 8 MHz possui um clock de instrução de 2 MHz (cada instrução consome 4 ciclos do oscilador, portanto o período de instrução é de 500 ns). Para gerar 500 ms de atraso, o laço deve consumir exatamente 500 \times 10^{-3} / 500 \times 10^{-9} = 1.000.000 ciclos de instrução.
O esqueleto já declara três variáveis de contagem (cnt1, cnt2, cnt3) no Access Bank da RAM e define os rótulos da subrotina de atraso. Sua tarefa é preencher tanto o laço de atraso quanto o laço principal de piscar.
; ex2_pisca_led.asm
; Pisca o LED em RD0 com período de 1 segundo - PIC18F4550
#include <p18f4550.inc>
CONFIG FOSC = HS
CONFIG WDT = OFF
CONFIG LVP = OFF
; --------------- Variáveis na RAM (Access Bank: 0x000-0x05F) ---------------
UDATA_ACS
cnt1 RES 1 ; contador externo do laço de atraso
cnt2 RES 1 ; contador intermediário
cnt3 RES 1 ; contador interno (mais interno)
; --------------- Vetores ---------------
ORG 0x0000
GOTO main
ORG 0x0008
RETFIE FAST
ORG 0x0018
RETFIE
; --------------- Código principal ---------------
ORG 0x0020
main:
BCF TRISD, 0, ACCESS ; RD0 como saída
BCF LATD, 0, ACCESS ; LED inicialmente apagado
loop_principal:
; SEU CÓDIGO AQUI:
; 1. Acenda o LED em RD0 (instrução de bit, 1 ciclo).
; 2. Chame a subrotina delay_500ms.
; 3. Apague o LED em RD0.
; 4. Chame delay_500ms novamente.
BRA loop_principal
; --------------- Subrotina de atraso ≈ 500 ms a 8 MHz ---------------
; Estrutura de três laços aninhados com DECFSZ.
; Os valores dos contadores determinam a duração total.
;
; Cálculo orientativo:
; laço interno (cnt3=200): cada iteração = 3 ciclos → 200 × 3 = 600 ciclos
; laço médio (cnt2=167): 167 × (2 + 600 + 3) ≈ 100.867 ciclos
; laço externo (cnt1=10): 10 × (2 + 100.867 + 3) ≈ 1.009.720 ciclos ≈ 500 ms
;
delay_500ms:
; SEU CÓDIGO AQUI:
; Implemente os três laços aninhados usando:
; MOVLW k / MOVWF cnt_x para carregar cada contador
; DECFSZ cnt_x, F para decrementar e testar zero
; BRA rótulo para voltar ao início do laço
; O laço mais interno usa cnt3, o médio usa cnt2, o externo usa cnt1.
RETURN
ENDQuestão de reflexão: a instrução DECFSZ f, d, a consome 1 ciclo quando não pula e 2 ciclos quando pula (na última iteração). Qual é o erro absoluto em milissegundos introduzido pela variação de ciclos na última iteração de cada laço, considerando o clock de instrução de 2 MHz? Para um LED piscando a olho nu, esse erro é perceptível? Em que tipo de aplicação (pense nos módulos futuros da disciplina) esse erro seria inaceitável e qual seria a solução adequada?
Exercício 3 — Operações Lógicas: COMF, XORWF e Padrões nos LEDs
Este exercício reproduz em assembly o mesmo desafio de máscaras de bits do Exercício 3 em C: produzir quatro estados de iluminação nos LEDs de PORTD aplicando uma única operação lógica sobre o valor atual de LATD para passar de um estado ao seguinte. A sequência e a restrição são idênticas às do exercício em C — mas agora você escreverá diretamente as instruções COMF e XORWF em vez de deixar o compilador descobri-las.
A sequência de estados é: Estado A (0x0F) → Estado B (0xF0) → Estado C (0xAA) → Estado D (0x55), com 400 ms entre cada transição, repetindo indefinidamente.
; ex3_padroes.asm
; Sequência de padrões de LEDs com operações lógicas - PIC18F4550
#include <p18f4550.inc>
CONFIG FOSC = HS
CONFIG WDT = OFF
CONFIG LVP = OFF
UDATA_ACS
cnt1 RES 1
cnt2 RES 1
cnt3 RES 1
ORG 0x0000
GOTO main
ORG 0x0008
RETFIE FAST
ORG 0x0018
RETFIE
ORG 0x0020
main:
CLRF TRISD, ACCESS ; PORTD todo como saída
loop_padroes:
; --- Estado A: 0x0F (quatro LEDs inferiores) ---
MOVLW 0x0F
MOVWF LATD, ACCESS
CALL delay_400ms
; --- Estado B: 0xF0 ---
; SEU CÓDIGO AQUI:
; Chegue a 0xF0 aplicando uma instrução sobre LATD.
; Dica: qual instrução inverte todos os bits de um
; registrador sem precisar de máscara nem de W?
CALL delay_400ms
; --- Estado C: 0xAA ---
; SEU CÓDIGO AQUI:
; Chegue a 0xAA aplicando uma operação XOR sobre LATD.
; Você precisará de duas instruções: uma para carregar
; a máscara em W e outra para aplicar o XOR.
CALL delay_400ms
; --- Estado D: 0x55 ---
; SEU CÓDIGO AQUI:
; Chegue a 0x55 da mesma forma que chegou ao Estado B.
CALL delay_400ms
BRA loop_padroes
; Subrotina de atraso ≈ 400 ms (cnt1=8, cnt2=167, cnt3=200)
delay_400ms:
MOVLW .8
MOVWF cnt1, ACCESS
d1: MOVLW .167
MOVWF cnt2, ACCESS
d2: MOVLW .200
MOVWF cnt3, ACCESS
d3: DECFSZ cnt3, F, ACCESS
BRA d3
DECFSZ cnt2, F, ACCESS
BRA d2
DECFSZ cnt1, F, ACCESS
BRA d1
RETURN
ENDQuestão de reflexão: na transição B → C, você usou MOVLW 0x5A seguido de XORWF LATD, F. Examine o formato binário de XORWF: o bit d seleciona o destino (W ou f) e o bit a seleciona o banco de acesso. Escreva os 16 bits completos da instrução XORWF LATD, F, ACCESS sabendo que o opcode base de XORWF é 0010 01, que d=1 (destino é f), a=0 (Access Bank) e que o endereço de LATD no Access Bank é 0xF8. Compare o formato com o da instrução COMF LATD, F, ACCESS (opcode 0001 11): qual é a diferença estrutural fundamental entre uma instrução de operação sobre bytes com literal implícito (COMF) e uma que usa o registrador W como segundo operando (XORWF)?
Exercício 4 — Leitura de Entrada: BTFSC, BTFSS e Desvio Condicional
Em assembly, não existe if. Em seu lugar existem instruções que testam um bit e pulam condicionalmente a próxima instrução. O padrão é simples e poderoso: BTFSC f, b pula a próxima instrução se o bit b de f for zero; BTFSS f, b pula se o bit for um. Combinadas com desvios relativos (BRA), essas instruções constroem toda a lógica condicional de programas assembly para PIC.
Este exercício implementa as duas versões do Exercício 4 em C: primeiro, o comportamento simples (botão pressionado acende todos os LEDs); depois, o contador binário que incrementa a cada pressão do botão.
; ex4_botao.asm
; Leitura de botão com BTFSC e contador binário - PIC18F4550
#include <p18f4550.inc>
CONFIG FOSC = HS
CONFIG WDT = OFF
CONFIG LVP = OFF
UDATA_ACS
cnt1 RES 1
cnt2 RES 1
cnt3 RES 1
contador RES 1 ; conta as pressões do botão (0-255)
estado_ant RES 1 ; estado anterior do pino RB0
ORG 0x0000
GOTO main
ORG 0x0008
RETFIE FAST
ORG 0x0018
RETFIE
ORG 0x0020
main:
CLRF TRISD, ACCESS ; PORTD como saída
BSF TRISB, 0, ACCESS ; RB0 como entrada (botão)
BCF INTCON2, 7, ACCESS ; Habilita pull-ups internos do PORTB
CLRF LATD, ACCESS ; LEDs apagados
CLRF contador, ACCESS ; contador começa em zero
BSF estado_ant, 0, ACCESS ; estado anterior = 1 (botão solto)
; --- Parte 1: comportamento simples ---
; (comente este bloco e descomente a Parte 2 quando pronto)
loop_simples:
; SEU CÓDIGO AQUI:
; Teste o bit 0 de PORTB com BTFSC ou BTFSS.
; Se o botão estiver pressionado (RB0 = 0): SETF LATD
; Se estiver solto (RB0 = 1): CLRF LATD
;
; Dica: use dois desvios. A estrutura geral é:
; BTFSC PORTB, 0 ; pula se RB0 = 0 (pressionado)
; BRA solto ; não foi pulada: botão solto
; pressionado:
; ...
; BRA fim_teste
; solto:
; ...
; fim_teste:
BRA loop_simples
; --- Parte 2: contador binário (descomente para implementar) ---
; loop_contador:
; SEU CÓDIGO AQUI:
; Detecte a borda de descida de RB0 (transição de 1 para 0).
; Para detectar a borda:
; 1. Leia PORTB bit 0 para uma variável temporária.
; 2. Compare com estado_ant:
; se estado_ant = 1 E estado_atual = 0 → borda de descida.
; 3. Incremente 'contador' com INCF.
; 4. Copie 'contador' para LATD.
; 5. Atualize estado_ant.
; Adicione um pequeno atraso de debounce após detectar a borda.
; BRA loop_contador
; Subrotina de atraso ≈ 20 ms (debounce)
delay_20ms:
MOVLW 1
MOVWF cnt1, ACCESS
d1: MOVLW .167
MOVWF cnt2, ACCESS
d2: MOVLW .40
MOVWF cnt3, ACCESS
d3: DECFSZ cnt3, F, ACCESS
BRA d3
DECFSZ cnt2, F, ACCESS
BRA d2
DECFSZ cnt1, F, ACCESS
BRA d1
RETURN
ENDQuestão de reflexão: em assembly, não existe o operador && (E lógico) do C. Para implementar a condição estado_ant == 1 AND estado_atual == 0 (detecção de borda), você precisou de dois testes de bit separados com desvios aninhados. Descreva em prosa a sequência de instruções que você utilizou e explique por que a ordem dos testes importa. Em seguida, compare com o que o compilador gera para if (prev == 1 && cur == 0) em C: o assembly gerado é estruturalmente idêntico ao que você escreveu?
Exercício 5 — Tabela de Padrões: RETLW e Endereçamento Computado
Este exercício introduz uma das técnicas mais elegantes e características da programação assembly para PIC: a tabela de despacho por RETLW. A ideia é armazenar valores constantes diretamente como operandos de instruções RETLW na memória de programa. Para acessar o elemento de índice i, o programa salta para dentro da tabela usando um desvio computado (ADDWF PCL, F), cai sobre a instrução RETLW correspondente e retorna com o valor desejado em W. Isso elimina a necessidade de TBLPTR e TBLRD para tabelas pequenas.
O mecanismo funciona assim: ADDWF PCL, F lê o valor atual de PCL (que aponta para a instrução seguinte, dois bytes à frente), soma o deslocamento em W e reescreve PCL. Como cada RETLW ocupa exatamente 2 bytes (1 palavra de instrução), o deslocamento correto para o elemento i é 2 \times i. Por isso, antes de chamar a sub-rotina de acesso, W deve ser carregado com 2 \times \text{índice}.
A tabela deve ser posicionada em um endereço alinhado em 256 bytes (usando ORG 0x0100) para garantir que a soma de PCL não cause carry que transbordaria para PCLATH, corrompendo o salto.
; ex5_tabela_retlw.asm
; Animação de LEDs com tabela RETLW - PIC18F4550
#include <p18f4550.inc>
CONFIG FOSC = HS
CONFIG WDT = OFF
CONFIG LVP = OFF
UDATA_ACS
cnt1 RES 1
cnt2 RES 1
cnt3 RES 1
indice RES 1 ; índice atual na tabela (0-7)
ORG 0x0000
GOTO main
ORG 0x0008
RETFIE FAST
ORG 0x0018
RETFIE
; --------------- Tabela de padrões na Flash (RETLW) ---------------
; DEVE estar em endereço alinhado em 256 bytes para que ADDWF PCL
; não cause carry para PCLATH.
ORG 0x0100
get_padrao:
; Esta instrução salta para dentro da tabela.
; Quando ADDWF PCL executa, PC já aponta 2 bytes à frente
; (para o primeiro RETLW). Somando 2*i a PCL, cai sobre
; o RETLW do índice i.
ADDWF PCL, F, ACCESS
RETLW 0x01 ; índice 0
RETLW 0x03 ; índice 1
RETLW 0x07 ; índice 2
RETLW 0x0F ; índice 3
RETLW 0x1F ; índice 4
RETLW 0x3F ; índice 5
RETLW 0x7F ; índice 6
RETLW 0xFF ; índice 7
; --------------- Código principal ---------------
ORG 0x0020
main:
CLRF TRISD, ACCESS
CLRF indice, ACCESS ; começa no índice 0
loop_animacao:
; SEU CÓDIGO AQUI — Parte 1: percurso simples 0 → 7
;
; Para cada índice de 0 a 7:
; 1. Carregue o índice em W.
; 2. Some W com W (ADDWF WREG, W) para obter 2*indice.
; 3. Chame get_padrao: W retorna com o padrão.
; 4. Copie W para LATD.
; 5. Chame delay_150ms.
; 6. Incremente 'indice'.
; 7. Quando indice chegar a 8, zere-o e recomece.
; Dica: após INCF indice, compare com 8 usando MOVLW 8 / CPFSEQ indice
; ou simplesmente use ANDLW 0x07 se o tamanho for potência de 2.
BRA loop_animacao
; Subrotina de atraso ≈ 150 ms (cnt1=3, cnt2=167, cnt3=150)
delay_150ms:
MOVLW .3
MOVWF cnt1, ACCESS
d1: MOVLW .167
MOVWF cnt2, ACCESS
d2: MOVLW .150
MOVWF cnt3, ACCESS
d3: DECFSZ cnt3, F, ACCESS
BRA d3
DECFSZ cnt2, F, ACCESS
BRA d2
DECFSZ cnt1, F, ACCESS
BRA d1
RETURN
ENDQuestão de reflexão: o mecanismo ADDWF PCL, F constitui um desvio computado (computed goto): o endereço de destino é calculado em tempo de execução, não hardcoded na instrução. Qual é a restrição de alinhamento que faz com que a tabela precise começar em um múltiplo de 256? O que aconteceria se a tabela começasse em ORG 0x00F0 e o índice fosse 6 (W = 12 antes do ADDWF)? Calcule o endereço resultante e mostre por que o salto seria para um endereço errado. Em seguida, como a técnica TBLPTR + TBLRD (vista no material do Módulo 3) eliminaria essa restrição de alinhamento?
Desafio adicional para o Exercício 5: implemente a versão “vai e vem” da animação: o índice avança de 0 a 7 e depois recua de 7 a 0, repetindo indefinidamente. Você precisará de uma variável direcao no Access Bank (valor 1 para avançar, 0xFF para recuar, pois em aritmética de 8 bits com sinal −1 = 0xFF) e da instrução ADDWF indice, F para avançar ou DECF indice, F para recuar.
Entregue um arquivo TXT por grupo contendo: o código-fonte completo de cada exercício, as respostas às questões de reflexão e, para o Exercício 5, o cálculo mostrando o endereço errado que resultaria do alinhamento incorreto.
Somente 1 entrega por grupo.