Múltiples lapsus largos de tiempo con una sola interrupción.A.- Conceptos involucrados:Hay ocasiones en que nuestro programa tiene la necesidad de disparar un multitud de eventos cada uno con un lapsus relativamente largos de tiempos distintos, y que además no sabemos cuando ha de dispararse cada uno de ellos.
Puede ser que necesitemos que cierto pin se ponga en alto durante 1 segundo, otro durante 3 segundos y aún otro más que lo haga durante solo alguna fracción se segundo, algunas centenas de milisegundos, y en cualquier relación entre ellos, solapándose entre sí a veces y sin que por ello nuestro programa pueda ni deba esperar a que uno de ellos termine o comience para disparar cualquiera de los otros.
En estas circunstancias nos está totalmente prohibido el usar instrucciones del tipo delay_ms() ya que su uso implica detener toda actividad de nuestro programa hasta que dicho lapsus de tiempo termine de pasar.
La idea es implementar un juego de contadores, de segundos o milisegundos según nos interese la unidad de medida a utilizar, que se decrementen dentro de una interrupción de alguno de los timers de nuestro PIC. Cuando uno de estos contadores alcanza el valor de 0 activa un flag indicándonos que el tiempo ha transcurrido completo.
Para activar entonces uno de estos contadores no tenemos mas que ponerles el valor de las unidades de tiempo que deseamos medir y entonces esperar a que su valor llegue a cero, que nos será indicado mediante el flag correspondiente. Detectamos dicho flag, lo bajamos y podemos proceder a hacer que lo íbamos ha hacer transcurrido el tiempo deseado.
B.- Técnica a Aplicar:Para implementar nuestros disparadores de eventos vamos a utilizar un recurso del que disponen la inmensa mayoría de los PIC's, tanto los de la serie 18F como los 16F: El
TIMER1.
* El TIMER1 es un
contador de 16 bits que se incrementa cada
4 ciclos de Reloj (
FOSC /4). A este tiempo le vamos a llamar
Tick de TIMER1.
Cuando el
TIMER1 alcanza el valor
0xFFFF continúa de nuevo por 0x0000, y así hasta el infinito y mas allá. Cuando pasa de 0xFFFF a 0x0000 genera una Interrupción por Desbordamiento de Timer1 que es la que vamos a utilizar para definir nuestra unidad mínima de cómputo de tiempo.
Si el cristal de cuarzo con el calzamos nuestro PIC es de 20 Mhz, por ejemplo, entonces cada 4 * 1/20.000.000 = 0,0000002 segundos (0.2 microsegundos) se produce un Tick de TIMER1.
Para una vuelta completa del TIMER1, desde 0x0000 hasta 0xFFFF (65536 pasos), ocupa un tiempo total de 0,0000002 * 65.536 = 0,0131072 segundos (13,1072 milisegundos) o sea 65536 Ticks del Timer1.
Para estos tiempos calculados con este cristal en concreto podemos hacer un sencillo cálculo de cuantas interrupciones deben ocurrir para que contemos un segundo transcurrido: 1000 / 13,1072 = 76,2939453125 lo que significa que con 76 interrupciones contaremos 76 * 13,1072 =
996,1472 milisegundos que va a constituir nuestro "segundo" (Para un artículo posterior veremos la técnica necesaria para conseguir un "segundo perfecto").
Así establecemos una constante a la que llamaremos
nTimer1_for_1_second que nos dice cuantas interrupciones de Timer1 necesitamos para computar un segundo y en este caso va a valer
76.
Con distintos Cristales vamos a tener distintos tiempos de disparo y entonces debemos rehacer los cálculos tal como hemos visto anteriormente para obtener el valor correcto de nTimer1_for_1_second.
El núcleo central de nuestra técnica va a estar construida alrededor de la interrupción
#INT_TIMER1 en la que vamos a disponer de dos recursosen uno distintos: El contador de interrupciones propiamente dicho: una variable en RAM a la que llamaremos
nTimer1Overflow y que incrementaremos cada vez que entremos en la interrupción. Y en paralelo un "contador de segundos" que vamos a conseguir comparando nuestro contador de interrupciones
nTimer1Overflow con la constante de
nTimer1_for_1_second, cuando ambos sean iguales sabremos que ha transcurrido un segundo, haremos lo que tengamos que hacer y pondremos a cero
nTimer1Overflow para empezar a contar otro segundo.
Para cada uno de los distintos Disparadores de Evento que implementemos vamos a necesitar un par de variables en RAM: Un contador retrógrado de segundos que tenemos que esperar para disparar el evento al que llamaremos genéricamente
nSECS_for_Event_1 y un flag llamado
flag_for_Event_1 que nos indique que ese lapsus en concreto ha transcurrido.
Disparar un evento consiste en poner a cero su flag, cargar su contador de segundos con el valor que deseemos y esperar a que el flag se ponga a 1.
C.- Implementación en C:Para implementar nuestro
Código en C vamos a hacerlo primero con la estructura de la rutina de servicio de la interrupción y después atacaremos un ejemplo de su uso desde el main().
Las constantes, la RAM y la interrupción queda de la siguiente forma:
// Constantes
const int8 nTimer1_for_1_second=76;
// Variables en RAM
int8 nTimer1Overflow; // Contador de Interrupciones
int8 nSECS_for_Event_1; // Contador Segundos para Evento 1
int1 flag_for_Event_1; // Flag para detectar evento 1
// Interrupción por desbordamiento de Timer1
#int_timer1
void interrupt_service_rutine_timer1(void) {
////////////////////////////////////////////////////////////////////////////////////////////////
// Cómputo de segundos completos (13,1072 milisegundos * _nTimer1_for_1_second = 996,1472 milisegundos)
// Todo lo que se controla dentro de este bloque sólo se computa por segundos completos.
////////////////////////////////////////////////////////////////////////////////////////////////
if(++nTimer1Overflow==nTimer1_for_1_second){
// Control de Evento Número 1
if(nSECS_for_Event_1!=0){ // si hay algo que contar ...
if(--nSECS_for_Event_1==0){ // Descuento un segundo y si he terminado ...
flag_for_Event_1=1; // Activo el flag de notificación del Evento 1.
}
}
// Restauro contador de interrupciones para cómputo de segundos completos
nTimer1Overflow=0;
}
}
Para mostraros un ejemplo de su uso vamos a suponer que tengo que activar .... hum ... un
Relé, por ejemplo, conectado por
PIN_C1 durante
3 segundos cuando recibo un pulso de un
Botón pulsador por
PIN_C0.
void main(void){
// Pongo a cero todas las variables
nTimer1Overflow=0;
nSECS_for_Event_1=0;
flag_for_Event_1=0;
// Habilito las interrupciones
enable_interrupts(int_timer1);
enable_interrupts(global);
// Bucle infinito ...
do{
/////////////////////////////////////////////////////////////////////////////////////////////
// Detecto la pulsación del botón (con antirrebote incluido) y pongo en marcha
// el relé y el Disparador de eventos
/////////////////////////////////////////////////////////////////////////////////////////////
if(!input(PIN_C0)){ // Si pulso el botón ...
delay_ms(125);
if(!input(PIN_C0)){ // Si 125 ms después sigue pulsado el botón ...
delay_ms(125);
if(!input(PIN_C0)){ // Si 250 ms después sigue pulsado el botón ...
output_high(PIN_C1); // Activo lo que deseo activar, o sea mi relé
flag_for_Event_1=0; // Pongo a cero el flag (por si acaso)
nSECS_for_Event_1=3; // Cargo los segundos que deseo esperar
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Detecta fin del lapsus del Evento 1 y
// desactivo el relé
/////////////////////////////////////////////////////////////////////////////////////////////
if(flag_for_Event_1==1){ // Si ha transcurrido el lapsus ...
flag_for_Event_1 =0; // Desactivo la notificación que he recibido
output_low(PIN_C1); // y actúo en consecuencia, desactivo el relé.
}
}while(1);
}
Como fácilmente puede verse ampliar el número de eventos a controlar es solo repetir una y otra vez todas las rutinas donde aparece nSECS_for_Event_X y flag_for_Event_X; todas ellas van a controlar de forma independiente cada evento perfectamente solapados entre si y sin que el programa principal esté esperando "sordo" al resto de tareas que le hemos encomendado.
Hay un
interesante ampliación que consiste en contar también lapsus de solo un número determinado de interrupciones, no solo de segundos completos. Si en lugar de utilizar variables del estilo de
nSECS_for_Event_X las usamos computando en Numero de Interrupciones
nTOF_for_Event_X y en la interrupción hacemos su decremento
fuera del if() de segundos podremos contar en
unidades de tiempo de una sola interrupción, 13,1072 ms en nuestro ejemplo.
Así un disparador de evento que nos avise por ejemplo cuando transcurra un lapsus aproximado de medio segundo debería esperar solo 500 / 13,1072 = 38,14697265625 interrupciones, con lo que con 38 interrupciones nos avisaría transcurridos 498,0736 milisegundos.
Ampliamos nuestro código fuente para contemplar también estos eventos "cortos" y nos queda de la siguiente forma:
// Constantes
const int8 nTimer1_for_1_second=76;
// Variables en RAM
int8 nTimer1Overflow; // Contador de Interrupciones
int8 nSECS_for_Event_1; // Contador Segundos para Evento 1
int1 flag_for_Event_1; // Flag para detectar evento 1
int8 nTOFS_for_Event_2; // Contador Interrupciones para Evento 2
int1 flag_for_Event_2; // Flag para detectar evento 2
// Interrupción por desbordamiento de Timer1
#int_timer1
void interrupt_service_rutine_timer1(void) {
////////////////////////////////////////////////////////////////////////////////////////////////
// Cómputo de segundos completos (13,1072 milisegundos * _nTimer1_for_1_second = 996,1472 milisegundos)
// Todo lo que se controla dentro de este bloque sólo se computa por segundos completos.
////////////////////////////////////////////////////////////////////////////////////////////////
if(++nTimer1Overflow==nTimer1_for_1_second){
// Control de Evento Número 1
if(nSECS_for_Event_1!=0){ // si hay algo que contar ...
if(--nSECS_for_Event_1==0){ // Descuento un segundo y si he terminado ...
flag_for_Event_1=1; // Activo el flag de notificación del Evento 1.
}
}
// Restauro contador de interrupciones para cómputo de segundos completos
nTimer1Overflow=0;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Cómputo de periodos de tiempo individualizados en NTOFS.
// Dentro de este bloque se computan periodos de n x 13,1072 ms.
////////////////////////////////////////////////////////////////////////////////////////////////
// Control de Evento Número 2
if(nTOFS_for_Event_2!=0){
if(--nTOFS_for_Event_2==0){
flag_Event_2=1;
}
}
}
Su tratamiento pues en el main() es idéntico al anterior, salvo que en lugar de cargar segundos cargamos número de interrupciones a esperar.
Bueno, y ya está bien por hoy. Mañana más.