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

    END

Questã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

    END

Questã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

    END

Questã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

    END

Questã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

    END

Questã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.