INTRODUÇÃO A INTERRUPÇÕES E PCINT

A compreensão do funcionamento de interrupções é essencial para uma programação eficiente de microcontroladores. Entretanto existem muitas dúvidas em torno desse tópico. Nesse post veremos uma pequena explicação de como interrupções funcionam e em seguida focaremos na interrupção por mudança de estado (PCINT). Na abordagem dos conceitos a seguir o microcontrolador atmega328 presente na placa Arduino Uno será utilizado. Apesar de utilizarmos o atmega328, os conceitos abordados aqui podem ser estendidos para outros atmegas.  Para acompanhar esse tutorial é indicado que você tenha entendido o post sobre manipulação de registradores de entrada e saída que pode se encontrado aqui.

O QUE SÃO INTERRUPÇÕES?

Imagine que você está em casa extremamente focado em escrever um texto no computador, porém de repente seu celular toca. Mesmo sendo uma ligação inesperada, você é capaz de parar a escrita do texto, atender a ligação e voltar para onde você parou quando o telefone tocou.

Dessa maneira que funcionam as a interrupções em um microcontrolador. Nós podemos programá-lo para observar eventos externos, como um pino mudando de estado, enquanto o microcontrolador executa as instruções do código principal. Quando um evento ocorrer, o microcontrolador interromperá a execução do código principal, tratará o evento chamando uma função especificada por nós e retornará a execução do código principal. Dessa maneira nosso programa ganha flexibilidade, uma vez que não é mais necessário ficar checando a todo momento se o evento ocorreu. Nós podemos simplesmente configurar a interrupção e a função de tratamento e assim que o evento ocorrer, ele será tratado.

É importante lembrar que nós não estamos fazendo duas coisas ao mesmo tempo. O programa principal será parado enquanto a função de tratamento estiver sendo executada. Esta função de tratamento é denominada ISR (Interrupt Service Routine). Cada interrupção terá seu vetor de interrupção que nada mais é que um índice em uma tabela de interrupções que apontará para nossa rotina de tratamento.

PCINT – PIN CHANGE INTERRUPT

Como o nome indica este tipo de interrupção ocorrerá quando houver uma mudança no estado do pino escolhido. No atmega328 existem 3 vetores de interrupção para esse tipo de interrupção. Cada um deles está ligado a um PORT. Consequentemente, caso dois pinos de um mesmo PORT estejam utilizando essa interrupção ambos compartilharão a mesma rotina de interrupção. Cabe a nós então, implementar a rotina de interrupção de forma que esta seja capaz de identificar em qual dos dois pinos ocorreu a interrupção. O diagrama da Figura 1 nos ajudará no entendimento das explicações a seguir.

PCICR – Pin Change Interrupt Control Register

Este registrador é reponsável por habilitar a interrupção em um determinado PORT quando o respectivo bit PCIEx for setado para 1.

PCMSK – Pin Change Mask Register

Este registrador é responsável por habilitar a interrupção de um pino  em um determinado PORT. Logo, existem 3 registradores desse tipo PCMSK0, PCMSK1 e PCMSK2 referentes aos PORTS B, C e D respectivamente.

SREG – Global Interrupt Flag

Essa flag é responsável por controlar as interrupções de todo o microcontrolador. Funciona como uma chave geral. Uma maneira de modifica-la é através das macros sei() e cli().

  • sei() – Habilita as interrupções globalmente;
  • cli() – Bloqueia as interrupções globalmente.

Analogia com Chaves

Para facilitar a visualização do papel de cada registrador nós preparamos o diagrama a seguir. Ele mostra o caminho que a interrupção deve fazer até chegar ao respectivo vetor de interrupção. As chaves representam cada bit do registrador especificado. Chave fechada representa que o bit está setado (1) e chave aberta que ele está limpo (0).


EXEMPLOS

Exemplo 1

Nesse exemplo nós veremos como habilitar a interrupção por mudança de estado no pino D12. Como podemos ver no diagrama da Figura 1 o pino D12 é equivalente ao pino PB4 do microcontrolador atmega328. Começamos o programa configurando este pino como uma entrada. Durante a execução das configurações devemos desligar as interrupções globalmente. O pino PB4 possui a interrupção PCINT4 essa interrupção é habilitada por PCMSK0 que por sua vez é habilitado por PCIE0. Logo, devemos setar todos esses registradores. Finalmente devemos configurar a função que será chamada quando a interrupção ocorrer. Isso é feito com o auxílio da macro ISR() que recebe como parâmetro o vetor de interrupção que desejamos configurar. No nosso caso o vetor é o PCINT0_vect.

void setup() {
cli();

// Equivalente a pinMode(12, INPUT_PULLUP);
DDRB &= ~(1 << DDB4); // Seta D12 como entrada;
PORTB |= (1 << PORTB4); // Liga Pull-up;

// Seta as "chaves" necessárias para que a interrupção chegue a seu vetor;
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << PCINT4);

sei();
}

void loop() {
//...
}

/* Função de Tratamento de Interrupção
Como somente o pino D12 foi configurado para chamar esta função, não
precisamos realizar checagens complexas para entender o que ocorreu.
*/
ISR(PCINT0_vect) {
if (PINB & (1 << PINB4)) {
// D12 mudou de LOW para HIGH;
}
else {
// D12 mudou de HIGH para LOW;
}
}

Exemplo 2

Nesse exemplo nós veremos como habilitar a interrupção por mudança de estado nos pinos D12, D11 e D10. Como podemos ver no diagrama da Figura 1 o estes pinos são equivalentes aos pinos PB4, PB3 e PB2 respectivamente do microcontrolador atmega328. Repetimos todos os passos anteriores, só que agora para todos os pinos. Diferentemente do exemplo anterior agora um mesmo vetor será chamado por vários pinos. Para definir qual pino causou a interrupção devemos guardar um histórico do ultimo estado de todo o PORTB.

void setup() {
cli();

/* Equivalente a
pinMode(12, INPUT_PULLUP);
pinMode(11, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
*/
DDRB &= ~( (1 << DDB4) | (1 << DDB3) | (1 << DDB2) );
PORTB |= ( (1 << PORTB4) | (1 << PORTB3) | (1 << PORTB2) );

// Seta as "chaves" necessárias para que as interrupções cheguem ao vetor;
PCICR |= (1 << PCIE0);
PCMSK0 |= ( (1 << PCINT4) | (1 << PCINT3) | (1 << PCINT2) );

sei();
}

void loop() {
//...
}
// Variáveis globais que são acessadas por interrupções devem ser declaradas volatile;
volatile uint8_t last_PINB = PINB;

/* Função de Tratamento de Interrupção */
ISR(PCINT0_vect) {
uint8_t changed_bits;
changed_bits = PINB ^ last_PINB;
last_PINB = PINB;

if (changed_bits & (1 << PINB4))
{
if (PINB & (1 << PINB4)) {
// D12 mudou de LOW para HIGH;
}
else {
// D12 mudou de HIGH para LOW;
}
}
else if (changed_bits & (1 << PINB3))
{
if (PINB & (1 << PINB3)) {
// D11 mudou de LOW para HIGH;
}
else {
// D11 mudou de HIGH para LOW;
}
}
else if (changed_bits & (1 << PINB2))
{
if (PINB & (1 << PINB2)) {
// D10 mudou de LOW para HIGH;
}
else {
// D10 mudou de HIGH para LOW;
}
}
}

FINALIZANDO

Este conteúdo foi totalmente voltado para esclarecer alguns pontos à respeito das interrupções. Esperamos que você tenha gostado deste conteúdo, sinta-se à vontade para nos dar sugestões, críticas ou elogios. Lembre-se de deixar suas dúvidas nos comentários abaixo.

 

Privacy Preference Center