block-beta
columns 1
block:flash["Flash — 32 KB"]:1
A["0x000000–0x000FFF<br/>Bootloader (não sobrescrever!)"]
B["0x001000<br/>vetor de reset do usuário (GOTO main)"]
C["0x001008 / 0x001018<br/>vetores de interrupção do usuário"]
E["0x001020–0x007FFF<br/>seu código e dados const"]
end
Manual de Referência: C para o PIC18F4550
Pense neste apêndice como o caderninho que você deixa aberto ao lado do teclado. Ele não ensina a teoria de cada módulo — ele responde, rápido, às perguntas práticas que sempre voltam: “como era mesmo aquele #pragma?”, “por que meu LED não acende?”, “qual a conta do baud rate?”. Leia uma vez no começo do semestre para saber o que tem aqui e, depois, volte sempre que travar. Tudo é para o PIC18F4550 no kit ACEPIC PRO V8.2, com o compilador XC8.
Por que C, e não só assembly
Olha, dá para fazer tudo em assembly no PIC18 — são só 75 instruções. Mas em assembly você precisa pensar, ao mesmo tempo, no problema (piscar o LED, ler o sensor) e na mecânica do processador (registradores, bancos, pilha). É atenção demais dividida. O compilador XC8 tira esse peso: você escreve LATD = 0xFF; e ele gera a instrução mais enxuta possível — neste caso, setf LATD. Você raciocina no nível do problema; ele raciocina no nível das instruções. Mesmo assim, parte do aprendizado é justamente olhar o assembly que o compilador produz — por isso o material de cada módulo mostra os dois lados.
Criando o projeto no MPLAB X
O fluxo é sempre o mesmo. Em File → New Project → Microchip Embedded → Standalone Project, escolha o dispositivo PIC18F4550 (cuidado para não pegar o PIC18F4520 ou o 4525, que têm periféricos diferentes). Na seleção de ferramenta (Tool), marque No Tool ou Simulator: como a gravação no kit é feita pelo bootloader via USB, o MPLAB X não precisa de um programador físico. No compilador, escolha XC8. Depois crie o main.c em Source Files → New → C Source File.
A estrutura mínima de todo programa
Esse esqueleto se repete em todos os exemplos da disciplina. Repare na ordem: cabeçalhos, fusíveis de configuração, a frequência do cristal e a função main com seu laço infinito.
#include <xc.h> /* cabeçalho do processador selecionado */
#include <stdint.h> /* uint8_t, uint16_t, etc. */
#pragma config FOSC = HS /* cristal externo de alta velocidade */
#pragma config WDT = OFF /* watchdog desligado */
#pragma config LVP = OFF /* obrigatório com o bootloader */
#pragma config MCLRE = ON /* pino MCLR como reset externo */
#pragma config PBADEN = OFF /* PORTB<0:4> como digital */
#pragma config CPUDIV = OSC1_PLL2
#define _XTAL_FREQ 8000000UL /* 8 MHz — usado por __delay_ms/__delay_us */
void main(void)
{
/* inicialização dos periféricos */
while (1)
{
/* laço principal */
}
}Seu primeiro programa: piscar um LED
Antes de qualquer teoria, vamos fazer algo acender e apagar — é o “olá, mundo” do embarcado e o melhor jeito de confirmar que ambiente, compilação e gravação estão todos no lugar. Configure o PORTD como saída e alterne o LED de RD0 com meio segundo de intervalo:
#include <xc.h>
#pragma config FOSC=HSPLL_HS, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF, CPUDIV=OSC1_PLL2
#define _XTAL_FREQ 8000000UL
void main(void)
{
TRISD = 0x00; /* PORTD como saída (LEDs) */
LATD = 0x00; /* começa apagado */
while (1)
{
LATDbits.LATD0 = 1; /* acende RD0 */
__delay_ms(500);
LATDbits.LATD0 = 0; /* apaga RD0 */
__delay_ms(500);
}
}Se o LED piscar, você acabou de validar o ciclo inteiro: editou, compilou, gerou o .hex, gravou pelo bootloader e o chip executou. A partir daqui, o resto do manual é só ir trocando o miolo do laço.
Os fusíveis de configuração (#pragma config)
Esses fusíveis são gravados junto com o código e decidem como o chip se comporta antes de a primeira instrução rodar. Um bit errado aqui faz o programa simplesmente não funcionar — e você procura o erro no lugar errado por horas. Por isso vale memorizar os valores certos para o kit.
Três deles merecem atenção especial. O FOSC = HSPLL_HS seleciona o oscilador de cristal de alta velocidade, que é o cristal de 8 MHz soldado no kit; o clock de instrução é sempre F_{OSC}/4, ou seja, 2 MHz. O LVP = OFF é, na prática, o mais perigoso de errar: com LVP = ON, o pino RB5 vira sinal de programação e o bootloader do kit deixa de responder — você precisaria de um gravador externo para recuperar. E o PBADEN = OFF garante que RB0–RB4 nasçam como pinos digitais; se ficar em ON, ler um botão em RB0 devolve lixo, porque o pino está ligado ao conversor analógico.
| Campo | Use | Por quê |
|---|---|---|
| FOSC | HS | cristal de 8 MHz do kit |
| WDT | OFF | evita resets durante o desenvolvimento |
| LVP | OFF | obrigatório para o bootloader não travar |
| MCLRE | ON | botão de reset físico do kit |
| PBADEN | OFF | PORTB digital por padrão |
| CPUDIV | OSC1_PLL2 | divisor padrão com FOSC=HSPLL_HS |
O bootloader e o mapa de memória
O kit não usa gravador de hardware: ele tem um bootloader gravado de fábrica que recebe seu programa pela própria USB. Em troca, ele ocupa uma faixa da Flash que você não pode pisar. A Flash do PIC18F4550 tem 32 KB (de 0x000000 a 0x007FFF); no kit, o bootloader fica nos primeiros 4 KB e seu código começa em 0x001000.
Se você não configurar o início do código em 0x001000 (no Project Properties → XC8 Linker), a ferramenta de gravação escreve a partir de 0x000000 e apaga o bootloader. O kit para de responder pela USB e só um gravador externo (PICkit) o recupera. Confira isso antes de gravar.
Para gravar: compile com F11, gere o .hex, segure o reset enquanto conecta o USB (o kit entra em modo bootloader e o LED pisca de um jeito característico), abra o software HID Bootloader, selecione o .hex e clique em Program. Reconecte o USB e seu programa roda.
Como o C enxerga a memória do chip
Vale ter na cabeça onde cada coisa mora, porque isso explica vários comportamentos que parecem mágica. A Flash (32 KB) guarda o código compilado e tudo que você declara com const — e aqui há um detalhe que aparece nos exercícios: ler um array const na Flash exige as instruções TBLRD, então o compilador gera código bem diferente para const int tabela[] e para um array normal na RAM. A RAM (2 KB) guarda as variáveis; as locais vão para a pilha de software, as globais e estáticas ficam em posições fixas que o linker escolhe.
Dentro da RAM há uma região especial, o Access Bank (0x000–0x05F mais os SFRs em 0x060–0x0FF), que o processador acessa sem trocar de banco — por isso é mais rápida. O XC8 tenta colocar ali as variáveis mais usadas; você pode forçar com o qualificador __near. Os SFRs (TRIS, LAT, PORT, ADCON1, RCSTA…) são posições de RAM mapeadas para os periféricos, e estão todos declarados em <xc.h> — por isso você os usa por nome, sem decorar endereço.
| 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 de acesso rápido |
| RAM — SFRs | 0x060–0x0FF | periféricos mapeados |
| RAM — bancos 1–14 | 0x100–0xEFF | variáveis normais |
| EEPROM de dados | — | 256 bytes não voláteis |
Entrada e saída: TRIS, LAT e PORT
Esse é o trio que mais confunde no início, então vamos com calma. Cada porta (PORTA até PORTE) tem três registradores. O TRIS define a direção de cada pino: bit 0 é saída, bit 1 é entrada (e tudo nasce como entrada, por segurança). O LAT é por onde você escreve a saída. O PORT é por onde você lê o nível real do pino.
A regra de ouro: para escrever, use LAT; para ler, use PORT. Escrever direto em PORT funciona, mas dispara um problema sutil chamado read-modify-write hazard — a escrita lê o nível físico do pino antes de gravar, e se ele ainda não estabilizou, você pega o valor errado. LAT guarda o que você quis impor, então nunca mente.
flowchart LR
LAT["LAT (escrita)"] -->|"TRIS=0 (saída)"| PAD["pino físico"]
PAD -->|"TRIS=1 (entrada)"| PORT_R["PORT (leitura)"]
EXT["sinal externo"] --> PAD
TRIS["TRIS (direção)"] -->|controla| PAD
E aqui mora o bug clássico da disciplina: na partida, RA0–RA5 e RB0–RB4 estão em modo analógico, e ler PORT nesses pinos devolve sempre 0, mesmo com 5 V no pino. A cura é desligar o analógico logo no começo: ADCON1 = 0x0F deixa todos os pinos de RA e RB digitais, e CMCON = 0x07 desliga os comparadores. Faça disso um reflexo.
void main(void)
{
ADCON1 = 0x0F; /* RA e RB como digital (senão PORT lê 0) */
CMCON = 0x07; /* comparadores desligados */
TRISD = 0x00; /* PORTD saída (LEDs) */
TRISB = 0xFF; /* PORTB entrada (botões) */
LATD = 0x00; /* começa com os LEDs apagados */
INTCON2bits.RBPU = 0; /* pull-ups internos de PORTB ligados */
while (1)
{
if (PORTBbits.RB0 == 0) /* botão em RB0 (lógica invertida) */
LATD = 0xFF; /* acende tudo */
else
LATD = 0x00; /* apaga tudo */
}
}Para mexer em um bit só, use a notação de campo: LATDbits.LATD3 = 1; acende o LED de RD3. O XC8 compila isso direto para bsf/bcf, que fazem leitura-modificação-escrita em um ciclo — bem mais barato que mexer no byte inteiro.
Tempo: __delay_ms e __delay_us
Para esperar, o XC8 dá duas macros: __delay_ms(t) e __delay_us(t). As duas dependem de _XTAL_FREQ estar definida com o clock certo (8 MHz no kit), porque é desse valor que o compilador calcula quantas voltas de laço gastam o tempo pedido. Com clock de instrução de 2 MHz, cada ciclo dura T_{CY} = 1/2\,\text{MHz} = 500\,\text{ns}, então 1 ms são 2000 ciclos.
Dois detalhes que pegam todo mundo: o argumento precisa ser uma constante (o cálculo é feito na compilação, não em tempo de execução), e a macro trava a CPU durante toda a espera. Para atraso variável, faça um laço for (i=0;i<ms;i++) __delay_ms(1);. Para tempo de verdade sem desperdiçar a CPU, use os Timers (módulos de periféricos).
Quando o atraso precisa variar em tempo de execução (o argumento de __delay_ms precisa ser constante), o jeito é envolver a macro num laço — cada volta gasta exatamente 1 ms:
Lendo o assembly que o XC8 gera
Parte do que você aprende nesta disciplina é olhar por baixo do C. Sempre que compila, o MPLAB X pode mostrar o assembly produzido (na janela de desmontagem, ou no arquivo de listagem), e isso é ouro para entender custo e para depurar. Quando você escreve LATD = 0xFF;, o XC8 gera uma única setf LATD; quando escreve LATDbits.LATD3 = 1;, gera um bsf LATD, 3. Um if (PORTBbits.RB0 == 0) vira um par btfsc/bra. E o tal código diferente para arrays: um const int t[] (que mora na Flash) acessa elementos com a família tblrd, enquanto um array na RAM usa endereçamento indireto via FSR. Ver esse mapeamento explica por que duas linhas de C parecidas podem ter custos bem diferentes — e o Manual de Assembly deste apêndice é o seu dicionário para ler o que aparece ali.
Depurando sem gravador: as três técnicas que funcionam
No kit você não tem um depurador de hardware conectado (os pinos de ICSP são usados pelo bootloader). Então a gente depura por software, e funciona muito bem.
A primeira técnica é depurar com os LEDs. Mapeie estados do programa para padrões binários no PORTD: acenda 0x01 ao terminar a inicialização, 0x03 ao configurar os periféricos, 0xFF ao entrar no laço. Se o kit parou com 0x03 aceso e 0xFF nunca apareceu, você sabe exatamente entre quais etapas a coisa quebrou.
A segunda, mais poderosa, é mandar texto pela serial (USART). O PIC18F4550 transmite por RC6 (TX) e recebe por RC7 (RX). Redefinindo a função putch, você ganha printf direto para um terminal no PC (PuTTY, Tera Term). O ponto delicado é a conta do baud rate. No modo assíncrono com BRGH=1:
SPBRG = \frac{F_{OSC}}{16 \times BaudRate} - 1
Para 9600 bps com 8 MHz, dá SPBRG = 8\,000\,000 / (16 \times 9600) - 1 \approx 51. Com 51, o baud real é 8\,000\,000/(16 \times 52) = 9615 bps — erro de só 0,16%, bem dentro dos ±2% que o protocolo tolera.
void putch(char c) { while (!TXSTAbits.TRMT); TXREG = c; }
void uart_init(void) {
TRISCbits.TRISC7 = 1; /* RX entrada */
SPBRG = (_XTAL_FREQ / (16UL * 9600)) - 1;
TXSTA = 0x24; /* SYNC=0, BRGH=1, TXEN=1 */
RCSTA = 0x90; /* SPEN=1, CREN=1 */
}
/* depois disso, printf("contador=%u\r\n", c); funciona */A terceira é o simulador do MPLAB X (Project Properties → Tool → Simulator). Ele roda o programa sem hardware, deixa pôr breakpoints e inspecionar variáveis e SFRs. É ótimo para validar a lógica, mas não emula bem o tempo real nem a USB — para timing e resposta do hardware, grave no kit e use LED/UART.
Em todos os casos, depure com método: isole o menor trecho que ainda mostra o problema, levante uma hipótese (“desconfio que contador não incrementa”), teste com uma das técnicas, e só então corrija. Tentativa e erro às cegas custa caro.
Um idioma que você vai repetir: debounce de botão
Botão mecânico “treme” ao ser pressionado (bounce), gerando várias transições para um único toque. Sem tratar isso, um toque vira vários eventos. O remédio simples é esperar um tempinho e confirmar:
Referência rápida
| Porta | Direção | Saída | Leitura | Observação |
|---|---|---|---|---|
| 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 | totalmente digital; LEDs do kit |
| PORTE | TRISE | LATE | PORTE | RE3=MCLR quando MCLRE=ON |
| SFR | Valor | Efeito |
|---|---|---|
| ADCON1 | 0x0F | RA e RB digitais |
| CMCON | 0x07 | comparadores desligados |
| INTCON2 (RBPU) | 0 | pull-ups de PORTB ligados |
| TXSTA | 0x24 | assíncrono, BRGH=1, TX ligado |
| RCSTA | 0x90 | SPEN=1, CREN=1 |
Checklist antes de gravar no kit
Antes de cada gravação, passe os olhos por aqui — economiza muita depuração. Primeiro, confira os #pragma config (principalmente LVP=OFF). Segundo, _XTAL_FREQ é 8000000UL. Terceiro, o linker XC8 começa o código em 0x001000. Quarto, há ADCON1 = 0x0F e CMCON = 0x07 se você usa pinos de RA ou RB. Quinto, todo pino tem o TRIS ajustado antes de ser usado. Sexto, a compilação fechou sem erros nem avisos estranhos. Só então: kit em modo bootloader e gravar.
Para aprofundar
Quando quiser o nome exato de um campo de bit, abra o arquivo de definições do XC8 (.../xc8/vX.Y/pic/include/proc/pic18f4550.h): ele declara cada SFR e cada bit. A referência definitiva de todos os periféricos é o PIC18F4550 Data Sheet (DS39632E) da Microchip; as extensões do compilador (incluindo os #pragma config) estão no MPLAB XC8 C Compiler User’s Guide (DS50002053).