miércoles, 31 de julio de 2024

TMR0 Control de Potencia AC220

Control de Potencia AC220V

Te doy la bienvenida a este sitio, que fue creado para compartir el mutuo interés en la electrónica y en especial la programación de microcontroladores PIC. Agradecido por visitar mi blog quiero recordarte que estoy atento a cualquier consulta o critica constructiva relacionada con esta publicación. 
En esta oportunidad vamos a ver como controlar la potencia o energía que consume una carga resistiva, considerando que el suministro eléctrico es la linea AC220 de una instalación domiciliaria.
La programación del ejemplo se realizara utilizando 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 v2.45>>
Mencionar también que es necesario contar con conocimientos mínimos sobre programación en lenguaje C y el uso de microcontroladores PIC16.

Introducción al Tema

De manera breve describiremos que la señal de onda que se muestra en la figura 1, es el resultado de la rotación continua y constante de un <fasor> dentro de un campo magnético, donde la longitud del <fasor> determina la amplitud de la señal, y la velocidad de rotación determina su frecuencia, esta explicación corresponde a una señal alterna como la suministrada en una instalación domiciliaria, en mi país, la red de baja tensión posee un voltaje eficaz de E=220V y frecuencia de f=50Hz, La figura 1 muestra la relación amplitud y tiempo.

Fig1.  Ciclo de Señal en linea AC220V, Voltaje (V) / Tiempo(S)

Observe que la tensión maxima es Em=311V, y el tiempo total del ciclo T=0.02 segundos, esto se determina fácilmente aplicando las ecuaciones básicas: 

Cuando consideramos el uso de cargas resistivas consideramos que la onda sinusoidal del voltaje siempre estará en fase con la onda de corriente, eso si con magnitudes diferentes, entonces para controlar la energía suministrada a la carga resistiva, el circuito de control debe ser capaz de abrir y cerrar el circuito eléctrico de manera sincronizada con la fase de onda, este circuito se representa en la figura 2,

Fig2. Circuito básico para control de Carga

Comentaremos dos maneras para realizar el control de potencia en un carga, ambos sincronizados con la fase de onda. 

Forma 1: Control de Potencia por Ciclos activos

En este caso la llave de control, opera en función de la cantidad de semi-ciclos de la onda sinusoidal, abriendo o cerrando el circuito solo en los puntos de cruce por cero, de esta forma  se determina la cantidad energía que consume la carga en un lapso de tiempo, como notara en la figura 3.

Fig3. Control de Potencia por Ciclos

El lapso de tiempo mencionado que viene a ser el periodo (Tpwm) para el control es fijo, y el tiempo de activación (td) es variable, su valor sera siempre un múltiplo del periodo que tiene cada semi-ciclo (THALF), por ejemplo, si la frecuencia de onda es F=50Hz, se pueden calculan los siguientes valores considerando la figura 3:
El consumo de energía se determina por el ciclo de trabajo en porcentaje, aplicando la siguiente ecuación:
Este tipo de control de potencia se utiliza bastante para controlar la temperatura de las resistencias eléctricas de una gran variedad de equipos(horno, plancha, calefactor, etc).

Forma 2: Control de Potencia por Fase de disparo
En este caso, la llave de control se sincroniza con el inicio de cada semi-ciclo, para abrir el circuito y luego cerrarlo en función del ángulo de fase (θ) que tiene la onda, en la practica en ángulo de fase se controla por un tiempo de espera (td) que es variable, y el periodo de control (Tpwm) fijo es el tiempo que dura el semi-ciclo (THALF), tal como se observa en la figura 4. 

Fig4. Control de Potencia por Fase
Para una frecuencia de onda F=50Hz, el periodo de control sera:
TPWM = THALF = 0.001s
El ángulo de fase para cerrar el circuito en función del tiempo se determina por la siguiente ecuación y siendo la figura 5 una tabla con valores de tiempo(td) para un determinado ángulo de fase (θ).
Fig5. Tiempo de espera por ángulo de fase
  
Notara que el tiempo de espera necesario para el disparo, no debe exceder al periodo de control, y que su relación con la potencia entrada a la carga es inversamente proporcional, es decir que a mayor tiempo de espera (td), menor es la  potencia en la carga (dt%)

De la mima manera que el caso anterior, el consumo de energía se determina por el ciclo de trabajo en porcentaje, aplicando la siguiente ecuación:

Vamos a implementar un circuito de control de potencia considerando la forma 2, y para este fin utilizaremos una lampara incandescente de 100W, un circuito de potencia con tiristor (TRIAC) y un microcontrolador (PIC16F887).
 
Circuito de Control AC
En esta parte, ya con los conceptos vistos al inicio, analizaremos el control de fase que se implementara con un <TRIAC> y en la que utilizaremos las siguientes señales que se relacionas con la onda sinusoidal del voltaje AC, la figura 6, ilustra la representación en colores, donde: 
PWM = Representa la Energía que consume la carga en cada semi-ciclo
ZCD = Pulso de entrada para indicar el cruce por cero de la onda sinusoidal
TDT = Pulso de salida para activar el disparo que cerrara el circuito
Fig6. Control de disparo de Fase
Una característica que tiene un TRIAC, es la capacidad de conducir la corriente eléctrica en ambos semi-ciclos positivo y negativo de la onda sinusoidal. Para que este dispositivo cierre el circuito entre sus terminales T1 y T2 una de sus entrada conocida como puerta (G) debe activarse, una vez que este entra en conducción, el circuito se mantendrá cerrado hasta que la corriente que pasa por las terminales T1 y T2, caiga por debajo del valor conocido como corriente de mantenimiento, con lo cual las terminales T1 y T2 dejaran de conducir quedando el circuito abierto. Esta condición ocurre cuando la señal sinusoidal cruza por cero, donde teóricamente la corriente por las terminales T1 y T2 es 0.

Fig7. Simbología de un TRIAC

En la figura 8, se muestra el circuito de control utilizado para nuestro ejemplo, donde se separan dos bloques distinguidas por el color, la zona roja corresponde al bloque AC que trabaja con el suministro eléctrico 220V / 50Hz, la zona verde es el bloque DC cuyas señales operan con niveles lógicos digitales, es importante mantener esta separación de forma física en el circuito que va implementarse, y proteger del contacto humano los elementos que están en el bloque AC. Recordar también que el circuito que se muestra en la figura 6, tiene fines educativos, por lo cual se excluyen varios elementos de protección adicionales necesarias para un producto final.  
Fig8. Circuito de Control con TRIAC
Desde un punto de referencia en el bloque DC, la señal para indicar el cruce por cero, es generado por la polarización de los diodos que posee el opto-acoplador U1 en ambos ciclos, donde la corriente AC que circula se reduce al orden de los miliamperios debido a las resistencias R1 y R2. Cuando la onda sinusoidal pasa por el punto cero, ninguno de los diodos de U1 esta polarizado y por lo tanto el colector y emisor del transistor quedara abierto, conforme la onda pase el punto cero y vaya incrementándose, el voltaje aplicado a los diodos se incrementara hasta que uno de ellos se polarice haciendo que el transistor se cierre; Considerando esto podemos deducir que el nivel logico en el colector de U1, sera 1 solo cuando la señal AC cruce por cero y el resto de tiempo permanecerá con valor 0, esto se representa con el pulso ZCD de la figura 6.
Para controlar la puerta (G) del TRIAC, y mantener aislado el bloque AC del circuito, se utiliza un opto-acoplador tipo triac U2, con esto la activación dependerá de la polarización del diodo en U2, en la figura 6 este evento se representa con la señal TDT. Finalmente el Microcontrolador utilizara como entrada la señal ZCD para detectar el instante donde se produce el cruce por cero y la salida TDT para disparar el TRIAC.
Lista de componentes que muestra el circuito de la figura 8, es la siguiente:
  • Fusible F1 0.5A / 220V
  • Resistencia R1,R2 100kΩ / 1.0W
  • Resistencia R3 220Ω / 0.5W
  • Resistencia R4 10kΩ / 0.25W
  • Resistencia R5 1kΩ / 0.25W
  • Triac T1 BT-136 600V
  • Opto-Acoplador U1 PC-814
  • Opto-Acoplador U2 MOC-3022
En el lado del Microcontrolador, emplearemos un circuito bastante simple utilizando el PIC16F887, observe la figura 9. La señal de entrada ZCD que proviene del circuito de control se conectara al pin de interrupción externa INT, y la señal de salida TDT al circuito de control sera el pin RB1, además se utilizara una resistencia variable para dividir la tensión y hacer lectura de su valor analógico en el pin AN0, finalmente un diodo conectado al pin RE2 indicar con un destello que el programa del PIC esta en funcionamiento.
Fig9. Circuito PIC16F887

Programación del PIC

Ya en esta sección trataremos la programación del microcontrolador PIC, como sabrán no es sencillo describir el funcionamiento de un programa en pocas palabras y que sea fácil de entender,  me disculpo si no he sido claro en alguna parte, por eso para ir entrando en contexto, la figura 10 ilustra el flujo que sigue el programa principal y también la rutina de servicio a la interrupción.
Fig10. Flujo-grama Programa principal e ISR
 
Programa Principal: Posterior a la configuracion de los puertos, el modulo ADC, el temporizador y las interrupciones, el programa principal ingresa en un bucle continuo para ejecutar tres tareas cada milisegundo, estas tres tareas son: destellar el LED, hacer lectura del canal analógico ADC y ajustar el tiempo de disparo. 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>
Rutina de Servicio a la Interrupción ISR: El código de esta rutina atiende a dos eventos, la interrupción del temporizador T0IF que permite ejecutar las tareas del programa principal cada milisegundo, y la interrupción externa INT que indica el cruce por cero ZCD, con la que sincronizara el tiempo de disparo TDT basado en un contador. Este contador se incrementa con cada interrupción del temporizador.
Si bien los comentarios en la codificación del programa describen el propósito de cada linea, se ha establecido la configuracion de variables considerando lo siguiente:
  • La interrupción del temporizador se ajusto a 0.0002 segundos, con este tiempo para cada semi-ciclo de onda, la rutina ISR atiende hasta 50 interrupciones T0IF (0.010 / 0.0002 = 50). Un registro contador CNT se inicia en 0 cada vez que ocurre la interrupción externa INT(Cruce por cero) y se incrementa con cada interrupción T0IF,  el control del disparo se da cuando el contador CNT alcanza el valor del tiempo TDT (Pulso de disparo).
  • El valor para el tiempo TDT estará en función de la lectura ADC al canal AN0, cuya voltaje analógico es ajustable por un potenciómetro, entonces para que el resultado de la conversion de 10-bit, permanezca en el rango 0 a 49, se divide entre 21, y con ello se actualiza al valor del tiempo TDT. 
A continuación se muestra el código del programa para el PIC16F887, considerando que la frecuencia del oscilador es Fosc=8MHz.
 
#include <xc.h>
#include <stdio.h>
#define ZCDpin PORTBbits.RB0 //Entrada para indicar Cruce por cero
#define TDTpin PORTBbits.RB1 //Salida para el pulso de disparo TRIAC
#define LEDpin PORTEbits.RE2 //Salida para destellar el LED
volatile uint8_t tick1ms = 0;
volatile uint8_t TDTval = 0; //Variable de control para disparo 0-49
void setupMCU(void);
void taskLED(void);
void taskADC(void);
void main(void)
{
    setupMCU(); //Configuración
    while(1)
    {
        if(tick1ms) //Valida en cada milisegundo
        {
            tick1ms = 0; //Limpia bandera
            taskLED();   //Tarea para destello
            taskADC();   //Tarea de lectura ADC
        }
    }
}
void __interrupt() isr(void) //Rutina ISR
{
    static uint8_t cnt0, cnt1, toffset;
    if(INTCONbits.INTF) //pulso ZCD
    {
        INTCONbits.INTF = 0;
        toffset = 0; //Reinicia corrección del contador
    }    
    if(INTCONbits.T0IF) //Activa cada 0.0002s
    {
        INTCONbits.T0IF = 0;  //Limpia bandera
        TMR0 += 156; //Reinicia contador T0
        if(cnt0++ > 4) //Valida 5 x 0.0002 = 0.001s
        {
           tick1ms = 1; //Activa cada 0.001s
           cnt0 = 0;
        }
        if(toffset == 6) //Valida corrección ZCD
        {
            cnt1 = 0; //Reinicia contador TDT
            toffset++;
        }
        if(toffset < 6) toffset++;
        if(cnt1 == TDTval) TDTpin = 1; //Valida y activa disparo
        else TDTpin = 0;
        if(cnt1 < 50)  cnt1++; //t=50*0.2m = 10m
    }
    
}
void setupMCU(void)
{
    OSCCONbits.IRCF = 0b111; //Ajusta Fosc=8MHz, Tcy=0.5u
    while(OSCCONbits.HTS == 0) {}
    /* CONFIGURACION PUERTOS*/
    ANSEL = 0;
    ANSELH = 0;
    TRISEbits.TRISE2 = 0; //Salida LED
    TRISBbits.TRISB1 = 0; //Salida control TDT
    LEDpin = 0; //Apaga el LED
    TDTpin = 0; //Nivel bajo en pulso TDT
    TRISBbits.TRISB0 = 1; //Entrada ZCD
    OPTION_REGbits.nRBPU = 0; //Pull-up en ZCD
    OPTION_REGbits.INTEDG = 1; //Flanco Ascendente
    INTCONbits.INTF = 0; //Limpia bandera INT
    INTCONbits.INTE = 1; //Activa interrupcion externa
    /* CONFIGURACION TIMER0 0.2MS */
    OPTION_REGbits.T0CS = 0;//Modo Termporizador
    OPTION_REGbits.PSA = 0; //Con prescala
    OPTION_REGbits.PS = 0b001; //Prescala 1:4
    TMR0 = 156; //256-[(time*Fosc)/(pre*4)] time=0.0002 seg
    INTCONbits.T0IF = 0; //Limpia bandera
    INTCONbits.T0IE = 1; //Activa interrupcion del TMR0
    /* CONFIGURACION ADC-10 Canal0*/
    ANSELbits.ANS0 = 1; //Activa canal AN0
    ADCON0bits.ADCS = 0b10; //TAD=4us > 1.6us (8MHz/32)
    ADCON0bits.CHS = 0; //Selecciona Canal 0
    ADCON0bits.ADON = 0; //Desactiva el modulo ADC
    INTCONbits.GIE = 1; //Habilita las interrupciones
}
void taskADC(void) //Ciclo 1ms, Lectura ADC de 10Hz
{
    static uint8_t state = 0, cnt = 0;
    static uint16_t adcraw;
    cnt++;
    switch(state)
    {
        case 0: //Activa ADC para captura
            ADCON0bits.ADON = 1;
            cnt = 0;
            state = 1;
            break;
        case 1: //Inicia conversion AN0
            ADCON0bits.GO = 1;
            state = 2;
            break;
        case 2://Espera fin de conversion AN0
            if(ADCON0bits.GO == 0)
            {
                adcraw = ADRESL; //ADRESL First
                adcraw |= (uint16_t) (ADRESH << 8);
                adcraw >>= 6; //Corrige alineacion de ADH:ADL
                ADCON0bits.ADON = 0;
                state = 3;
            }
            break;
        case 3: //Espera fin de ciclo
            if(cnt++ > 99) //valida 100 x 1ms = 100ms
            {
                TDTval = (uint8_t) (adcraw / 22U); //Ajusta a TDT
                state = 0;
            }
    }
}
void taskLED(void) //Destella led ciclo 1ms
{
    static uint16_t tcnt = 0;
    if(tcnt++ > 999)
    {
        tcnt = 0;
        LEDpin = 1;
    }
    if(tcnt == 200) LEDpin = 0;
}

Pruebas de Funcionamiento

Se ensayo el código inicial en el microcontrolador para llevar a cabo las pruebas de funcionamiento utilice una placa de control TRIAC que disponía de un proyecto anterior, en esta placa de cuatro salidas, solo se utilizo uno de los canales.
 
Fig11. Circuito de control y PIC
En la primera prueba sin considerar la corrección del tiempo de disparo TDT, la lampara comenzó a oscilar cuando se realizo el ajuste total del potenciómetro para entregar toda la potencia. Esto se debe a que el pulso detector ZCD tiene un tiempo de duración,  que se extiende antes y después del cruce por cero, por lo que fue necesario agregar un tiempo offset para corregir el disparo, finalmente con un valor de ajuste igual a 7 (7x0.0002 = 0.0014), se corrige el problema de oscilación. Una forma practica para determinar este valor de corrección, es medir con un instrumento el porcentaje de nivel alto en la señal ZCD, en mi caso registre 28%, del cual la mitad corresponde al instante en que el detector de cruce se activa, esto es 14% del semi-ciclo (0.010 x 0.14 = 0.0014)
 
Abajo dejo un breve vídeo que muestra el funcionamiento de este circuito y el código del proyecto MPLABX.

 
Aquí dejo los enlaces para que puedas descargar el proyecto, se encuentra en formato gzip, debes descomprimir y abrir con MPLABX 
Si quieres ver como compilar e implementar los proyectos de este blog, elaborados con MPLABX mira este vídeo.<Compilando proyectos MPLABX>

Conclusiones

Se ha demostrado el funcionamiento adecuado del circuito de control de potencia, con el microcontrolador PIC16F887, los valores de tiempo que utilice pueden modificarse en base a requisitos propios de cada proyecto, Si estas en planes de poner en practica este tipo de control como recomendación debe considerar los siguiente puntos: 
  • Este circuito como se menciona al inicio, considera el uso de una carga puramente resistivas, donde no habrá desface entre el voltaje y la corriente AC.
  • El control de intensidad de de la luz que se muestra en el ejemplo es solo para lamparas incandescentes, y no se aplica a las luces tipo LED ya que operan con otro principio.
  • Puede incrementar la resolución para el control de energía en la carga a mas pasos de los que se trato en el ejemplo, pero debe saber que la relación del tiempo de disparo con la cantidad de energía que se entrega no es lineal debido a la magnitud variable que la tensión eléctrica. 
  • Por ultimo el punto mas importante de todo: Si no sabe lo que esta conectando en este circuito, deje de armarlo y no continué hasta comprender su funcionamiento. Una mala conexión o manipulación del mismo puede provocar serios daños material y personal(riesgo de muerte).
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



sábado, 27 de julio de 2024

Programar PIC16F con Bootloader


Programación de PIC16 con TinyBootloader

Que tal, soy Pablo y te doy la bienvenida por visitar mi blog relacionado con la programación de microcontroladores PIC, mencionar que estoy abierto a cualquier critica constructiva con referencia a esta publicación.

En esta entrada quiero mostrar como utilizar un cargador o bootloader en los microcontroladores PIC16F88x, si bien es posible adaptar el código fuente a una gran variedad de modelos  PIC16F y PIC18F,  solo considerare esos modelos porque son los que habitualmente utilizo en los ejemplos publicados en este blog. Entre las herramientas que utilizaremos están:
  • Un programador para PIC, que permite grabar el bootloader
  •  Una cable de interfaz o adaptador USB-UART
  • El software del bootloader <TinyBootloader> para Windows o la variante <TinyBootloader> para linux.
  • Software de desarrollo MPLABx cualquier versión.

Introducción

Un bootloader es un programa reducido que permite transferir el código de nuestro programa al microcontrolador MCU, esto sin necesidad de utilizar grabador como el pickit, para llevar a cabo esta transferencia se utiliza uno de los puertos de comunicación serial uart o usb del MCU. La ventaja que conlleva utilizar un bootloader es sobre todo facilitar la actualización del código o firmware de un circuito basado en MCU, utilizando una PC.
 
Un bootloader o cargador aprovecha la característica que tienen los MCU de modificar su memoria de programa en tiempo de ejecución, en el caso de los PIC esta característica la denominan Self-Write Program Memory, no todos los modelos poseen esta opción, pero gracias a esto el código bootloader que reside en la memoria del PIC, puede transferir cada byte de un nuevo firmware desde una PC hasta la memoria de programa (Actualización)
Existen varios cargadores bootloader disponibles para PIC, cada uno con características propias en cuanto a soporte y requerimientos de memoria, para nuestro caso utilizaremos el conocido TinyBooloader, siendo uno los de aspectos mas relevantes su tamaño, que ocupar solo 100 posiciones de memoria y es adaptable a varios modelos PIC, este cargador utiliza el modulo UART de comunicación serial.

Fig1. Software del Cargador Tiny Bootloader

Encontrara mayor información sobre las características de esta cargador y una clara descripción de su funcionamiento en la pagina de su autor: Claudiu Chiculita, abajo dejo el enlace
 
 En su pagina también encontrara una lista de PIC's que han sido probados con TinyBootloader, y una tabla comparativa con otros cargadores.  
 
Desarrollo

Para hacer uso del cargador TinyBootloader en nuestro PIC16F, debemos descargar el software que es gratuito y además posee el código fuente del cargador:

<Enlace para descarga de TinyBootloader>

Una vez descargado este software, que es un archivo zip, lo descomprimiremos en un carpeta y notaremos que en su estructura están el programa ejecutable, las fuentes de cargador y algunos ficheros boot generados, utilizaremos un programador Pickit3 para cargar un bootloader en la memoria del PIC. Hay dos caminos para abordar este punto:

  • Utilizar un firmware bootloader(ficheros boot) generado para un modelo de PIC, con la frecuencia del oscilador y velocidad de comunicación serial, preestablecidos.

  • Compilar el código fuente, para ajustar al modelo de PIC, la frecuencia del oscilador y velocidad de comunicación serial que necesitamos en nuestro circuito, para esto recomiendo utilizar la antigua versión MPLAB, dado que el código fuente esta escrito en este IDE.

Como verán la primera opción es la forma mas simple de implementar el cargador, porque solo necesitamos descargar el firmware bootloader para nuestro modelo de PIC, por eso dejare una lista de enlace para los PIC16F887 y PIC16F886 que utilizo en los ejemplos de este blog:
 
 
Los pasos para grabar el bootloader en el PIC, con nuestro programador Pickit3 o cualquier otro que se disponga, son primero realizar la conexión necesaria del programador al PIC, respetando las indicaciones de la programación en circuito <ICSP>, luego utilizando el software del programador en mi caso MPLABX IPE pasamos el bootloader al PIC, como se ve en las figura 2 y 3. 

Fig2. Carga del Bootloader en Tarjeta PIC16F
 

Fig2. Grabando el bootloader en el PIC

Una vez cargado el bootloader en el PIC, conectamos el adaptador USB-UART al puerto de nuestro ordenador y el otro extremo del adaptador a los pines RXD, TXD del PIC en forma cruzada, así mismo se deberán conectar las lineas de GND en ambos extremos, como se observa en el diagrama que muestra la figura 3. El adaptador que utilizare sera el CP2102 de Silicon Labs, el cual debe ser reconocido por el sistema operativo, si usas Windows el asistente iniciara el proceso de instalación de manera automática y como resulta se asignara un serial COMxx en la lista de dispositivos. En caso de que el asistente de instalación no funcione deberá proceder a la instalación manual, para lo cual debe descargar del driver desde la pagina que provee el fabricante.

Fig3. Conexión USB-UART al PIC
 
Una vez completada la instalación del controlador para el adaptador USB-UART, abrimos el programa TinyBootloader, seleccionamos el puerto COMx asignado en la instalación y la velocidad de comunicación. 
 
Luego presionado el boton [CheckPIC] verificamos el reconocimiento del PIC, este paso nos sirve también para confirmar la comunicación correcta entre la aplicación y el bootloader del PIC. Para llevar a cabo esta prueba es importante mencionar que el PIC debe estar en modo boot, por lo tanto deberá reiniciarlo.
 
(Nota) Cuando suministra energía al PIC, este se mantiene en modo bootloader por unos 5 segundos, y luego pasara a modo normal, la verificación [CheckPIC] y actualización [Write Flash] solo puede llevarse a cabo en modo bootloader 

Fig4. Ajuste y verificación TinyBootloader
 
Ya en este punto solo resta cargar nuestro código al PIC, seleccionando el archivo hex desde la carpeta de nuestro proyecto MPLABX, y luego presionamos el botón de reinicio del PIC, para después de un lapso no mayor a los 5 segundos presionar el botón [Write Flash]. La trasferencia demorar unos segundos dependiendo del tamaño del archivo hex, finalmente nuestro PIC quedara actualizado y listo para funcionar.

Fig5. Programación del PIC con TinyBootloader
 
Conclusiones

Se ha comprobado el funcionamiento del cargador TinyBootloader de forma satisfactoria, utilizando programas básicos para el PIC16F887, si bien la tabla provista por el auto indica pruebas correctas para otros modelos, queda pendiente ir actualizando esta informacion conforme realice pruebas de este cargador en otros modelos de PIC, como recomendación es importante considerar los siguiente aspectos:
  • TinyBootloader no permite realizar cambios a los fusibles de PIC, ya que estos valores se establecen al momento de programar el bootloader vía ICSP.
  • Como el bootloader se aloja en las ultimas 100 posiciones de la memoria Flash del PIC, es necesario que el compilador reserve estas posiciones para su uso. En el caso de XC8 se adicionara al linker dentro de MPLABX los siguientes parámetros --ROM=default,-1F80-1FFF. Donde los últimos valores resaltados, representan el rango de posiciones reservadas. PIC16F887 Flash 8K posiciones, 0000h-1FFFh, ultimas posiciones 1F80-1FFF.  

Fig6. Configuración Proyecto MPLABX para bootloader
 

Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.

 
Atte. Pablo Zárate Arancibia
email: pablinza@me.com / pablinzte@gmail.com, @pablinzar
Santa Cruz de la Sierra - Bolivia

miércoles, 24 de julio de 2024

Programacion por Estados con Temporizador

 Programación por Estados con Temporizador


Este sitio fue creado para compartir el mutuo interés en la electrónica y en especial la programación de microcontroladores PIC. Agradecido por visitar mi blog te quiero recordar que estoy atento a critica constructiva relacionada con esta publicación. 

Hoy quiero explicar como lograr de forma practica y sencilla la concurrencia de tareas dentro del bucle secuencial de un programa, para lo cual haremos uso del temporizador con interrupción, en microcontroladores PIC y AVR.

Para el caso de un PIC, la programación se realiza utilizando el software MPLABX junto con el compilador XC8 ambos disponibles en la pagina oficial de microchip y en el caso de microcontrolador AVR, utilizaremos la plataforma Arduino dada la facilidad y el amplio uso que tiene.

Mencionar también que en la presente publicación de da por entendido que el visitante o lector, posee medios de programación en lenguaje C y experiencia en el uso de microcontroladores PIC / AVR

Introducción

Dado que los microcontroladores disponen de un microprocesador para ejecutar las instrucciones del programa almacenadas en su memoria, el desarrollo de software se lleva a cabo principalmente con la programación estructurada secuencial, esto es básicamente ejecutar en secuencia múltiples tareas, una después de otra y repitiendo este ciclo de forma indefinida, como puede ver en la figura 1.

Fig1. Ejecución secuencial de cuatro Tareas

Si bien cada tarea puede incluir sentencias selectivas e iterativas, una vez iniciada la ejecución, la tarea debe finalizar para continuar con la siguiente, a simple vista podemos mencionar que la limitaciones que conlleva esta programación se dan debido a los siguientes aspectos:

  • La ejecución de cada tarea demora un tiempo, y no siempre es constante dado que en el proceso pueden existir retardos y condiciones sujetas a eventos que son asíncronos, como notara en la figura 1. se resaltan las magnitudes de tiempo utilizada por cada tarea, siendo en total un valor de 1000 por cada ciclo de ejecución, podemos imaginar que la unidad de medida son millonésimas(us) o milésimas(ms) de segundos.
  • Durante el ciclo secuencial, una tarea deberá esperar la ejecución del resto de tareas, si el tiempo es muy largo se verán afectados aquellos eventos que requieran atención inmediata, por ejemplo la tarea 1 debe esperar un tiempo de 750(50+500+200) para iniciar nuevamente, mientras que la tarea 3, debe esperar 500(200+250+50).

Observe la figura 2 que muestra el código de un par de tareas tomadas del ejemplo, la tarea 1 esta  encargada de obtener un valor de un canal analógico y guardarlo en memoria, y como sabemos este proceso debe primero iniciar la conversion y esperar a que finalice, lo que conlleva a tener tiempos de espera., en cuanto a la tarea 2, responsable de destellar una luz LED, posee tiempos de esperar suficientes para notar el cambio de encendido y apagado.

Fig2. Ejemplo de Código Tarea 1 y Tarea 3

Un enfoque de programación alternativo que vamos a revisar, es la ejecución de tareas concurrentes pero de forma simplificada ya que los PIC/AVR que estamos revisando poseen recursos limitados. No se tocaran detalles del concepto de concurrencia, ya que es un tema bastante amplio y complejo en algunos casos debido al uso que tienen dentro de los sistemas operativos. por lo que enfocare la explicación mas a la practica. 

Entonces sobre este tema mencionare que a diferencia de la estructura secuencial ya vista con antelación, la concurrencia permite que las tareas se lleven a cabo al mismo tiempo, y una técnica para conseguir esto tomando en cuenta un sistema con un solo microprocesador, es hacer que las tareas gestionen su propio estado y el tiempo total del ciclo de ejecución sea muy corto, para que todas las tareas que se ejecutan durante el ciclo secuencial lo mas rápido posible. Observe la figura 3, donde cada una de las cuatro tareas utilizan 0.25 de tiempo, para completar la unidad 1.0.  

Fig3. Concurrencia de cuatro tareas

Se preguntara entonces, ¿Como? es posible hacer esto, y la respuesta seria:

No esperar que las tareas finalicen su trabajo en cada ciclo, y por el contrario hacer que cada tarea tenga control de su estado en base al tiempo y el numero de veces que es invocado. Un modelo de programación aplicable a esta técnica, es la maquina de estados finitos, donde la tarea se divide en estados y las transiciones entre estados están sujetas a condiciones, que se validan únicamente cuando se ejecuta la tarea, observe como seria el código de las tareas 1 y 3 considerado esta técnica. 

Fig4. Código Tarea 1 y Tarea 3

Como notara en la figura 4, ahora la tarea 1, dispone de cinco estados, cada uno de ellos realiza una actividad, lo interesante es que ya no existen esperas, por ejemplo el estado 2, que debe esperar la conversion, ahora solo revisa si la bandera de ocupado (ADC_Busy) sigue activa, y dependiendo esta validación se determina si la tarea mantiene su estado o pasa el estado siguiente. En el caso de la tarea 2, se utiliza un contador de ciclos para determinar los momentos para encender y apagar la luz, pero la tarea nunca se queda esperando.

El concepto de las Maquinas de Estados Finitos al igual que otras técnicas como GRAFCET, implican el uso de diagramas de estado y simbologías propias que no se aplicaran a nuestro ejemplo practico, por esa razón de aqui en adelante haré referencia a esta técnica como Programación por Estados con Temporizador.

Ahora veamos la practica, para conseguir que el ciclo de ejecución posea un tiempo preciso, utilizare un Temporizador y su interrupción, entonces describiré la configuracion de este recurso considerando los módulos temporizadores del PIC16F886 y el ATmega328.

Si quieres conocer mas a profundidad los detalles de los módulos TMR de un PIC16, te recomiendo que revises las siguientes entradas: 

Configuración y uso del <TMR0>  <TMR1>  del PIC16F

Temporizadores en el PIC16F

El microcontrolador PIC16F887 dispone de los siguientes módulos:

  • TMR0 Modulo Contador/Temporizador de 8-bit
  • TMR1 Modulo Contador/Temporizador de 16-bit
  • TMR2 Modulo Temporizador de 8-bit

La operación de estos módulos es similar. Si hablamos de un temporizador significa que un registro contador incrementara su valor con cada pulso proveniente del oscilador utilizado por el PIC, en el caso del TMR1, este registro es de 16-bit, siendo su capacidad de contar desde 0 hasta 65535. Para incrementar la capacidad de contar, cada modulo cuenta con etapas divisoras de frecuencia que puede ajustarse a escalas determinadas.

Cuando el registro contador llega al valor limite, con el siguiente pulso ocurrirá un desbordamiento, donde el contador se reinicia a cero, y la bandera TxIF se activara para notificar este evento, esta bandera debe ser desactivada por programa cuando se complete la atención al evento.

Ahora veremos como configurar el temporizador con el modulo TMR0 y TMR1, para lo cual consideraremos los siguiente: 

La frecuencia del oscilador Fosc = 8000000 Hz, y el tiempo base requerido para ajustar el temporizador sera Tb = 0.001 segundo (1ms)

Configuración del  TMR0

La figura 5, describe los registros específicos asociados a la operación del modulo TMR0, donde se muestran los bits y sus valores por defecto.

Fig5. Registros SFR del TMR0
El código de configuracion considerando Fosc=8MHz y Tb=1ms sera.

 OPTION_REGbits.T0CS = 0;//Modo Termporizador
 OPTION_REGbits.PSA = 0; //Con pre-escala
 OPTION_REGbits.PS = 0b011; //Pre-escala 16:1
 TMR0 = 131; //Tb=1ms TMR0=256-(0.001*Fosc)/(pre*4)
 INTCONbits.T0IF = 0; //Limpia bandera
 INTCONbits.T0IE = 1; //Habilita la interrupción del TMR0
 INTCONbits.GIE = 1; //Habilitador Global ISR

La rutina de servicio a la interrupción tendrá lo siguiente:

void __interrupt() isr() //Rutina de interrupción
{
  if(INTCONbits.T0IF)
//Bandera desbordamiento TMR0
  {
    TMR0 = 131;
//Reinicia contador
    INTCONbits.T0IF = 0; 
//Limpia bandera
  }
}

Configuración del TMR1

La figura 6, describe los registros específicos asociados a la operación del modulo TMR0, donde se muestran los bits y sus valores por defecto.

Fig6. Registros SFR del TMR1
El código de configuracion considerando Fosc=8MHz y Tb=1ms sera.
      T1CONbits.TMR1CS = 0; //Modo temporizador
   T1CONbits.T1CKPS = 0b00;
//Ajuste pre-escala 1:1
   TMR1H = 0xF8;
//Tb=1ms TMR1=65536-[(0.001*8M)/(1:1*4)]
   TMR1L = 0x30;
// 63536 F830h
   PIR1bits.TMR1IF = 0;;
//Limpia la bandera
   T1CONbits.TMR1ON = 1;
//Arranca temporizador
   PIE1bits.TMR1IE = 1; 
//Activa interrupción del T1
  
INTCONbits.PEIE = 1;//Activa interrupción de periféricos
   INTCONbits.GIE = 1;
//Activador global ISR

La rutina de servicio a la interrupción tendrá lo siguiente:

void __interrupt() isr(void) //Rutina de servicio a interrupciones

    if(PIR1bits.TMR1IF)
//Evento de desbordamiento TMR1
    {
        PIR1bits.TMR1IF = 0;
//Limpia la bandera TMR1
        T1CONbits.TMR1ON = 0;
//Para al contador
        TMR1H = 0xF8;
//Actualiza valor del contador
        TMR1L = 0x30;

        T1CONbits.TMR1ON = 1;
//Arranca al contador
    }
}
 

Temporizadores ATmega

El microcontrolador ATmega328 que es utilizado por la tarjeta Arduino UNO dispone de los siguientes módulos:

  • TC0 Contador/Temporizador de 8-bit
  • TC1 Contador/Temporizador de 16-bit
  • TC2 Contador/Temporizador de 8-bit

Cada modulo cuenta con los  modos de operación: Normal, CTC, PWM y PWM con corrección de fase. Para nuestro caso trataremos únicamente el modo Normal que representa el modo de operación mas simple donde el registro contador se incrementa con cada pulso de reloj hasta su desbordamiento. En el caso de los AVR la bandera TOVxF se reinicia automáticamente cuando se atiende el evento.

Considerando que utilizaremos una tarjeta Arduino UNO, es importante mencionar que los modulo son utilizados de acuerdo a lo siguiente:

  • TC0: Funciones delay(), millis(), micros() y PWM pines 5,6.
  • TC1: Librería se servomotores Servo.h y PWM 9,10
  • TC2: Función Tone() y PWM 3,11

Por esta razón, para no afectar la funcionalidad de Arduino solo trataremos la configuracion de los temporizadores TC1 y TC2 considerando que la frecuencia del oscilador es Fosc = 16MHz y el tiempo base Tb = 1ms

Configuración del TC1

La figura 7, describe los registros específicos asociados a la operación del modulo TC1, donde se muestran los bits y sus valores por defecto.

Fig7. Registros SFR del TC1
El código de configuracion, considerando los valores defecto sera el siguiente:

  TCCR1A = B00000000;//Registro A Operación Modo Normal
 
TCCR1B = B00000011;//Registro B Ajusta la prescala CS=1:64
 
TCNT1H = 0xFF; //Tb=1ms TCNT1=65535-(0.001*8M/64)=FF05h
 TCNT1L = 0x05;
 TIMSK1 |= 0x01;
//Activa interrupcion TOIE del T1

La rutina de servicio a la interrupción tendrá lo siguiente:

ISR(TIMER1_OVF_vect) //Evento de desbordamiento T1
{
   TCNT1H = 0xFF;
//Reinicia el valor del contador
   TCNT1L = 0x05;
}

Configuración del TC2

La figura 8, describe los registros específicos asociados a la operación del modulo TC2, donde se muestran los bits y sus valores por defecto.

Fig8. Registros SFR del TC2

El código de configuracion, considerando los valores defecto sera el siguiente:

 TCCR2A = B00000000;//Registro A Operación Modo Normal
 
TCCR2B = B00000100;//Registro B Ajusta Prescala CS=1:64
 TCNT2 = 5;
//Tb=1ms TCNT2=255-(0.001*8M/64)=05h
 TIMSK2 |= 0x01;
//Activa la interrupción TOIE del T2

La rutina de servicio a la interrupción tendrá lo siguiente:

 ISR(TIMER2_OVF_vect) //Evento de desbordamiento T2
 {
   TCNT2 = 5;
//Reinicia el valor del contador
 }

Código de Ejemplo

Como practico vamos a crear un programa que permita ejecutar tres tareas de forma concurrente, las tareas que se llevaran a cabo son:

  • Tarea 1: Destello de LED indicador de actividad (taskLED)
  • Tarea 2: Lectura ADC del canal y almacenamiento de resultado (taskADC)
  • Tarea 3: Control de velocidad para motor de pasos (taskSTM)

Realizamos el programa considerando los esquemas de circuito que se observan en la figura 9 y 10. en ambos casos del PIC y AVR utilizaremos el modulo TMR1 y TC1 respectivamente.

Tome en cuenta que los esquemas que se observan en la figuras 9 y 10, son únicamente para fines de simulación,  obviando las conexiones de alimentación y amplificadores de corriente para el motor de pasos.

Circuito y Programa del PIC16F886

Fig9. Esquema de circuito PIC para simulación con Proteus

  Codigo Proyecto MPLABX 

#pragma config FOSC=INTRC_NOCLKOUT,WDTE=OFF,LVP=OFF
#include <xc.h>
void setup(void);
void taskLED(void);
//Prototipo de función
void taskADC(void);
void taskSTM(void);
uint16_t speed = 0;
//Variable para control de velocidad
volatile uint8_t tickms = 0;
void main()
{
    setup();
    while(1)
    {
        if(tickms)
//Bandera activada en ISR cada 1ms
        {
            tickms = 0;
//Limpia la bandera
            taskLED();
//Destella el led cada segundo
            taskADC();
//Lectura ADC canal 0
            taskSTM();
//Control de pasos en motor
        }
    }
}
void __interrupt() isr()
//Rutina de interrupción
{

  if(INTCONbits.T0IF)
//Bandera desbordamiento TMR0
  {
    TMR0 = 131;
//Reinicia contador TMR1
    INTCONbits.T0IF = 0;
//Limpia bandera del TMR1
    tickms = 1;
  }
}
void setup(void)
{
    OSCCONbits.IRCF = 0b111;
//Selecciona Fosc=8MHz
    while(OSCCONbits.HTS == 0);
//Espera Fosc estable
    TRISBbits.TRISB5 = 0;
//Modo Salida LED
    TRISB &= 0b11110000;
//
Salidas B0=L1 B1=L2 B2=L3 B4=L4
    ANSEL = 0; //Deshabilita pines analógicos AN0-7
    ANSELH = 0;
//
Deshabilita pines analógicos AN8-13
    /* CONFIGURACIÓN ADC-10 Canal0 para Fosc=8Mhz*/
    ANSELbits.ANS0 = 1;
//Habilita canal AN0
    ADCON0bits.ADCS = 0b10;
//TAD=4us > 1.6us (8MHz/32)
    ADCON0bits.CHS = 0;
//Selecciona Canal 0
   
/* CONFIGURACION TMR0 1ms para Fosc=8MHz*/
    OPTION_REGbits.T0CS = 0;
//Modo Temporizador
    OPTION_REGbits.PSA = 0;
//Con pre-escala
    OPTION_REGbits.PS = 0b011;
//Pre-escala 16:1
    TMR0 = 131;
//Tb=1ms TMR0=256-(0.001*Fosc)/(pre*4)
    INTCONbits.T0IF = 0;
//Limpia bandera
    INTCONbits.T0IE = 1;
//Habilita interrupción del TMR0
    INTCONbits.GIE = 1;
//Habilitador Global ISR
}
void taskLED(void)
//Tarea para destellar el led
{
  static uint16_t cnt = 0;
  if(cnt++ > 999)
//Cada segundo
  {
    cnt = 0;
    PORTBbits.RB5 = 1;
//Activa led a 0 seg
  }
  if(cnt == 200) PORTBbits.RB5 = 0;
//Apaga led a 0.2 seg
}
void taskADC(void)
//Tarea para leer el canal ADC0
{
  static uint8_t state = 0;
  static uint16_t cnt = 0;
  uint16_t adcval;
  switch(state)
  {
    case 0:
      ADCON0bits.ADON = 1;
//Activa el modulo AD
      state = 1;
      break;
    case 1:
      ADCON0bits.GO = 1;
//Inicia la conversion AD
      state = 2;
      break;
    case 2:
      if(ADCON0bits.GO == 0)
//Espera fin conversion
      {
        adcval = ADRESL;
        adcval |= (uint16_t) (ADRESH << 8);
        adcval >>= 6;
//Corrige alineación ADRESH:ADRESL
        speed = adcval;
        ADCON0bits.ADON = 0;
//Desactiva el modulo AD
        cnt = 0;
        state = 3;
      }
      break;
    case 3:
      if(cnt > 499)
//Espera 500 ms
        state = 0;
      else cnt = cnt + 1;
      break;
  }
}
void taskSTM(void)
{
  static uint8_t state = 0, res;
  static uint16_t cnt = 0;
  cnt = cnt + 1;
  switch(state)
  {
    case 0:
//Estado de Motor Paso 1
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | 0b0011;
        cnt = 0;
        state = 1;
      } break;
    case 1:
//Estado de Motor Paso 2
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | 0b0110;
        cnt = 0;
        state = 2;
      } break;
    case 2:
//Estado de Motor Paso 3
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | 0b1100;
        cnt = 0;
        state = 3;
      } break;
    case 3:
//Estado de Motor Paso 4
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | 0b1001;
        cnt = 0;
        state = 0;
      } break;
  }
}

Circuito y Programa del ATmega328
Fig10. Esquema de circuito AVR para simulación en Proteus

  Código sketch Arduino  

volatile uint8_t tickms = 0; //Bandera para indicar 1ms
uint16_t speed = 0;
//Variable para control de velocidad
void setup()
{
  DDRB |= _BV(DDB5);
//Salida pin RB5 UNO=D13
  DDRB |= B00001111;
//Salida pines B0=L1 B1=L2 B2=L3 B3=L4
  DDRC = 0xFF;
//Salidas pines del PORTC
  DDRC &= ~_BV(DDC0);
//Entrada pin PC0
 
/*CONFIGURACION TC1 como temporizador a 1ms Fosc=16MHz*/
  TCCR1A = 0x00;
//Modo Normal
  TCCR1B = 0x03;
//Registro B Prescala CS=1:64
  TCNT1H = 0xFF;
//t=1ms TCNT1=65535-(0.001*Fosc/64)=FF05h
  TCNT1L = 0x05;
  TIMSK1 |= 0x01;
//Activa interrupción TOIE del T1
 
/*CONFIGURACIÓN ADC Canal 0, Modo Free Running a 125Khz*/
  ADMUX = 0x40;
//AVcc referencia, MUX canal 0
  ADMUX |= _BV(ADLAR);
//ADC Alineado a la izquierda ADCH:ADCL
  ADCSRA = 0x07;
//Pre 1:128 16M/128 = 125Khz
  DIDR0 |= _BV(ADC0D);
//Habilita pin en modo analógico
}
void loop()
{
  if(tickms)
//Bandera activada en ISR cada 1ms
  {
    tickms = 0;
//Limpia la bandera
    taskLED();
//Destella el led cada segundo
    taskADC();
//Lectura ADC canal 0
    taskSTM();
//Control de pasos en motor
  }
}
ISR(TIMER1_OVF_vect)
//Interrupción Desbordamiento del T1
{
  TCNT1H = 0xFF;
//Reinicia el contador
  TCNT1L = 0x05;
  tickms = 1;
//Activa la bandera
}
void taskLED()
//Tarea para destellar el led
{
  static uint16_t cnt = 0;
  if(cnt++ > 999)
//Cada segundo
  {
    cnt = 0;
    PORTB |= _BV(PORTB5);
//Activa led a 0 seg
  }
  if(cnt == 200) PORTB &= ~_BV(PORTB5);
//Apaga led a 0.2 seg
}
void taskADC()
//Tarea para leer el canal ADC0
{
  static uint8_t state = 0;
  static uint16_t cnt = 0;
  uint16_t adcval;
  switch(state)
  {
    case 0:
      ADCSRA |= _BV(ADEN);
//Activa el modulo AD
      state = 1;
      break;
    case 1:
      ADCSRA |= _BV(ADSC);
//Inicia la conversion AD
      state = 2;
      break;
    case 2:
      if((ADCSRA & _BV(ADSC))==0)
//Espera fin conversion
      {
        adcval = ADCL;
        adcval |= (ADCH << 8);
        adcval >>= 6;
//Corrige alineación ADCH:ADCL
        speed = adcval;
        ADCSRA &= ~_BV(ADEN);
//Desactiva el modulo AD
        cnt = 0;
        state = 3;
      }
      break;
    case 3:
      if(cnt > 499)
//Espera 500 ms
        state = 0;
      else cnt = cnt + 1;
      break;
  }
}
void taskSTM()
{
  static uint8_t state = 0, res;
  static uint16_t cnt = 0;
  cnt = cnt + 1;
  switch(state)
  {
    case 0:
//Estado de Motor Paso 1
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | B00000011;
        cnt = 0;
        state = 1;
      } break;
    case 1:
//Estado de Motor Paso 2
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | B0110;
        cnt = 0;
        state = 2;
      } break;
    case 2:
//Estado de Motor Paso 3
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | B1100;
        cnt = 0;
        state = 3;
      } break;
    case 3:
//Estado de Motor Paso 4
      if(cnt > speed)
      {
        res = PORTB & 0xF0;
        PORTB = res | B1001;
        cnt = 0;
        state = 0;
      } break;
  }
}

Conclusiones 

Como recomendación, considerar los siguientes aspectos:
  • Hay una relación entre el tiempo base (Tb) y el tiempo utilizado en cada ciclo, este ultimo va depender de la cantidad de tareas y las instrucciones que se ejecutan a nivel individual, donde la suma total de tiempo no debe ser mayor al tiempo base. 
  • Si utiliza un tiempo base menor a 1ms, el MCU debe poseer la capacidad de ejecutar las instrucciones de todas las tareas en un ciclo, este punto se puede garantizar incrementando la frecuencia del oscilador, pero hay limites que deben ser revisados.
  • Si utiliza un tiempo de base mayor a 1ms, no habrá inconvenientes con la ejecución, pero debe contar que incrementar el tiempo base, hace que el sistema sea menos sensible, y podrían ocasiones algunos inconvenientes en la tratamiento de los puertos E/S.
Abajo dejare un breve vídeo que muestra el funcionamiento del programa, en ambos casos utilice una placa de pruebas donde la conexión al motor de 5V se realizo con un driver basado en el amplificador ULN2003, el montaje del circuito se observa en las siguientes imágenes.
 
Fig11. Montaje del circuito con ATmega328

Fig12. Montaje del circuito con PIC16F



Repositorio git con ejemplo sencillos que utilizan la ejecución de tareas concurrentes con temporizador: https://github.com/pablinza/elt436
Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.
Atte. Pablo Zárate Arancibia
email: pablinza@me.com / pablinzte@gmail.com, @pablinzar
Santa Cruz de la Sierra - Bolivia