lunes, 6 de octubre de 2025

TMR2 Control de Servomotores

Control de Servomotor con TMR2

Un gran saludo a los visitantes de mi blog, donde comparto mi interés en el maravilloso mundo de la electrónica digital y en especial la programación de microcontroladores PIC/AVR. En esta ocasión quiero mostrar como hacer uso del modulo Temporizador TMR2 de 8-bit para controlar varios servomotores conectados a un puerto de 8-bit del Microcontrolador. 

Esta entrada es complementaria a una publicación previa donde describo como hacer este control con el temporizador TMR1 de 16-bit <Ver Entrada>, por esta razón no voy extender la explicación en el entendido que ya sabes como trabajan los temporizadores de un Microcontrolador, por otra parte te recomiendo revises los siguientes enlaces: 

El objetivo que se pretende en esta publicación, es demostrar como se puede llevar a cabo el control de mas de un servomotor con el temporizador TMR2 de un PIC16F, en la practica podemos controlar hasta ocho servomotores conectados a un puerto de salida digital de 8-bit, cada salida de control PWM tiene asignada su ranura de tiempo (slot) con la que se establece el rango de movimiento 0 a 180 grados en el servomotor.

La programación del microcontrolador PIC se lleva a cabo con el software  de diseño  MPLABX  y el compilador de lenguaje C para PIC XC8, ambos disponibles en la pagina de microchip de forma gratuita. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:   

<<MPLABX v6.20>>   

<<XC8 v3.0>>

Introducción

Fig1. Servomotor Eje y Brazo
Un servomotor como el tan conocido SG90, posee un eje de rotación girar y posicionar un brazo desde los 0 hasta los 180° grados de movimiento, este control se lleva a cabo variando el ancho de pulso (1.0-2.0 ms) de una señal digital periódica cuya frecuencia regular es de 50Hz, la figura 2 muestra la relación entre el ancho del pulso de control y el movimiento en el eje del servomotor.

Fig2. Señal de control de un Servomotor

La señal de control de un servomotor es en esencia una señal tipo PWM (Pulse Width Modulation), con las siguientes consideraciones:

  • La señal es de baja frecuencia, de forma general un servomotor trabaja con frecuencia de 50Hz, aunque existen modelos que pueden trabajar con frecuencias de hasta 400Hz o incluso mas allá.

  • El control de un servo requiere que el ciclo activo de la señal PWM este entre 5% y 10%, note esto en la figura 2.

Tomando en cuenta lo citado previamente utilizaremos un temporizador del Microcontrolador junto al recurso de interrupción para generar la señal de control PWM en los pines del puerto digital.

Señal de Control PWM

La figura 3 nos ilustra con mayor detalle como es la señal de control PWM que requiere un servomotor, en la gráfica se observa que el periodo de 20ms corresponde con una frecuencia de operación de 50Hz y el ancho del pulso varia desde los 1.0 a 2.0 milisegundos, por lo que una variación en el ciclo de trabajo (tc) desde el 5%  hasta el 10% establecerá el rango de movimiento en el servomotor.

Fig3. Señal de control PWM del servomotor

Generar la señal de control PWM con un temporizador de 8-bit es algo sencillo si analizamos la figura 4 y tomamos en cuenta lo siguiente:

  • El slot de tiempo corresponde al valor máximo ajustado al temporizador para generar una interrupción. (recuerde que la interrupción puede ocurrir por desbordamiento o comparación del registro contador). En un registro contador de 8-bit el valor máximo no superara los 255, en el programa este valor se definirá como (SVSLOT

  • Los incrementos en el temporizador son pasos de tiempo (tp) que determinan el movimiento del servomotor (Resolución). Por ejemplo si los pasos son de 10us, entonces el control de movimiento con un registro contador de 8-bit sera de (2.000/10)*0.5=100, recuerde que la señal de control esta entre el 1.0 y 2.0 ms, es decir que la mitad de este ciclo siempre estará activo, y el resto es variable.

Fig4. Control de servo, ancho del pulso

Con la explicación descrita hasta este punto veremos como generar la señal de control a varios servomotores utilizando solo un temporizador, para esto el programa utiliza un contador de slot de tiempo con las que se establecerá el periodo de la señal PWM, además se debe asociar cada slot de tiempo a un pin de salida de cada servomotor conectado, observe la figura 5 y notara que se deben contar diez slots para completar un periodo de tiempo (2ms*10=20ms), de los cuales solo ocho se utilizaran para el control de servomotores, esto se debe a que los puertos digitales de un PIC/AVR son de 8-bit, es decir hasta ocho pines de salida. 

Fig5. Señal de control para cuatro servomotores
Con el fin de facilitar la programación analizaremos algunas secciones importantes de la librería <mservo.h> que vamos utilizar describiendo algunas definiciones de control y sus respectivos cálculos:
  • MSVNUM. Define la cantidad de servomotores que se utilizaran, este valor debe estar entre 1 a 8 según los servomotores conectados.
  • MSVSLOT. Define el valor del registro contador para que el temporizador se ajuste a los 2.0ms de un slot, este valor deberia estar entre 150 y 250, con 250 se tiene mejor resolución para el control de movimiento. Además el valor establecido determinara los pasos del temporizador, por ejemplo si el valor se ajusta a 250 (SVSLOT), entonces los pasos (tp) serán de 8us, ver ecuaciones de la figura 4.
  • MSV.svxpos. Es una variable de 8-bit que controla el movimiento del servomotor, por lo que este valor decimal corresponde con el ciclo de trabajo variable (tc_var) de la señal. Considerando las ecuaciones de la figura 4, podemos determinar los valores que se muestran en la siguiente tabla:
Tabla1. Ajustes para movimiento en grados

Revisando la tabla 1, en la primera fila si los pasos a contar son 16us, conseguiremos una menor precisión de movimiento en el servomotor, por ello optaremos usar siempre pasos de 8 a 10us. Para determinar los pasos en un PIC16F debemos considerar la relación que tiene el ciclo de instrucción (Tcy) con respecto a la frecuencia de operación,  El un PIC16F esta relación es Tcy=4/Fosc, por lo sera necesario seleccionar adecuadamente el divisor de pre-escala o post-escala del temporizador para que los incrementos de registros contador ocurran en pasos de 8us a 10us.

Tabla 2. Configuración pasos con TMR2 PIC16F

En el caso de los Microcontrolador AVR, como ser el conocido ATmega328, la relación es directa Tcy=1/Fosc.

Para llevar a cabo la programación de control para los servomotores, utilizaremos dos procedimientos que se encuentran en la librería local <mservo.c>, que se encuentra en la carpeta del proyecto MPLABX, estos procedimientos facilitaran aplicar el proceso de señalización a todas la salidas de control PWM de forma simple utilizando la interrupciones del temporizador TMR2. A continuación resumo una descripción de ambos procedimientos:

  • TSERVOSetup(): Configura los pines de salida asignado a cada servomotor, esto en base al ajuste de las definiciones que se encuentran en la cabecera <mservo.h>. Este procedimiento debe ser llamado por única vez al momento de inicializar el sistema.

  • TSERVOHandler(): Esta función es invocada desde la rutina de servicio a la interrupción ISR correspondiente al temporizador TMR2, y permite controlar el estado de la señalización en cada unos de los pines PWM utilizados para el control de un servomotor. La función devuelve el valor de tiempo con la que se actualizara nuevamente el temporizador.

Esquema del Circuito

 Para poner en practica lo aprendido utilizaremos la simulación en ISIS Proteus 8.5 con el esquema de circuito que se muestra en la figura 6.

Fig6. Esquema de circuito PIC16F

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 ejecuta 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.

<Programación por estados con Temporizador>

Programación del PIC

El programa de control se encuentra dentro de la carpeta de proyecto MPLABX, disponible en el siguiente enlace <Click AQUI>, por lo que bastara con abrir el proyecto, proceder a la compilación, carga del firmware al circuito del simulador  y proceder con la simulación del programa. Pero a manera de analizar el programa, describiré algunas secciones importantes del mismo.

Sección 1: Cabecera tservo.h

/* USER PORT DEFINITION */
#define MSVNUM 2   
//Numero de servos a conectar, Maximo 8. 20ms/8 > 2.5ms.
#define MSVPIN 0   
//Indica el numero de pin indice 0-7
#define MSVSLOT 250U 
//Maximo desplazamiento 250 x 8us = 2ms
#define MSVPOS0 188U 
//Posicion central servo 188 x 8us = 1.5ms
#define MSVPORT PORTD
//Port <pos6><pos5><pos4><pos3><pos2><pos1>
#define MSVTRIS TRISD
//Tris servo port
/* END USER PORT DEFINITIOS*/

Sección 2: Programa principal

#include "mservo.h" //Cabecera de la librería servo
MSV_t MSV;
//Declaración de estructura Servo
void setupMCU(void);//Prototipo de función
void main(void)
{
    setupMCU();
//Configuración del PIC
    MSERVOSetup();
//Configuración del SERVO
    MSV.sv1pos = MSVPOS0;
//Ajusta servo al 50%
    MSV.sv2pos = MSVPOS0;
//Ajusta servo al 50%
    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) {};
   
 
    /* CONFIGURACION TIMER2 CON INCR 8us A 8MHz */
   
T2CONbits.T2CKPS = 0b10; //Prescala 1:16 x Tcy = 8us
    TMR2 = 0;
    PR2 = 125;
//Periodo 8uS * 125 = 1.0ms
    T2CONbits.TMR2ON = 1;
//Activa el temporizador
    PIR1bits.TMR2IF = 0; 
//Limpia bandera
    PIE1bits.TMR2IE = 1; 
//Activa interrupción
   
INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
}
 
Sección 3: Rutina de Interrupción.

void __interrupt() isr(void) //Rutina de servicio de interrupción
{
   
if(PIR1bits.TMR2IF)
    {
        PR2 = MSERVOHandler();
        PIR1bits.TMR2IF = 0;  
    }
}

 

Dejo a continuación un vídeo donde se muestra el uso de los servomotores utilizando un Microcontrolador ATMega328. <Click Aquí>


Conclusiones y Recomendaciones

De manera general vimos realizar el control de varios servomotores conectados al puerto de nuestro microcontrolador PIC16F, el modelo de programación aplicado hace uso de funciones y procedimiento no bloqueantes utilizando interrupciones del microcontrolador, además para este ejemplo en particular se utiliza el modulo TMR2.  Mencionar que este ejemplo es solo una de las varias maneras en que existen para este tipo de control, por lo que debes verificar si esta técnica es adecuada para cumplir con los requisitos particulares que conlleva cada aplicación.

Con relación al programa y la técnica de control utilizada es importante considerar los siguientes puntos:

  • Es posible emplear diferentes frecuencias del oscilador como los que se muestran en la tabla2,  y considerar también que la frecuencia de control PWM del servomotor que es de 50Hz, esta pueden operar desde los 40Hz, hasta mas allá de 100Hz.
  • Si bien los servomotores vistos en esta sección, se diseñan para operar en un rango de movimiento que va de los 0 a 180 grados, es recomendable no alcanzar las limitaciones de movimiento, con el propósito de reducir la fatiga estructural del mecanismo o fallas en el servomotor.
  • La latencia de las interrupciones puede provocar oscilaciones en el movimiento del servomotor cuando en la rutina ISR se atiende otras fuentes de interrupción que requieren mayor tiempo de atención; Debe analizar a detalle la implicación que conlleva atender agregar un nuevo evento a la ISR.

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

martes, 25 de marzo de 2025

TMR Multiplexacion 7 Segmentos

 Multiplexación Pantallas de 7 Segmentos

 

Fig1. Pantallas de 7 Segmentos

Muchas gracias por tu visita a este blog relacionado con la programación de micro-controladores PIC, en esta entrada quiero mostrarles como utilizar las pantallas de 7 segmentos mediante la técnica de multiplexación para así reducir la cantidad de lineas necesarias para su control.

El objetivo de esta entrada es aplicar la técnica de multiplexación por tiempo de dos o mas pantallas de 7 segmentos con un microcontrolador PIC16F en la que se pretende mostrar un numero de cuatro dígitos correspondiente al resultado de una conversion ADC de 10-bit.

La programación del microcontrolador PIC se lleva a cabo con el software  de diseño  MPLABX  y el compilador de lenguaje C para PIC XC8, ambos disponibles en la pagina de microchip de forma gratuita. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:   

<<MPLABX v6.20>>   

<<XC8 v3.0>>

Introducción al Tema

Fig2. Display de 7 Segmentos

Esta pantalla de visualización conocido como display de 7 segmentos, esta constituido por siete a ocho diodos LED con uno de sus extremos conectados a un punto común y físicamente distribuidos para representar un numero decimal. Dependiendo del modo en como se conectan los diodos veremos dos tipos de pantalla conocido como Ánodo Común (CA) y Cátodo Común (CC), observe la figura 2. 

Fig3. Tipo de configuracion 7 segmentos

Es importante considerar el tipo de pantalla para su correcta polarización ya que de ello dependerá el numero que se desea mostrar, en ambos casos podemos observar en la figura 3, una tabla de equivalencia de los niveles lógicos necesarios para los símbolos que se pueden mostrar. 

Fig4. Numeración decimal con 7 segmentos
Tabla1 Decodificación de los segmentos (CC)

Considerando la tabla anterior podemos notar que se necesitara un circuito combinatorio cuya salida active los segmentos en base a un numero binario presente en su entrada, este circuito es conocido como decodificador binario a 7 segmentos, dos circuito integrados de función fija muy utilizados para este fin son el 74LS47(CA) y 74LS48(CC), pero en el caso de un MCU esta operación pueden llevarse a cabo fácilmente con el uso de tablas en memoria, tal como se observa en la siguiente declaración del código para segmentos del tipo cátodo común.

/* Mapa de conversion a 7 Segmentos Cátodo Común dp-G-F-E-D-C-B-A */
uint8_t dmap[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7C,0x07,0x7F,0x67};

Este arreglo de memoria con diez elementos de un byte, contiene el valor necesario para representar el numero según su índice de posición, por ejemplo el primer elemento, tiene el índice cero por lo tanto se debe referencia como dmap[0] que tiene el valor 0x3F, siendo el bit0 de menor peso el segmento A y el bit6 el segmento G.  Si queremos aplicar esta tabla de conversion a una pantalla del tipo Ánodo Común (CA), bastara únicamente invertir los valores al momento de hacer referencia a cada elemento aplicando el operador logico de complemento ~dmap[x] o reemplazar con lo siguiente:

/* Mapa de conversion a 7 Segmentos Ánodo Común dp-G-F-E-D-C-B-A */
uint8_t dmap[10] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x83,0xF8,0x80,0x98};

Multiplexación de Pantallas

Debido a que una pantalla necesitar al menos 7 a 8 lineas para polarizar los LED, en la practica es muy común utilizar la técnica de multiplexación por tiempo en la que las lineas para activar los segmentos se comparten a todas las pantallas, pero solo una permanecerá activa en el tiempo. Observe la siguiente figura animada que muestra este proceso y comprenderá que al incrementar la frecuencia o barrido de activación por pantalla se percibirá menos el cambio debido a la retención visual del ojo humano, al punto que el cambio no se notara si la frecuencia supera los 25Hz.

Fig5. Multiplexación de 4 pantallas (Autor: Editorial Staff)

Los modulo de  cuatro dígitos como ser el LTC-5623HR y LTC-5723HR facilitan bastante la implementación del circuito electrónico en la que se requiere mostrar informacion de números decimales, esto porque los módulos ya llevan las conexiones poseen las conexiones de todos segmentos listos para el control por multiplexación, reduciendo significativamente la cantidad de pines.

Las siguiente figuras por ejemplo se han extraído de la hoja de datos LTC-5623HR para mostrar la interconexión de los segmentos para los cuatro dígitos.  

Fig 6. Modulo de 4 Dígitos LTC-5623 tipo Ánodo Común

Esquema del Circuito

Para realizar una demostración de la multiplexación de estas pantallas utilizaremos uno de los circuitos que se muestra en los siguientes esquemas de circuito que se diferencian en el tipo de modulo de cuatro dígitos. 

Fig7. Configuración modulo LTC-5623 tipo Ánodo Común

Fig8. Configuración modulo LTC-5723HR tipo Cátodo Común

Se necesitaran ocho pines del PORTD para activar cada segmento de la pantalla incluido el punto que corresponde al pin RD7 y cuatro pines del PORTC para activar cada una de las cuatro pantallas mediante transistores que permiten la circulación de corriente necesaria para que mas de un segmentos a la vez se iluminen (segmentos en paralelo).

En el primer circuito de la figura 7, la activación de cada segmento así como las pantallas, se consigue aplicando niveles bajos en todas las lineas de control, esto quiere decir que en estado de reposo las lineas estarán en niveles altos. Por otro lado con el segundo circuito de la figura 8, la activación de segmentos y pantallas se realizar con niveles altos, siendo el nivel bajo el estado de reposo por defecto. Se debe considerar que las resistencias en serie utilizadas en cada segmentos influyen en la intensidad de iluminación, y respecto a los transistores para activar las pantallas puede utilizar un BC557 para el modulo LTC-5623HR y un BC547 con el modulo LTC-5723HR.

En ambos esquemas de circuito se utilizara un potenciómetro conectado a la entrada AN0 del PIC para que el resultado de la conversion ADC de 10-bit, que son cuatro dígitos 0000 al 1024 se visualicen en pantalla. Por ultimo el diodo LED1 que esta en la salida RE2 destellara cada segundo para indicar que nuestro circuito y su programa esta en operación normal.

Programación del PIC16F

Entonces elaboraremos un programa para el PIC16F887 considerando el uso del modulo LTC-5623 que es del tipo Ánodo Común, por lo tanto el control de las pantallas se lleva a cabo con niveles bajos. La estructura que tiene el programa principal se conforma por la ejecución de tres tareas con intervalos de un milisegundo, y en las que cada tarea controla su estado de ejecución de forma independiente. Considera hacer lectura de la siguiente  entrada en la que explico con mas detalle la ejecución de tareas concurrentes con interrupción del temporizador. <Concurrencia de tareas con Temporizador>.

Con respecto al funcionamiento del código su descripción esta en los propios comentarios, por lo que espero pueda ser entendible y ante cualquier duda no dudes en escribirme.

La sección inicial del programa, muestra la configuracion de fusibles, definición de pines, mapa de conversion y variables del programa. 

#pragma config FOSC=INTRC_NOCLKOUT, WDTE = OFF, BOREN = OFF, LVP = OFF
#include <xc.h>
#define LED1pin PORTEbits.RE2
#define SEGPORT PORTD
#define DP1pin PORTCbits.RC0
#define DP2pin PORTCbits.RC1
#define DP3pin PORTCbits.RC2
#define DP4pin PORTCbits.RC3
volatile __bit tickms;
/* Mapa de conversion a 7 Segmentos dp-G-F-E-D-C-B-A */
uint8_t dmap[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7C,0x07,0x7F,0x67};
uint16_t adcvalue;
//Valor del ADC 10-bit
uint8_t dvalue[4];
//Valores decimales de cada dígito

El procedimiento principal refleja el orden de ejecución concurrente de las tareas por cada milisegundo, la bandera tickms es activada por interrupción del temporizador TMR0. 

void main(void)
{
    MCUSetup();
    adcvalue = 0;
    while(1)
    {
        if(tickms)
//Valido cada 1ms
        {
            tickms = 0;
            taskLED();
//Destello de LED1
            taskADC();
//Lectura y actualización datos
            taskTDM();
//Multiplexación de pantallas
        }
    }
}

En la Tarea de multiplexación de cuatro pantallas, tomando en cuenta que la llamada a este procedimiento se realiza cada 1ms que equivale a 1kHz en frecuencia, este valor se divide entre las cuatro pantallas siendo la frecuencia de barrido 250Hz.

void taskTDM(void) //Tarea para control Pantalla t=1ms
{
    static uint8_t state = 0;
    uint8_t res;
    res = dvalue[state];
    switch(state)
    {
        case 0:
//Activa solo DP1
            DP4pin = 1;
            SEGPORT = ~dmap[res];
//Dígito de mayor peso
            DP1pin = 0;
            state++;
            break;
        case 1:
//Activa solo DP2
            DP1pin = 1;
            SEGPORT = ~dmap[res];
            DP2pin = 0;
            state++;
            break;
        case 2:
//Activa Solo DP3
            DP2pin = 1;
            SEGPORT = ~dmap[res];
            DP3pin = 0;
            state++;
            break;
        case 3:
//Activa solo DP4
            DP3pin = 1;
            SEGPORT = ~dmap[res];
//Dígito de menor peso
            DP4pin = 0;
            state = 0;
            break;
    }

La tarea de lectura ADC y actualización de dígitos se repiten a intervalos de 200ms, es decir una frecuencia de 5Hz. Puede reducir o incrementar este valor si desea modificar la sensibilidad de cambio en el potenciómetro.

void taskADC(void) //Tarea de control ADC t=1ms
{
    static uint16_t cnt = 0;
    static uint8_t state = 0;
    cnt++;
    switch(state)
    {
        case 0:
//Inicia Conversión
            adcvalue = 0;
            ADCON0bits.GO = 1;
            state++;
            break;
        case 1: //Lectura del valor ADC
            if(ADCON0bits.GO == 0)
            {
                adcvalue = ADRESL;
//ADRESL First
                adcvalue |= (uint16_t) (ADRESH << 8);
                adcvalue >>= 6;
//Left aligment of ADH:ADL
                state++;
            }
            break;
        case 2:
//Actualiza dígitos de pantalla y espera
            if(cnt >= 200)
//Intervalo 200ms
            {
                cnt = 0;
                if(adcvalue > 999)
//Miles
                {
                    dvalue[3] = (uint8_t)(adcvalue / 1000U);
                    adcvalue = adcvalue % 10;
                }
                else dvalue[3] = 0;
                if(adcvalue > 99)
//Centenas
                {
                    dvalue[2] = (uint8_t)(adcvalue / 100U);
                    adcvalue = adcvalue % 10;
                }
                else dvalue[2] = 0;
                if(adcvalue > 9)
//Decenas
                {
                    dvalue[1] = (uint8_t)(adcvalue / 10U);
                    adcvalue = adcvalue % 10;
                }
                else dvalue[1] = 0;
                dvalue[0] = (uint8_t) adcvalue;
                state = 0;
            }
            break;
    }
}

Fig9. Funcionamiento del circuito PIC

Los procedimientos que se mostraron con antelación son parte del archivo principal main.c del proyecto MPLABX, este archivo contiene código adicional que no se ha detallando en esta publicación por esta razón dejo el enlace para su descarga y así puedas hacer una mejor revisión mediante MPLABX. <Multiplexación 7 Segmentos>

Si quieres ver como compilar e implementar los proyectos de este blog, que están elaborados con el IDE MPLABX y el compilador XC mira este vídeo. <Compilando proyectos MPLABX> 

Conclusiones y recomendaciones

Vimos como hacer el control varias pantallas de 7 segmentos aplicando la multiplexación por tiempo en nuestro microcontrolador, y como habrás notado el código es relativamente simple y mantiene una clara separación de otras tareas que se pueden ejecutar desde el programa principal, por lo que podrás agregar esta pantalla a la mayoría de tus proyectos con facilidad. 

Un punto que debes considerar es que al incrementar la cantidad de pantallas, la intensidad en el brillo se reduce debido a que el tiempo de activación por dígito es menor, entonces para compensar esto puedes reducir el valor de las resistencias que van en serie con los segmentos,  tomando en cuenta la corriente pico por segmento a un determinado ciclo de trabajo, este valor se muestra en la hoja de datos de la pantalla, por ejemplo el modulo LTC-5623HR posee los siguiente valores:

-Corriente continua por segmento de 25mA
-Corriente pico por segmento de 100mA (ciclo 1/10, ancho de pulso 0.1ms)

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



domingo, 2 de marzo de 2025

Lectura de Codificador Rotatorio

Codificador Rotatorio con PIC

Fig1. Modelos de Encoder Rotativos

Bienvenido a mi blog que tiene por objetivo compartir el interés en la electrónica y en especial la programación de microcontroladores PIC. Antes de empezar quiero recordarte que estoy atento a cualquier sugerencia o critica constructiva relacionada con esta publicación. En la parte final dejo mis datos de contacto.

En esta ocasión veremos algo simple e interesante de usar como elemento de entrada para nuestro circuito PIC,  este dispositivo conocido como codificador rotatorio (Encoder) esta presente en gran cantidad de equipos electronicos y es un componente económico y fácil de conseguir, por lo veremos como implementar en el programa las rutinas necesarias para el uso adecuado.
La programación del microcontrolador PIC se lleva a cabo con el software  de diseño  MPLABX  y el compilador de lenguaje C para PIC XC8, ambos disponibles en la pagina de microchip de forma gratuita. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:   
Recordarte que es importante contar con los conocimientos mínimos de programación en lenguaje C y el uso de microcontroladores PIC16F.

Introducción al Tema

Un codificador rotatorio, también llamado codificador del eje o generador de pulsos, suele ser un dispositivo electromecánico usado para convertir la posición angular de un eje a un código digital, esto lo convierte en una clase de transductor de entrada.
Por lo general la salida del codificador utiliza el código binario Gray, puesto que una ventaja importante para la construcción de este código es que la diferencia entre dos pasos adyacentes (distancia) es siempre un bit. La figura 2 muestra la secuencia del código generado por las dos salidas de codificador y en la parte inferior se observa la mecánica de rotación utilizado en su construcción.

Fig2. Operación del Codificador (fuente: wikipedia)
 

Modulo KY-040

Este modulo cuyo esquema de circuito se observa en la figura 3, dispone de un codificador rotativo que a diferencia de un potenciómetro este puede girar libremente sin topes mecánicos, además cuenta con un pulsador que resulta útil para la entrada de nuestro circuito. El modulo KY-040 trabaja con un voltaje de 5V y emite hasta 20 pasos de la secuencia (Gray) por revolución.

Fig3. Modulo Codificador KY-040
 

Esquema del Circuito PIC

Realizaremos la prueba del codificador rotativo modulo KY-040 montando el circuito que se muestra en la figura 4. El PIC16F887 utilizara el oscilador interno para una frecuencia de 8Mhz, la entrada RB0 tendrá configurado el resistor pull-up interno para validar los niveles lógicos cuando presionamos el botón central ESW1  (S1-S2) del codificador, en cuanto a los leds de salida todos se conectaran a una misma resistencia limitadora tomando en cuenta que solo un led permanecerá activo en el tiempo.

En cuanto a las señales de salida del codificador CK(A) y DT(B), en el esquema se conectan a la entrada RB1 (ECK1) y RB2 (EDT1)

Fig4. Esquema del circuito PIC16F

Programación del PIC16F

Vamos a crear un programa que en base a la señal del codificador se activara uno de los ocho leds conectados al PORTD del PIC,  de tal manera que se generara un desplazamiento del led activo según el sentido de rotación que se tenga en el codificador, adicionalmente el programa destellara el LED1 indicando el funcionamiento normal del programa, verificara si pulsador central ESW1 esta presionado para colocar en todos los pines del PORTD en nivel bajo (Apaga todos los leds). La definición de los pines junto al programa principal sera el siguiente código:

#define ECK1pin  PORTBbits.RB1    //Definición de pines
#define EDT1pin  PORTBbits.RB2
#define ESW1pin  PORTBbits.RB0

void main(void) //Rutina principal
{
    MCUSetup();
//Procedimiento para configurar el PIC
    while(1)
    {
        if(tickms)
//Se valida cada milisegundo
        {
            tickms = 0;
//Limpia bandera del temporizador
            taskLED1();
//Tarea para destellar LED1 a 1Hz
            taskSW1(); 
//Tarea para lectura del pulsador ESW1
            taskENC1();
//Tarea del Codificador
        }
    }  
}

Como notara en la estructura que tiene el programa principal, el proceso de ejecución de las tres tareas se realiza en cada milisegundo, donde cada tarea maneje su estado de ejecución de forma separada. Considera hacer lectura de la siguiente  entrada en la que explico con mas detalle la ejecución de tareas concurrentes con interrupción del temporizador. <Concurrencia de tareas con Temporizador>. No abordaremos detalles de las dos primeras tareas, ya que su operación no es algo relevante para la lectura del codificador rotatorio.

No enfocaremos en desarrollar la tercera tarea de la lista, taskENC1(). que es la encargada de hacer lectura del codificador y en base al sentido de rotación, efectuara el desplazamiento del led activo en el puerto de salida.

Para lograr esto, el primer paso sera crear la función para detectar el sentido de rotación del codificador, para lo cual utilizaremos como referencia el patrón de cambio de las señales CK / DT y un diagrama de Estados y Transiciones basado en el modelo Mealy para una Maquina de Estados Finitos (FSM). Observe el diagrama de la figura 5.

Fig5. Señalización(Izq) con Diagrama Estados y Transiciones(Der)

En base a la figura 5, vamos a describir dos métodos para crear la función de detección al cual llamaremos ENCGetSense(), describiendo las diferencias en cuanto a su aplicación.  

Método 1: Se utiliza una codificación basada en el diagrama de estados y transiciones donde la función deberá ejecutarse ante el cambio de estado en cualquiera de las señales CK o DT, el valor retornado indica el sentido de rotación. La ventaja de este método es la sensibilidad con la rotación, ya que la función responde con cada fase o paso del codificador. En el caso del modulo KY-040 notara esta sensibilidad con un sutil desplazamiento de rotación, lo que podría ser un inconveniente.

uint8_t ENCGetSense(void) //Debe ejecutarse con cada cambio en CK o DT
{
    static uint8_t state;
    uint8_t res = 0;
    switch(state)
    {
        case 0b00:
//Estado STA00
            if((ECK1pin == 1) && (EDT1pin == 0))
            {   res = 1;
                state = 0b10;
            }
            if((ECK1pin == 0) && (EDT1pin == 1))
                state = 0b01;
        break;
        case 0b01:
//Estado STA01
            if((ECK1pin == 0) && (EDT1pin == 0))
            {   res = 1;
                state = 0b00;
            }
            if((ECK1pin == 1) && (EDT1pin == 1))
                state = 0b11;
        break;
        case 0b10:
//Estado STA10
            if((ECK1pin == 1) && (EDT1pin == 1))
            {   res = 1;
                state = 0b11;
            }
            if((ECK1pin == 0) && (EDT1pin == 0))
                state = 0b00;
        break;
        case 0b11:
//Estado STA11
            if((ECK1pin == 0) && (EDT1pin == 1))
            {   res = 1;
                state = 0b01;
            }
            if((ECK1pin == 1) && (EDT1pin == 0))
                state = 0b10;
        break;
    }
    return res;
//retorna el sentido de rotación
}

Método 2: Esta codificación utiliza como referencia la transición en CK, por tanto la función deberá ejecutarse posterior a un cambio de nivel de esta señal. Observe la parte derecha de color azul en la figura 5 donde hay dos puntos de transición en CK, notara que posterior a cada transición si ambas señales son iguales el sentido es derecho y sera opuesto si las señales difieren.  El inconveniente de este método es que la función responde solo a los cambios en CK, por lo tanto no es sensible a todos las fases o pasos del codificador.

uint8_t ENCGetSense(void) //Debe ejecutarse con cada cambio en CK
{
    uint8_t res, val;
    res = ECK1pin;
    val = EDT1pin;
    res = (res ^ val); 
//XOR entre CK y DT
    return (res & 0x01);
//Retorna el sentido de giro

Este segundo método de codificación se adapta mejor a la lectura de nuestro modulo KY-040, por lo que en adelante consideraremos su uso en nuestro programa.
Como ya tenemos la función para detectar el sentido de giro del codificador ENCGetSense(). es momento de elaborar el procedimiento final taskENC1() que debe ejecutarse de manera concurrente cada milisegundo. Este procedimiento deberá llamar a la función de detección únicamente cuando cuando cambie la señal en CK, por esta razón observara que se realizar una enmascaramiento al pin con la finalidad de guardar un valor previo para realizar la comparativa con cada nuevo ciclo de ejecución, solo se enmascara el pin RB1 ya que utilizaremos para la detección el método 2.

void taskENC1(void) //Ejecución a cada milisegundo
{
    static uint8_t portbval = 0x02;
//Mascara inicial en CK
    static uint8_t portdval = 0x01;
    if((PORTB & 0x02) != portbval)
//Verifica modificación de la señal CK
    {
        portbval = PORTB & 0x02;
//Actualiza nuevo valor CK
        if(ENCGetSense())
//Si sentido es horario
        {
            if(portdval == 0) portdval = 0x80;
            else portdval >>= 1;
//Desplazamiento a la derecha
        }
        else
//Si sentido opuesto
        {
            if(portdval == 0) portdval = 0x01;
            else portdval <<= 1;
//Desplazamiento a la izquierda
        }
        PORTD = portdval;
    }    
}

Los procedimientos que se mostraron con antelación son parte del archivo principal main.c que contiene el proyecto, este archivo contiene código adicional que no se ha detallando en la presente publicación, por esta razón dejo el enlace para que puedas descargarlo y analizar con mas detalles desde el IDE MPLABX. <Proyecto MPLABX del Codificador Rotatorio>

Si quieres ver como compilar e implementar los proyectos de este blog, elaborados con MPLABX mira este vídeo.<Compilando proyectos MPLABX>

Conclusiones y recomendaciones

De manera general vimos como hacer uso del codificador rotatorio KY-040, la codificación utilizada se baso en un modelo de maquinas de estado finito aplicando el modelo Mealy para determinar el valor de salida en función de las transiciones de la señal CK y DT.  Mencionar que el ejemplo representa una de las varias maneras en que se puede crear un procedimiento para leer estos dispositivos codificadores, por lo que la viabilidad de cualquier método utilizado dependerá bastantes de los requisitos particulares de una aplicación.

Con relación al programa y la manera en que abordamos el procedimiento de lectura del codificador, es importante considerar los siguientes puntos:

  • La sensibilidad para la detección de cambios en las salidas del codificador, se limitan al periodo de un milisegundo correspondiente al tiempo de la interrupción del temporizador, por lo este ejemplo no funcionara si la frecuencia de cambios es mayor o igual a 1Khz.
  • Debido a que en la generación de niveles lógicos en la salida del codificador intervienen elementos mecánicos, es posible la presencia de rebotes, por lo que resulta una buena opción agregar al programa rutinas de validación, así como un divisor para controlar la relación de cambios.
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