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