Interrupciones en todos los pines de Arduino con las PCINT Deja un comentario

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.

Los procesadores como los Atmel Atmega 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.

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.

Activar o desactivar las PCINT

En primer lugar, podemos activar o desactivar las PCINT asociadas a un grupo de pines con el registro PCICR (Pin Change Interrupt Control Register).

Aquí tenemos 3 bits, que controlan la activación o desactivación de las PCINT para cada grupo de pines.

PCICR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
PCIE2 PCIE1 PCIE0

Activar o desactivar para un pin

Una vez activada la PCINT para un grupo de pines, debemos decir que pines del grupo pueden disparar la interrupción. Para eso tenemos los registros PCMSK0, PCMSK1 y PCMSK2 (Pin Change Mask), en los que cada bit indica si el pin dispara o no la PCINT.

PCMSK0

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
PCINT7 PCINT6 PCINT5 PCINT4 PCINT3 PCINT2 PCINT1

PCMSK1

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
PCINT14 PCINT13 PCINT12 PCINT11 PCINT10 PCINT9 PCINT8

PCMSK2

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
PCINT23 PCINT22 PCINT21 PCINT20 PCINT19 PCINT18 PCINT17 PCINT16

Limpiar el registro de flag

Por otro lado, tenemos el registro PCIFR (Pin Change Interrupt Flag Register). Los bits de este registro se activan cada vez que ocurre un cambio en un pin del grupo.

PCIFR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
PCIF2 PCIF1 PCIF0

Para reiniciarlo, tenemos que poner un ‘1’ en el registro correspondiente. Los flag se reinician automáticamente cuando se lanza la ISR asociada.

Definir las ISR

Por último, en el código tenemos que asociar las ISR que queramos emplear. Así, en el caso del Atmega 328p tenemos las funciones

  • ISR (PCINT0_vect) para grupo de pines D8 a D13
  • ISR (PCINT1_vect) para grupo de pines A0 a A5
  • ISR (PCINT2_vect) para grupo de pines D0 a D7

Estas ISR están asociadas, respectivamente, con cada uno de los grupos indicados.

Funcionamiento de la PCINT

Ya tenemos todos los componentes para explicar el funcionamiento de las interrupciones pin change. En modo resumen, cuando se dispara un cambio un pin de uno de los grupos se activa el flag correspondiente en PCIFR.

Si este grupo está activado en el PCICR, el pin que originado el disparo está activado en su PCMSKx, y el grupo tiene su ISR oportuna definida en el código, se dispara la ISR.

Tras la ejecución de la ISR se limpia el registro de flags PCIFR, dejando el sistema listo para recibir otro evento de pin change.

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.

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.

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.

Atmega 128/328p (Arduino Uno y Nano)

Pin Port INT Arduino Pin
2 PD2 INT0 2
3 PD3 INT1 3
Pin Port PCINT Pin Port PCINT Pin Port PCINT
2 PD2 PCINT18 8 PB0 PCINT0 A0 PC0 PCINT8
3 PD3 PCINT19 9 PB1 PCINT1 A1 PC1 PCINT9
4 PD4 PCINT20 10 PB2 PCINT2 A2 PC2 PCINT10
5 PD5 PCINT21 11 PB3 PCINT3 A3 PC3 PCINT11
6 PD6 PCINT22 12 PB4 PCINT4 A4 PC4 PCINT12
7 PD7 PCINT23 13 PB5 PCINT5 A5 PC5 PCINT13

Atmega 32u4 (Arduino Leonardo y Micro)

Pin Port INT
0 PD2 INT2
1 PD3 INT3
2 PD1 INT1
3 PD0 INT0
7 PE6 INT6
Pin Port PCINT
SCK/15 PB1 PCINT1
MOSI/16 PB2 PCINT2
MISO/14 PB3 PCINT3
8/A8 PB4 PCINT4
9/A9 PB5 PCINT5
10/A10 PB6 PCINT6
11 PB7 PCINT7

Atmega2560 (Arduino Mega)

Pin Port INT Arduino Pin
2 PE4 PCINT4 6
3 PE5 PCINT5 7
21 PD0 PCINT0 43
20 PD1 PCINT1 44
19 PD2 PCINT2 45
18 PD3 PCINT3 46
n/c PE6 PCINT6 8 (fake 75)
n/c PE7 PCINT7 9 (fake 76)
Pin Port PCINT Pin Port PCINT Pin Port PCINT
10 PB4 PCINT4 SS PCINT0 PB0 A8 PK0 PCINT16
11 PB5 PCINT5 SCK PCINT1 PB1 A9 PK1 PCINT17
12 PB6 PCINT6 MOSI PCINT2 PB2 A10 PK2 PCINT18
13 PB7 PCINT7 MISO PCINT3 PB3 A11 PK3 PCINT19
14 PJ1 PCINT10 A12 PK4 PCINT20
15 PJ0 PCINT9 A13 PK5 PCINT21
A14 PK6 PCINT22
A15 PK7 PCINT23

Podemos adaptar nuestro código para los distintos procesadores o, mucho mejor, usar una librería que se encargue de ello y nos evite los quebraderos de cabeza como veremos a continuación.

 

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

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.

¡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.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Enviar Whatsapp
Hola 👋
¿En qué podemos ayudarte?