Desenvolva um código em C que habilite as interrupções externar pelo INT0 e INT1 a nível de registradores. Neste código, quando a interrupção pelo INT0 ocorrer, apenas os bits ímpares do PORTB devem ter seu nível lógico comutado. Quando a interrupção pelo INT1 ocorrer, apenas os bits pares do PORTB devem ter seu nível lógico comutado. O perfil de gatilho da interrupção deve ser “por mudança de nível lógico”. Esse código deve ser depurado no Microchip Studio.
Passo 1
O que o enunciado pede e o que nos foi dado:
- Pedem um código em C, usando manipulação direta de registradores, que habilite as interrupções externas INT0 e INT1.
- Perfil de gatilho: “por mudança de nível lógico” (ou seja, qualquer alteração lógica no pino — qualquer transição 0→1 ou 1→0 deve disparar).
- Quando ocorrer INT0: devem ser comutados (toggle) apenas os bits ímpares de PORTB.
- Quando ocorrer INT1: devem ser comutados (toggle) apenas os bits pares de PORTB.
- O código deve ser depurado no Microchip Studio.
- Não foi especificado o modelo exato do AVR no enunciado.
Passo 2
Escolha do MCU e justificativa (importante para quais registradores usar)
- Vou usar como referência um AVR clássico suportado pelo Microchip Studio, por exemplo o ATmega16 / ATmega32 (família bastante usada em aulas). Nesses dispositivos os registradores relevantes são:
- MCUCR: configura o tipo de gatilho das interrupções externas (bits ISC01/ISC00 para INT0 e ISC11/ISC10 para INT1).
- GICR: habilita as interrupções externas (bits INT0 e INT1).
- GIFR: flags de interrupção (podem ser limpas escrevendo 1 no bit).
- DDRB / PORTB: controle do porto B (saída e nível).
- DDRD / PORTD: controle dos pinos de entrada onde normalmente estão INT0 e INT1 (no ATmega16/32: INT0 = PD2, INT1 = PD3).
- Por que escolhi esse modelo: muitos laboratórios e exercícios usam ATmega16/32 e os registradores MCUCR/GICR aparecem em enunciados quando o professor pede “a nível de registradores”. Se você usar outro AVR (por ex. ATmega328P), os nomes mudam (EICRA / EIMSK em vez de MCUCR / GICR); eu explico como adaptar mais abaixo.
Passo 3
Explicando os registradores e conceitos necessários (aula curta, didática)
- DDRB e PORTB:
- DDRB controla direção dos pinos do porto B: se um bit de DDRB = 1, o respectivo pino de PORTB é saída; se = 0, é entrada.
- PORTB define o nível lógico nos pinos quando são saídas; quando são entradas, escrever 1 em PORTB ativa o pull-up interno.
- Toggle com XOR:
- Para inverter bits selecionados de PORTB usa-se XOR (operador ^). Ex.: para inverter apenas um bit use PORTB ^= (1<<n).
- Para inverter vários bits de uma vez, constrói-se uma máscara com 1s nas posições a inverter e aplica-se XOR com essa máscara.
- Máscaras que vamos usar (assumindo numeração de bits começando em 0 — bit 0 é o LSB):
- bits pares (0,2,4,6): máscara = 0b01010101 = 0x55.
- bits ímpares (1,3,5,7): máscara = 0b10101010 = 0xAA.
- Interrupções externas e MCUCR / GICR (ATmega16/32):
- MCUCR contém ISC01 ISC00 ISC11 ISC10:
- Para INT0: ISC01 ISC00 = 00 -> nível baixo; 01 -> qualquer mudança lógica; 10 -> borda de descida; 11 -> borda de subida.
- Analogamente para INT1 via ISC11 ISC10.
- Como queremos “por mudança de nível lógico” configuramos ISC01=0 e ISC00=1 (para INT0) e ISC11=0 e ISC10=1 (para INT1).
- GICR contém bits INT0 e INT1 — escrevendo 1 nesses bits habilitamos a interrupção externa correspondente.
- GIFR contém os flags INTF0/INTF1; escrever 1 nesses bits limpa os flags (boa prática limpar antes de habilitar).
- MCUCR contém ISC01 ISC00 ISC11 ISC10:
- Global Interrupt Enable:
- Para que qualquer interrupção funcione é preciso habilitar interrupções globais (bit I no SREG). Em C com AVR libc usa-se a função sei() (do <avr/interrupt.h>).
- Pinos físicos:
- No ATmega16/32, INT0 está no PD2 e INT1 no PD3. É importante que esses pinos estejam configurados como entradas (DDR D bits 2 e 3 = 0) e, se não houver sinal externo firme, habilitar pull-ups internos para evitar flutuações e interrupções espúrias.
Passo 4
Plano de implementação (resumo antes de codificar)
- Configurar DDRB para saída (tornar os 8 pinos de PORTB saídas): DDRB = 0xFF.
- Inicializar PORTB num estado conhecido (ex.: 0x00).
- Garantir que PD2 e PD3 são entradas e habilitar pull-ups internos nelas para estabilidade: DDRD bits 2 e 3 = 0; PORTD bits 2 e 3 = 1.
- Configurar MCUCR para “any logical change” (ISC01=0, ISC00=1; ISC11=0, ISC10=1).
- Limpar flags de interrupção em GIFR (escrever 1 nos bits INTF0/INTF1).
- Habilitar INT0 e INT1 em GICR.
- Habilitar interrupções globais com sei().
- Implementar rotinas de tratamento:
- ISR de INT0: PORTB ^= 0xAA;
- ISR de INT1: PORTB ^= 0x55;
- Loop principal vazio (pode ficar em sleep ou em while(1){ }).
Passo 5
Código C com comentários detalhados (explicando linha a linha)
- Abaixo o código completo que implementa tudo a nível de registradores para ATmega16/32. Em cada linha crucial há comentário ensinando o porquê.
- Observação: se usar outro AVR (por exemplo ATmega328P), substitua MCUCR por EICRA, GICR por EIMSK e os nomes de bits conforme o datasheet (ISC00/ISC01 tornam-se ISC00/ISC01 em EICRA etc.). As ideias e máscaras permanecem as mesmas.
Passo 6
Dicas para depuração no Microchip Studio (passo a passo curto)
- Crie um projeto “AVR GCC” e escolha o dispositivo ATmega16 (ou o AVR que você tiver na placa).
- Cole o código abaixo no main.c.
- Build -> Start Without Debugging para compilar; Start Debugging (F5) para abrir o simulador/debugger.
- No Microchip Studio, abra a janela “AVR Simulator -> I/O View”. Em “Port D” você verá os pinos PD2 e PD3. Ao alternar manualmente o nível lógico desses pinos na I/O View (clicando nas caixas), como configuramos para “any logical change”, cada mudança acionará a ISR correspondente no simulador e o conteúdo de PORTB será atualizado. Na janela de I/O View também possa observar PORTB mudando.
- Se estiver usando hardware real: conecte sinais aos pinos PD2/PD3 (botões com resistor pull-down ou use os pull-ups internos e botão para GND), programe o MCU e observe LEDs em PORTB para ver os bits sendo alternados.
Passo 7
Observações finais e adaptações rápidas
- Se preferir que apenas alguns pinos de PORTB sejam saídas, ajuste DDRB para ter 1 somente nos pinos desejados em vez de DDRB = 0xFF.
- Se estiver usando ATmega328P (Arduino Uno por ex.) altere os registradores para EICRA/EIMSK e adapte os nomes de bits. A lógica das máscaras 0xAA/0x55 e do XOR permanece igual.
- Lembre-se: sem pull-up ou sem um sinal externo definido, pinos de interrupção podem flutuar e gerar interrupções aleatórias — por isso habilitei pull-ups para estabilidade.
Resposta
/*
* Exemplo para ATmega16/ATmega32 - interrupções externas INT0 e INT1
* - INT0: toggle dos bits ímpares de PORTB (máscara 0xAA)
* - INT1: toggle dos bits pares de PORTB (máscara 0x55)
* - Trigger: any logical change (ISCx1 = 0, ISCx0 = 1)
* Compilar e depurar no Microchip Studio (AVR GCC).
*/
#include <avr/io.h>
#include <avr/interrupt.h>
/* ISR para INT0: inverte bits ímpares de PORTB */
ISR(INT0_vect)
{
PORTB ^= 0xAA; /* 0xAA = 0b10101010 -> bits 1,3,5,7 */
}
/* ISR para INT1: inverte bits pares de PORTB */
ISR(INT1_vect)
{
PORTB ^= 0x55; /* 0x55 = 0b01010101 -> bits 0,2,4,6 */
}
int main(void)
{
/* 1) Configurar PORTB como saída (todos os pinos como saída) */
DDRB = 0xFF;
PORTB = 0x00; /* estado inicial conhecido */
/* 2) Garantir PD2 (INT0) e PD3 (INT1) como entradas e habilitar pull-ups */
DDRD &= ~((1<<PD2) | (1<<PD3)); /* entradas */
PORTD |= (1<<PD2) | (1<<PD3); /* pull-ups internos ativados */
/* 3) Configurar MCUCR para "any logical change" nos dois interrupts:
* INT0: ISC01 = 0, ISC00 = 1
* INT1: ISC11 = 0, ISC10 = 1
* Aqui apenas ajustamos os bits relevantes, sem alterar outros bits indesejados.
*/
MCUCR &= ~((1<<ISC01) | (1<<ISC11)); /* garantir ISC01=0 e ISC11=0 */
MCUCR |= (1<<ISC00) | (1<<ISC10); /* colocar ISC00=1 e ISC10=1 */
/* 4) Limpar flags de interrupção (escrever 1 limpa em GIFR) */
GIFR |= (1<<INTF0) | (1<<INTF1);
/* 5) Habilitar INT0 e INT1 no GICR */
GICR |= (1<<INT0) | (1<<INT1);
/* 6) Habilitar interrupções globais */
sei();
/* Loop principal: MCU fica aguardando interrupções */
while (1)
{
/* pode-se colocar sleep para economizar energia, ou deixar vazio */
}
/* nunca alcança aqui */
return 0;
}