Manual

do

Maker

.

com

IOC - Interrupt On Change com PIC16F1827

IOC - Interrupt On Change com PIC16F1827

Esse post está diretamente relacionado com um projeto pronto a ser apresentado no blog após a publicação dos demais recursos empregados nessa MCU. Especificamente neste, vamos falar de interrupções externas na mudança de estado de um pino qualquer que contenha o recurso IOC - ou, Interrupt On Change.

Nesse modelo de PIC o IOC está disponível em todos os pinos da PORTB.

Falei sobre interrupções em outros 3 posts, aqui, também aqui e um outro aqui.

Existem interrupções internas e externas e nesses posts anteriores falo apenas de interrupções internas. Com Interrupt On Change podemos "sentir" uma interrupção externa em um pino da mesma forma que controlamos eventos em programação de janelas (por exemplo onmouseover e onmouseout em javascript, para detectar quando o ponteiro está sobre algo e quando não está mais), tendo os estados de LOW e HIGH para cada um dos pinos da porta B. Então podemos ter um counter que contabiliza o tempo de um botão pressionado e finaliza-se o counter quando o botão for liberado. Mas isso é só um exemplo, porque no projeto que deu origem a esse post será necessário mais de um pino e o motivo será detalhado mais adiante, com exemplo de polling em substituição à interrupção.

Existem várias vantagens em se utilizar interrupções, das quais citarei o wake up da MCU em outro post, onde colocaremos esse modelo de PIC pra dormir até que um evento ocorra, e assim economizaremos energia.

Outra vantagem é o fato de que as interrupções funcionam de forma similar a threads; enquanto a MCU monitora os eventos (sejam elas interrupções internas ou externas), o código principal pode estar rodando em um loop e ser interrompido quando um evento ocorrer - o evento pode então ser tratado por outra porção de código dentro da função de interrupções, uma função externa ao tratamento de interrupções ou simplesmente apontar uma outra flag controlada no código principal.

A MCU (MicroControler Unit) escolhida para esse projeto é o PIC16F1827 que trabalha a partir de 1.8v até 5.5v, e conta com uma série de interessantes recursos periféricos. Esse modelo conta também com um ótimo oscilador interno que vai de 31kHz a 32MHz.

Na primeira imagem do post está o diagrama de IOC, estando ele por trás do pino, como pode ser visto. Isso significa que o pino pode ser utilizado para interrupções independente de qualquer outra utilização para ele, ou seja, não se preocupe em configurar TRIS e PORT.

Se você está familiarizado com Arduino (onde também se pode tratar algumas interrupções de um modo simples), talvez falar de PIC te cause estranheza, mas é bastante divertido e te aproximará mais do hardware do que Arduino em seu modo de uso habitual. Acima citei a configuração da porta; enquanto em Arduino uma porta se configura assim:

pinMode(PIN,OUTPUT);
...
digitalWrite(PIN, LOW);

Em PIC deve-se indicar a direção em uma variável e a utilização do pino em outra (LOW,HIGH):

TRISA1_bit = 0; //0 é output e 1 é input na porta A1.
// Para saber a qual pino corresponde, olhar no datasheet.
RA1_bit = 1; //1 high e 0 low

Nesse modelo de PIC, RA1 é o primeiro pino à direita (pino 18). Então pode-se fazer uma associação dessa forma:

sbit RA1_PIN_18 at RA1_bit;

Assim utilizamos uma variável clara relacionada ao LOW e HIGH do pino. Alguns pinos possuem mais do que uma funcionalidade, mas não vou me extender para que isso não vire uma aula de PIC.

Configurando Interrupt On Change

Sendo franco, passei várias horas sofrendo para entender como configurar IOC. Eu já havia utilizado a porta RB0 para receber interrupção externa e associado a um polling já resolveria meu problema, mas fiz questão de utilizar o recurso mais apropriado para este projeto. O problema foi que lí sobre as interrupções no datasheet a partir da página 81, porém a informação que eu precisava estava na página 131, o que só descobri no dia seguinte.

Ativando o Módulo

A melhor maneira de associar é lembrando que a partir do núcleo, chaves vão se conectando para ativar os recursos de hardware. A primeira chave a ser ligada saíndo do núcleo é o IOCIE. Esse bit deve ser configurado no registrador INTCON, portando dentro de main() (para quem usa MikroC) essa referencia é fundamental:
INTCON.IOCIE = 1

Configuração individual dos pinos para Interrupt On Change

Para permitir interrupções individuais nos pinos da porta B, primeiramente deve-se setar os bits das portas que receberão interação. Para pull up utiliza-se os bits IOCBPx do registrador IOCBP e os bits de IOCBNx do registrador IOCBN para identificar a queda, podendo-se utilizar os dois simultaneamente:
IOCBP.IOCBP4 = 1 ativará IOC na porta RB4, lembrando que não há necessidade de configurar o pino para nada mais.

IOCBN.IOCBN4 = 1

Interrupt Flag

Quando um evento ocorre, pelo menos 2 bits são levantados no registrador INTCON:

INTCON.INTF - Interruption Flag. A partir de então deve-se varrer os bits relacionados aos pinos, caso queira identificar o pino de origem da interrupção:
INTCON.IOCBF4 é o bit da RB4. Se esse bit estiver em 1, então foi quem originou a interrupção.

Limpando as interrupções

A remoção das interrupções fica a encargo do programador, ou seja, existe uma rotina importante para manipulação de interrupções.

Lembre-se sempre de que o bit que é utilizado para identificar o pino que recebeu interrupção deve ser limpo para que possa ser reutilizado. Para tal, simplesmente volte-o para 0:
INTCON.IOCBF4 = 0

Para não fazer polling desnecessariamente atrás de algum bit com interrupção, o melhor é aguardar que INTCON.INTF tenha sido levantado. Logo, esse é mais um bit que deve ser limpo:
INTCON.INTF = 0

Tratando interrupções

Para não tirá-lo da leitura, vou revisar um pouco do que expliquei em outro post sobre interrupções.

Partindo do core, a primeira coisa que queremos é que as interrupções sejam percebidas, então levantamos a flag INTE (INTerrupt Enabled).

Deve-se então levantar a chave geral para começar a receber interrupções:
GIE (General Interrupt Enabled)

Interrupções periféricas:
PEIE (Peripheral Interrupts Enabled)

E como estamos tratando interrupções do IOC, devemos levantar esse bit:
IOCIE (Interrupt On Change Interrupt Enabled)

Quando uma interrupção acontece, a primeira coisa a fazer é desligar a chave geral, porque outras interrupções podem acontecer e só dá pra tratar uma interrupção por vez; se a chave geral permanecer ativa, então qualquer execução será interrompida quando outra interrupção chegar. A rotina para isso é simples:
1- desligar a chave geral para não receber novas interrupções
2- tratar o evento
3- limpar os bits INTF e do pino
4- levantar a chave geral.

ioc_bb-1.webp

Circuito para teste

O teste foi feito com esse circuito acima. Tive uma rotina dura até perceber que anomalias que estavam acontecendo era pela falta de um filtro. Até então não estava utilizando capacitor em VDD (VDD para entrada e VCC para ground) e só descobri os ruídos um dia após iniciar a busca pelo funcionamento da interrupção.

Código para o teste para Interrupt On Change

O código sequer está limpo, mas nem me dei ao trabalho porque até o último post apresentarei o código final empregado no projeto, então "entre mas não repare a bagunça".


//MAP
/*
RA2 - 1.18 - RA1
RA3 - 2.17 - RA0
RA4 - 3.16 - RA7
RA5 - 4.15 - RA6
Vss - 5.14 - Vdd
RB0 - 6.13 - RB7
RB1 - 7.12 - RB6
RB2 - 8.11 - RB5
RB3 - 9.10 - RB4
*/

sbit LED_TRIS at TRISA3_bit; // led vermelho
sbit LED_PINO at RA3_bit; // led vermelho

int i = 0;
int j = 0;

void blink(int numBlinks){
  for (j=0;j<numBlinks;j++){
    LED_PINO = 1;
    Delay_ms(5);
    LED_PINO = 0;
    Delay_ms(5);
  }
}

void interrupt(void){
 if (INTCON.INTF){
    //desativar chave geral
    INTCON.GIE = 0;

  if (INTCON.IOCBF0){
    blink(4);
    INTCON.IOCBF1 = 0;
  }
  if (INTCON.IOCBF1){
    blink(4);
    INTCON.IOCBF1 = 0;
  }
  if (INTCON.IOCBF2){
    blink(4);
    INTCON.IOCBF2 = 0;
  }
  if (INTCON.IOCBF3){
    blink(4);
    INTCON.IOCBF3 = 0;
  }
  if (INTCON.IOCBF4){
    blink(4);
    INTCON.IOCBF4 = 0;
  }
  if (INTCON.IOCBF5){
    blink(4);
    INTCON.IOCBF5 = 0;
  }
  if (INTCON.IOCBF6){
    blink(4);
    INTCON.IOCBF6 = 0;
  }
  if (INTCON.IOCBF7){
    blink(4);
    INTCON.IOCBF7 = 0;
  }

  INTCON.INTF = 0;
  INTCON.GIE = 1;
 }
}

void main() {
  ANSELA = 0; //Desliga analogicos em A e B
  ANSELB = 0;
  C1ON_bit = 0; // Desliga comparadores
  C2ON_bit = 0;

  //IOC subindo e descendo - apenas ativando, nao confunda com as flags
  IOCBP.IOCBP0 = 1;
  IOCBP.IOCBP1 = 1;
  IOCBP.IOCBP2 = 1;
  IOCBP.IOCBP3 = 1;
  IOCBP.IOCBP4 = 1;
  IOCBP.IOCBP5 = 1;
  IOCBP.IOCBP6 = 1;
  IOCBP.IOCBP7 = 1;

  IOCBP.IOCBN0 = 1;
  IOCBP.IOCBN1 = 1;
  IOCBP.IOCBN2 = 1;
  IOCBP.IOCBN3 = 1;
  IOCBP.IOCBN4 = 1;
  IOCBP.IOCBN5 = 1;
  IOCBP.IOCBN6 = 1;
  IOCBP.IOCBN7 = 1;

  LED_TRIS = 0;
  //TRISB eh 1nput
  TRISB = 1;
  //Desliga conversor AD
  ADCON0 = 0;

  //Ativa chave geral das interrupcoes
  INTCON.GIE = 1;
  //peripheral interrupts
  INTCON.PEIE = 1;
  //Ativa chave Interrupt Enabled
  INTCON.INTE = 1;
  //interrupt on change
  INTCON.IOCIE = 1;

  while (1){
    //nada aqui
  }
}

Pra finalizar essa etapa, um video de alguns segundos mostrando algumas interrupções sendo geradas.

Eu havia informado possíveis mudanças no post. Existem mudanças importantes a considerar, mas invés de mudar esse post, leia esse novo post, contendo o código limpinho e um video onde falo 13 vezes a palavra 'interrupção/interrupções', mas mostro como funciona os 2 estados da IOC.

O projeto é um contabilizador de moedas, ou 'porquinho eletrônico', espero que acompanhem até o final!

Se gostou, não deixe de compartilhar; dê seu like no video e inscreva-se no nosso canal Manual do Maker Brasil no YouTube.

Próximo post a caminho!

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.