flowchart LR
XTAL["Cristal<br/>8 MHz"] --> OSC["Oscilador<br/>Interno HS"]
OSC --> FOSC_MUX{"FOSC<br/>MUX"}
FOSC_MUX -->|"FOSC=HS"| SYSCLK["FOSC = 8 MHz"]
FOSC_MUX -->|"FOSC=HSPLL"| PLL["PLL × 12<br/>= 96 MHz"]
PLL --> USBD["Divisor ÷ 2<br/>→ 48 MHz<br/>(USB)"]
PLL --> CPUDIV_MUX{"CPUDIV<br/>MUX"}
SYSCLK --> CPUDIV_MUX
CPUDIV_MUX -->|"÷ 4"| FCLK["Clock de Instrução<br/>(Tosc × 4)"]
FCLK --> CPU["Núcleo<br/>da CPU"]
Manual de Programação em C para o PIC18F4550
Este manual foi escrito para acompanhar você durante toda a disciplina. Ele reúne, em um único documento de referência, tudo que você precisa saber para criar, compilar, gravar e depurar programas em C para o PIC18F4550 no kit ACEPIC PRO V8.2. Leia-o integralmente pelo menos uma vez no início do semestre e volte a consultá-lo sempre que tiver dúvidas sobre configuração ou depuração. O conteúdo aqui é complementar ao material de cada módulo — ele não repete a teoria do conjunto de instruções nem dos periféricos, mas fornece o contexto prático sem o qual essa teoria fica suspensa no ar.
Por Que C Para um Microcontrolador de 8 Bits?
Antes de entrar em qualquer detalhe técnico, vale a pena entender a decisão de usar C — e não assembly puro — como linguagem principal desta disciplina. O PIC18F4550 é um processador de 8 bits com 75 instruções, e na teoria é perfeitamente possível escrever qualquer programa diretamente em assembly. Na prática, porém, o assembly impõe um custo cognitivo elevado: você precisa gerenciar registradores, bancos de memória, a pilha de hardware e os modos de endereçamento ao mesmo tempo em que tenta resolver o problema de domínio (piscar um LED, ler um sensor, exibir um valor no LCD). Esse custo dificulta a aprendizagem, porque a atenção fica dividida entre a mecânica da linguagem e o conceito que você está tentando entender.
O compilador C resolve exatamente esse problema. Quando você escreve LATD = 0xFF;, o compilador XC8 da Microchip traduz essa linha para a instrução de máquina mais eficiente disponível — no caso, SETF LATD, ACCESS — sem que você precise se lembrar do opcode. Você raciocina no nível do problema; o compilador raciocina no nível das instruções. Isso não significa que você pode ignorar o que o compilador gera: parte essencial do seu aprendizado nesta disciplina é entender como as construções C se mapeiam para as instruções da ISA do PIC18, e é por isso que o material de cada módulo mostra sistematicamente o assembly produzido pelo compilador. Mas o ponto de partida é C, não assembly.
O compilador XC8 da Microchip foi projetado especificamente para microcontroladores PIC de 8 bits. Ele segue o padrão C99, com algumas extensões proprietárias para acessar registradores de função especial, configurar fusíveis e manipular bits individuais. Ao longo deste manual você aprenderá a usar essas extensões com segurança.
O Ambiente de Desenvolvimento: MPLAB X IDE e o Compilador XC8
O MPLAB X IDE é o ambiente de desenvolvimento integrado oficial da Microchip para todos os seus microcontroladores. Ele é baseado na plataforma NetBeans e roda em Windows, macOS e Linux. Para usar o PIC18F4550 com C, você precisa de dois componentes instalados: o próprio MPLAB X IDE e o compilador XC8.
Criando um Projeto para o PIC18F4550
Após instalar o MPLAB X IDE e o compilador XC8, o fluxo de criação de um projeto segue uma sequência bem definida. Pelo menu File → New Project, selecione a categoria Microchip Embedded e o tipo Standalone Project. Na tela seguinte, o campo Device deve ser preenchido com PIC18F4550 — atenção ao número correto, pois existem variantes próximas como PIC18F4520 e PIC18F4525 que possuem periféricos diferentes. Na seleção de ferramenta (Tool), quando você usa o bootloader do kit ACEPIC PRO V8.2, selecione No Tool (ou Simulator): o bootloader não é um programador no sentido que o MPLAB X entende, e a gravação será feita manualmente pelo software utilitário após a compilação. Na seleção do compilador, escolha XC8.
Ao final do assistente de criação, o projeto está vazio. O arquivo principal deve ser criado em Source Files → New → C Source File; por convenção, nomeie-o main.c. O MPLAB X gera o arquivo com um cabeçalho mínimo e é nele que você escreverá toda a inicialização e o laço principal do programa.
Estrutura Mínima de um Programa C para o PIC18F4550
Todo programa C para o PIC18F4550 tem uma estrutura mínima obrigatória que você verá repetida em todos os exemplos desta disciplina:
/*
* Estrutura mínima para PIC18F4550 no kit ACEPIC PRO V8.2
* Compilador: XC8 v2.x ou superior
* Clock: cristal externo de 8 MHz (modo HS)
*/
#include <xc.h> /* Inclui o arquivo de cabeçalho do processador selecionado */
#include <stdint.h> /* Define uint8_t, uint16_t, int8_t, etc. */
/* -------- Configurações do processador (Configuration Bits) -------- */
#pragma config FOSC = HS /* Oscilador: cristal externo de alta velocidade */
#pragma config WDT = OFF /* Watchdog Timer desativado */
#pragma config LVP = OFF /* Programação em baixa tensão desativada */
#pragma config MCLRE = ON /* Pino MCLR habilitado como reset externo */
#pragma config PBADEN = OFF /* PORTB pinos 0-4 como digital (não analógico) */
#pragma config CPUDIV = OSC1_PLL2 /* Divisor do clock da CPU */
/* Informa ao compilador a frequência do oscilador para __delay_ms/__delay_us */
#define _XTAL_FREQ 8000000UL /* 8 MHz */
void main(void)
{
/* Inicialização dos periféricos aqui */
while (1)
{
/* Laço principal aqui */
}
}Cada diretiva #pragma config configura um dos fusíveis de hardware do processador. Esses valores são gravados junto com o código na memória de programa e determinam o comportamento do chip antes mesmo que a primeira instrução do seu programa execute. A seção seguinte explica cada um deles em detalhes.
As Configurações do Processador (Configuration Bits)
Os configuration bits (fusíveis de configuração) são campos especiais armazenados nas palavras de configuração na região mais alta da memória de programa do PIC18F4550. Diferente de registradores de função especial (SFRs), que podem ser alterados durante a execução, os configuration bits são gravados uma única vez no momento da programação e só podem ser alterados regravando o chip. Para o compilador XC8, cada configuration bit é definido por uma diretiva #pragma config. Entender cada campo não é opcional: um único bit errado pode fazer seu programa não executar, o oscilador não funcionar ou o bootloader deixar de responder.
FOSC — Seleção do Oscilador
O campo FOSC determina a fonte de clock do processador. O kit ACEPIC PRO V8.2 equipa um cristal de quartzo de 8 MHz conectado aos pinos OSC1 e OSC2 do PIC18F4550. Para usar esse cristal, o valor correto é FOSC = HS, que significa High Speed Crystal/Resonator — o modo de oscilador externo otimizado para cristais entre 4 MHz e 20 MHz. Outros valores possíveis incluem XT (para cristais de 200 kHz a 4 MHz) e EC (para clock externo sem amplificador interno), mas nenhum deles é adequado para o kit.
Existe também a opção HSPLL, que ativa o Phase-Locked Loop (PLL) interno do PIC18F4550. Com HSPLL, o chip multiplica a frequência do cristal de 8 MHz por um fator interno para gerar 96 MHz, que depois é dividido para fornecer até 48 MHz ao núcleo da CPU. Essa opção é necessária quando você usa o periférico USB do PIC18F4550, porque o módulo USB exige exatamente 48 MHz de clock. Para os exercícios das primeiras semanas da disciplina, que não utilizam USB, FOSC = HS é a escolha certa: ela é mais simples de entender, produz um clock de instrução de 2 MHz (8 MHz ÷ 4), e não requer configuração adicional do PLL.
CPUDIV — Divisor de Clock da CPU
Este campo configura o divisor entre o clock do oscilador selecionado por FOSC e o clock real fornecido ao núcleo da CPU. Quando FOSC = HS, o clock de instrução é sempre F_{OSC} / 4 independentemente de CPUDIV, resultando em 8\,\text{MHz} / 4 = 2\,\text{MHz} para o clock de instrução. Quando FOSC = HSPLL, o CPUDIV divide o clock do PLL para alimentar a CPU: OSC1_PLL2 divide por 2, resultando em 48 MHz / 2 = 24 MHz para o núcleo. O valor de _XTAL_FREQ que você define no seu programa deve refletir o clock que efetivamente chega à CPU multiplicado por 4 (ou seja, o valor de FOSC antes da divisão por 4 que acontece internamente), porque a macro __delay_ms usa esse valor para calcular o número de iterações do laço de espera.
WDT — Watchdog Timer
O Watchdog Timer é um temporizador de hardware autônomo que, se não for zerado periodicamente pelo programa, provoca um reset do processador. Ele serve como mecanismo de segurança em sistemas embarcados de produção: se o programa travar em um estado inválido, o WDT reinicia o chip automaticamente. Para desenvolvimento e exercícios de laboratório, porém, o WDT pode causar resets inesperados enquanto você ainda está depurando o código. Por isso, durante toda esta disciplina, use WDT = OFF. Se você observar que seu programa reseta sozinho sem motivo aparente — especialmente quando usa __delay_ms com valores longos — o primeiro passo de diagnóstico é verificar se WDT está OFF.
LVP — Low-Voltage Programming
Este é talvez o campo mais importante quando se usa o bootloader do ACEPIC PRO V8.2. LVP = ON habilita o modo de programação em baixa tensão (Low Voltage Programming), no qual o pino RB5 é reservado para o sinal PGM do programador. Quando LVP está ON e RB5 recebe uma tensão específica, o PIC entra em modo de programação — o que significa que o microcontrolador pode inadvertidamente entrar em modo de programação se o pino RB5 oscilar durante a operação normal do kit. Além disso, o bootloader gravado de fábrica no kit exige LVP = OFF, pois ele foi projetado para o modo de programação em alta tensão (onde MCLR recebe +12 V). Se você gravar código com LVP = ON no ACEPIC PRO V8.2, o kit pode parar de responder ao bootloader e você precisará de um programador externo (PICkit ou similar) para regravar o bootloader. Portanto: sempre use LVP = OFF.
MCLRE — Master Clear Reset Enable
O pino MCLR (Master Clear Reset) é o pino de reset externo do PIC18F4550. Com MCLRE = ON, o pino funciona como reset ativo em nível baixo: ao receber GND, o chip é imediatamente resetado. O kit ACEPIC PRO V8.2 possui um botão de reset físico conectado a este pino; manter MCLRE = ON é o correto para o kit, pois garante que o botão de reset funcione conforme esperado.
PBADEN — PORTB Analog/Digital Select
O PIC18F4550 compartilha vários pinos com funções analógicas (entradas do conversor AD) e digitais. PBADEN = ON configura os pinos RB0 a RB4 como entradas analógicas ao ligar o chip, enquanto PBADEN = OFF os configura como digitais. Se você deixar PBADEN = ON e tentar ler um botão em RB0 como entrada digital, obterá valores inconsistentes porque o registrador PORTB estará lendo o conversor AD em vez do nível lógico do pino. Para evitar esse problema desde o início, use sempre PBADEN = OFF e configure explicitamente o ADCON1 no início do seu programa se precisar de entradas analógicas.
Tabela de Referência Rápida
| Campo | Valor recomendado | Razão |
|---|---|---|
| FOSC | HS | Cristal de 8 MHz no kit ACEPIC |
| WDT | OFF | Evita resets durante desenvolvimento |
| LVP | OFF | Obrigatório para o bootloader do kit |
| MCLRE | ON | Botão de reset físico do kit |
| PBADEN | OFF | PORTB como digital por padrão |
| CPUDIV | OSC1_PLL2 | Divisor padrão para FOSC=HS |
O Bootloader do ACEPIC PRO V8.2
O Que É um Bootloader e Por Que Ele Importa
Um bootloader é um programa pequeno gravado permanentemente em uma região protegida da memória de programa do microcontrolador. Sua função é permitir a reprogramação do restante da memória sem a necessidade de um programador de hardware externo (como PICkit ou ICD). No caso do ACEPIC PRO V8.2, o bootloader está gravado nas primeiras posições da memória Flash do PIC18F4550 e usa o periférico USB nativo do chip para receber o novo firmware via cabo USB do computador.
A presença do bootloader tem duas consequências práticas para você como programador. A primeira é positiva: você não precisa de nenhum equipamento além do cabo USB para gravar seu programa no kit. A segunda exige atenção: o bootloader ocupa uma faixa de endereços da memória de programa, e seu código de usuário deve ser posicionado a partir do endereço imediatamente após o final do bootloader, caso contrário você sobrescreverá o bootloader ao gravar seu programa e o kit deixará de funcionar via USB.
Mapa de Memória com o Bootloader
O PIC18F4550 possui 32 KB de memória de programa (Flash), endereçada de 0x000000 a 0x007FFF. O bootloader gravado no kit ACEPIC PRO V8.2 ocupa os endereços de 0x000000 a 0x000FFF (os primeiros 4 KB). Seu programa de usuário deve, portanto, começar a partir do endereço 0x001000.
block-beta
columns 1
block:flash["Flash — 32 KB (0x000000 a 0x007FFF)"]:1
A["0x000000 – 0x000FFF<br/>(4 KB)<br/>Bootloader<br/>(protegido — não sobrescrever!)"]
B["0x001000 – 0x001007<br/>Vetor de Reset do Usuário<br/>(GOTO main)"]
C["0x001008 – 0x00100F<br/>Vetor de Interrupção Alta do Usuário"]
D["0x001018 – 0x00101F<br/>Vetor de Interrupção Baixa do Usuário"]
E["0x001020 – 0x007FFF<br/>(~28 KB)<br/>Código do Usuário<br/>e dados const"]
end
Para informar ao linker XC8 que seu código deve começar em 0x001000, abra as propriedades do projeto no MPLAB X (Project Properties → XC8 Linker → Additional Options) e acrescente a flag de configuração de ROM:
-Wl,-pRESET_VEC=01000h
Ou, alternativamente, use a seção de opções do linker para especificar o endereço de início. A forma mais robusta é criar um arquivo de configuração do linker (.lkr) personalizado derivado do arquivo padrão 18f4550.lkr que acompanha o XC8, alterando apenas as faixas de ROM para excluir o intervalo 0x000000–0x000FFF do espaço de uso do código.
Atenção: se você não configurar o endereço de início do linker e gravar seu programa via bootloader, a ferramenta de gravação tentará escrever nos endereços a partir de 0x000000 — e sobrescreverá o bootloader. O kit parará de responder ao software de gravação USB. Para recuperar um kit nessa situação, é necessário um programador externo (PICkit 3/4/5 ou similar) para regravar o bootloader de fábrica.
O Processo de Gravação Via Bootloader
O fluxo completo para gravar um programa no kit usando o bootloader é o seguinte. Primeiro, você compila seu projeto no MPLAB X: Build → Build Project (ou F11). Se a compilação for bem-sucedida, o MPLAB X gera um arquivo .hex na pasta dist/<configuração>/production/ dentro do diretório do projeto. Esse arquivo .hex contém a imagem binária do seu programa no formato Intel HEX padrão.
Em seguida, você coloca o kit em modo bootloader. Para isso, mantenha pressionado o botão de reset (que força MCLR ao nível baixo) e, com o reset pressionado, conecte o cabo USB ao computador. Ao conectar o USB, o kit sai do reset e entra no modo bootloader (em vez de executar seu programa de usuário), pois o bootloader detecta que a entrada USB ficou disponível durante o reset. Neste ponto, o LED indicador do kit piscará de forma característica para sinalizar que está aguardando um novo firmware.
Abra o software de gravação HID Bootloader (ou equivalente fornecido com o kit) no computador. O software detecta o kit como dispositivo USB HID e exibe uma interface para você selecionar o arquivo .hex. Selecione o arquivo gerado pelo MPLAB X, clique em Program e aguarde. O software transmite o firmware via USB, o bootloader escreve na Flash a partir do endereço 0x001000 (respeitando sua própria área protegida), e ao final exibe a mensagem de sucesso. Desconecte e reconecte o USB (ou pressione reset sem manter pressionado): o kit agora executa seu programa.
sequenceDiagram
participant DEV as Desenvolvedor
participant MPLAB as MPLAB X IDE
participant KIT as Kit ACEPIC
participant SOFT as HID Bootloader SW
DEV->>MPLAB: Compila projeto (F11)
MPLAB-->>DEV: Gera arquivo .hex em dist/
DEV->>KIT: Pressiona RESET + conecta USB
KIT-->>SOFT: Aparece como dispositivo USB HID
DEV->>SOFT: Seleciona .hex e clica Program
SOFT->>KIT: Transmite firmware via USB
KIT-->>KIT: Bootloader grava Flash a partir de 0x001000
SOFT-->>DEV: Confirmação de sucesso
DEV->>KIT: Reconecta USB (ou pressiona reset)
KIT-->>KIT: Executa programa do usuário
Organização da Memória do PIC18F4550 Sob a Perspectiva do C
Memória de Programa (Flash)
A memória de programa do PIC18F4550 tem capacidade de 32 KB, endereçada de 2 em 2 bytes (cada instrução ocupa uma palavra de 16 bits). O compilador XC8 coloca nessa memória o código compilado e os dados declarados com o qualificador const. Acesso a dados const na Flash requer as instruções especiais TBLRD da ISA do PIC18, e o compilador gera esse código automaticamente quando você usa const — é por isso que, como você verá nos exercícios, um array const e um array sem const geram código assembly completamente diferente para o acesso a elementos.
Memória de Dados (RAM)
O PIC18F4550 possui 2 KB de RAM para dados de usuário, distribuídos em 16 bancos de 256 bytes cada (embora nem todos os bancos sejam completamente disponíveis). Variáveis locais de funções são alocadas na pilha de software; variáveis globais e estáticas são alocadas em posições fixas determinadas pelo linker. O compilador XC8 gerencia essa alocação automaticamente.
A faixa de endereços 0x000–0x05F é o Access Bank de dados — uma área especial de 96 bytes de RAM (mais os SFRs na faixa 0x060–0x0FF) que pode ser acessada sem a instrução de troca de banco (MOVLB). O compilador XC8 tenta automaticamente alocar variáveis frequentemente usadas no Access Bank para gerar código mais eficiente; você pode forçar essa alocação com o qualificador __near.
Registradores de Função Especial (SFRs)
Os SFRs são endereços de memória de dados mapeados para registradores internos de periféricos — TRIS, LAT, PORT, ADCON, RCSTA, e assim por diante. No XC8, todos os SFRs do PIC18F4550 estão declarados no arquivo de cabeçalho <xc.h> (que automaticamente inclui o arquivo de definições específico do processador, como <pic18f4550.h>). Ao incluir <xc.h> no início do seu arquivo C, você tem acesso a todos os SFRs e a todos os campos de bits individuais por nome, sem precisar saber os endereços numéricos.
| Região | Endereço | Conteúdo |
|---|---|---|
| Flash — Bootloader | 0x000000 – 0x000FFF | Bootloader (não tocar) |
| Flash — Usuário | 0x001000 – 0x007FFF | Código e dados const |
| RAM — Access Bank | 0x000 – 0x05F | Variáveis no banco de acesso |
| RAM — SFR no Access | 0x060 – 0x0FF | SFRs mapeados no Access Bank |
| RAM — Banco 1–14 | 0x100 – 0xEFF | Variáveis normais |
| EEPROM | 0xF00000 – 0xF000FF | EEPROM de dados (256 bytes) |
Programação de I/O em C
Os Três Registradores: TRIS, PORT e LAT
Cada grupo de pinos do PIC18F4550 (PORTA, PORTB, PORTC, PORTD, PORTE) é controlado por três registradores independentes. Entender a diferença entre eles é indispensável para programar I/O corretamente.
O registrador TRIS (tri-state) define a direção de cada pino: escrever 0 em um bit torna o pino uma saída, e escrever 1 torna o pino uma entrada. O nome vem do fato de que um pino configurado como entrada está em estado de alta impedância (tri-state), desconectado do driver interno. A condição padrão ao ligar o chip é todos os bits de TRIS iguais a 1 (todos os pinos como entradas), o que é seguro por evitar curto-circuitos acidentais.
O registrador LAT (latch) é o registrador de saída. Ao escrever um valor em LAT, você define o nível lógico que o driver de saída tentará impor no pino. Para pinos configurados como saída (TRIS = 0), escrever em LAT efetivamente muda o nível lógico do pino. Escrever em PORT (em vez de LAT) também funciona para saída, mas existe um risco sutil: a instrução MOVWF PORTD realiza uma operação de leitura-modificação-escrita que lê o nível do pino físico antes de escrever. Se o pino estiver carregando uma carga capacitiva que não respondeu ainda à escrita anterior, a leitura pode retornar o valor antigo, causando o chamado problema de read-modify-write hazard. Usar LAT elimina esse problema porque o LAT armazena o valor que você quer impor, não o nível físico atual do pino.
O registrador PORT é o registrador de leitura. Ao ler PORT, você obtém o nível lógico atual do pino físico, independentemente de ele estar configurado como entrada ou saída. Para ler um botão, um sensor ou qualquer sinal externo, leia PORT. Para controlar LEDs, relés ou qualquer saída, escreva em LAT.
flowchart LR
LAT["LAT<br/>(escrita de saída)"] -->|"TRIS=0<br/>(saída)"| PAD["Pino físico<br/>do chip"]
PAD -->|"TRIS=1<br/>(entrada)"| PORT_R["PORT<br/>(leitura)"]
EXTERNO["Sinal<br/>externo"] --> PAD
TRIS_REG["TRIS<br/>(direção)"] -->|"controla"| PAD
ANSEL, ADCON1 e CMCON — Desabilitando Periféricos Analógicos
Um erro comum em programas para PIC é tentar ler um pino digital que, na configuração padrão, está conectado ao conversor analógico-digital ou ao comparador de tensão. Quando um pino está em modo analógico, o registrador PORT daquele pino retorna sempre 0 independentemente do nível lógico físico. O motivo é que a lógica de entrada digital está desconectada do pino por um interruptor controlado pelo configurador analógico.
No PIC18F4550, o registrador ADCON1 controla quais pinos de PORTA e PORTB estão conectados ao ADC. O valor padrão ao ligar o chip configura RA0–RA5 e RB0–RB4 como entradas analógicas. Para usar esses pinos como digitais, você deve escrever em ADCON1 explicitamente no início do programa. O valor 0x0F em ADCON1 configura todos os pinos de RA e RB como digitais (tensões de referência internas do ADC). O registrador CMCON controla os comparadores internos; escrever 0x07 nele desativa todos os comparadores, liberando os pinos RA0–RA5 completamente para uso digital.
#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
void main(void)
{
/* Configura todos os pinos de RA e RB como digital
(sem essa linha, RA0-RA5 e RB0-RB4 são entradas analógicas
e leituras de PORT retornam 0 mesmo com nível alto no pino) */
ADCON1 = 0x0F;
/* Desativa comparadores analógicos nos pinos de PORTA */
CMCON = 0x07;
/* Configura PORTD como saída, PORTB como entrada */
TRISD = 0x00; /* Todos os bits de PORTD como saída */
TRISB = 0xFF; /* Todos os bits de PORTB como entrada */
LATD = 0x00; /* Apaga todos os LEDs ao inicializar */
while (1)
{
if (PORTBbits.RB0 == 0) /* Botão em RB0 pressionado (lógica invertida) */
{
LATD = 0xFF; /* Acende todos os LEDs */
}
else
{
LATD = 0x00; /* Apaga todos os LEDs */
}
}
}#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
void main(void) {
ADCON1 = 0x0F;
CMCON = 0x07;
TRISD = 0x00;
TRISB = 0xFF;
LATD = 0x00;
while (1) {
/* BTFSC PORTB, 0 / BRA solto / SETF LATD / ... */
LATD = PORTBbits.RB0 ? 0x00 : 0xFF;
}
}A inicialização de ADCON1 e CMCON deve ser um dos primeiros passos em qualquer programa desta disciplina que use as portas A ou B. Tornar isso um hábito desde o início vai prevenir horas de depuração frustrante.
Acesso Individual a Bits
O XC8 declara, para cada SFR que contém campos de bits, uma estrutura de bits com o mesmo nome do registrador seguido de bits. Por exemplo, TRISDbits.TRISD0 é o bit 0 do registrador TRISD; LATDbits.LATD3 é o bit 3 do registrador LATD. Esse acesso individual é compilado diretamente para as instruções BSF (Set bit) e BCF (Clear bit) da ISA do PIC18, que são operações de leitura-modificação-escrita atômicas em 1 ciclo. Elas são mais eficientes do que a sequência MOVF + ANDLW/IORLW + MOVWF que seria necessária para modificar um bit via acesso ao byte inteiro.
Temporização por Software: __delay_ms e __delay_us
O compilador XC8 disponibiliza duas macros para geração de atraso por software: __delay_ms(t) para atraso em milissegundos e __delay_us(t) para atraso em microssegundos. Ambas requerem que a macro _XTAL_FREQ esteja definida com o valor correto do clock do oscilador em Hz, pois é a partir desse valor que o XC8 calcula quantas iterações de laço são necessárias para consumir o tempo especificado.
O clock de instrução do PIC18 é sempre F_{OSC} / 4. Com FOSC = HS e cristal de 8 MHz, o clock de instrução é 2 MHz, e cada ciclo de instrução dura T_{CY} = 1 / 2\,\text{MHz} = 500\,\text{ns}. Para gerar um atraso de 1 ms, são necessários 1 \times 10^{-3} / 500 \times 10^{-9} = 2000 ciclos de instrução.
A macro __delay_ms expande para um laço de instruções NOP cujo número de iterações é calculado em tempo de compilação. O argumento deve ser uma constante — não uma variável — porque o cálculo é feito pelo compilador, não pelo processador em tempo de execução. Para atrasos variáveis, você precisará implementar sua própria rotina de atraso usando laços e temporizadores de hardware (o que você aprenderá nos módulos de periféricos).
Limitação importante: a macro __delay_ms bloqueia o processador durante todo o atraso — o núcleo da CPU fica executando instruções de espera e não pode fazer nada útil nesse intervalo. Essa abordagem é adequada para exercícios simples, mas em sistemas reais você usará os módulos de Timer do PIC para gerar eventos temporais sem desperdiçar ciclos de CPU.
Depuração em Tempo Real no Kit ACEPIC PRO V8.2
Esta é a seção mais prática deste manual. Depurar um microcontrolador embarcado é fundamentalmente diferente de depurar um programa no computador, porque você não tem acesso ao terminal interativo do sistema operacional. Quando algo não funciona, o chip não imprime uma mensagem de erro — ele simplesmente não faz o que você esperava. Dominar as técnicas de depuração descritas a seguir é tão importante quanto escrever o código.
Por Que Não É Possível Usar um Depurador de Hardware Diretamente
O MPLAB X IDE suporta depuradores de hardware como o ICD 4 e o PICkit 4/5, que permitem executar o programa passo a passo, definir breakpoints e inspecionar variáveis em tempo real. Para usar esses depuradores, o MPLAB X grava um firmware especial de depuração no chip que ocupa uma pequena área da memória, e a comunicação entre o depurador e o chip acontece via ICSP (In-Circuit Serial Programming).
O problema é que, no kit ACEPIC PRO V8.2, os pinos de ICSP (PGC, PGD, MCLR) são utilizados pelo bootloader para a gravação USB. Conectar um hardware debugger via ICSP ao mesmo tempo que o bootloader está ativo pode causar conflitos. Além disso, o firmware de depuração do ICD e do PICkit normalmente precisa ser gravado pelo próprio depurador — o que sobrescreve o bootloader. Em termos práticos: para usar um hardware debugger com o kit, você precisaria de um PICkit externo, gravar diretamente (sobrescrevendo o bootloader), depurar, e depois regravar o bootloader para restaurar a capacidade de gravação USB. Para exercícios de laboratório, isso é inviável.
Por isso, as técnicas de depuração neste manual são todas in-software — elas não dependem de hardware externo e funcionam com o bootloader intacto.
Depuração por LEDs
A técnica mais simples e imediata de depuração em hardware embarcado é usar os LEDs do kit como indicadores de estado. O princípio é direto: você mapeia diferentes condições do seu programa para padrões binários nos LEDs e observa qual padrão aparece quando o comportamento inesperado ocorre.
Para depurar laços, você pode acender o LED de PORTD0 antes de entrar no laço e apagá-lo ao sair — se o LED não apagar, você sabe que o programa ficou preso dentro do laço. Para depurar condições, você pode escrever o valor de uma variável diretamente em LATD antes de uma operação e verificar se o valor está correto. Para depurar sequências de inicialização, você pode acender LEDs progressivamente a cada etapa concluída: se o kit para com o LED 3 aceso e o LED 4 apagado, a falha ocorreu entre a etapa 3 e a etapa 4.
#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
/* Macro auxiliar: acende todos os LEDs de PORTD, aguarda 200 ms e apaga.
Use para marcar um ponto de passagem no código durante depuração. */
#define DEBUG_BLINK_PORTD(valor) do { \
LATD = (valor); \
__delay_ms(200); \
LATD = 0x00; \
__delay_ms(200); \
} while(0)
void inicializa_hardware(void)
{
ADCON1 = 0x0F;
CMCON = 0x07;
TRISD = 0x00;
LATD = 0x00;
/* Ponto de verificação: se chegar aqui, pisca padrão 0x01 */
DEBUG_BLINK_PORTD(0x01);
}
void inicializa_perifericos(void)
{
/* Supondo configuração adicional de algum periférico */
/* ... */
/* Ponto de verificação: chegou aqui, pisca 0x03 */
DEBUG_BLINK_PORTD(0x03);
}
void main(void)
{
inicializa_hardware();
inicializa_perifericos();
/* Ponto de verificação: chegou ao laço principal, pisca 0xFF */
DEBUG_BLINK_PORTD(0xFF);
while (1)
{
/* Código principal */
}
}#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
static inline void dbg(uint8_t v) {
LATD = v; __delay_ms(200); LATD = 0; __delay_ms(200);
}
void main(void) {
ADCON1 = 0x0F; CMCON = 0x07; TRISD = 0x00; LATD = 0x00;
dbg(0x01);
dbg(0xFF);
while (1);
}Depuração Via USART (UART)
A técnica mais poderosa de depuração sem hardware externo é usar o módulo EUSART (Enhanced Universal Synchronous Asynchronous Receiver Transmitter) do PIC18F4550 para transmitir mensagens de texto para o computador via serial. O PIC18F4550 possui o módulo EUSART com os pinos de transmissão (TX) em RC6 e recepção (RX) em RC7. Se o kit ACEPIC PRO V8.2 disponibiliza esses pinos em conectores acessíveis, você pode conectar um adaptador USB-UART (FTDI, CH340, CP2102 ou similar) e usar um emulador de terminal no computador (PuTTY, RealTerm, Tera Term) para receber as mensagens enviadas pelo PIC.
A implementação de printf redirecionado para a USART no XC8 exige a redefinição da função putch, que é chamada internamente por todas as funções de saída formatada (printf, puts, putchar). O código a seguir implementa esse redirecionamento de forma completa.
#include <xc.h>
#include <stdint.h>
#include <stdio.h> /* Para printf */
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
/* Baud rate desejado: 9600 bps
* Fórmula para SPBRG (modo assíncrono, BRGH=1):
* SPBRG = (F_OSC / (16 * BaudRate)) - 1
* SPBRG = (8000000 / (16 * 9600)) - 1 = 51,08 ≈ 51
* Erro percentual: |9600 - real| / 9600 × 100
* real = 8000000 / (16 * (51+1)) = 9615 bps → erro ≈ 0,16%
*/
#define BAUD_RATE 9600
#define SPBRG_VAL ((uint8_t)( (_XTAL_FREQ / (16UL * BAUD_RATE)) - 1 ))
/* Esta função é chamada por printf/puts/putchar para cada caractere */
void putch(char c)
{
/* Aguarda o buffer de transmissão estar vazio */
while (TXSTAbits.TRMT == 0);
TXREG = c; /* Carrega o byte no registrador de transmissão */
}
void uart_init(void)
{
/* RC6 = TX: configurado como saída pelo módulo USART automaticamente.
RC7 = RX: deve ser entrada. */
TRISCbits.TRISC7 = 1; /* RX como entrada */
TRISCbits.TRISC6 = 0; /* TX como saída (o módulo sobrescreve, mas boa prática) */
/* Configura o baud rate */
SPBRG = SPBRG_VAL;
SPBRGH = 0;
/* Configura TXSTA: modo assíncrono, BRGH=1 (alta velocidade), TX habilitado */
TXSTAbits.SYNC = 0; /* Modo assíncrono */
TXSTAbits.BRGH = 1; /* Divisor de alta velocidade (÷16 em vez de ÷64) */
TXSTAbits.TXEN = 1; /* Habilita transmissão */
/* Configura RCSTA: habilita o módulo serial e a recepção contínua */
RCSTAbits.SPEN = 1; /* Habilita o módulo USART (ativa RC6/RC7 como serial) */
RCSTAbits.CREN = 1; /* Habilita recepção contínua */
/* BRG16 = 0: usa SPBRG de 8 bits (para baud rates ≤ 9600 com 8 MHz é suficiente) */
BAUDCONbits.BRG16 = 0;
}
void main(void)
{
uint8_t contador = 0;
ADCON1 = 0x0F;
CMCON = 0x07;
TRISD = 0x00;
LATD = 0x00;
uart_init();
/* Mensagem de inicialização — confirma que o programa começou */
printf("Sistema inicializado. FOSC=8MHz, Baud=9600\r\n");
while (1)
{
contador++;
LATD = contador;
/* Imprime o valor do contador em formato decimal e hexadecimal */
printf("Contador: %3u (0x%02X)\r\n", contador, contador);
__delay_ms(500);
}
}#include <xc.h>
#include <stdint.h>
#include <stdio.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
void putch(char c) { while (!TXSTAbits.TRMT); TXREG = c; }
static void uart_init(void) {
TRISCbits.TRISC7 = 1;
SPBRG = (_XTAL_FREQ / (16UL * 9600)) - 1;
TXSTA = 0x24; /* BRGH=1, TXEN=1, SYNC=0 */
RCSTA = 0x90; /* SPEN=1, CREN=1 */
}
void main(void) {
ADCON1 = 0x0F; CMCON = 0x07; TRISD = 0x00; LATD = 0x00;
uart_init();
printf("OK\r\n");
uint8_t c = 0;
while (1) { LATD = ++c; printf("%u\r\n", c); __delay_ms(500); }
}O cálculo do SPBRG merece atenção especial. Para o modo assíncrono com BRGH=1 (divisor de 16), a fórmula é:
SPBRG = \frac{F_{OSC}}{16 \times BaudRate} - 1
Com F_{OSC} = 8\,\text{MHz} e BaudRate = 9600\,\text{bps}:
SPBRG = \frac{8.000.000}{16 \times 9600} - 1 = 52,08 - 1 = 51,08 \approx 51
O baud rate real obtido com SPBRG = 51 é 8.000.000 / (16 \times 52) = 9615\,\text{bps}, com erro de apenas 0,16\% em relação ao desejado — bem dentro da tolerância de \pm 2\% permitida pelo protocolo UART.
Depuração via Simulador do MPLAB X
O MPLAB X IDE inclui um simulador de software completo que permite executar seu programa sem nenhum hardware. O simulador emula o processador, os registradores de SFR, a memória RAM e, em parte, os periféricos de I/O. Para usar o simulador, vá em Project Properties → Tool e selecione Simulator. Compile e execute com F5 (Run) ou F7 (Debug).
No modo de depuração com o simulador, você pode definir breakpoints clicando na margem esquerda do editor (um ponto vermelho aparece na linha). Ao executar, o programa para no breakpoint e você pode inspecionar o valor de qualquer variável no painel Variables ou de qualquer SFR no painel Registers. Você também pode usar Window → Simulator → Stimulus para simular sinais digitais em pinos de entrada — muito útil para testar o comportamento de código que depende de botões ou sensores.
As principais limitações do simulador são que ele não emula com precisão o timing real do hardware (delays por software podem ser imprecisos), não simula USB e não captura interações físicas como ruído em sinais ou variações de tensão. Use o simulador para validar a lógica do programa antes de gravar no kit; para validar o comportamento temporal e a resposta do hardware, você precisará gravar e observar com as técnicas anteriores.
Estratégia de Depuração Sistemática
Uma depuração eficaz segue uma estratégia, não um processo aleatório de tentativa e erro. Quando um programa não se comporta como esperado, o primeiro passo é isolar o problema: remova partes do código até encontrar o trecho mínimo que ainda exibe o comportamento inesperado. O segundo passo é formular uma hipótese: “suspeito que a variável contador não está sendo incrementada corretamente.” O terceiro passo é testar a hipótese com uma das técnicas descritas acima (LED, UART ou simulador). O quarto passo é agir com base no resultado e formular uma nova hipótese se necessário.
flowchart TD
A["Programa não<br/>funciona como esperado"] --> B["Isola o trecho<br/>mínimo com o problema"]
B --> C["Formula hipótese<br/>sobre a causa"]
C --> D{"Usa LED, UART<br/>ou Simulador"}
D -->|"LED"| E["Observa padrão<br/>nos LEDs"]
D -->|"UART"| F["Lê mensagens<br/>no terminal"]
D -->|"Simulador"| G["Inspeciona<br/>variáveis/registradores"]
E --> H{"Hipótese<br/>confirmada?"}
F --> H
G --> H
H -->|"Não"| C
H -->|"Sim"| I["Corrige o código"]
I --> J{"Problema<br/>resolvido?"}
J -->|"Não"| B
J -->|"Sim"| K["Fim"]
Padrões e Idiomas Comuns em C para PIC18
Estrutura de Inicialização Completa
Em qualquer projeto desta disciplina, a função main deve começar com uma sequência de inicialização bem definida antes de entrar no laço principal. A ordem importa: você deve configurar os periféricos analógicos antes de configurar as direções dos pinos, e configurar as direções dos pinos antes de escrever em LAT.
#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
void inicializa(void)
{
/* Passo 1: desativa periféricos analógicos
Sem isso, leituras de PORTA e PORTB retornam 0 mesmo com nível alto. */
ADCON1 = 0x0F; /* Todos os pinos de RA e RB como digital */
CMCON = 0x07; /* Comparadores desativados */
/* Passo 2: configura direções dos pinos */
TRISD = 0x00; /* PORTD: todos como saída (LEDs) */
TRISC = 0xFF; /* PORTC: todos como entrada (por enquanto)*/
TRISB = 0xFF; /* PORTB: todos como entrada (botões) */
TRISA = 0xFF; /* PORTA: todos como entrada */
/* Passo 3: inicializa saídas em estado seguro */
LATD = 0x00; /* LEDs apagados */
LATC = 0x00;
LATA = 0x00;
/* Passo 4: habilita pull-ups internos do PORTB (para botões) */
INTCON2bits.RBPU = 0; /* 0 = pull-ups habilitados (lógica invertida) */
}
void main(void)
{
inicializa();
while (1)
{
/* Laço principal */
}
}Debounce de Botão por Software
Botões mecânicos geram múltiplas transições rápidas ao ser pressionados ou soltos — fenômeno conhecido como bounce. Se o seu código detectar a borda do botão sem debounce, ele contará vários eventos para uma única pressão física. A técnica de debounce por software mais simples é aguardar um tempo após a detecção da borda e só então confirmar o estado:
#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
#define BOTAO_RB0 PORTBbits.RB0
#define TEMPO_DEBOUNCE_MS 20
uint8_t botao_foi_pressionado(void)
{
if (BOTAO_RB0 == 0) /* Detectou nível baixo (botão possivelmente pressionado) */
{
__delay_ms(TEMPO_DEBOUNCE_MS); /* Aguarda o bounce estabilizar */
if (BOTAO_RB0 == 0) /* Confirma: ainda está pressionado */
{
/* Aguarda o botão ser solto antes de retornar */
while (BOTAO_RB0 == 0);
__delay_ms(TEMPO_DEBOUNCE_MS); /* Debounce da soltura */
return 1; /* Sim, foi pressionado e solto */
}
}
return 0;
}
void main(void)
{
uint8_t contador = 0;
ADCON1 = 0x0F; CMCON = 0x07;
TRISD = 0x00;
TRISBbits.TRISB0 = 1;
INTCON2bits.RBPU = 0;
LATD = 0x00;
while (1)
{
if (botao_foi_pressionado())
{
contador++;
LATD = contador; /* Exibe em binário nos 8 LEDs */
}
}
}#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
static uint8_t btn(void) {
if (PORTBbits.RB0) return 0;
__delay_ms(20);
if (PORTBbits.RB0) return 0;
while (!PORTBbits.RB0);
__delay_ms(20);
return 1;
}
void main(void) {
ADCON1=0x0F; CMCON=0x07; TRISD=0x00;
TRISBbits.TRISB0=1; INTCON2bits.RBPU=0; LATD=0x00;
uint8_t c=0;
while (1) if (btn()) LATD=++c;
}Atraso Variável com Laço Explícito
Quando você precisa de um atraso cuja duração só é conhecida em tempo de execução (não pode usar __delay_ms com constante), a alternativa é um laço com __delay_ms(1) repetido o número necessário de vezes:
#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
/* Gera um atraso de 'ms' milissegundos.
'ms' pode ser qualquer valor de 0 a 65535.
Resolução: 1 ms. Overhead do laço: desprezível. */
void delay_ms_var(uint16_t ms)
{
uint16_t i;
for (i = 0; i < ms; i++)
{
__delay_ms(1); /* Cada iteração consome exatamente 1 ms */
}
}
void main(void)
{
uint16_t periodo = 100; /* Período inicial: 100 ms */
ADCON1 = 0x0F; CMCON = 0x07;
TRISD = 0x00;
LATD = 0x00;
while (1)
{
LATDbits.LATD0 = 1;
delay_ms_var(periodo);
LATDbits.LATD0 = 0;
delay_ms_var(periodo);
/* O período poderia variar conforme uma entrada, por exemplo */
if (periodo < 1000) periodo += 100;
else periodo = 100;
}
}#include <xc.h>
#include <stdint.h>
#pragma config FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
static void dly(uint16_t ms) { while(ms--) __delay_ms(1); }
void main(void) {
ADCON1=0x0F; CMCON=0x07; TRISD=0x00; LATD=0x00;
uint16_t p=100;
while(1) {
BTG(LATD, 0, ACCESS); /* Toggle bit — instrução BTG gerada pelo XC8 */
dly(p);
if (p < 1000) p += 100; else p = 100;
}
}Referência Rápida dos Registradores de I/O
| Porta | TRIS | LAT | PORT | Observações |
|---|---|---|---|---|
| PORTA | TRISA | LATA | PORTA | RA0-RA5 analógicos por padrão (ADCON1) |
| PORTB | TRISB | LATB | PORTB | RB0-RB4 analógicos por padrão (PBADEN) |
| PORTC | TRISC | LATC | PORTC | RC6=TX, RC7=RX (USART) |
| PORTD | TRISD | LATD | PORTD | RD0-RD7 totalmente digitais; LEDs no kit |
| PORTE | TRISE | LATE | PORTE | RE0-RE2; RE3=MCLR quando MCLRE=ON |
| SFR | Valor recomendado | Efeito |
|---|---|---|
| ADCON1 | 0x0F | Todos os pinos RA e RB como digital |
| CMCON | 0x07 | Comparadores desativados |
| INTCON2 | bit RBPU=0 | Pull-ups internos de PORTB habilitados |
| SPBRG | calculado | Baud rate da USART |
| TXSTA | 0x24 | Modo assíncrono, BRGH=1, TX habilitado |
| RCSTA | 0x90 | SPEN=1, CREN=1 |
Checklist de Projeto: Antes de Gravar no Kit
Antes de qualquer gravação, verifique sistematicamente os itens a seguir. Um projeto que passa por este checklist tem muito menos probabilidade de exibir comportamentos inesperados no hardware.
Primeiro, confirme que as diretivas #pragma config estão presentes e corretas: FOSC=HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF. Segundo, verifique se _XTAL_FREQ está definida como 8000000UL (oito milhões, sem vírgula decimal, com sufixo UL). Terceiro, confirme que o linker está configurado para iniciar o código em 0x001000 — abra as propriedades do projeto e verifique as opções do XC8 Linker. Quarto, certifique-se de que ADCON1 = 0x0F e CMCON = 0x07 estão na inicialização se você usar pinos de PORTA ou PORTB. Quinto, verifique se todos os pinos usados estão configurados com TRIS antes de serem usados como LAT ou PORT. Sexto, compile com Build → Build All e confirme que não há erros nem avisos inesperados na janela de saída. Somente após passar por todos esses itens, coloque o kit em modo bootloader e grave.
Para Aprofundamento
O arquivo de definições do PIC18F4550 que acompanha o XC8 — localizado em <caminho de instalação>/xc8/vX.Y/pic/include/proc/pic18f4550.h — declara todos os SFRs e suas estruturas de bits. Abrir esse arquivo e ler as declarações dos registradores que você está usando é uma das melhores formas de aprender os nomes corretos de cada campo. O PIC18F4550 Data Sheet (DS39632E), disponível no site da Microchip, é a referência definitiva para todos os periféricos; cada registro descrito neste manual tem uma seção detalhada no datasheet com a descrição de cada bit, os valores padrão e as fórmulas de cálculo. O MPLAB XC8 C Compiler User’s Guide (DS50002053) documenta as extensões proprietárias do XC8, incluindo as diretivas #pragma config, os qualificadores de memória e as macros de I/O.