interrupciones-en-todos-los-pines-de-arduino-con-pcint

Interrupciones en todos los pines de Arduino con las PCINT

  • 12 min

En esta entrada vamos a ver cómo usar las interrupciones Pin Change en Arduino. Esto, por ejemplo, nos va a permitir tener interrupciones en todos los pines en placas basadas en el Atmega328P.

Pero un momento ¡Sacrilegio, los Arduino tienen sólo 2 pines de interrupciones! Buenos, la historia no es exactamente así.

En esta entrada veremos qué son y cómo funcionan las interrupciones Pin Change (PCINT), unas interrupciones distintas a las interrupciones normales (INT) a las que estamos acostumbrados.

Por supuesto también veremos algún ejemplo de código. Sin embargo, normalmente usaremos una librería para gestionar las PCINT. Esta visión más práctica la veremos al final del artículo, y veréis que es muy sencillo usar las interrupciones Pin Change.

Qué son las PCINT

Los procesadores como los Atmel tienen distintos tipos de interrupciones tanto internas como externas. En nuestro caso estamos interesado en interrupciones externas, es decir, las que disparan al cambiar el estado de uno de los pines.

Tenemos dos tipos de interrupciones externas:

  • INT, interrupciones de hardware externo.
  • PCINT, interrupciones pin change (Pin Change INTerrupt).

Normalmente, cuando se habla de interrupciones nos referimos a las interrupciones externas de tipo INT, que ya vimos en esta entrada. Y es cierto que de estas tenemos un número muy limitado de pines con interrupciones INT.

Mucho menos conocidas son las interrupciones pin change (PCINT), cuyo modo de funcionamiento es similar, pero actúan en número muy superior de pines del procesador.

Lógicamente no todo iba a ser tan bonito y las PCINT también tienen algunas desventajas respecto a las habituales INT. Pero nada que no podamos salvar o impida que las usemos.

En primer lugar, a diferencia de las interrupciones INT que actúan sobre un único pin, las PCINT actúan sobre un grupo de pines de forma simultánea (normalmente sobre un puerto).

Sí tenemos un único pin asociado en cada PCINT podremos deducir sin más que se ha actuado sobre este pin. Pero, en general, tendremos más de un pin y deberemos hacer una consulta posterior a un registro para saber el pin sobre el que ha actuado.

En segundo lugar, a diferencia de las interrupciones INT que permiten configurar el disparo CHANGE, FALLING, RISING, LOW y HIGH, las interrupciones INT únicamente distinguen eventos de CHANGE.

Si queremos detectar flancos de subida o de bajada deberemos guardar el estado del registro en una variable y realizar la comparación con el estado anterior en la ISR.

Finalmente, por los motivos anteriores, son ligeramente más lentas que las interrupciones INT. Pero en general no es algo que nos deba preocupar, es una diferencia irrelevante salvo en casos muy extremos.

Cómo usar las PCINT

Hay varios registros implicados en la activación y uso de las interrupciones pin change. Vamos a ver el proceso paso a paso, empleando de referencia el Atmega 328p por ser el más empleado en Arduino Uno y Nano. Aunque más abajo veremos cómo extrapolarlo a otros procesadores Atmel.

PCINT ejemplo de código

Vamos a poner todo lo anterior junto en un código con ejemplo sencillo sobre el uso de las interrupciones pin change. De momento vamos a seguir usando el Atmega389p como referencia.

El siguiente ejemplo muestra cómo activar las tres ISR disponibles para los tres grupos y cómo asociarlas a ciertos pines de cada grupo.

// Activar PCINT en un PIN
void pciSetup(byte pin)
{
    *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // activar pin en PCMSK
    PCIFR  |= bit (digitalPinToPCICRbit(pin)); // limpiar flag de la interrupcion en PCIFR
    PCICR  |= bit (digitalPinToPCICRbit(pin)); // activar interrupcion para el grupo en PCICR
}

// Definir ISR para cada puerto
ISR (PCINT0_vect) 
{    
    // gestionar para PCINT para D8 a D13
}

ISR (PCINT1_vect) 
{
    // gestionar PCINT para A0 a A5
}  

ISR (PCINT2_vect) 
{
    // gestionar PCINT para D0 a D7
}  

void setup() 
{  
  // Activar las PCINT para distintos pins
  pciSetup(7);
  pciSetup(8);
  pciSetup(9);
  pciSetup(A0);
}

void loop() 
{
}

En este ejemplo solo hemos activado las tres ISR, pero no distinguimos en que pin ha disparado, ni el tipo de evento. Tenéis un ejemplo completo con la gestión en este enlace.

El código resultante es, digamos, poco intuitivo. Afortunadamente, la comunidad ha desarrollado varias librerías que nos evitan el trabajo de tener que manejar este código por nosotros mismos. Las veremos al final de la entrada.

PCINT en otros procesadores

En los ejemplos hemos empleado el procesador Atmega 328p pero ¿qué pasa en los otros modelos de Atmega? Bueno, en general es muy parecido pero cada uno tiene su propia definición de pines.

A continuación, tenéis unas tablas con las INT y PCINT de algunos de los procesadores Atmega más frecuentes.

Para más información consultar el Datasheet del procesador

PCINT en una librería

Como decíamos, hay muchas librerías para gestionar las interrupciones pin change disponibles en el gestor de librarías. Algunos ejemplos son Sodaq_PcInt, PinChangeInterrupt, EnableInterrupt, PciManager.

Personalmente, a mí me gusta la librería YetAnotherArduinoPcIntLibrary, porque es fácil de usar, el código es pequeño y eficiente, y está bien escrita. Además, distingue entre modos RISING/FALLING/CHANGE y permite pasar variables a las funciones de callback de las ISR.

La verdad es que es una maravilla de librería y hace que usar las interrupciones pin change sea tan cómodo como una INT normal. Y aquí tenemos un ejemplo de cómo usar la librería.


#define PCINT_PIN A5

#include <YetAnotherPcInt.h>

void pinChanged(const char* message, bool pinstate) {
  Serial.print(message);
  Serial.println(pinstate ? "HIGH" : "LOW");
}

void setup() {
  Serial.begin(115200);
  pinMode(PCINT_PIN, INPUT_PULLUP);
  PcInt::attachInterrupt(PCINT_PIN, pinChanged, "Pin has changed to ", CHANGE);
}

void loop() {}

¡Más cómodo no puede ser! Así de fácil podemos usar las interrupciones pin change en nuestros proyectos lo que permite, en el caso del Atmega 328p, disponer de interrupciones en todos los pines.

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github. github-full