Arduino y las interrupciones: primera aproximación

Cuando un programa tiene que esperar a que ocurra un evento (p.ej. que se pulse un interruptor), tiene dos formas de hacerlo:

  • polling: comprobar el estado del interruptor. Si se activa, hacer lo que sea, y mientras no se active, volver a comprobar. Es fácil de implementar, pero presenta el inconveniente de que, hasta que no se active el interruptor, el código se quedará estancado haciendo siempre lo mismo y sin poder hacer otra cosa.
  • interrupciones: ir a hacer otra cosa (mismamente a dormir, para ahorrar energía), y cuando ocurra el evento que le avisen. El aviso se hace mediante una interrupción: el micro recibe una señal en una patilla y acto seguido ejecuta una función que previamente se la ha indicado.

El uso de interrupciones a veces es conveniente, a veces no, a veces es imprescindible y a veces puede ser horrible, ya que se puede recibir una interrupción en casi cualquier momento (p.ej. mientras estamos procesando otra interrupción).

Un ejemplo con Arduino. El software.

Hay formas de evitarse estos problemas, pero de momento, voy a presentar el uso básico de las interrupciones, y además lo haré desde la perspectiva del IDE de Arduino: con las directrices de Wiring, y no manipulando los registros del AVR.

En este ejemplo, primero configuramos el microcontrolador para hacer algo cuando se reciba una interrupción determinada:

const unsigned short pinInt0=2;
void setup()
{
pinMode(pinInt0, INPUT);
Serial.begin(9600);
attachInterrupt(0,INT0_vector,RISING);
}

Lo más interesante es esto:

interrupción puede ser 0 o 1 para, respectivamente, INT0 o INT1 en Arduino Duemilanove. Arduino Mega dispone de otras cuatro interrupciones más (INT2..5). INT0 se corresponde con el pin digital 2, e INT1 con el 3.

función es la función que se ejecutará cuando se active tal interrupción. A veces se llama rutina de servicio de interrupción (Interruption Service Routine, ISR)

modo es qué se entiende por activación de la interrupción: cuando pasa a estar alta (RISING), cuando cambia de valor (CHANGE), cuando está baja (LOW) o cuando pasa a estar baja (FALLING).

Una vez ejecutemos esa función, cada vez que la patilla digital 2 (INT0) suba de nivel se ejecutará la función INT0_vector:

void INT0_vector()
{
moehren++;
}

que lo que hace es incrementar una variable global:

volatile unsigned short moehren=0;

Está declarada como volatile, para evitar que el compilador la mantenga en los registros del micro. Cada vez que se vaya a utilizar se leerá su valor de memoria, y cada vez que se escriba, se escribirá su valor en memoria.

Y mientras se reciben interrupciones, pues vigilamos a ver si cambia el valor:

void loop()
{
static unsigned short oldMoehren=999;
if(oldMoehren!=moehren) {
Serial.print(“Ich hab neue Moehren gekriegt!! Sie sind schon “);
Serial.println(moehren);
oldMoehren=moehren;
}
delay(600);
}

Si el valor anterior (oldMoehren) es distinto al actual, se manda un mensaje y se actualiza su valor. En cualquier caso, a continuación se espera durante 600ms.

Acabando el ejemplo: el circuito.

Una resistencia (la mía es de 5,6K porque la tenía a mano, pero una de 10K, 1K o valores similares también son válidas) conectada a 5V por un lado y a un interruptor al otro.
El otro extremo del interruptor va conectado a GND. Así, cuando se cierre el interruptor, circulará corriente por la resistencia y por el interruptor.
La resistencia causa una caída de tensión de los 5V. de diferencia que hay, por lo que, cuando el interruptor está abierto, ambos extremos de la resistencia están a 5V. ya que no circula corriente.
Cuando el interruptor se cierra, circula corriente y los 5V. caen en la resistencia, por lo que en el extremo de la resistencia que va conectado al interruptor el voltaje es 0V.
Si finalmente conectamos un cable desde ese punto al pin 2 (correspondiente a INT0), el resultado es que cada vez que el interruptor se abra, el voltaje en ese punto vuelve a subir de 0V. a 5V., pasando la línea INT0 de bajo a alto y lanzando la función INT0_vector().

Y finalmente, la prueba

Su ejecución con el circuito:

Ich hab neue Moehren gekriegt!! Sie sind schon 0
Ich hab neue Moehren gekriegt!! Sie sind schon 1
Ich hab neue Moehren gekriegt!! Sie sind schon 2
Ich hab neue Moehren gekriegt!! Sie sind schon 3
Ich hab neue Moehren gekriegt!! Sie sind schon 6
Ich hab neue Moehren gekriegt!! Sie sind schon 7
Ich hab neue Moehren gekriegt!! Sie sind schon 9
Ich hab neue Moehren gekriegt!! Sie sind schon 10
Ich hab neue Moehren gekriegt!! Sie sind schon 11
Ich hab neue Moehren gekriegt!! Sie sind schon 13
Ich hab neue Moehren gekriegt!! Sie sind schon 15
Ich hab neue Moehren gekriegt!! Sie sind schon 16

Cada vez que se suelta el interruptor, la patilla 2 vuelve a valor alto y se ejecuta la función, incrementando la variable moehren. Posteriormente en la siguiente ejecución de loop() se detectará el cambio de valor y se notificará por el puerto serie.

¿Qué ocurre si hay dos subidas de nivel antes de que se ejecute loop() de nuevo? Pues que el valor que se notifica se habrá incrementado en dos unidades, una por cada subida.

Arduino

Comments (0)

Permalink

Las variables volátiles

Son aquellas variables cuyo valor siempre que se lee, se lee de memoria, y siempre que se escribe, se escribe en memoria. Pasan el mínimo tiempo posible en los registros del microprocesador.
Al declararlas volátiles se le está indicando al compilador que no haga optimizaciones que eviten accesos a memoria para leerlas/escribirlas.

En un código como éste:

moehren+=7;
moehren*=2;

El compilador podría optimizar y generar código que:

  1. lee el valor de la variable de la pila (p.ej. x<-[moehren]) y lo guarda en un registro del micro
  2. suma 7 al registro (x+7)
  3. multiplica el registro por 2 ((x+7)*2)
  4. guarda el registro en la posición de la pila dedicada a la variable moehren ((x+7)*2->[moehren])

Hasta aquí todo en orden, el valor de mohren==(mohren+7)*2 y el número de accesos a memoria se minimiza, lo que (casi) siempre es de agradecer.

Y ahora vamos a suponer, que mientras una sentencia y otra, se recibe una interrupción y se ejecuta entre medias una sentencia:

moehren-=8;

El orden de ejecución sería como sigue:

  1. moehren+=7;
  2. moehren-=8;
  3. moehren*=2;

Con las optimizaciones del compilador esto queda:

  1. lee el valor de la variable (x<-[moehren]) de la pila y lo guarda en un registro del micro
  2. suma 7 al registro (x+7)
  3. salta a la ISR (rutina de servicio de la interrupción, que es, sencillamente, una función que se ejecutará en el medio)
  4. guarda los registros en la pila
  5. lee el valor de la variable de la pila (y<-[moehren]) y lo guarda en un registro del micro
  6. le resta 8 a ese registro (y-8)
  7. guarda ese registro en la posición de la pila dedicada a la variable moehren (y-8 -> [moehren])
  8. restaura los registros guardados en la pila y retorna a donde estaba antes de saltar a la ISR
  9. multiplica el registro por 2 ((x+7)*2)
  10. guarda el registro en la posición de la pila dedicada a la variable moehren ((x+7)*2->[moehren])

Es decir, que el valor de moehren==(moehren+7)*2, como si la ISR no se hubiera ejecutado.

Si por el contratio declaramos moehren como volatile:

volatile int moehren;

Cada vez que se lea/escriba esa variable, se accederá a memoria, lo cual es más lento, pero nos evita situaciones como la anterior.

El orden de ejecución del código generado en un caso similar al ejemplo anterior sería algo así:

  1. lee el valor de la variable (x<-[moehren]) de la pila a un registro
  2. suma 7 al registro (x+7)
  3. guarda el registro en la variable moehren (x+7 -> [moehren])
  4. salta a la ISR
  5. guarda los registros en la pila
  6. lee el valor de la variable (y<-[moehren], o sea y==x+7) de la pila a un registro
  7. le resta 8 a ese registro (y-8, que es lo mismo que x+7-8)
  8. guarda el registro en la variable moehren (y -> [moehren], o bien x+7-8 -> [moehren])
  9. restaura los registros guardados en la pila y retorna a donde estaba antes de saltar a la ISR
  10. lee el valor de la variable (z<-[moehren], z==x+7-8) de la pila a un registro
  11. multiplica el registro por 2 (z*2, (x+7-8)*2)
  12. guarda el registro en la variable moehren (z -> [moehren], o bien (x+7-8)*2 -> [moehren])

El valor que tiene ahora moehren==(moehren-1)*2. Ahora sí que se nota la ejecución de la ISR.

Ojo, porque con esto no se evita la necesidad de las primitivas de sincronización. Nada garantiza que dos procesos no accedan paralelamente y de forma no atómica a la variable en memoria. Si estamos trabajando en un entorno monotarea (un microcontrolador, normalmente), esto nos dará igual.

Lenguajes de Programación

Comments (0)

Permalink

Arduino + microSD: más funciones de la librería

Entre las funciones que aún no hemos visto están:
  • SD.usedBytes(<fichero>), que devuelve un long con los bytes empleados en el fichero. Es decir, los bytes desde el primero hasta el EOF, que para esta librería es el código ASCII 3.
  • SD.del(<fichero>), fija el primer byte del fichero a 0×03, es decir a EOF. Con esto la longitud del fichero pasa a ser 0, por lo que, a ojos de la librería, estará vacío y disponible para escribir. No borra el fichero, sólo su contenido. Y en realidad, tampoco borra todo su contenido: si después del del() y antes de escribir examinamos el fichero, veremos que se mantienen todos los contenidos, excepto el primer byte.
  • SD.startSector(<fichero>), retorna un long con el primer sector del fichero.
Y además, una serie de funciones interactivas que hablan (y escuchan) por el puerto serie, por lo que requieren que esté iniciado:
  • SD.ls(<fichero>), muestra en la terminal cuántos bytes y sectores tiene asignado el fichero, y cuántos bytes está utilizando.
  • SD.cat(<fichero>), vuelca por el puerto serie el contenido de fichero
  • SD.write(<fichero>), escucha por el puerto serie bytes hasta recibir un . (el punto normal y corriente). Guarda todo lo recibido en el fichero. No se hace eco de lo recibido.
  • SD.printEvent(<fichero>), manda un mensaje por el puerto serie informando de qué operación va a hacer o a hecho.
  • SD.append(<fichero>), añade al final del fichero lo recibido por el puerto serie hasta encontrar un punto. Se imprime lo recibido.
Lo mejor para hacerse una idea de cómo funcionan es jugar con el programa que suministran de ejemplo.

Arduino

Comments (0)

Permalink

Arduino + microSD: programación de la librería

Una vez que tenemos instalada la librería, que tenemos una tarjeta que funciona con la librería y que hemos probado que Arduino es realmente capaz de leer/escribir en ella, vamos a hacer nuestros propios programas.

En primer lugar, incluímos la interfaz de la librería:

#include “SDuFAT.h”

Esto nos proporciona un objeto llamado SD para acceder a la tarjeta.

Probablemente usemos el pin digital 8 para alimentar el shield, así que definimos una constante:

const unsigned short pinSDPower=8;

Y en la inicialización nos preocuparemos de alimentar el shield, poniendo a alto el pin 8:

pinMode(pinSDPower, OUTPUT);
digitalWrite(pinSDPower, HIGH);

Y, optativamente, silenciar los mensajes de la librería:

SD.verbose(OFF);

Por defecto, la librería arranca en modo parlanchín, por lo que cada vez que haya un error o haga algo, nos lo comentará mediante el puerto serie (que deberemos haber inicializado). Para activarlo o desactivarlo está el método SD.verbose(estado), siendo estado ON u OFF (constantes definidas en SDuFAT.h)
No he leído en ningún sitio que haya que dejarle tiempo para arrancar y no sé si hay que hacerlo.

Podemos usar el método SD.println(fichero, cadena) para añadir cadena al final de fichero, seguida de un salto de línea:

SD.println(“hola.txt”, “tengo hambre”);

O bien, sin salto de línea usamos la función print():

char pez[]=”quiero bananas”;
char glu[2];
for(int i=0; i glu[0]=pez[i]
glu[1]=0;
SD.print(“hola.txt”, glu);
}
SD.print(“hola.txt”, “\n”);

Los métodos print y println reciben siempre dos punteros a cadena de caracteres: uno para el nombre de fichero y otro para la cadena a imprimir. No se puede imprimir un caracter directamente, ni un entero ni un real. Con la implementación actual sólo se puede una cadena de caracteres.

En imprimir la frase letra por letra (15 bytes, 15 accesos de escritura), Arduino emplea 10 segundos. La velocidad de escritura es muy lenta porque la librería (creo yo, no he mirado la implementación en tanto detalle) debe recorrer todas las estructuras de datos de la FAT almacenadas en la microSD. Por lo que es mejor guardar lo que queramos imprimir en una cadena, y luego imprimir la cadena en el fichero. Además, mantener el número de escrituras al mínimo aumenta la vida de la tarjeta y podría, en mi opinión, ayudar a disminuir el consumo de corriente.

Arduino

Comments (0)

Permalink

Arduino + microSD: instalación de la librería

La gente de Libelium desarrollaron una librería para trabajar con este shield en el año 2008. Al parecer ahora David Cuartielles e Ino Schlaucher han adoptado el proyecto, aunque la realidad es que no hay mucho desarrollo en torno a él.

Una vez descargada, debe descomprimirse sobre el subdirectorio libraries/ de la instalación de arduino, y la próxima vez que iniciemos el IDE podremos utilizar con un simple #include “SDuFAT.h”.

Para probarla desarrollaron un pequeño programa que podemos compilar y subir al arduino. El programa está disponible en el propio ZIP de la librería, bajo el directorio examples/ (/libraries/examples). Lo tengo por aquí un poco modificado, porque había algún mensaje para el usuario que no estaba claro.

Antes de probarla, deberemos formatear la tarjeta microSD en formato FAT, si no lo estaba ya, y copiarle el fichero hola.txt que viene empaquetado con la librería (/libraries/hola.txt). La librería es limitada, por lo que no podemos crear un fichero nuevo, sino que escogemos uno ya creado, que podremos vaciar, añadirle nuevo contenido o leer. El tamaño de este fichero no es modificable por la librería, así que si se intenta escribir más allá del final del fichero, obtendremos un error.

Este programa, una vez quemado en al Arduino y ejecutándose, esperará órdenes del usuario vía el puerto serie. Mismamente con el Monitor de puerto serie del IDE de Arduino (Menú Tools/Serial Monitor, configurado a 9600, aunque en el ejemplo original se usan 19200 baudios) podemos enviar una ‘L’ para que nos muestre el tamaño del fichero hola.txt, y cuánto de él estamos utilizando. Otros comandos nos permiten ver el contenido del fichero, vaciarlo, añadirle contenido a voluntad, etc. La ‘H’ ofrece una breve ayuda.

Arduino

Comments (0)

Permalink