viernes, 21 de diciembre de 2018

ADC Convertidor Analógico Digital del PIC16F

Convertidor Analógico Digital ADC



Gracias por visitar este blog dedicado a la programación de microcontroladores PIC, En esta ocasión veremos como trabaja el modulo conversor analógico a digital(ADC) de un PIC16F, tomaremos como ejemplo el microcontrolador PIC16F887.

El objetivo de esta sección es poder utilizar este modulo para capturar las señales continuas provistas por una gran variedad de sensores del tipo analógico, los cuales permiten medir magnitudes de presión, temperatura, humedad, luminosidad, ruido, etc presentes en nuestro medio.

La programación se realizara utilizando el software MPLABX y el compilador XC8, ambos disponibles para descarga gratuita en la pagina oficial de microchip: https://www.microchip.com/
 

Introducción al modulo ADC.
 
Muchas de las señales eléctricas que se obtienen mediante sensores o transductores poseen una naturaleza analógica, es decir son del tipo continuo y toman infinitos valores a lo largo del tiempo. El interés por convertir una señal analógica en un valor digital surge por varios motivos y necesidades que pueden resumirse en Almacenar, Procesar y Transmitir la información.
Los convertidores ADC son dispositivos electrónicos que establecen una relación biunívoca entre el valor de la señal en su entrada y un número digital obtenido en su salida. La relación se establece en la mayoría de los casos, con la ayuda de una tensión de referencia, existen varias técnicas utilizadas para la conversión que determinan la categoría o tipo de convertidor, algunos ejemplos son:

  • Conversión directa FLASH. Utiliza un comparador para cada rango de voltaje decodificado, aunque la conversión rápida, normalmente la resolución no pasa los 8 bits debido a la gran cantidad de comparadores que se necesitan.
  • Delta-Sigma. Compuesto por la combinación de un conversor FLASH y filtros que eliminan la señal de ruido no deseada e incrementan la resolución de salida.
  • Codificación Delta. Utiliza un contador asociado a un DAC(convertidor Digital-Analógico) la salida del DAC es comparada con la señal de entrada para determinar el valor, estos convertidores poseen rangos amplios y alta resolución, pero el tiempo de conversión depende del nivel de la señal de entrada.
  • Comparador tipo Rampa. También conocido como integrador o doble rampa, porque utiliza una señal diente de sierra para efectuar la comparación y captura de la entrada, estos convertidores requieren una menor cantidad de transistores y comparadores pero son sensibles a la temperatura debido a las variaciones del oscilador.
  • Aproximación Sucesiva. Utiliza un comparador para rechazar rangos de voltaje en forma sucesiva hasta encontrar el valor final, realizan una búsqueda binaria donde la primera comparación debe decidir cual es bit mas significativo de la salida, luego la siguiente comparación decide el siguiente bit y así hasta llegar al bit menos significativo, poseen una buena velocidad y resolución.

Los PIC16F de gama media incorporan un módulo ADC de aproximaciones sucesivas con una resolución de 10 bits, es decir puede cuantificar una señal analógica en 1024 posibles valores, el modulo ADC de un PIC utiliza varios canales designados como ANx que están asociados a un pin de entrada, por ejemplo en el PIC16F887 cuenta con 13 canales distribuidos de la siguiente manera:
Fig1. Canales ANx asociado a pines E/S

Cada pin ANx asociado a un canal del ADC deberá ser configurado en modo analógico y como entrada para poder leer señales continuas dentro de las referencias establecidas.
La configuración del modo se realiza mediante los registros ANSELH:L descritos a continuación:

Fig2. Registro de configuracion ANx

Algunos modelos no disponen de los 14 canales en su totalidad, por ejemplo el PIC16F886 solo cuentan con 11, el PIC16F873 tan solo 5. Por esa razón es importante que revise la hoja de datos del modelo que va utilizar.

Nota. Los PIC16F87x y algunos otros modelos mas antiguos no llevan registros ANSEL, en su lugar utilizan unos bits PCFG en el registro ADCON1.


Operación del modulo ADC

Para comprender como funciona el modulo ADC del micro-controlador PIC16F, haremos una breve explicación en base al siguiente diagrama que representa la composición interna de un PIC16F88x.
 
Fig3. Esquema del modulo ADC PIC16F88x


El modulo ADC puede hacer la lectura por cualquiera de los canales disponibles gracias a un multiplexor, el canal es seleccionado uno por vez mediante los bis CHS del registro ADCON0, la conversión inicia cuando se activa el bit GO siendo necesario esperar un tiempo hasta completar todo el proceso, una vez finalizada la conversión el bit GO se reiniciara automáticamente, activándose también la bandera de interrupción ADIF. El resultado de la conversion es un numero binario de 10 bits(0-1023) que se encuentra almacenado en el par de registros ADRESH y ADRESL con la siguiente alineación: 

Fig4. Formato del resultado binario

La conversión digital se realiza dentro de las referencias establecidas en la configuración del modulo, por defecto estas referencias corresponden con valores de GND para la referencia negativa y VCC para la positiva, pero es posible utilizar referencias externas a través de los pines AN2/VREF- y AN3/VREF+. La configuracion de la referencias del modulo ADC permiten determinar la sensibilidad de cambio por cada bit aplicando la siguiente formula:
Por ejemplo si las referencias utilizadas son Ref- = 0 y Ref+ = 5V la sensibilidad de cambio sera de (5V-0V)/1024 bit = 0.0049V/bit, es decir 4.9mV/bit, con este valor sera posible encontrar la amplitud de una señal multiplicándolo por el numero binario resultante del conversor.

El uso de voltajes de referencia externas posibilitan una mejor adaptación del conversor ADC a las características de sensor a medir, pero esta configuracion debe ser realizada conforme a la hoja de datos proporcionado por el fabricante. En el caso del PIC16F887, con relación a este punto la sección 17, tabla 10, parámetro AD06 indica que el voltaje de referencia mínimo es de 2.7V.

La configuración inicial del modulo se realiza mediante los registros ADCON0:1 a continuación se muestra la descripción de los bits que por defecto están con un valor 0.

Fig5. Registros de configuración ADC

Un parámetro de gran importancia es el tiempo de adquisición por bit definido como TAD, dentro de las especificaciones eléctricas del PIC16F887, nos indica que este valor debe ser entre 1.6us – 9us cuando el voltaje de referencia sea mayor igual a 3.0V. Los dos bits ADCS del registro ADCON0 permiten seleccionar el valor TAD mas adecuado para la frecuencia del oscilador, la opción FRC con un TAD de 4us6us, solo se recomienda cuando le frecuencia del oscilador no supera el 1MHz y cuando se opere en modo Sleep.
Como ejemplo para nuestro caso, el microcontrolador utiliza el oscilador interno de 4MHz, por lo tanto el valor de los bits ADCS adecuados para esta frecuencia sera 01(Fosc/8), con un TAD de 2us, recuerde que el tiempo es la inversa de frecuencia (1/[Fosc/8]), la validación correcta es porque los 2us se encuentra dentro del rango  de 1.6us – 9us.
 
Secuencia de conversión

La siguiente figura describe el tiempo de conversion total desde que se mide la magnitud de la señal analógica hasta que se obtiene el numero binario de 10-bits.

Fig6. Tiempo de conversion total

Tiempo de Adquisición TACQ: Representa la suma de los siguientes tiempos
  • TAMP. Tiempo de ajuste del amplificador, el valor se indica en la hoja de datos, el PIC16F887 posee un valor nominal de 2uS y un máximo de 5uS.
  • TC. Tiempo de carga del condensador de retención(CHOLD), que se calcula en base al modelo del circuito de entrada descrito a continuación:
Fig7. Modelo de la entrada ADC
 
Para calcular el TC se utiliza la siguiente ecuación:
- En el modelo los valores de CHOLD y RIC están dados.      
- Se debe estimar el valor de la impedancia de entrada RS
- La resistencia RSS se obtiene de la siguiente tabla.           
Fig8. Valor de resistencia de muestro Rss
  • TCOFF. Coeficiente de Temperatura es calculado con la siguiente ecuación:

Considerando las anteriores ecuaciones, la formula para calcular el tiempo de adquisición total sera:

Ahora nos queda definir los valores de la impedancia de entrada RS y la temperatura de ambiente TAMB, pero estos valores no son fijos porque el sensor o transductor conectado a la entrada no mantendrá fija la impedancia salvo que siempre se mida un valor constante y pasa lo mismo con la temperatura del ambiente, por esa razón se definen limites de operación máximos de ambos.

Por ejemplo si la impedancia de entrada no va ser mayor a 10k, entonces RS=10k y si la temperatura no va supera los 50°C, entonces TAMB = 50°C. Reemplazando estos valores en la ecuación:


Si la impedancia RS es menor a 10k, el tiempo de adquisición sera menor al calculado en la ecuación, y por lo tanto se garantizaran los mínimos del tiempo.
En el caso de que la impedancia RS supere los 10k utilizados en la ecuación,  el resultado final se vera afectado debido a la reducción de corriente que ingresa al canal, y se necesitara mas tiempo para completar la carga del condensador de retención.
La ecuación descrita con anterioridad, sustenta matemáticamente los parámetros que influyen en el tiempo de adquisición TACQ. Para no complicar el proceso de configuracion del modulo ADC, en la practica este valor se obtendrá directamente de la hoja de datos, en el caso del PIC16F887, lo encontramos en la sección 17, la tabla 17-10, donde se indica un valor TACQ = 11.5uS.

Tiempo de Conversión:  Cuando el condensador de retención esta cargado es posible dar inicio a la conversión, para esto se deberá desconectar la entrada analógica e iniciar la secuencia de captura con el bit mas significativo hasta completar los 10 bits, en total el tiempo de conversión toma 11xTAD.
La espera de este tiempo se apoya en uso de bits ADIF y GO que indican el final de la conversión.

Tiempo de Espera:Se debe esperar un tiempo de 2TAD, después de completar la conversion, o cuando el proceso finalice antes de completar la conversion, en este tiempo el condensador de retención permanece desconectado de la entrada.
 
Esquema del circuito
La figura 9 muestra el esquema de circuito utilizado en el desarrollo de nuestro del programa para el PIC16F887, con el objetivo de llevar a cabo mediciones de voltaje utilizando el modulo ADC, El esquema esta simplificado para mostrar únicamente las conexiones a los canales AN0/AN3 y los pines de comunicación TXD/RXD, se da por hecho las conexiones requeridas para la operación del microcontrolador, como ser fuente de alimentación, circuito de reinicio y programación..

El canal AN0 tendrá conectado un potenciómetro variable de 10k, actuando como un divisor de tensión controlado manualmente. El canal AN13 estará conectado a un MCP1700A, que es un sensor lineal de temperatura, teniendo como valor de salida 500mV a 0 °C (grados centígrados), y una sensibilidad de 10mV/°C.

Fig9. Esquema del circuito

La configuracion utilizara los voltajes de referencia por defecto que son VCC y GND, con esta condición y considerando los 10 bits que tiene el ADC del PIC, la sensibilidad en mV por bit se calcula como: (Vref+ - Vref-) / 2^10-bits = (5V - 0V) / 1024 = 4.88 mV.

Por otro lado la comunicación serial con la PC, utiliza un convertidor USB a UART, en nuestro caso es un CP-2102, pero puede ser cualquier otro conversor, ya que solo requiere un par de conexiones a los pines TXD y RXD del PIC16F, a través de este medio se enviara un mensaje cada segundo con el resultado digital producto de la conversion realizada a los dos canales del circuito.


Configuración y Lectura de datos.

Con todo lo visto hasta este punto, pondremos en practica mediante la programación, la configuracion y uso del modulo ADC del PIC16F887. Los procedimientos y funciones utilizados se encuentran en un solo archivo main.c por lo que describiremos su estructura al inicio utilizando comentarios dentro de la codificación.

Inicialmente se describe el procedimiento de configuración de los puertos, y los modulo requeridos para las siguientes operaciones:

  • Modulo ADC: Para la captura de señal analógica provenientes de un divisor de tensión y un sensor de temperatura.
  • Modulo USART: Utilizado para enviar la informacion con el resultado de las lecturas para el divisor y sensor de temperatura
  • Modulo TIMER0: Permite el control de tiempo mediante interrupciones.
void setup()
{
OSCCONbits.IRCF = 0b110; //Ajusta INTOSC=4MHz
while(OSCCONbits.HTS == 0) {};
ANSEL = 0; //Desactiva los canales AN0-7
ANSELH = 0; //Desactiva los canales AN8-13
TRISAbits.TRISA0 = 1; //Pin RA0/AN0 como entrada
TRISBbits.TRISB5 = 1; //Pin RB5/AN13 como entrada
ANSELbits.ANS0 = 1; //Activa canal AN0
ANSELHbits.ANS13 = 1;//Activa canal AN13
ADCON0bits.ADCS = 0b01; //TAD=2us (Fosc/8)
ADCON0bits.ADON = 0; //Desactiva el modulo ADC
/* CONFIGURA USART A 9600 BPS*/
BAUDCTLbits.BRG16 = 0; //8-bit BaudGen
TXSTAbits.BRGH = 1; //Alta del Generador
TXSTAbits.TXEN = 1; //Activa el transmisor
RCSTAbits.SPEN = 1; //Habilita el modulo USART
SPBRG = 25; //Formula [4M/(16 * 9600)] - 1
/* CONFIGURACION TIMER0 1MS*/
OPTION_REGbits.T0CS = 0;//Modo Termporizador
OPTION_REGbits.PSA = 0; //Con prescala
OPTION_REGbits.PS = 0b010; //Prescala 1:8
TMR0 = 130; //255-(time/((pre)*(4/Fosc))) time=0.001s
INTCONbits.T0IF = 0; //Limpia bandera
INTCONbits.T0IE = 1; //Activa interrupción del TMR0
INTCONbits.GIE = 1; //Habilita las interrupciones
}
  
El código descrito a continuación corresponde a la función que permite realizara la lectura del canal analógico seleccionado, el resultado es un numero entero de 16-bit de los cuales solo 10-bit corresponden con la magnitud medida.
 
unsigned int ADCRead()
{
    unsigned int res;
    ADCON0bits.ADON = 1;
//Activa el ADC
    _delay(12);
//Espera tiempo de Adquisición 11.5 us.
    ADCON0bits.GO = 1;
//Inicia conversion
    while(ADCON0bits.GO) {};
//Espera fin de conversion
    res = ADRESH;
//Recupera los 8-bits de mas peso
    res = res << 8;
//Desplaza 8 bits a la izquierda
    res = res | ADRESL;
//Recupera los 2-bits de menor peso
    res = res >> 6;
//Desplaza 6 bits a la derecha
    ADCON0bits.ADON = 0;
//Desactiva el ADC
    return res;
}
 
También se describe el código de la rutina de interrupción ISR en la que se observa el control de eventos por milisegundo utilizando el modulo TMR0.

void __interrupt() isr() //Rutina de interrupción
{
    if(INTCONbits.T0IF)
//Activa cada 1ms
     {
        tick1ms = 1;
//Activa bandera 1ms
        INTCONbits.T0IF = 0; 
//Limpia bandera
        TMR0 = 130;
//Reinicia contador        
    }
}


Finalmente se observa un procedimiento necesario para las funciones de salida que son parte de la librería stdio del lenguaje C. esta referencia es necesaria para la ejecución de printf.

void putch(char byte) //requerido por <stdio.h>
{
    while(PIR1bits.TXIF == 0) {};
    TXREG = byte;
}

Funcionamiento del circuito
En este punto debemos contar con un circuito físico ya implementado para nuestras pruebas, también es posible utilizar un simulador como ISIS Proteus.
En particular he preferido llevar a cabo las pruebas en circuito físico utilizando una placa de pruebas y efectuado las conexiones mínimas sobre la misma, tal como se observa en la figura 10.



Fig10, Circuito de prueba PIC16F

Tomaremos el siguiente circuito como ejemplo para hacer la lectura de dos potenciómetros conectados a los canales AN0 y AN13, el resultado de la conversion se enviara al puerto seria como un mensaje cada segundo.

Entonces el procedimiento principal de programa, sera el siguiente código

 

#pragma config FOSC = INTRC_NOCLKOUT, WDTE = OFF, LVP = OFF
#include <xc.h>
#include <stdio.h>
unsigned int adres1, adres2, onesec = 0;
volatile char tick1ms;
  void main()
{
    setup();
    while(1)
    {
        if(tick1ms == 1)
//Verifica bandera de 1ms
        {
            tick1ms = 0;
//Limpia bandera de 1ms
            onesec++;
//incrementa contador de ms
            if(onesec > 999)
//Se cumple cada segundo
            {
               onesec = 0;
//Limpia contador
               ADCON0bits.CHS = 0;
//Selecciona Canal 0
               adres1 = ADCRead();
//Lectura ADC
               ADCON0bits.CHS = 13;
//Selecciona Canal 13
               adres2 = ADCRead();
//Lectura ADC
               printf("AN0:%u AN1:%u\r\n", adres1, adres2);
            }
        }
    }
    
}

 
Una vez cargado el programa a la memoria del PIC16F y conectado a la alimentación, podremos ver los mensajes enviados cada segundo con la informacion del resultado lectura en los canales 0 y 13. La lectura de estos mensajes se realiza utilizando un software para leer el puerto serial, también conocido como terminal serial. Yo utilice en este caso HTerm, un programa gratuito y multi-plataforma que puedes descargar con el siguiente enlace: <<HTERM>>.

Fig11. Mensajes recibidos por la terminal serial

Como analisis de la informacion que muestra la terminal, se describe el siguiente resultado:
El canal 0, producto de la variación manual aplicado al potenciómetro va desde un mínimo AN:0 hasta un máximo AN;1022.
El canal 13, que en este caso se muestra como AN1 se observa un valor casi constante de AN1:165, este valor considerando la sensibilidad de 4.88mV/bit equivale a un voltaje de 806mV (165 * 0.00488). Ahora bien si el canal 13 tiene conectado un sensor de temperatura, ¿Que representa este resultado de 806mV?. Para responder esta cuestión sera necesario conocer las características del sensor, en este caso el MCP9700A, es un sensor lineal de temperatura con las siguientes características resumidas de la hoja de datos:
  • Rango de operación de -40°C a +150°C,
  • Relación de salida de 10mV/°C
  • Voltaje de salida a 0°C de 500mV.
Con esta informacion del sensor ya podemos responder a la pregunta, llevando a cabo el siguiente cálculo: La salida de 500mV, corresponde a los 0°C, entonces de los 806mV del canal 13 , el voltaje de la temperatura medida es 306mV (806 - 500). Estos 306mV se convertirán en grados °C, dividiendo entre la relación de salida del sensor, lo cual nos da un resultado final de 30.6°C (306mV/10mV).
 
Conclusiones y Recomendaciones
Se debe tomar en cuenta lo siguiente:
  • El voltaje de referencia no debe ser inferior a 2.7V, ver hoja de datos;
  • El tiempo de adquisición por bit TAD, se ajusta de acuerdo a la frecuencia de operación y el rango sera de 1.6 - 6us, ver hoja de datos;
  • La impedancia de salida de la fuente de señal analógica no debe superar los 10k.
En una aplicación real se requiere asociar el resultado de la conversión a una cantidad medible en unidades establecidas que permitan aplicar algún tipo de control al sistema, para este fin se necesita adecuar la señal del sensor con la finalidad de lograr un mejor resultado.

Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.
Atte. Pablo Zárate Arancibia
email: pablinza@me.com / pablinzte@gmail.com, @pablinzar
Santa Cruz de la Sierra - Bolivia

jueves, 6 de diciembre de 2018

TMR1 Contador Temporizador del PIC16F

Utilizando el modulo TMR1 del PIC16F



Gracias por visitar este blog dedicado a la electrónica y en particular a la programación de microcontroladores PIC,  En esta ocasión aprenderemos a manejar el modulo TMR1 o TIMER1 del microcontrolador haciendo uso de los registros.

El objetivo de esta sección es poder utilizar este interesante modulo para generar rutinas de retardo en tiempo con una gran precisión, contar pulsos externos y sobre todo hacer un uso adecuado de este recurso logrando integrar la funcionalidad que ofrece a nuestro programa principal.

La programación se realizara utilizando el compilador XC8 y el entorno MPLABX disponibles en la pagina de microchip de forma gratuita, ademas también necesitara de los siguientes archivos peripheral.h y peripheral.c que contienen procedimientos y funciones básicas para el PIC16F887, puede prescindir de ellos o incluso modificarlos a su criterio si posee conocimientos sobre los registros SFR y sus respectivo bits.

Parte 1. Introducción al Modulo TMR1.

A menudo nos encontramos en la necesidad de manejar el tiempo con un mayor grado de precisión y confiabilidad, para lo cual es ideal que un micro-controlador disponga de recursos de hardware que ayuden con esta tarea, en un PIC este recurso se conoce como modulo TIMER y puede cumplir la función de contador o temporizador dependiendo de su diseño.

La serie de micro-controladores PIC16F, normalmente integra 3 módulos designados como:

  • TMR0: Temporizador y Contador de 8 bits (Todos los modelos)
  • TMR1: Temporizador y Contador de 16 bits (Revisar modelo)
  • TMR2: Temporizador de 8 bits(Revisar modelo)

El principio básico de funcionamiento es similar en los tres modelos, pero presentan particularidades únicas que podrían adaptarse mejor a ciertas aplicaciones en nuestro programa.

De acuerdo a la entrada de este blog nos enfocaremos en como utilizar modulo TMR1, para ello simplificaremos su composición en tres etapas que se ilustran en la siguiente figura:



En la primera etapa (Modo) se debe definir si el modulo actuara como un Contador o Temporizador.
En un Contador los pulsos digitales provienen de forma externa ingresando por el pin T1CKI (pin RC0 en el PIC16F88x), el modulo capturara estos pulsos que podrían llegar en cualquier instante y a cualquier frecuencia. En un Temporizador los pulsos se generan desde el reloj del sistema, su frecuencia siempre sera un cuarto de la frecuencia del oscilador Fosc/4, el modulo captura estos pulsos internos que tienen un periodo igual al ciclo de maquina(4*Tosc) y servirán para determinar el tiempo transcurrido.

También es posible conectar un crystal de baja frecuencia 32.768KHz a los pines T1OSO y T1OSI para generar retardos de tiempo incluso cuando el PIC ingresa en modo de bajo consumo(sleep).

La segunda etapa(Pre-escala), aqui los pulsos capturados ingresan a un divisor que conforme al ajuste en su relación pueden dividir la frecuencia en 4 diferentes pre-escalas.
Por ejemplo para una pre-escala 1:4, la salida del divisor genera un pulso cada vez que se capturan cuatro pulsos.

La tercera etapa(registro Contador), finalmente los pulsos que salen del divisor incrementan un registro contador de 16 bits formado por los registros TMR1H(bits superiores) y TMR1L(bits inferiores), el rango de este contador sera de 0 a 65535 (0000h-FFFFh).
Es posible leer y escribir este registro en cualquier momento para calcular el tiempo transcurrido o contar el total de pulsos capturados en el pin T1CKI.

Cuando este registro contador esta en su limite de 65535, un incremento adicional provocara una condición conocida como desbordamiento que consiste en que el registro contador se reinicia con su valor inicial 0 y la bandera TMR1IF se activa para generar una interrupción si esta opción esta habilitada.

La configuración del modulo TMR1 se realiza mediante el registro T1CON que se describe en la siguiente imagen.


El modulo TMR1 posee otras particularidades que no se detallan en este blog, si desea conocer mejor las prestaciones de este recurso deberá consultar la hoja de datos.

Parte 2. Implementar un Contador.

Al implementar un contador lo que se busca es capturar pulsos externos que se generan desde un sensor, un oscilador externo o simplemente un pulsador que emite cambios de estado que deben contabilizarse en nuestra aplicación, para este fin veamos el siguiente ejemplo:

Si nuestra aplicación necesita detectar 700 pulsos externos que pueden darse en cuestión de segundos, horas, días, etc. debemos considerar los siguientes pasos:
  • Se debe seleccionar el modo contador para que los pulsos externos ingresen por el pin T1CKI.
  • Como la capacidad del registro contador TMR1H:TMR1L es de 0 a 65535, se seleccionara una pre-escala 1:1 para capturar los 700 pulsos. 
  • Para que la bandera TMR1IF se active al momento de captura los 700 pulsos, debemos ajustar el registro contador a un valor de 65536 - 700 para que se genere un desbordamiento cuando ingrese el total de pulsos.
  • Se debe reiniciar la bandera TMR1IF y colocar en marcha el modulo TMR1.
  • La rutina deberá esperar hasta que la bandera TMR1 se active.
Ahora realizaremos la programación necesaria para los pasos descritos anteriormente. 

    T1CONbits.TMR1CS = 1; //Modo contador
    T1CONbits.T1CKPS = 0b00; //Ajuste pre-escala 1:1
    TMR1H = 0xFD; //65536-700 = 64836 o FD44h
    TMR1L = 0x44;
    PIR1bits.TMR1IF = 0;; //Limpia la bandera
    T1CONbits.TMR1ON = 1; //Arranca la captura
    while(PIR1bits.TMR1IF == 0); //Espera hasta capturar los 700 pulsos
    TMR1ON = 0; //Para la captura
      //** 700 Pulsos capturados **// 

Ahora si nuestra aplicación requiere contar 500000 pulsos externos, notara que este excede el máximo valor del registro contador(65535), aquí la solución seria aplicar una pre-escala que permita obtener un múltiplo exacto, por ejemplo si dividimos los 5000000 entre 8 obtenemos como resultado 62500, entonces ajustaremos el registro contador a 65536 - 62500 con pre-escala 1:8 y nuestra aplicación detectara la captura de los 500000 pulsos cuando ocurran el desbordamiento.



El ajuste del contador para una determinada cantidad de pulsos externos obedece al siguiente calculo donde la pre-escala deberá ajustarse para que el valor nunca exceda los 65536, en caso de no lograr un valor inferior la implementación solo sera posible con apoyo de variables en la programación.



Parte 3. Implementar un Temporizador.

El objetivo final de implementar un temporizador es calcular con una gran precisión un lapso o periodo de tiempo transcurrido, brindando la posibilidad de utilizar retardos, pausas controladas, generar de señales, alarmas y recordatorios en nuestra aplicación.

En este modo el modulo captura pulsos internos que poseen un tiempo de duración equivalente a un ciclo de maquina o instrucción Tcy, para determinar el valor adecuado del registro contador utilizaremos la siguiente ecuación, donde n representa el tiempo que se requiere en segundos y se debe seleccionar una pre-escala adecuada donde el resultado no supere el limite de 65535.



Por ejemplo si necesitamos generar un retardo de una décima de segundo (0.1), antes de realizar el calculo se debe conocer la frecuencia del oscilador y seleccionar un valor de pre-escala adecuada, en nuestro caso si la frecuencia es 4MHz y usamos una pre-escala 1:4, aplicamos el siguiente calculo:



Veamos como implementar la programación de este ejemplo en las siguientes lineas.

    T1CONbits.TMR1CS = 0; //Modo temporizador
    T1CONbits.T1CKPS = 0b10; //Ajuste pre-escala 1:4
    TMR1H = 0x9E; //65536-2500 = 40536 o 9E58h
    TMR1L = 0x58;
    PIR1bits.TMR1IF = 0;; //Limpia la bandera
    T1CONbits.TMR1ON = 1; //Arranca la captura
    while(PIR1bits.TMR1IF == 0); //Espera los 50ms
    TMR1ON = 0; //Para la captura
      //** Tiempo transcurrido de 50ms **// 



Parte 4. Rutinas y uso de interrupciones

A fin de facilitar la rápida implementacion del modulo TMR1 en nuestra aplicación, se crearon los siguientes procedimientos y funciones incluidas en el archivo peripheral.c

Para la configuración inicial del modulo utilizaremos el siguiente procedimiento, donde los parámetros cs y pre deberán ser asignados al momento de la llamada.
    
    void TMR1Setup(char cs, char pre)
    {
      T1CONbits.TMR1CS = cs;
      T1CONbits.T1CKPS = pre;
      TMR1H = 0;
      TMR1L = 0;
      PIR1bits.TMR1IF = 0;
    }

Por ejemplo si necesitamos configurar el modulo como contador con una pre-escala de 1:4 debemos hacer la llamada así TMR1Setup(COUNTER, T1PRE4).

El siguiente procedimiento permite ajustar el valor del registro contador TMR1H:TMR1L, como parámetro se utiliza una variable(val) de 16 bits con la que se iniciara el registro contador, en el archivo peripheral.h se implementa este procedimiento como una definición.

    void TMR1Setval(unsigned int value)
    {
      TMR1L = value;
      TMR1H = value >> 8;
    }

También es posible leer en cualquier instante el registro contador TMR1H:TMR1L, utilizando la siguiente función que nos retornara un valor de 16 bits correspondiente al contador.  
    
    unsigned int TMR1Getval()
    {
      unsigned int value;
      value = TMR1H;
      value = value << 8;
      value |= TMR1L; 
      return value;

    }


Seguidamente se listan algunas definiciones empleadas en el uso de los procedimientos y funciones descritas anteriormente. 

    #define T1PRE8 3  //Definición de pre-escala 1:8
    #define T1PRE4 2  //Definición de pre-escala 1:4
    #define T1PRE2 1  //Definición de pre-escala 1:2
    #define T1PRE1 0  //Definición de pre-escala 1:1
    #define TMR1Start() T1CONbits.TMR1ON = 1 //Arranca el modulo TMR1
    #define TMR1Stop() T1CONbits.TMR1ON = 0 //Para el modulo TMR1

Es posible hacer uso de las interrupciones como un recurso que nos permite generar y capturar pulsos en un segundo plano, es decir fuera del programa principal, para esto se crea una rutina de interrupción que se ejecutara solo cuando ocurra un desbordamiento, esta rutina debe re-configurar los valores necesarios para una nueva captura y retornar al programa principal en el menor tiempo posible.

Los bits que deben activarse para esta funcionalidad son:
  • TMR1IE Habilitador de la interrupción por desbordamiento del TMR1
  • PEIE Habilitador de las interrupciones generadas por los periféricos(TMR1, TMR2, etc)
  • GIE Habilitador de las interrupciones en el PIC
En la rutina de interrupción se verificara la bandera correspondiente para atender al evento, en caso de repetir nuevamente las tareas asignadas, la bandera deberá limpiarse antes de salir de la rutina, para evitar que la interrupción reingrese por la misma condición.
    
    void interrupt isr()
    {
      if(PIR1bits.TMR1IF == 1)
      {
         PIR1bits.TMR1IF = 0; //Limpia la bandera
         //** Ajustar TMR1 para una nueva captura**//
      }
    }
    
Parte 5. Prueba y Simulación.

Si llegaste hasta este punto y lograste comprender el funcionamiento del modulo TMR1, ahora tomaremos un par de ejemplos que nos darán mayores luces de como aprovechar este recurso en nuestra aplicación. 

Ejemplo A. Tomando como referencia el circuito del diagrama inferior, se requiere elaborar un programa que destelle el LED1 cambiando de estado cada décima de segundo(100ms), por otra parte si el pulsador BUT1 es presionado, el LED2 deberá activarse por 1 segundo.


Ahora veamos el siguiente programa elaborado de manera habitual solo con fines de aprendizaje, este código inicial radica en crear un bucle principal en la cual se destella el LED1 y también se procede a la lectura del pulsador BUT1 sobre el mismo hilo o flujo.

#pragma config FOSC = INTRCIO, WDTE = OFF
#include <xc.h>
#define _XTAL_FREQ 4000000
#include "peripheral.h"
#define pinBUT1 PORTBbits.RB4 //Define el pin RB4 para el pulsador
#define pinLED1 PORTAbits.RA4 //Define el pin RA4 para el LED1
#define pinLED2 PORTAbits.RA5 //Define el pin RA5 para el LED2
char estadoLED1; //Variable temporal para el estado del LED1
void main()
{
    ANSEL = 0; //Deshabilita los puertos AN0-7
    ANSELH = 0;//Deshabilita los pines AN8-13
    TRISA = 0; //Configura los pines del PORTA como salida
    TRISBbits.TRISB4 = 1; //Configura el pin RB4 como entrada
    EnablePU(); //Activa las resistencias Pull-up del PORTA&B
    while(1)
    {
        __delay_ms(100); //Retardo para el destello del LED1
        estadoLED1 = !estadoLED1; //Cambia de lógica el valor
        pinLED1 = estadoLED1; //Actualiza el estado del LED1
        if(pinBUT1 == 0)//Si esta presionado el pulsador BUT1
        {
            pinLED2 = 1; //Activa el LED2
            __delay_ms(1000);//Espera un segundo
            pinLED2 = 0; //Apaga el LED2
        }
    }
}


Si corremos este programa y no presionamos el pulsador, el LED1 cambiara de estado según lo requerido(100ms). Si en cualquier instante se presiona el pulsador BUT1 sucede lo siguiente:
  • Se activara por la condición el LED2 con una espera de 1 segundo para luego apagarse.
  • El LED1 no destellara mientras dure la espera del LED2.
Sucede que al encontrarse las dos tareas en el mismo flujo o hilo del programa, la ejecución de una tarea(destellar el led) afectara en tiempo a la segunda(leer el pulsador y cambiar el led).

Aunque es posible modificar la programación en el bucle para mejorar la condición de ambas tareas, una manera practica de resolver esto es utilizando un temporizador y su evento de interrupción, veamos ahora el siguiente programa que ejecuta ambas tareas en flujos independientes:

#pragma config FOSC = INTRCIO, WDTE = OFF
#include <xc.h>
#define _XTAL_FREQ 4000000
#include "peripheral.h"
#define pinBUT1 PORTBbits.RB4 //Define el pin RB4 para el pulsador
#define pinLED1 PORTAbits.RA4 //Define el pin RA4 para el LED1
#define pinLED2 PORTAbits.RA5 //Define el pin RA5 para el LED2
char estadoLED1; //Variable temporal para el estado del LED1
void interrupt isr() //Rutina de interrupción
{
    if(PIR1bits.TMR1IF == 1) //Esta bandera se activa cada 100ms
    {
        TMR1Setval(40536); //Vuelva a ajustar el registro contador
        PIR1bits.TMR1IF = 0; //Limpia la bandera
        estadoLED1 = !estadoLED1; //Cambia de logica el valor
        pinLED1 = estadoLED1; //Actualiza el estado del LED1
    }
}
void main()
{
    ANSEL = 0; //Deshabilita los puertos AN0-7
    ANSELH = 0;//Deshabilita los pines AN8-13
    TRISA = 0; //Configura los pines del PORTA como salida
    TRISBbits.TRISB4 = 1; //Configura el pin RB4 como entrada
    EnablePU(); //Activa las resistencias Pull-up del PORTA&B
    TMR1Setup(TIMER, T1PRE4); //Configura el modulo TMR1
    TMR1Setval(40536); //Ajusta el registro contador para 100ms
    PIE1bits.TMR1IE = 1; //Activa la interrupción del TMR1
    INTCONbits.PEIE = 1; //Activa la interrupción de periféricos
    INTCONbits.GIE = 1;  //Habilita las interrupciones del PIC
    TMR1Start(); //Arranca el modulo TMR1
    while(1)
    {
        if(pinBUT1 == 0)//Si esta presionado el pulsador BUT1
        {
            pinLED2 = 1; //Activa el LED2
            __delay_ms(1000);//Espera medio segungo
            pinLED2 = 0; //Apaga el LED2
        }
    }

}

Recordemos que para ajustar el registro contador a un determinado tiempo aplicamos la siguiente formula considerando asignar una pre-escala 1:4.


Parte 6. Conclusiones y Recomendaciones.

Debe considerar que el modulo TMR1 es un recurso compartido con otro modulo CCP, esto quiere decir que si su aplicación emplea el modulo CCP el uso del TMR1 quedara restringido.

El concepto de utilizar una interrupción es detectar una condición y ejecutar una tarea en el menor tiempo posible, las funciones y procedimientos llevadas a cabo en la rutina ISR no deberían generar ciclos de espera o condiciones indefinidas, ya que esto afectaría a la totalidad del programa.


Esperando que este pequeño aporte te pueda servir de algo, agradezco una vez mas tu visita al blog.
 
Pablo Zarate Arancibia
Ingeniero Electrónico
pablinzte@gmail.com