sábado, 15 de septiembre de 2018

PWM Velocidad y Giro de motor DC



Control de Giro y Velocidad PWM con PIC16F




Un gran saludo a los visitantes de este blog, el cual esta dedicado a la programación de microcontroladores PIC, Hoy quiero mostrarles como hacer uso del modulo ECCP que poseen gran parte de los modelos PIC16F.
Nuestro objetivo es controlar la velocidad y giro de un motor DC, utilizando la modulación PWM a través del modulo ECCP de un PIC16F887. El funcionamiento es bastante simple, un solo potenciómetro de 10k determinara el giro y la velocidad en proporción a la rotación que altera su resistencia, considerando como punto de referencia la mitad de su valor(5k). Para controlar el motor se usara el modulo L9110 considerando los requerimientos de voltaje y consumo con los que opera un pequeño motor DC 12V / 0.3A

La programación del microcontrolador se realizara utilizando el compilador XC8 y el entorno MPLABX, ambos disponibles en la pagina de microchip. 

Paso 1. Sobre el modulo L9110


Fig1. Modulo de control con L9110
 
Este pequeño modulo de bajo coste puede operar con voltajes en el rango de 2.5 a 12V, suministrando hasta 800mA de corriente de forma continua, posee dos canales A y B cada uno lleva un circuito integrado ASIC L9110 que incorpora los respectivos diodos de protección en sus salidas.


Paso 2. Conexión Motor-L9110 al PIC16F.

Solo haremos uso de un canal de este modulo, básicamente cada canal lleva un circuito integrado L9110, el cual controla al motor DC en función a las señales de entrada Forward(Adelante) y Backward(Atrás), las cuales se activa con nivel lógico alto.
Fig2.Conexión del L9110

Paso 3. Modulación por ancho de pulso PWM.

La modulación por ancho de pulso PWM(Pulse-Width Modulation) se utiliza bastante en los sistemas digitales, como una técnica de control de energía aplicada a una carga.
 
Una salida digital tipo PWM presenta un comportamiento analógico en la carga, debido a la continua conmutación entre los estados lógicos; En otras palabras si aplicamos este patrón de señal para alimentar una fuente de luz nos sera posible controlar su brillo, tal como se observa en la siguiente imagen.
 
 
Fig3. Suministro de energía con PWM(https://www.mikroe.com)
 
Básicamente una señal PWM trabaja a una frecuencia fija o predefinida, sobre la cual se determinan los siguientes periodos de tiempo:

Fig4. Pulsos periódicos
  • T: Es el periodo de la señal, que depende directamente de la frecuencia de operación.
  • tcy: Es el tiempo en la cual la señal se mantiene activa, su valor debe ser menor que T

Los microcontroladores PIC16 poseen módulos conocidos como CCP(Capture-Compare-PWM) y ECCP(Enhanced Capture-Compare-PWM) siendo este ultimo un modelo con funcionalidades mejoradas con respecto al CCP, en particular un PIC16F887 cuenta con ambos módulos pero no son totalmente independientes, porque ambos comparten como recurso a los módulos TMR1/TMR2. 
 
En esta ocasión nos centraremos en utilizar el modulo ECCP para generar una modulación PWM que controle la velocidad y giro del motor.  En el PIC16F887 los pines de salida asociados al modulo son el RC2/P1A, RD5/P1B, RD6/P1C, RD7/P1D, a través de estos pines se generaran patrones de modulación que pueden aplicarse a la entrada de circuito puente o medio puente para controlar el motor.

Fig5. Patrones de modulación PWM

Aunque es posible cambiar la polaridad de las señales que se muestran en la fig5., para el control de nuestro motor DC no sera necesario porque los modos Full-Bridge Forward y Full-Bridge Reverse se adecuan para controlar el sentido de giro utilizando los pines P1D y P1B, estos se conectaran a las entradas A-IA(Forward) A-IB(Backward) en cualquiera de los dos canales que posee el modulo L9110.

Paso 4. Programación y control de velocidad.

Como parte de la programación, nos enfocaremos en la creación de tres rutinas que harán uso de la modulación PWM. 
Las ecuaciones a utilizar para determinar la periodicidad y ciclo de trabajo, se extrajeron de la hoja de datos del microcontrolador, donde intervienen los siguientes recursos:
  • Fosc. Frecuencia de Oscilación del reloj para el microcontrolador.
  • T. Periodo de la señal PWM.
  • PR2. Registro de 8-bits(TMR2) para determinar el periodo
  • T2CKPS. Pre-escala asignado el registro TMR2.
  • t1. Ciclo activo (tcy) de la señal PWM(Tiempo en alto).
  • CCPRxL. 8-bits MSB del ciclo activo de la señal.
  • DCxBx. 2-bits LSB del ciclo activo de la señal. 
Para establecer la periodicidad o periodo T de la señal PWM, utilizamos la siguiente ecuación:
Ec1. Periodo de la señal
 
El valor de PR2 puede ser de 0 a 255, mientras que T2CKPS puede ser únicamente 1,4 o 16.
 
Para establecer el tiempo en que la señal PWM permanece activa o en nivel alto, utilizamos la siguiente formula:
Ec2. Periodo activo de la señal
 
Observe que la combinación de ocho bits del registro CCPRx y los dos bits del registro DCxBx, conforman una longitud total de 10-bits. (0-1023)
 
Utilizando las ecuaciones Ec1 y Ec2 , podemos determinar el ciclo de trabajo de la señal PWM, calculando la siguiente relación:
Ec3. Ciclo de trabajo
 
Si aplicamos a esta ecuación Ec3. los valores máximos, es decir el registro PR2=255 y los 10-bits conformados por los registros CCPRxL:DCxB=1023, el resultado sera dt = 1, lo cual nos indica un ciclo activo al 100%.
 
La Ec3. también muestra que el control de la modulación PWM tendrá una resolución de 10-bits (1024 pasos), solo cuando PR2=255, por lo tanto si PR2 se ajustara a un valor menor a 255, la resolución se ajustara a la siguiente ecuación:
Ec4. Resolución PWM si PR2 < 255

El primer procedimiento del programa se llama CCPSetupPWM1, y contendrá las instrucciones para configurar el modulo ECCP1, para una señal PWM con periodo de 500us(0.5ms) o frecuencia de 2kHz,  todo la configuración se efectuara considerando que la frecuencia del PIC16 es Fosc=4MHz.

void CCPSetupPWM1() //Fosc = 4MHz, Fpwm=2KHz -> T=0.5mS
{
    TRISDbits.TRISD5 = 1;   //Coloca en tri-state el pin P1B
    TRISDbits.TRISD7 = 1;   //Coloca en tri-state el pin P1D
    PIR1bits.TMR2IF = 0;    //Limpia la bandera del TMR2
    CCP1CONbits.CCP1M = 0b1100; //P1A-P1C y
P1B-P1D Activo en Alto
    CCP1CONbits.P1M = 3; //1=Adelante(01), 3=Atras(11)
    PR2 = 127;  //PWM(T) = (127+1) * 4 * (4)/(4^6) = 0.0005 seg
    T2CONbits.T2CKPS = 1; //Pre-escala 1:4
    T2CONbits.TMR2ON = 1; //Arranca el Timer   
    while(PIR1bits.TMR2IF == 0); //Espera un nuevo ciclo PWM
    TRISDbits.TRISD5 = 0;   //Habilita salida P1B
    TRISDbits.TRISD7 = 0;   //Habilita salida P1D
}

Con esta configuración PR2=127 el control sera posible con una resolución de 9-bits (Ec4.), es decir que hay hasta 512 pasos intermedios para ajustar el ciclo activo o ciclo de trabajo de la señal. Ahora codificaremos otro procedimiento que permita establecer la resolución durante la ejecución del programa.
 
void CCPSetduty(unsigned int n)
{
    CCPR1L = n >> 2;        //Carga los 8-bits MSB del ciclo
    CCP1CONbits.DC1B = n;   //Carga los 2-bits LSB del ciclo
    PIR1bits.TMR2IF = 0;  
}


Finalmente crearemos un par de procedimientos para determinar el sentido de giro del motor, utilizando los bits P1M del registro CCP1CON que controlan este comportamiento, si el bit P1M1=0 el sentido de giro es hacia adelante y si P1M1=1 el giro es en reversa, notara que en las rutinas hay instrucciones adicionales que son necesarios para cambiar los bits P1M, esto como parte de las recomendaciones que se mencionan en la hoja de datos.


void MotorAdelante()
{
    TRISDbits.TRISD5 = 1;   //Deshabilita pin CCPx P1B
    TRISDbits.TRISD7 = 1;   //Deshabilita pin CCPx P1D
    PIR1bits.TMR2IF = 0;    //Limpia la bandera
    CCP1CONbits.P1M = 1;    //1=Forward
    while(PIR1bits.TMR2IF == 0);//Espera un nuevo ciclo
    TRISDbits.TRISD5 = 0;   //Habilita pin CCPx P1B
    TRISDbits.TRISD7 = 0;   //Habilita pin CCPx P1D
}
void MotorReversa()
{
    TRISDbits.TRISD5 = 1;   //Deshabilita pin CCPx P1B
    TRISDbits.TRISD7 = 1;   //Deshabilita pin CCPx P1D
    PIR1bits.TMR2IF = 0;    //Limpia la bandera
    CCP1CONbits.P1M = 3;    //3=Back
    while(PIR1bits.TMR2IF == 0);//Espera un nuevo ciclo
    TRISDbits.TRISD5 = 0;   //Habilita pin CCPx P1B
    TRISDbits.TRISD7 = 0;   //Habilita pin CCPx P1D
}

Ahora si, podemos elaborar un pequeño programa como ejemplo practico para controlar el motor DC. Agregaremos un potenciómetro al canal 0 del modulo ADC(Pin AN0), y como el convertidor ADC del PIC tiene una resolución de 10-bits, de los 1024 valores posibles, 512 corresponderán a cada sentido de giro, entonces el control de velocidad corresponderá con una resolución de 9-bit.

#pragma config FOSC=INTRC_NOCLKOUT, WDTE = OFF, LVP = OFF
#include <xc.h>
#include <stdlib.h>
unsigned int adval;
void MotorAdelante();
void MotorReversa();
void CCPSetduty(unsigned n);
void CCPSetupPWM1();
void ADCStart(char ch);
unsigned int ADCRead();
 
void main(void) 
{
    OSCCONbits.IRCF = 6; //Ajusta oscilador interno a 4MHz
    ANSEL = 0;
    ANSELH = 0;
    ANSELbits.ANS0 = 1; //Configura el pin AN0 como entrada
    TRISD = 0;
    TRISC = 0;
    TRISEbits.TRISE2 = 0;
    ADCON0bits.ADCS = 0b11; //Ajusta el TAD a FRC
    ADCON0bits.ADON = 1; //Habilita el modulo ADC
    CCPSetupPWM1(); //Prepara el modo PWM
    while(1)
    {
        ADCStart(0);    //Inicia captura del canal 0
        __delay_us(20); //Espera requerida para completar la captura
        adval = ADCRead();//Lee el valor ADC(valor entre 0-1023)
        if(adval >= 512) //Control a la mitad del POT para adelante
        {
           adval = adval - 512;
           if(adval > 512) adval = 512; //El valor no debe superar los 9bits.
           MotorAdelante();
        }
        else    //Control a la mitad del POT para atras
        {
           adval = 512 - adval;
           if(adval > 512) adval = 512; //El valor no puede superar los 9bits
           MotorReversa();
        }
        CCPSetduty(adval); //Ajusta el ciclo de trabajo
        PORTEbits.RE2 = 0; //Apaga led
        __delay_ms(100);
        PORTEbits.RE2 = 1;
        __delay_ms(100);
    }
}
 
void ADCStart(char ch)
{
    ADCON0bits.CHS = ch;
    __delay_us(20);
    ADCON0bits.GO = 1;
}
unsigned int ADCRead()
{
    unsigned int value;
    while(ADCON0bits.GO);
    value = ADRESH;
    value = value << 8;
    value = value | ADRESL;
    value = value >> 6;
    return value;
}


Paso 5. Prueba y Simulación.





Paso 6. Conclusiones y Recomendaciones.

Es recomendable utilizar una frecuencia PWM superior a 4KHz, para minimizar o evitar la percepción del ruido.

Se debería considerar alguna rutina o mecanismo para reducir la velocidad del motor cuando se aplica un cambio al sentido de giro, sobre todo si el ciclo de trabajo es alto.

El presente ejemplo solo se implemento con fines educativos, sin considerar elementos de protección adicionales a cualquier circuito de control para motores.

Pablo Zarate Arancibia
Ingeniero Electrónico
pablinzte@gmail.com

lunes, 3 de septiembre de 2018

TMR Distancia con HC-SR04


 Medición HC-SR04 con el PIC16F



Hola todos los visitantes aficionados a la electrónica y en especial a la programación de microcontroladores PIC, En esta ocasión veremos el conocido modulo HC-SR04 utilizado en muchos proyectos relacionados a la robótica y microcontroladores.

El objetivo de esta sección es leer la distancia a un objeto o superficie utilizando el microcontrolador PIC16F y su modulo TMR1 como recurso para medir el tiempo.

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.

Paso 1. Conocer el modulo HC-SR04.

Si revisamos la hoja de datos del modulo podemos resumir lo siguiente:
  • El voltaje de operación es de 5VDC y tiene un consumo de 15mA.
  • Su rango de aplicación va desde los 2cm hasta los 400cm, aunque en algunas especificaciones indica hasta los 500cm.
  • Posee con un rango de apertura total de 30 grados.
La siguiente imagen describe el principio de operación:



Para dar inicio al proceso de medición con el sensor, se debe aplicar un pulso de disparo inicial en el pin TRIGGER, el mismo deberá tener una duración de al menos 10uS, una vez recibido el pulso el modulo emitirá al espacio una señal no audible(ocho tonos pulsantes de 40KHz), que durante su recorrido podrá reflejarse en cualquier obstáculo siendo este nuestro objetivo de medición, entonces las señal reflejada es capturada por un receptor posibilitando de esta manera determinar la distancia en base al retardo entre la emisión y recepción de los pulsos, como salida final el modulo a través del pin ECHO emite un pulso con un tiempo de duración proporcional al retardo.
Para una lectura real el microcontrolador únicamente debe medir el ancho del pulso en la señal ECHO y calcular la distancia considerando que los pulsos viajan a la velocidad de 343m/s(velocidad del sonido), teniendo como referencia que por cada 1cm transcurren 29uS y como los pulsos emitidos recorren un trayecto de ida y de retorno, el tiempo total por cada 1cm sera de 58uS.

Paso 2. Conectar el modulo al PIC16F.

El modulo no requiere elementos adicionales y solo se necesita alimentarlo con 5V, en nuestro ejemplo la señal ECHO del sensor se  conectara al pin de entrada RD0 y el TRIGGER al pin RD1
Adicionalmente un diodo LED conectado al pin RE2 indicara el funcionamiento normal del programa mediante destellos con intervalos de un segundo

Paso 3. Programación y lectura.

Ahora describiré el código del programa en diferentes secciones:

#pragma config FOSC = INTRC_NOCLKOUT, WDTE = OFF, LVP = OFF
#include <xc.h>
#include <stdio.h>
#define _XTAL_FREQ 4000000
#define TRIGpin PORTDbits.RD1
#define ECHOpin PORTDbits.RD0
#include "peripheral.h"
unsigned int distcm = 0;

unsigned int SR04Getval(); //Función para la lectura del sensor
void DelayMS(unsigned int n); //Procedimiento para el generar retardo

 

Esta primera parte define los pines utilizados con el sensor y la frecuencia de operación del PIC a 4MHz para conseguir un ciclo de instrucción de 1uS, también se declara la variable distcm para almacenar la distancia en centímetros.

void main(void)
{
    ANSEL = 0;    //AN0-AN7 en modo digital
    ANSELH = 0;   //AN8-AN12 en modo digital
    TRISEbits.TRISE2 = 0; //Salida Led pin RE2
    TRISDbits.TRISD1 = 0; //Salida para el disparo TRIG
    TRISDbits.TRISD0 = 1; //Entrada para el pin ECO
    PORTD = 0;
    UARTSetup(9600);    //Configura el modulo Serial a 9600
    TMR1Setup(TIMER, T1PRE1); //Ajusta TMR1 en modo tempo con pre 1:1
 
    TMR0Setup(TIMER, T0PRE8); //Ajusta TMR0 en modo tempo con pre 1:8
    while(1)
    {
        distcm = SR04Getval(); //Lectura del sensor
        printf("Distancia cm:%u\r\n", distcm); //Enviar el mensaje cm:xx
        DelayMS(500);  //Genera un retardo de 500ms
        PORTEbits.RE2 = 0;
        DelayMS(500);
        PORTEbits.RE2 = 1;
    }
}


Esta sección corresponde a la rutina principal del programa, dentro de la secuencia repetitiva se procede a medir la distancia y mostrar su valor enviando un mensaje de distancia por el puerto serie, el ciclo se repite cada segundo, en cada intervalo hay dos esperas de medio segundo (500ms) que sirve para destellar el LED, en este punto quiero aclarar que este retardo representa un tiempo muerto no deseable en cualquier sistema secuencial y la razón de porque utilizo en el presente ejemplo solo es para simplificar la comprensión del modulo.
El recurso que permitirá conocer el tiempo el es modulo TMR1 a través de su registro contador, el cual ajustado sin pre-escala con relación 1:1 se incrementa con cada ciclo de maquina Tcy transcurrido, es decir cada 1uS.

void DelayMS(unsigned int n)
{
    while(n > 0)
    {
        TMR0Setval(131); //Para 1ms donde el Tcy=1uS y PRE8
        INTCONbits.TMR0IF = 0;
        while(INTCONbits.TMR0IF == 0);
        n--;
    }
}

 
El procedimiento anterior es para generar retardos en milisegundos utilizando el modulo TMR0, mismo que cuenta los intervalos de 1ms; El calculo para determinar el valor del registro TMR0 proviene de la siguiente ecuación:



unsigned int SR04Getval()
{
    unsigned int value;
    TRIGpin = 1;
    __delay_us(10); //Pulso de disparo al pin TRIGGER
    TRIGpin = 0;
    while(ECHOpin == 0); //Espera el inico del pulso de ECO
    PIR1bits.TMR1IF = 0;
    TMR1Setval(0);  //Inicia en 0 el registro contador.
    TMR1Start(); //Inicia el modulo TMR1
    while(ECHOpin == 1) //Espera a que finalice el pulso ECO
    {
        if(PIR1bits.TMR1IF == 1) //Verifica condición de desbordamiento
            break; //Finaliza si hay desbordamiento del registro contador
    }
    TMR1Stop(); //Para el modulo TMR1
    value = TMR1Getval(); //Lee el registro contador en la variable
    return value/58; //Se calcula la distancia dividiendo el total uS/58

}


Finalmente, se describe la función que permite calcular la distancia en función al ancho de pulso del eco, esta función retorna la distancia en cm.
El pulso de disparo que requiere el sensor se efectúa con las siguientes lineas: 

    TRIGpin = 1;
    __delay_us(10); //Pulso de disparo al pin TRIGGER
    TRIGpin = 0;

    
Una vez efectuado el disparo el sensor genera internamente una emisión de pulsos de ultrasonido, esta señal se reflejara en el objeto a medir y sera captada por el sensor y enviara un pulso en alto con una duración equivalente al tiempo transcurrido, por este motivo una vez que el microcontrolador genera el pulso de disparo, este deberá esperar el inicio del pulso de eco, medir su tiempo en alto y calcular asi la distancia.   

Paso 4. Simulación y Prueba. 

Para la prueba utilizo una placa de ensayos a la que llamo B8P40, que me facilita la conexión al convertidor UART/USB, de otra manera tendría que montar todo en un protoboard.


Se utilizo un programa terminal de puerto serial que se conecta al convertidor UART/USB incluida en la placa B8P40, de esta manera es posible recibir los siguientes mensajes que incluyen información de la distancia por cada segundo.



Paso 5. Conclusiones y recomendaciones.

El código de este ejemplo considera una situación ideal donde los pulsos de disparo siempre serán reflejados, y así el ancho de pulso en el pin eco tendría una duración no mayor a 38ms que es la distancia máxima del sensor; pero en una situación real estas condiciones podrían no ocurrir si el objeto esta muy distante o no existe un reflejo de los pulsos, entonces se sugiere tomar en cuenta estos detalles en la programación en caso de una implementación real.

Debido a que la lectura implica tiempos de espera para la señal de eco, el código presentado en este ejemplo bloquea la secuencia o sondeo del programa principal, y no es posible determinar un tiempo fijo, porque esto va depender de la distancia del objeto, una alternativa a esta situación puede ser hacer uso del pin RB0/INT para detectar mediante una interrupción el inicio y fin del pulso eco.

También es recomendable utilizar el promedio de varias lecturas con la finalidad de reducir el error que normalmente se deben a las interferencias de los pulsos en el trayecto.
 
Pablo Zarate Arancibia
Ingeniero Electrónico
pablinzte@gmail.com
Santa Cruz - Bolivia