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 Configuracion y Uso 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 PIC haciendo uso de los registros de configuracion SFR,  con el objetivo de utilizar este modulo en la generación de rutinas para manejar el tiempo con gran precisión, y sobre todo hacer un uso adecuado de este recurso logrando integrar la funcionalidad que ofrece a nuestro programa principal.

Esta publicación es complementaria a la entrada <TMR0 Configuración y uso PIC16F>, por lo que te recomiendo puedas revisarlo para que tengas un mejor criterios en cuando a las diferencias que hay en ambos.

La programación se realizara utilizando MPLABX y el compilador XC8 ambos disponibles en la pagina de microchip. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:  <<MPLABX v6.20>>   <<XC8 v2.45>>

Mencionar también que es necesario contar con conocimientos mínimos sobre programación en lenguaje C y el uso de microcontroladores PIC16.

Introducción al Modulo TMR1.
A menudo nos encontramos en la necesidad de manejar el tiempo con un mayor grado de precisión, para lo cual es ideal que un microcontrolador MCU incorpore recursos que ayuden con esta tarea, en el paso de un PIC este recurso se conoce denomina TIMER o TMR y puede cumplir la función de contar pulsos externos (Contador) que ingresan por un pin, o pulsos internos (Temporizador) que provienen del reloj principal.
En cualquier caso un registro contador incrementa su valor con los pulsos internos o externos hasta llegar a su limite, y posteriormente reiniciar la cuenta en 0, esta condición de reinicio se llama desbordamiento.
Los microcontroladores PIC de gama media, integran por lo general tres modulo TIMER que están designados como:
  • TMR0: Temporizador y Contador de 8 bits (Todos los modelos)
  • TMR1: Temporizador y Contador de 16 bits (Todos los modelos)
  • TMR2: Temporizador de 8 bits (Revisar modelo)
Aunque el principio de funcionamiento de los contadores o temporizadores de es similar en cualquiera de los módulos, los cierto es que ciertas diferencias en su diseño, brindan particularidades únicas que permiten una mejor adaptación a ciertas aplicaciones.

En este caso, dada la temática de esta publicación, nos enfocaremos en como utilizar el modulo TMR1, la figura 1 corresponde al diagrama de bloques del módulo TMR1, que se describe en el <Manual de Referencia de los PIC16F>

Fig1. Diagrama del modulo TMR1
 

A diferencia del resto, el modulo TMR1 es un Temporizador y/o Contador de 16-bit debido al ancho que tiene su registro de conteo TMR1, el funcionamiento de este modulo siguiendo a la secuencia que se observa en la figura 1, es la siguiente:

  1. El bit de configuracion TMR1CS permite seleccionar el modo de operación: Contador si los pulsos ingresar por el pin externo T1CKI; Temporizador si los pulsos provienen del oscilador principal, cuya frecuencia seria Fosc/4.  Note que el periodo del Temporizador equivale a al ciclo de instrucción Tcy.
  2. Este punto corresponde al bloque de pre-escala programable, y se trata de un divisor de frecuencia con cuatro escalas seleccionables a través de los bits T1CKPS[1:0], las escalas posibles se muestran en la figura 2.
  3. El bit de configuracion T1SYNC, permite sincronizar la salida del divisor de frecuencia con la frecuencia del reloj principal Fosc/4, este requerimiento puede darse en modo contador, debido a que la señal externa T1CKI es asíncrona, es decir que el pulso puede darse en cualquier instante.
  4. El bit TMR1ON como puede ver en el diagrama, habilita la continuidad de los pulsos que salen del divisor de frecuencia, por lo tanto este bit arranca o paraliza la operación del TMR1. Es importante mencionar que las operaciones de lectura o escritura al registro TMR1, deben efectuarse cuando el modulo esta paralizado.
  5. Este punto corresponde al registro contador TMR1 que al ser de 16-bits, esta conformado por los registros TMR1H y TMR1L, entonces este par de registros TMR1H:TMR1L incrementa su valor con cada pulso que proviene del divisor de frecuencia, desde un valor 0 hasta 65535, posterior al limite final ocurre un desbordamiento donde TMR1H:TMR1L se reinician en 0.
  6. El bit TMR0IF indica la ocurrencia del desbordamiento en el registro contador TMR1, es decir cuando el par TMR1H:TMR1L pasa de FFFFh a 0000h. Esta bit de bandera deberá restablecerse por programa. 
  7. Aunque es menos habitual hacer uso de este punto, es posible que el incremento de TMR1H:TMR1L con cada pulso del divisor de frecuencia, pueda ser controlado bajo las siguientes condicionales adicionales:
Si el bit TMR1GE se activa, entonces el incremento de TMR1H:TMR1L se controla con la función de puerta T1G, caso contrario TMR1H:TMR1L solo incrementa.  La función de puerta T1G, posibilita controlar el incremento del contador TMR1 a través del pin externo T1G, en este caso el incremento puede ocurrir cuando este pin tenga nivel alto si el bit T1GINV=1 o nivel bajo si el bit T1GINV=0.
 
Los registros y bits utilizados para la configuracion del modulo TMR1 se describen en la figura 2, debe tomar en cuenta los registros ya tiene valores por defecto, y en muchos casos podría no ser necesario reconfigurarlos.

Fig2. Registros de configuracion TMR1

El modulo TMR1 posee otras particularidades relacionados con el modulo de captura, comparación y modulación CCP/ECCP que poseen muchos PIC de gama media, si desea conocer mejor las prestaciones de este recurso deberá consultar la hoja de datos.

Configuración de un Contador.

Al implementar un contador lo que se busca es capturar pulsos externos que ingresan por el pin T1CKI, que comparte función con la entrada RA4, los pulsos que ingresan serán contabilizados en el registro contador TMR1H:TMR1L y para este fin consideraremos el siguiente ejemplo:
 
Si nuestra aplicación necesita detectar 700 pulsos externos independientemente del tiempo en el que ocurran, entonces debemos considerar realizar lo siguiente:
  • Configurar el pin T1CKI como entrada, es decir el bit TRISA4=1, en este punto si lo requiere puede considerar el uso de una resistencia pull-up. 
  • Seleccionar la operación de TMR1 como Contador, activando el bit TMR1CS=1, esta manera los pulsos ingresen por el pin de entrada T1CKI.
  • Debe seleccionar una pre-escala para el divisor, y en este caso como solo se requiere contar 700 pulsos, la escala sera 1:1 T1CKPS=00, esto es así porque la capacidad del registro contador TMR1H:TMR1L puede llegar hasta los 65536 pulsos. 
  • Inicializar el valor del registro contador TRM1H:TMR1L, recuerde que el desbordamiento de este registro provoca el evento del modulo TMR1, entonces si el propósito es contar 700 pulsos, este registro debe ser cargado con el valor 65536 - 700.
  • Limpiar la bandera TMR1IF del modulo TMR1, asegurando así el inicio de la espera para los 700 pulsos que ingresaran al Contador.  
  • Iniciar la operación del modulo TMR1, activando el bit TMR1ON = 1.
  • 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
    T1CONbits.TMR1ON = 0; //Para la captura
     //** 700 Pulsos capturados **//
 
Ahora si nuestra aplicación requiere contar 500.000 pulsos externos, notara que este valor excede el limite de 65535 en el registro contador, entonces la solución a este caso seria seleccionar una pre-escala del divisor que permita obtener un múltiplo exacto y dentro de los limites del registro contador, por ejemplo si dividimos los 5000000 entre 8 obtenemos como resultado 62500, entonces ajustaremos el registro contador al valor 65536 - 62500 utilizando una pre-escala 1:8 y nuestra aplicación detectara la captura de los 500000 pulsos cuando ocurran el desbordamiento, este calculo se resumen en la siguiente ecuación:

De forma generalizada la figura 3. muestra la ecuación de ajuste del registro contador TMR1H:TMR1L para una determinada cantidad de pulsos externos obedece al siguiente calculo donde la pre-escala deberá ajustarse para que el valor nunca exceda los 65535, en caso de no lograr un valor inferior la implementación debe apoyarse en variables en la programación.
Fig3. Ecuación del contador TMR1

Configuración de 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 eventos controlados, alarmas y recordatorios para 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 que se muestra en la figura 4, donde n representa el tiempo que se requiere en segundos y la pre-escala es el valor de división seleccionado para que el resultado no supere el limite de 65535.

Fig4. Ecuación del temporizador TMR1

La configuracion de un temporizador sigue la misma secuencia que lo visto en el contador, siendo la únicas diferencia lo siguiente: 
El el bit TMR1CS debe permanecer desactivado para que los pulsos ingresen del oscilador interno Fosc/4;
El calculo utilizando la ecuacion 4 requiere conocer la frecuencia del oscilador.
Por ejemplo si necesitamos generar un retardo de una décima de segundo, considerando Fosc=4MHz y una pre-escala 1:4, calculamos lo siguiente:
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; //TMR1=65536-[(time*Fosc)/(pre*4)]
    PIR1bits.TMR1IF = 0;; //Limpia la bandera
    T1CONbits.TMR1ON = 1; //Arranca temporizador
    while(PIR1bits.TMR1IF == 0); //Espera los 100ms
    T1CONbits.TMR1ON = 0; //Para el temporizador
      /** Tiempo transcurrido de 100ms */ 

TMR1 con interrupciones
Sin duda que unos de los recursos mas valiosos que posee el microcontrolador, son las interrupciones, gracias a ello es posible manejar los eventos asociados el modulo TMR1 con la mínima intervención del programa principal. 
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.
  • GIE Habilitador de las interrupciones en el PIC
En la rutina de interrupción se verificara la bandera correspondiente para identificar al evento, la bandera debe limpiarse antes de salir de la rutina, para evitar que la interrupción reingrese por la misma condición.
Como ejemplo practico vamos hacer uso del  temporizador utilizando la interrupción del TMR1 para que nos permita destellar un LED1 conectado al pin RC0, con una frecuencia de un segundo. Note que se adicionara la rutina de servicio a la interrupción ISR para atender el evento del temporizador que se configurara a 1ms considerando una Fosc=8MHz.
 
#pragma config FOSC = INTRCIO // Oscillator Selection
#pragma config WDTE = OFF    
// Watchdog Timer
#pragma config PWRTE = OFF   
// Power-up Timer
#pragma config MCLRE = ON    
// RA5/MCLR/VPP Pin Function
#pragma config BOREN = OFF   
// Brown-out Detect
#pragma config CPD = OFF     
// Data EE Memory Code Protection
#pragma config CP = OFF      
// Flash Program Memory Code Protection
#include <xc.h>
#include <stdint.h>
volatile __bit tick1ms;  
//Bandera de ms
uint16_t cnt = 0;         
//Contador de ms
__bit led1st;           
 //bandera de estado led
void setup(void);
void __interrupt() isr(void)
//Rutina ISR

    if(PIR1bits.TMR1IF)
//Evento de desbordamiento TMR1
    {
        PIR1bits.TMR1IF = 0;
        T1CONbits.TMR1ON = 0;
        TMR1H = 0xF8;
//TMR1=65536-[(time*Fosc)/(pre*4)]
        TMR1L = 0x30;
//     65536-63536 = 2000 o 7D0h
        T1CONbits.TMR1ON = 1;
        tick1ms = 1;
    }
}
void main(void)
{
    setup();
//Configura el PIC
    while(1)
    {
        if(tick1ms)
//Se valida cada 1ms
        {
            tick1ms = 0;
//Limpia bandera de ms
            cnt++;
//incrementa variable contador
            if(cnt > 499)
//hasta llegar a 500 x 1ms
            {
                cnt = 0;
//Reinicia contador de ms
                led1st = !led1st;
//Invierte el calor logico
                PORTCbits.RC0 = led1st;
            }
        }
    }
}
void setup(void)
//Procedimiento de configuracion PIC
{
    OSCCONbits.IRCF = 0b111;
//Fosc=8MHz Tcy=0.5uS
    while(OSCCONbits.HTS == 0){};
    ANSEL = 0; //Desactiva los pines AN0:AN7
    ANSELH = 0;//Desactiva los pines AN8:AN13
    TRISCbits.TRISC0 = 0;
//Salida LED1
    TRISCbits.TRISC1 = 0;
//Salida LED2
    PORTC = 0;
//Apaga LED1 y LED2
    OPTION_REGbits.nRABPU = 0;
//Habilita pullup A/B
   
/* CONFIGURACION TIMER1 1MS Fosc=8Mhz*/
    T1CONbits.TMR1CS = 0;
//Modo temporizador
    T1CONbits.T1CKPS = 0b00;
//Ajuste pre-escala 1:1
    TMR1H = 0xF8;
//TMR1=65536-[(time*Fosc)/(pre*4)]
    TMR1L = 0x30;
//     63536 F830h
    PIR1bits.TMR1IF = 0;;
//Limpia la bandera
    T1CONbits.TMR1ON = 1;
//Arranca temporizador
    PIE1bits.TMR1IE = 1; 
//Activa interrupcion
   
/* CONFIGURA INTERRUPCIONES*/
    INTCONbits.PEIE = 1;
//Activa interrupcion de periferico
    INTCONbits.GIE = 1;
//Activador global ISR
}   

 
Conclusiones y Recomendaciones.
Debe considerar que el modulo TMR1 es un recurso que comparte funciones con otros módulos como el CMP, CCP/ECCP y el oscilador secundario, por lo su uso podría estar restringido si alguno de estos módulos esta en operación.
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 utilizar demasiados de ciclos de instrucción debido a que podrían generar inconvenientes en el programa principal, un aspecto importante a tomar en cuenta es que durante la lectura o escritura del registro par TMR1H:TMR1L puede ocurrir un desbordamiento y causar error, esto se debe a que leer o escribir el valor de este contador requiere mas de dos ciclos de reloj porque son dos registros referenciados en momentos diferentes, y un desbordamiento en medio del proceso haría que uno de estos registros sea 0, y con ello el valor final de 16-bit erróneo. La figura 5, extraida del manual de referencia, muestra este problema.
Fig5. Secuencia de error TMR1
Por eso la solución simple es que para actualizar el registro contador TMR1H:TMR1L debe para el modulo TMR1 con el bit TMR1ON = 0.
  
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


viernes, 16 de noviembre de 2018

TMR1 Control de Servomotores


  Control de Servos con PIC16F


Un gran saludo para todos los visitantes  que comparten interés en este mundo de la electrónica y en especial para los aficionados a la programación de microcontroladores PIC. En esta ocasión quiero mostrar como hacer uso del modulo Temporizador TMR1 para controlar mas de un servomotor.
Si no tienes claro de como funciona el modulo TMR1 del microcontrolador PIC16F, te recomiendo revises la siguiente entrada, en donde explico con mayor detalle la operación del modulo:  <TMR1 Configuración y Uso >.
El objetivo en esta sección es utilizar mas de un servomotor, técnicamente hasta ocho servomotores con un solo PIC16F, en donde cada servomotor trabajara en su rango de tiempo asignado para realizar el movimiento, de 0 hasta los 180 grados. 
La programación se realizara utilizando MPLABX y el compilador XC8 ambos disponibles en la pagina de microchip. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:  <<MPLABX v6.20>>   <<XC8 v2.50>>
Mencionar también que es necesario contar con conocimientos mínimos sobre programación en lenguaje C y el uso de microcontroladores PIC16.

Acerca del Servomotor
Un servo esta compuesto por un motor DC, una caja reductora y un circuito electrónico de control que nos permite ubicar un eje central de accionamiento en cualquier posición dentro del rango de operación para el cual fue diseñado, en general de 0 a 180° grados. 
Suele contar con tres lineas de conexión, dos corresponden a la alimentación de +V / GND y uno para la señal de control que consiste en un pulso digital con modulación del ancho variable entre 1 y 2 milisegundos a 50 Hz de frecuencia. Observe la siguiente figura que muestra la relación del ancho que tiene el pulso digital con la posición del servomotor.


Fig1. Señal de Control para Servo

La señal de control de un servomotor es prácticamente del tipo PWM (Pulse Width Modulation) y tenemos mente utilizar un microcontrolador como el PIC16F debemos considerar los siguientes aspectos:
  • Por general los Microcontroladores, salvo algunos del tipo DSP poseen una cantidad de pines PWM limitadas, como es el caso del PIC16F88x que solo cuenta solo con dos salidas, y estos comparten el mismo recurso de temporización, por lo que no siempre se ajustaran a ciertas necesidades.
  • Los modulo PWM de un PIC16F, están diseñados para señales PWM con ciclos de trabajo que van de 0 al 100%, mientras que un servo solo requiere para el control un ciclo de trabajo entre el 5 y 10%.
  • El modulo CCP del PIC16F no se diseño para trabajar en baja frecuencia, de tal manera que para una salida de 50Hz, la frecuencia del oscilador no deberia superar los 1MHz.
Tomando en cuenta estas consideraciones vemos que una buena alternativa al uso de un modulo dedicado como el CCP, es utilizar un temporizador con su interrupción, de esta manera es posible controlar hasta ocho servomotores.
 
Señal de control PWM
Ahora veremos como se genera la señal de control PWM para un servomotor aplicando el uso del temporizador TMR1 y su interrupción.
La figura 2 nos muestra la distribución del pulso S1 dentro del periodo de control fijo que tiene el servomotor, donde el ancho del pulso dura entre entre 1 y 2 ms con un periodo fijo de 20 ms.

Fig2. Periodo de la señal PWM
 
Para establecer una salida PWM, nuestro programa utilizara las siguientes variables de control, junto a los cálculos que se muestran en la ecuación 1, estos valores pueden ajustarse en el archivo de cabecera tservo.h
  • tsvnum. Establece la cantidad de salidas PWM a utilizar, de 1 a 8.
  • tsvslot. Es la fracción de tiempo o ranura dentro del periodo en la cual debe enviarse el pulso de control PWM de cada salida, su valor representa los micro-segundos necesarios para un desbordamiento del TMR1 y su calculo obedece a la siguiente ecuación.
Ec1. Calculo de ranura de tiempo en microsegundos
  • TSV.svxpos. Este valor decimal corresponde a la duración del ancho del PWM de cada salida, como valores de referencia se tiene: 0=0 grados, 50= 90 grados y 100=180 grados.
Si bien es posible hacer uso de cualquier modulo temporizador que incremente en pasos de un microsegundo (us), el TMR1 posibilita contabilizar hasta los 20000us que tiene el periodo de la señal PWM del servomotor, gracias a su registro de 16-bits. Para determinar los pasos en un PIC16F debemos considerar la relación que tiene el ciclo de instrucción con respecto a la frecuencia de operación: 
Entonces aplicando la formula anterior vemos que para una frecuencia de  8MHZ, los pasos serán de 0.5us y se necesitara un división 1:2 para conseguir incrementos de un 1.0us. (2 * 0.5u).
 
Para llevar a cabo la programación de control para los servomotores, he creado la librería tservo.c, que nos facilitara aplicar el proceso de señalización a todas la salidas de control PWM. Describiré como hacer uso de las dos funciones que contiene esta librería, que son:
  • TSERVOSetup(): Configura la cantidad de pines requeridos para la señalización PWM, cada pin esta asociado al puerto definido en TSVPORT, se numera desde 1 a 8, siendo 1 el pin0 y 8 el pin7.
  • TSERVOHandler(): Esta función es invocada desde la rutina de servicio a la interrupción ISR, cuando finaliza el temporizador y permite controlar el estado de la señalización para cada pin PWM, al final devuelve el valor de tiempo que se requiere para actualizar el temporizador.
El uso de estas funciones dentro del programa principal se resume en las siguientes secciones de código:

 Sección 1: Cabecera tservo.h

/* USER PORT DEFINITION */
#define TSVNUM 2
//Numero de servos a conectar, Máximo 8. 20ms/8 > 2ms.
#define TSVSLOT (65536UL -(20000U/TSVNUM))
//Se puede adicionar un offset +30
#define TSVTIME0 (65536UL - 900U)
//Time offset for -90
#define TSVPORT PORTD
//Puerto servo control <posn>..<pos2><pos1>
#define TSVTRIS TRISD
//Tris Puerto servo
/* END USER PORT DEFINITIOS*/

Sección 2: Programa principal

#include "tservo.h" //Cabecera de la librería servo
void setupMCU(void);
//Prototipo de función
void main(void)
{
    setupMCU();
//Configuración del PIC
    TSERVOSetup();
//Configuración del SERVO
    TSV.sv1pos = 10;
//Ajusta servo al 10%
    TSV.sv2pos = 80;
//Ajusta servo al 80%
    while(1)
    {
        if(tickms)
//Intervalo para la concurrencia
        {
            tickms = 0;
//Limpia bandera
           
//Código del programa principal
                                           
        }
    }   
}
void setupMCU(void)
//Procedimiento de configuracion del MCU
{
    OSCCONbits.IRCF = 0b111;
//Ajusta frecuencia a 8MHz
    while(OSCCONbits.HTS == 0) {};
   
 
    /* CONFIGURACIÓN TIMER1 8MHZ PASOS DE 1US */
    T1CONbits.T1CKPS = 1;  
//Pre-escala 1:2
    T1CONbits.TMR1CS = 0;  
//Modo temporizador
    T1CONbits.TMR1ON = 1;  
//Activa el modulo
    PIR1bits.TMR1IF = 0;   
//Limpia bandera
    PIE1bits.TMR1IE = 1;   
//Activa interrupción timer1
   
INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
}
 
Sección 3: Rutina de Interrupción.

void __interrupt() isr(void) //Rutina de servicio de interrupción
{
    uint16_t res;
    if(PIR1bits.TMR1IF)
//Evento cuando finaliza el temporizador
    {
        res = TSERVOHandler();
//Función de control PWM
        T1CONbits.TMR1ON = 0;
//Para el temporizador
        TMR1L = (uint8_t) res;
//Recarga el temporizador
        TMR1H = (uint8_t) (res >> 8);
        PIR1bits.TMR1IF = 0; 
//Limpia la bandera
        T1CONbits.TMR1ON = 1;
//Inicia el temporizador
    } 
}

 
La función de control TSERVOHandler() administra el estado de cada pin en función del numero de servomotores definidos en TSVNUM,  determinando de forma automática en cambio de estado logico con base a los limites establecidos para a cada ranura de tiempo dentro del periodo PWM. Con esta técnica es posible asignar entre uno y ocho ranuras de tiempo dentro del periodo de 20ms que utiliza un servomotor. Comprenderá mejor este punto observando la distribución de tiempos que se ilustran en las siguientes figuras.

Fig3. Ranuras de tiempo PWM para 2 Servomotores

Fig4. Ranuras de tiempo PWM para 4 Servomotores

Fig5. Ranuras de tiempo PWM para 8 Servomotores

Conexión del Servo 
Como mencionamos anteriormente un servo suele contar con tres lineas de conexión, dos corresponden a la alimentación de energía y una para el control de posición. La siguiente figura ilustra la disposición de los pines y colores utilizados por algunos fabricantes.
Fig6. Puerto de conexión servo

Mas allá de la disposición de pines para un servomotor, es importante considerar aspectos técnicos que son proporcionados por el fabricante y que deben considerarse durante la etapa de diseño de un circuito electrónico.
 
Programa de control PIC16F
En este apartado veremos como crear un programa que nos permite controlar de forma independiente la posición de dos servomotores utilizando dos potenciómetros. El código del programa principal contiene procedimientos adicionales necesarios para el funcionamiento de este programa, estos incluyen la lectura ADC y la concurrencia de tareas con el TMR0.

El programa principal ingresa en un bucle continuo para ejecutar tareas cada milisegundo, estas tareas permiten destellar un LED y hacer lectura de los dos canales analógico ADC para cargar la variable de control de posición sv1pos y sv2pos. Cabe resaltar que ninguna de estas tareas representa un proceso bloqueante, la codificación utiliza una técnica de programación por estados, si quieres conocer mas detalles al respecto puedes revisar la siguiente publicación.
Fig7. Circuito para control de dos servomotores

Alternativamente a los potenciómetros que se observan en el esquema de circuito de la figura 7, podemos utilizar un modulo joystick y una estructura de movimiento de dos ejes como el que se observa en la figura 8.

Fig8. Joystick y Servo para 2-ejes
 

Conectado estos modulo a nuestro PIc16F887 conseguiremos ensayar el funcionamiento del programa de manera simple y rápida, observe como quedo el circuito de prueba.

Fig9. Circuito de Prueba PIC16F887

Aquí dejo el enlace para descargar el programa, mismo que se encuentra en formato gzip, para descomprimir y abrirlo desde MPLABX:
Si quieres ver como compilar e implementar los proyectos de este blog, en MPLABX mira este vídeo.

Conclusiones y Recomendaciones
Pudimos ver mediante un ejemplo como hacer uso del TMR1 para controlar hasta ocho servomotores utilizando las funciones presentes en la librería tservo,  Adicionalmente quiero comentar los siguientes aspectos con relación al tema:
  • Es posible adaptar esta técnica de control para servomotores utilizando temporizadores de 8-bit como el modulo TMR0 o TMR2, pronto actualizare la publicación con un ejemplo de uso para el caso.
  • Si bien se recomienda utilizar múltiplos de 4MHZ para la frecuencia del cristal, es posible emplear valore diferentes como ser 10MHZ o 20MHz, donde el control de pasos no siempre serán múltiplos exactos de 1us, si bien la frecuencia de control PWM para el servomotor es de 50Hz, estos pueden operar en el rango de 40Hz hasta los 400Hz.
  • La latencia de las interrupciones puede provocar oscilaciones en el movimiento del servomotor cuando la rutina ISR atiende otras fuentes de interrupciones. Debe analizar a detalle la implicación que conlleva atender cada unos de los eventos.

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.

Atentamente, Pablo Zárate Arancibia  email: pablinza@me.com / pablinzte@gmail.com, @pablinzar

Santa Cruz de la Sierra - Bolivia