Hola a todos,
Ahora voy a comentar sobre la implementacion de los temporizadores.
Todos sabemos como hacer una demora de una determinada cantidad de milisegundos, sin duda es lo primero que se aprende para encender y apagar un led
Normalmente se utiliza un enfoque donde mediante un bucle, se gastan ciclos de CPU. Pero que sucede si se necesitan contar varios intervalos de tiempo de forma independiente?
Supongamos que tenemos el siguiente problema, queremos encender y apagar ocho leds conectados al puerto B del pic, pero con diferentes intervalos de tiempo, por ejemplo:
RB0 cada 150 ms
RB1 cada 160 ms.
RB2 cada 170 ms.
RB3 cada 180 ms.
RB4 cada 190 ms.
RB5 cada 200 ms.
RB6 cada 210 ms.
RB7 cada 220 ms.
Una solucion seria usar un temporizador por hardware por cada salida, pero los PIC 16F873 solo disponen de tres timer internos. Por lo que hay que encontrar otra solucion.
Otro problema tambien es la resolucion variable. Los temporizadores cuentan invervalos de tiempo, la resolucion o base de tiempo del temporizador determina la duracion de cada intervalo. En algunos PLCs comerciales se asignan resoluciones fijas de acuerdo al nro del temporizador, por ejemplo para 100 temporizadores, el T0 al T3 tienen resolucion de 1ms, del T4 al T20 tienen 10ms de resolucion y el resto T21 al T99 tienen resolucion de 100ms. En los Siemens S7-200 es así, y de acuerdo a la aplicacion uno debe elegir el temporizador adecuado. Sin embargo los Allen Bradley Micrologix 1500 pueden asignar cualquier resolucion a todos los temporizadores, como lo hacen ? Esa sin duda es una buena caracteristica para nuestro PLC.
Cual es el rango de medicion de los temporizadores? Normalmente cuentan de 0 a 32767. Para poder almacenar este valor de cuenta se necesitan 15 bits (2^15=32768) es decir que se usan dos registros en RAM de ocho bits, en el primero se almacenan los 8 bits menos significativos (LSB) y en el segundo se almacenan los 7 restantes (MSB). Por lo general el bit 8 del MSB se utiliza para almacenar el signo. Es decir que el tipo de datos es int16.
Una solucion al problema anterior es la siguiente, queremos implementar ocho temporizadores T0 al T7, con una resolucion variable de 10ms, de 1seg y de 1 minuto. Para esto utilizo dos temporizador por hardware del PIC: el timer 0 configurado para que genere una interrupcion exactamente cada 10 ms y el timer 1 configurado para que genere una interrupcion exactamente cada 1 seg (1000 ms). (Sin duda esto se podría hacer con un solo temporizador y un contador, ademas no creo que la demora de un minuto se utilize muy seguido, pero bueno es solo una prueba de concepto y seguro se mejorará luego. Por otro lado aumentando la velocidad del cristal a 20 Mhz se podria hacer que la resolucion minima de 10ms baje a 1ms. Otra razon para usar la interrupcion de un segundo es que luego se agrego una rutina de Reloj de Tiempo Real usando el Timer2 del PIC y un cristal de 32.768 Khz, y para esto cada un segundo se debe actualizar el reloj. Seguro que esto tambien se puede hacer con un chip externo dedicado que ademas soportan el corte de energia mediante el uso de una pila).
Luego por cada temporizador asocie seis registros en RAM que controlan su funcionamiento.
Asi para el T0 y el T1 tenemos:
Tim0Config ;Configuracion y banderas del T0.
Tim0Presc ;Prescaler del T0 (multiplicador).
Tim0LCount ;LSB de la Cuenta del T0.
Tim0HCount ;MSB de la Cuenta del T0.
Tim0LPreset ;LSB del Preset del T0.
Tim0HPreset ;MSB del Preset del T0.
Tim1Config ;Configuracion y banderas del T1.
Tim1Presc ;Prescaler del T1 (multiplicador).
Tim1LCount ;LSB de la Cuenta del T1.
Tim1HCount ;MSB de la Cuenta del T1.
Tim1LPreset ;LSB del Preset del T1.
Tim1HPreset ;MSB del Preset del T1.
y asi sucesivamente para el T2 al T7.
El primer registro TimXConfig es un registro de estado y configuracion, y utilizo los bits en forma individual.
;Bits del registro TimConfig:
;- Prescaler (7-6) 00-10 ms, 01-1 seg, 10 10 seg.
;- Tipo de Timer (5-4) 00-TON, 01-TOF, 10-TDR, 11-TAD.
;- Actualizando (3) Se está modificando el temporizador.
;- PLC_AccAnt (2) Almacena el valor anterior de la entrada del temporizador.
;- DN (1) Salida del temporizador.
;- TimerActivado (0) Se está usando el temporizador en el programa de usuario.
Funciona con dos partes, una rutina de servicio de interrupción generada por overflow de los temporizadores y otra rutina que se ejecuta en el programa normal del PIC.
Rutina de interrupción
Cada vez que ocurre una interrupción, se verifica si es la de 10ms, por overflow del timer 0 o la de 1 seg, por overflow del timer 1. Esto luego se marca mediante la variable BandBase10ms utilizada como bandera.
A continuacion, si corresponde, se incrementa el valor de la CUENTA de cada temporizador. Esto se hace en la subrutina "Inc_Timers".
Como la interrupcion puede ocurrir en cualquier momento, existe la posibilidad que justo en ese momento se estén actualizando los datos del temporizador, en el programa normal del PIC, por lo que utilizo el bit 3 de su registro TimConfig (Actualizando) como bandera para indicar esta situacion.
Si esta bandera está activada, significa que el temporizador se está actualizando y no se deben modificar sus datos, por lo que se pasa al siguiente temporizador. En cambio si esta bandera está desactivada se controla si se está utilizando en el programa de usuario, para esto se revisa el bit 0 de su registro TimConfig. Si el temporizador no se utiliza no tiene sentido revisarlo y se pasa al siguiente temporizador.
Si el bit 0 esta activado significa que el temporizador se está utilizando en el programa, por lo que se revisa la resolucion del temporizador.
La resolucion de cada temporizador se configura usando los bits 6 y 7 de su registro de estado TimConfig:
Bit 7 Bit 6
Resolucion 10 ms 0 0
Resolucion 1 seg 0 1
Resolucion 60 seg 1 0
Si ocurrió la interrupcion de 10ms (BandBase10ms=1) y la resolucion del temporizador es de 10 ms, se incrementa su valor de CUENTA en uno.
Si ocurrió la interrupcion de 1seg (BandBase10ms=0) y la resolucion del temporizador es de 1seg, se incrementa su valor de CUENTA en uno.
En cambio si ocurrió la interrupcion de 1 seg (BandBase10ms=0), pero la resolucion del temporizador es de 10seg, se incrementa su valor de PRESCALER en uno. Si el valor de PRESCALER, es igual a 60, se vuelve a 0 y se incrementa su valor de CUENTA en uno. (Ya pasaron 60 segundos).
Luego se verifica si su valor de CUENTA es mayor o igual que su valor de PRESET, de ser asi se activa la salida del temporizador (bit DN del registro de estado TimConfig) y se desactiva el temporizador (esto ultimo no deberia ser asi, en futuras versiones, se deberia dejar el temporizador activado). Un caso especial ocurre si el temporizador esta configurado como Temporizador de Intermitencia TFL, aqui cuando la CUENTA es mayor o igual al PRESET se pone a cero el valor de CUENTA y se cambia de estado la salida (si estaba activada se desactiva y si estaba desactivada se activa).
Esto se realiza por cada temporizador. Se utiliza mucho el direccionamiento indirecto (FSR e INDF) para acceder a los registros individuales de cada temporizador. Una vez que se revisan todos, termina la subrutina y se sale de la interrupcion.
Programa normal del PIC.
Por ejemplo supongamos que el PLC debe ejecutar las siguientes sentencias en Lista de Instrucciones:
LD I0,0 // Leer la entrada I0,0 y copiar su valor al acumulador.
TON T0, 500 // Usar el temporizador T0 como retardo a la conexion con un preset de 500 milisegundos.
LD T0, DN // Cargar el bit DN del temporizador T0 (la salida del temporizador)
OTE Q0, 1 // Si pasan los 500 milisegundos activar la salida Q0, 1
Es decir que si la entrada I0.0 queda activada durante medio segundo, recien se activa la salida Q0, 1.
En el programa de usuario cada vez que se encuentra una instruccion que utilice un temporizador se llama a la subrutina "ActTimers".
Siempre el valor que activa la entrada del temporizador se lee del acumulador, pero tambien es importante conocer si ocurrio un flanco ascendente o un flanco descendente sobre esta entrada. Para poder realizar esto se necesita conocer el valor anterior del acumulador y compararlo contra su valor actual.
Con este fin se utiliza el bit 2 de su registro de estado TimConfig que almacena el estado anterior del acumulador.
Es importante porque cada temporizador puede configurarse de diferente manera como:
TON Retardo a la conexion.
TOF Retardo a la desconexion.
TIP Temporizador de impulso.
TFL Temporizador de intermitencia.
Esta configuracion se realiza usando los bits 5 y 4 del registro de estado TimConfig:
Bit 5 Bit 4
Temporizador TON 0 0
Temporizador TOF 0 1
Temporizador TIP 1 0
Temporizador TFL 1 1
Si el estado actual de la entrada del temporizador es uno y el estado anterior era 0, ocurrió un flanco ascendente. Si el estado actual de la entrada del temporizador es cero y el estado anterior era 1, ocurrio un flanco descendente. Si el estado actual es igual al estado anterior no hubo cambio en la entrada del temporizador. De acuerdo al tipo de temporizador se comporta de manera diferente.
Para un temporizador TON (Retardo a la conexion)
---------------------------------------------------------------------------------
-Si no hubo ningun cambio:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado).
-Si hubo un flanco ascendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 1)
Desactivar la interrupcion mientras se configura el timer (bit 3 del registro de estado=1)
Borrar la salida del temporizador DN (bit 1 del registro de estado=0).
Activar el timer (bit 0 del registro de estado=1).
Configurar el tipo de timer a TON (bits 5 y 4 del registro de estado=00)
Configurar la base de tiempo (bits 6 y 7 del registro de estado) segun corresponda.
Borrar la cuenta del PRESCALER (Registro TimPresc = 0)
Borrar la cuenta del temporizador (Registros TimLCount y TimHCount).
Setear el valor de PRESET del temporizador (Registros TimLPreset y TimHPreset)
Reactivar la interrupcion porque se termino de configurar el timer (bit 3 del registro de estado=0)
-Si hubo un flanco descendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 0)
Desactivar la interrupcion mientras se configura el timer (bit 3 del registro de estado=1)
Borrar la salida del temporizador DN (bit 1 del registro de estado=0).
Desactivar el timer (bit 0 del registro de estado=0).
Reactivar la interrupcion porque se termino de configurar el timer (bit 3 del registro de estado=0)
Para un temporizador TOF (Retardo a la desconexión)
--------------------------------------------------------------------------------------
-Si no hubo ningun cambio:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado).
-Si hubo un flanco ascendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 1)
Desactivar la interrupcion mientras se configura el timer (bit 3 del registro de estado=1)
Borrar la salida del temporizador DN (bit 1 del registro de estado=0).
Desactivar el timer (bit 0 del registro de estado=0).
Reactivar la interrupcion porque se termino de configurar el timer (bit 3 del registro de estado=0)
-Si hubo un flanco descendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 0)
Desactivar la interrupcion mientras se configura el timer (bit 3 del registro de estado=1)
Borrar la salida del temporizador DN (bit 1 del registro de estado=0).
Activar el timer (bit 0 del registro de estado=1).
Configurar el tipo de timer a TOF (bits 5 y 4 del registro de estado=01)
Configurar la base de tiempo (bits 6 y 7 del registro de estado) segun corresponda.
Borrar la cuenta del PRESCALER (Registro TimPresc = 0)
Borrar la cuenta del temporizador (Registros TimLCount y TimHCount).
Setear el valor de PRESET del temporizador (Registros TimLPreset y TimHPreset)
Reactivar la interrupcion porque se termino de configurar el timer (bit 3 del registro de estado=0)
Para un temporizador TIP (Temporizador de Impulso)
-------------------------------------------------------------------------------------
-Si no hubo ningun cambio:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado).
-Si hubo un flanco ascendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 1)
Desactivar la interrupcion mientras se configura el timer (bit 3 del registro de estado=1)
Activar la salida del temporizador DN (bit 1 del registro de estado=1).
Activar el timer (bit 0 del registro de estado=1).
Configurar el tipo de timer a TOF (bits 5 y 4 del registro de estado=10)
Configurar la base de tiempo (bits 6 y 7 del registro de estado) segun corresponda.
Borrar la cuenta del PRESCALER (Registro TimPresc = 0)
Borrar la cuenta del temporizador (Registros TimLCount y TimHCount).
Setear el valor de PRESET del temporizador (Registros TimLPreset y TimHPreset)
Reactivar la interrupcion porque se termino de configurar el timer (bit 3 del registro de estado=0)
-Si hubo un flanco descendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 0)
Para un temporizador TFL (Temporizador de Impulso)
-------------------------------------------------------------------------------------
-Si no hubo ningun cambio:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado).
-Si hubo un flanco ascendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 1)
Desactivar la interrupcion mientras se configura el timer (bit 3 del registro de estado=1)
Desactivar la salida del temporizador DN (bit 1 del registro de estado=1).
Activar el timer (bit 0 del registro de estado=1).
Configurar el tipo de timer a TOF (bits 5 y 4 del registro de estado=11)
Configurar la base de tiempo (bits 6 y 7 del registro de estado) segun corresponda.
Borrar la cuenta del PRESCALER (Registro TimPresc = 0)
Borrar la cuenta del temporizador (Registros TimLCount y TimHCount).
Setear el valor de PRESET del temporizador (Registros TimLPreset y TimHPreset)
Reactivar la interrupcion porque se termino de configurar el timer (bit 3 del registro de estado=0)
-Si hubo un flanco descendente:
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 1)
Desactivar la interrupcion mientras se configura el timer (bit 3 del registro de estado=1)
Borrar la salida del temporizador DN (bit 1 del registro de estado=0).
Desactivar el timer (bit 0 del registro de estado=0).
Reactivar la interrupcion porque se termino de configurar el timer (bit 3 del registro de estado=0)
Guardar el estado actual de la entrada del timer o sea el acumulador (bit 2 del registro de estado = 0)
Bueno, espero que se haya entendido algo, la verdad que me llevo bastante tiempo mejorar la redaccion. Como prueba de concepto y para fines educativos funciona perfectamente logrando implementar ocho temporizadores (T0 al T7) completamente independientes, con resoluciones variables. El problema es el gran consumo de registros en RAM (Seis registros por cada temporizador) lo que nos limita la cantidad de temporizadores disponibles. En comparacion el Siemens S7-200 tiene 256 temporizadores, para poder llegar a esto hace falta conectar una RAM estática externa al PIC, por ejemplo una 62XXX. En realidad la mayoria de los PLC industriales hacen esto. Luego subo unas fotos para que vean.
Saludos a todos y se aprecia cualquier sugerencia.
Tec. Claudio J. Pérez