viernes, 21 de noviembre de 2025

Control de LED WS2812

Control de LED inteligente WS2812

Fig1. Controlador Inteligente WS2812

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 esta ocasión veremos como controlar los leds inteligentes WS2812 con un microcontrolador PIC16F que utiliza un cristal oscilador de 20MHz.  Como sabrán los tiempo necesarios para la señalización de estos leds son muy cortos lo cual limita su uso en microcontroladores PIC16 de gama media, esta limitación es menor en PIC mas modernos la serie PIC16F188xx, o algunos que disponen del modulo CLC como es el caso del PIC16F1509 donde Microchip publica una nota aplicativa <AN1606> referido al tema. 

En la búsqueda de practicar la programación utilizando instrucciones del ensamblador PIC-AS, decidí implementar el control para estos led utilizando el PIC16F887. La programación del microcontrolador 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>>

Recordar al lector que en esta ocasión se necesitara de conocimientos mínimos de programación en lenguaje C, y el juego de instrucciones de ensamblador.

Controlador WS2812

Fig2. Tira de LEDS Inteligentes (WS2812)

Este dispositivo forma parte de un tipo de luces conocidos como LED's inteligentes o NEO pixel, y se caracteriza porque posee un circuito integrado (controlador) incorporado en el mismo encapsulado, con la que es posible controlar individualmente su color y brillo a través de un protocolo serial basado en la retransmisión de informacion en cadena. Estas luces están diseñadas para trabajar con 5V y existe una variante WS2812B que difiere físicamente como notara en la imagen inferior y representa una versión mejorada.

Fig3. WS2812 (Izquierda) y WS2812B (Derecha)

El funcionamiento se resume en los siguientes pasos:

  • Cada LED recibe un dato de 24-bit con el comando de color GRB compuesto por tres datos de 8-bit que controllan el brillo de la luz Verde, Rojo y Azul en un rango de 0(Apagado) a 255(Maximo brillo).

  • Todos los LED's se conectan en serie formando un arreglo de puntos (pixels), la información se envía a través de un solo pin de datos, que corresponde a la entrada (DI) del primer LED. 

  • Cuando un LED recibe y almacena su dato de 24-bit, este encamina la señalización por el pin DO al resto de los LEDS conectados.

  • Mediante este protocolo de comunicación de alta velocidad, se deben enviar consecutivamente datos de 24-bit para cada LED conectado al circuito.

En la figura 4 se puede observar la lógica para representar cada bit del protocolo, y la conexión en cascada para la única linea de comunicación, en cuanto a la alimentación, todos los diodos deberán conectarse a 5V (VCC) y tierra (VSS). 

Fig4. Señalización del protocolo serial

En cuanto a los tiempo de cada bit, la siguiente imagen nos muestra valores en el rango de los microsegundos, por lo que es importante mantener esta temporización dentro del rango de tolerancia permisible. 

Fig5. Temporización para el protocolo de control

Aquí notamos que los tiempos para validar un bit del protocolo son muy cortos, consideramos el uso de un Microcontrolador PIC de gama media como el PIC16F8xx. De acuerdo a las especificaciones técnicas del PIC16F8xxx este puede trabajar hasta los 20MHz utilizando un cristal oscilador HS, pero debemos tomar en cuenta que la arquitectura de este PIC requiere de cuatro pulsos de oscilador para completar un ciclo de instrucción o maquina (Tcy),  por lo que al final este tiempo sera Tcy=4/20MHz = 0.2us.

Fig6. Transición de nivel bit 0/1.

Note en la figura 6 que la diferencia de señal para el bit 0 y el bit 1, es un retardo en la transición de nivel alto a bajo de solo 0.45 us, este tiempo es importante porque equivale a por lo menos dos ciclos de instruccion que serán utilizados para establecer correctamente los nivel lógicos del protocolo serial. La estructura de los datos y el protocolo a enviar se observa en la siguiente figura.

Fig7. Protocolo serie para el control de luces

El dato de 24-bit corresponde a una trama GRB(Verde, Rojo, Azul) que controla la intensidad de cada color en un rango de 0 - 255. Por lo tanto si tenemos un circuito con 20 luces WS2812 conectados en cascada, se deben enviar 20 * 24-bit consecutivamente, en este punto debemos tomar en cuenta que el tiempo de espera entre cada dato GRB de 24-bit no debe ser mayor a 50us. 

Una tiempo de espero mayor o igual a 50us con la salida DO en nivel bajo, indica que ya no hay mas datos a transmitir, quedando las luces de cada ajustado a los valores enviados previamente. Si se desea apagar todas las luces, entonces se deben enviar los datos GRB con valores en 0.

Esquema del circuito PIC

Fig8. Esquema del circuito PIC16F
 Se observa el esquema del circuito que utilizaremos para controlar un arreglo de ocho luces WS2812 conectados en linea recta. Un pulsador conectado al pin RB0 nos permitirá controlar el encendido y apagado de las luces con un nivel de brillo determinado por las variables de color GRB.

Programación del PIC

La programación de los tiempos requeridos para el protocolo de control necesitara el uso de instrucciones nativas de ensamblador, porque tomaremos como base el ciclo de instrucción Tcy=0.2us. Se creara una rutina en ensamblador PIC-AS que se llamara wswrite, esta rutina emitirá por un pin del puerto la trama de 24-bit correspondiente al dato GRB.  

Para es caso particular la rutina wswrite dispone las instrucciones asm ajustadas a los tiempos de bit del protocolo, considerando que el PIC16F opera con cristal de 20Mhz. La siguiente figura ilustra como se relaciona la duración de un ciclo de instrucción con cada fracción de tiempo que requiere la señal de control de un dato GRB. 

Fig9. Temporización de bit 0 y 1

A continuación se describe el código de la rutina wswrite que sera llamado desde el programa principal main.c

Código del fichero wswrite.s

#include <xc.inc>
WSPORT equ PORTD   
;Puerto de Control LED
WSPIND equ 0       
;Pin asignado DO
WSADDR equ 0x20    
;Dirección RAM GRB 24-bits    
psect   barfunc,local,class=CODE,delta=2
;PIC10/12/16
global _wswrite    
;extern of bar function in C
_wswrite:
    BANKSEL(WSADDR)
    movlw WSADDR   
;Inicia el array
    movwf FSR      
;Carga registro indirecto
   
//bit7 G-LED primer byte del Dato de 24-bit
    bsf WSPORT,WSPIND  
; 0.2u coloca DO=HIGH
    btfss INDF,7        
;+0.2u verifica el bit 7
    bcf WSPORT,WSPIND  
;+0.2u coloca DO=LOW si bit=0
    nop                
;+0.2u
    bcf WSPORT,WSPIND  
;+0.2u coloca DO=LOW si bit=1
    nop                
;+0.2u
    nop                
;+0.2u
   
//bit6
    bsf WSPORT,WSPIND
    btfss INDF,6
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop            
    nop
   
//bit5
    bsf WSPORT,WSPIND
    btfss INDF,5
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit4
    bsf WSPORT,WSPIND
    btfss INDF,4
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit3
    bsf WSPORT,WSPIND
    btfss INDF,3
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit2
    bsf WSPORT,WSPIND
    btfss INDF,2
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit1
    bsf WSPORT,WSPIND
    btfss INDF,1
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit0
    bsf WSPORT,WSPIND
    btfss INDF,0
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    incf FSR
   
//bit7 R-LED segundo byte del Dato de 24-bit
    bsf WSPORT,WSPIND  ; 0.2u coloca DO=HIGH
    btfss INDF,7       ;+0.2u verifica el bit 7
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=0
    nop                ;+0.2u
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=1
    nop                ;+0.2u
    nop                ;+0.2u
   
//bit6
    bsf WSPORT,WSPIND
    btfss INDF,6
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop            
    nop
   
//bit5
    bsf WSPORT,WSPIND
    btfss INDF,5
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit4
    bsf WSPORT,WSPIND
    btfss INDF,4
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit3
    bsf WSPORT,WSPIND
    btfss INDF,3
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit2
    bsf WSPORT,WSPIND
    btfss INDF,2
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    n
op
    //bit1

    bsf WSPORT,WSPIND
    btfss INDF,1
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
    //
bit0
    bsf WSPORT,WSPIND
    btfss INDF,0
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    incf FSR

   
//bit7 B-LED tercer byte del Dato de 24-bit
    bsf WSPORT,WSPIND  ; 0.2u coloca DO=HIGH
    btfss INDF,7       ;+0.2u verifica el bit 7
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=0
    nop                ;+0.2u
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=1
    nop                ;+0.2u
    nop                ;+0.2u
   
//bit6
    bsf WSPORT,WSPIND
    btfss INDF,6
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop            
    nop
   
//bit5
    bsf WSPORT,WSPIND
    btfss INDF,5
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit4
    bsf WSPORT,WSPIND
    btfss INDF,4
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit3
    bsf WSPORT,WSPIND
    btfss INDF,3
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
    /
/bit2
    bsf WSPORT,WSPIND
    btfss INDF,2
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit1
    bsf WSPORT,WSPIND
    btfss INDF,1
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit0
    bsf WSPORT,WSPIND
    btfss INDF,0
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
return

Observe que esta rutina utiliza el valor de tres posiciones consecutivas de memoria RAM a partir de la ubicacion definida en WSADDR, los valores de estas posiciones corresponden al brillo de luz GRB(Verde, Rojo, Azul). La referencia a esta ubicación tambien debe ser declarada en el programa principal con el fin de reservar el espacio de memoria. Así mismo debe definirse el puerto y pin para la salida de control ajustando las definiciones WSPORT y WSPIND.
El siguiente código muestra un programa principal que realiza el control de las luces del circuito descrito anteriormente, el ejemplo muestra como encender y apagar ocho luces WS2812 con un pulsador, cada vez que se presione el pulsador se cambiar el color de la luz.

Código de ejemplo fichero main.c

#pragma config FOSC=HS, WDTE = OFF, BOREN = OFF, LVP = OFF
#include <xc.h>
#define _XTAL_FREQ 20000000
uint8_t butstate, i, val=0;
//Variables de control de estado
typedef struct
{
    uint8_t green;
    uint8_t red;
    uint8_t blue;
} WS_t;
//datos de control GRB 24-bit
WS_t wsled
__at(0x20); //Reserva espacio RAM
extern uint8_t wswrite(void);
void main(void)
{
   
//Operacion FOSC=20MHz con Oscilador HS Tcy=0.2u
    ANSEL = 0;
//Deshabilita canales ANS0-7
    ANSELH = 0;
//Deshabilita canales ANS8-13
    TRISDbits.TRISD0 = 0;
//Salida de control WS2812
    PORTDbits.RD0 = 0;
    OPTION_REGbits.nRBPU = 0;
//Activa pull-ups del PORTB
    while(1)
    {
        if(butstate != PORTBbits.RB0)
//Si pulsador cambia de estado
        {
            butstate = PORTBbits.RB0;
//Actualiza estado de pulsador
            if(butstate)
//Si el pulsador esta liberado
            {
                wsled.red = 0; 
//Brillo luz roja
                wsled.green = 0;
//Brillo luz verde
                wsled.blue = 0;
//Brillo luz azul
                for(i=0;i<8;i++)
//Emite senal de control a ocho leds
                    wswrite();
//Apaga las luces
            }
            else
//Si el pulsador esta presionado
            {
                wsled.red = 0;
                wsled.green = 0;
                wsled.blue = 0;
                if(val==0) wsled.red = 100;
//Brillo luz roja
                if(val==1) wsled.green = 100;
//Brillo luz verde
                if(val==2) wsled.blue = 100;
//Brillo luz azul
                if(val++>=2) val = 0;
                for(i=0;i<8;i++)
//Emite senal de control a ocho leds
                   wswrite();
//Enciende la luz en color
            }
        }
        __delay_ms(100);
//retardo entre lecturas del pulsador
    }
}

Aquí dejo la animacion que muestra el funcionamiento del ejemplo anterior utilizando una placa de desarrollo basado en el PIC16F887.

Fig10. Funcionamiento del programa
En el siguiente video describo como realizar el control de encendido de estas luces de forma gradual utilizando el arreglo lineas de 8 leds y posterior uno circular de 24 leds, el codigo del proyecto MPLABX lo encuentras en mi reporsitorio git. <https://github.com/pablinza/piclab2>


Conclusiones y recomendaciones

Hemos visto como realizar el control de luces conformado por led inteligentes WS2812 utilizando un microcontrolador PIC16F a 20Mhz, si bien es tomar como base la rutina wswrite escrito en ensamblador para desarrollar proyectos de iluminación es importante tomar en cuenta los siguientes aspectos.

  • Al momento de enviar los datos a cada led llamando consecutivamente a la rutina wswrite se debe deshabilitar la interrupción global, puesto que una interrupción durante el proceso de envío, romperá los tiempos ajustados con cada instrucción ASM de la rutina.
  • Si bien es posible crear aplicaciones para mostrar mensajes o crear diversos efectos con imágenes para un arreglo lineal o incluso con disposicion matricial, se debe considerar las limitaciones de memoria RAM que tienen los PIC16F de rango medio, por lo que es recomendable migrar a un microcontrolador con mayores prestaciones como lo son la gama PIC18, PIC32 o dSPIC, pero necesitara realizar modificaciones en la rutina wswrite
  • Hacer notar que el tiempo entre llamadas a la rutina wswrite no deberia superar los 50us de acuerdo a la hoja de datos, puesto que este tiempo para reestablecer o indicar que ya se finalizo el envio de las tramas por cada LED es precisamente mantener en nivel bajo el pin de salida (DO=0) por al menos 50us. 

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

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