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).
  • 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;
}

Exercícios relacionados