8 Canales de PWM con un 16F628A

Que tal comunidad.

Voy directo al grano: Lean detenidamente este link; 8 Canales de PWM con un 16F628A antes de seguir, ya que sobre esa publicacion se basan mis dudas/preguntas.

El autor de esa publicacion (Mario Sacco), ha logrado quitarme el sueño...

Resulta ser que nuestro querido y respetado amigo Mario, muy alegremente, ha diseñado un hermoso codigo para controlar (en forma completa, y funcionalmente, individual) 8 canales PWM con un PIC 16F628A. Logicamente todo hecho por software, nada de perifericos PWMs... ya que dicho pic contiene, a duras penas, solo uno de aquellos modulos, el cual nisiquiera usa en el codigo.

Estaria todo mas que perfecto si hubiera publicado el codigo completo de su programa... pero no lo hizo... por lo que si uno quiere comprender bien que, y como, realizo dicha hazaña, debera tener que combinar la interpretacion del poco codigo publicado con la (relativamente) escasa explicacion que da de su funcionamiento...

Tal vez sea una boludes, tal vez no, no se; lo que si se es que ya van 3 madrugadas que paso analizando, pensando y probando y no logro descifrar ciertos puntos claves de como carambas hizo este muchacho para lograr lo que dice (y muestra en sus videos) que logro...

Lo que me quita el sueño no es el hecho en sí de programar 8 PWM con distintos dutys (logico)... ya que esto ya lo he realizado... si no que, por lo que logro interpretar, el muchacho éste, logra tener el MAIN limpio de "prendidos y apagados" de canales, y solo lo usa para variar los duys...

Explico...

Lo que yo logre tambin es con principio de funcionamiento en la utilizacion del TMR0 ( algo parecido a lo que explica "megatron" en este tema Controlador digital rgb (PWM) para leds ). Lo que pasa que de esta forma uno requiere que el programa principal este totalmente dedicado a hacer solo eso; prender y apagar los 8 canales. Si, con distintos dutys entre si, pero FIJOS. Si uno quisiera ir variando los dutys de cada canal en el mismo software en forma dinamica y practica se choca contra la pared... por que el programa solo esta concentrado en prender y apagar los leds... si pretendes agregar codigo para hacer efectos "locos" con los canales tenes que complicarte la existencia terriblemente para no alterar demaciado la duracion de los dutys ni la frecuencia de trabajo.

Por lo que a Mario se le ocurrio usar la interrupcion del TMR0 para prender y apagar los canales, dejando al codigo principal unicamente dedicado a hacer efectos "locos" variando los dutys de cada canal de la forma que mas les guste...

O bueno... al menos eso es lo que yo interpreto...

El tema es eso... que no entiendo como carambas hizo para lograr eso... sí, tengo una deduccion al respecto que tiene sus puntos muertos, pero es largo de explicar y prefiero hacerlo sobre la marcha de las ideas que vallan tirando todos uds.

Asi que bueno, espero se prendan a analizar conmigo este proyectito, ya que me parece muy interesante, practico y eficiente!

Saludos a todos!
 
Es muy simple... pero primero se tienen que sentar unas reglas para costruirlo:

  1. La frecuencia maxima de operacion no puede ser superior a la del timer
  2. La frecuencia de todos los canales siempre sera la misma
  3. El duty de cada canal puede variar independientemente

La primera regla es porque todo se basa en el timer.. y se necesitan hacer calculos para encender y apagar adecuadamente los pines. La segunda es lo mismo que la primera, todo esta basado en un solo timer, y la tercera es mera necesidad del programa


  • Primero necesitas reservar 8 localidades en la RAM general para que sirvan de dutycycle para cada canal del PWM (digamos duty1 al duty8)
  • Reservas una localidad extra que sirva como contador maestro
  • Divides la frecuencia del PWM en 255 partes y configuras al timer para que te de una interrupcion en cada uno de esos 255 tiempos
  • Finalmente en cada interrupcion primero comparas el valor del contador maestro contra cada uno de los registros de dutycycle individual, si es igual inviertes el estado del puerto, al final incrementas en 1 el contador maestro y esperas la siguiente interrupcion
 
Si si, esta todo perfecto lo que decis, y es lo que yo ya habia concluido... Pero eh aqui lo que a mi vision es un punto muerto...

Lo que sucede es que la frecuencia minima con la que trabaja el TMR0 es la frecuencia de reloj del pic dividido 2 (configurando el preescaler en 1 : 2). Por lo que si usas el oscilador en 4Mhz tendrias una frecuencia de incremento para el TMR0 de 2Mhz, lo que implica un periodo de medio microsegundo. Esto implica que el TMR0 se estaria interrumpiendo (como maximo) cada 255*0.5 = 127.5 microsegundos... Si cada paso de los dutys lo marca dicha interrupcion, y la frecuencia de trabajo esta dividida en 255 pasos ("contador maestro"), entonces el periodo de dicha frecuencia tiene, como mucho, 127.5*255 = 32512.5 microsegundos, lo que implica una frecuencia de trabajo limita en maxima de 30.76 Hz... o sea... :S me parece una frecuencia muy baja para una modulacion PWM... no? Por favor instruyanme al respecto... esa frecuencia es util para algo? sirve? se usa?

Ademas, si observamos uno de los pedazos de codigos que publico Mario, se puede ver claramente que configura el prescaler del TMR0 en 1 : 4, que es para que incremente en cada ciclo de instruccion (Fosc/4), o sea con un periodo de 1 microsegundo... lo que implica una interrupcion cada 255 microsegundos... lo que a su vez implica un periodo maximo de trabajo de 255*255 = 65025 microsegundos, y esto nos da una frecuencia de trabajo de 15.38 Hz... :O jo... si esta frecuencia sirve para algo por favor diganmelo ya, y me dejo de hacer problema al respecto... :)

Pero que se yo... por ejemplo si quisiera controlar fuentes conmutadas con esto no podria... necesito de 1 KHz a 200 KHz para eso...

Si kiero regular LEDs creo que tampoco... donde estamos hablando de 500 Hz para arriba...

Ayuda por favor :).

NOTA: Si, se me ocurren soluciones al respecto... como por ejemplo sacrificar resolucion del paso de los dutys para aumentar frecuencia. Si en lugar de resetear el contador maestro cuando el registro se llena (255), lo hacemos antes, digamos unos 16 pasos, tendriamos una frecuencia cercana a 500Hz... algo un poco mas aceptable.... pero... solo tendriamos 16 niveles de tension promedio distintos... cosa que, segun la aplicacion, puede ser bastante suficiente.

Otra es cargar el TMR0 con un valor para que tarde menos en desbordar...

Otra es combinar las dos cosas...

Pero lo que pasa es que esas dos cosas se ve bien claro que Mario no lo hace, y asi y todo sus LEDs se ven muy bien dimmerizados.. :(... esa dimmerizacion es a 15.38 Hz? si? no? si no uso esa frecuencia de trabajo, entonces, como carambas lo hizo??

:) :) XD
 
Hay un error en tus calculos... suponiendo que usas un cristal de 4MHz la frecuencia maxima de operacion del TMR0 es de 500KHz eso dividido en segmentos de 255 hace una frecuencia (teorica) maxima de operacion de 1.98Khz lo cual ya es utilizable...

Sin embargo yo creo que en la practica lo maximo que podras alcanzar es alrededor de 1Khz debido a los calculos del micro para realizar otras operaciones... pero es el precio que hay que pagar por no tener circuiteria dedicada.. igual puedes subir la frecuencia al maximo... 20MHz, lo que daria una frecuencia teorica maxima de 9.8KHz

Edit... revisando la pagina si estan los codigos fuentes... no los he visto a profundidad pero si parecen estar completos, aqui los adjunto
 

Adjuntos

  • PIC.ZIP
    60.2 KB · Visitas: 535
Última edición:
A ver que es lo que sucede aqui... por que hay algo que no estoy entendiendo, vamos despacio que soy lento :D.

Por favor me podrias explicar de donde sacas y/o deduces que la frecuencia maxima de operacion del TMR0 es 500 Khz con un Fosc de 4 Mhz? Y ademas no entiendo si con "frecuencia maxima de operacion del TMR0" te estas refiriendo a la frecuencia con que incrementa el registro TMR0, o a la frecuencia con la que este desborda, por que son dos cosas distintas.

Perdon, seguro debe ser una boludes... pero estoy trabado/bloqueado.. :(

PD: Chan! te juro que busque por toda la pagina los codigos fuentes.. :S GRACIAS! Mañana me pongo a mirarlos, ahi seguro voy a entender que hace :).
 
Periodo = (256 - TMR0)*(4/fosc)*(Prescaler)

Si cargamos el timer con el numero mas grande (255) y el preescaler con 2

Periodo = (256-255)*(4/4MHz)*2 = 2uS

Frec = 1/ Periodo = 500kHz

Ese valor se debe multiplicar por 255 ya que es el numero de ciclos necesarios para concretar un periodo de PWM, e igual al tener interrupciones tan elevadas el micro no tiene tiempo mas que de ejecutar la interrupcion, por lo que la frecuencia real solo puede ir hacia abajo, pero el calculo exacto se debe hacer sobre el codigo terminado... ya que necesitamos saber cuanto tiempo le toma al micro terminar todos los pendientes antes de ejecutar la siguiente interrupcion
 
Para que no te sigas desvelando te traigo aca un circuito que sí funciona, muy bien, con código, y el software de pc que lo controla.

saludos.
 

Adjuntos

  • 8 pwm pic16f84a.rar
    45.1 KB · Visitas: 774
Última edición:
Hola aqui estoy de vuelta, primero que nada gracias a todos por dar sus comentarios, y segundo disculpenme por tardar tanto en responder... No anduve con mucho tiempo en estos dias, y no queria escibir algo asi no mas.

Estuve experimentando bastante y estudiando varios codigos... y encontre algunas soluciones y genere varias dudas mas tambien XD.

A ver, no se por donde empezar...

Vamos por partes; Chico3001, tengo dudas sobre lo que pusiste, sobre la configuracion del preescaler del Timer0, y sobre la maxima frecuencia de operacion que dices que se puede conseguir.

Figense como este muchacho, Mario, configura el preescaler en su codigo, y lo que comenta al respecto:

'***********************************************************************************
' COMIENZA EL PROGRAMA PRINCIPAL
'***********************************************************************************

MAIN:
TEMP = 0 'PONEMOS LAS VARIABLES A CERO
ACC0 = 0
DUTY0 = 0

LOW PORTB 'PONEMOS EN ESTADO BAJO TODAS LAS SALIDAS DEL PUERTO
GIE = 0 'APAGAMOS LAS INTERRUPCIONES GLOBALES
WHILE GIE = 1:GIE = 0:WEND 'NOS ASEGURAMOS QUE SE TERMINEN DE APAGAR
PSA = 0 'ASIGNAMOS EL OSCILADOR EXTERNO AL PRESCALER
PS0 = 1 'SETEAMOS EL PRESCAER
PS1 = 0 'PARA INCREMENTAR EL TMR0
PS2 = 0 'A CADA CICLO DE INSTRUCCIÓN

T0CS = 0 'ASIGNAMOS EL RELOJ INTERNO AL TMR0
TMR0 = 0 'LIMPIAMOS EL TMR0
T0IE = 1 'HABILITAMOS EL DESBORDE DE LA INTERRUPCIÓN DEL TMR0
GIE = 1 'HABILITAMOS TODAS LAS INTERRUPCIONES

Esa configuracion, segun el datasheet, corresponde a un "TMR0 Rate" de 1 : 4 ... entonces, como dice Mario ahi, si 1 : 4 corresponde a incrementar el TMR0 en cada ciclo de instruccion, por logica, la configuracion 1 : 2 corresponderia a un incremento de cada medio ciclo de instruccion... de ese razonamiento provenian mis calculos...

Pero estudiando un poco me di cuenta que no, que eso esta mal... Mario tiene algo mal ahi.. por que un incremento del TMR0 en cada ciclo de instruccion se logra, justamente, no asignando el preescaler al Timer, es decir, usando el Timer sin preescaler...
Y por ende, utilizando el preescaler, la config. 1 : 2 daria un incremento cada 2 ciclos de instruccion... y la 1 : 4 es cada cuatro.. etc...

Por lo que entonces, el desborde del TMR0 se podria producir en cada ciclo de instruccion... (cargando 255 en el TMR0) lo cual de todas formas seria una locura... el PIC no tendria tiempo para nada...

Por un lado esa es una duda que me andaba dando vueltitas.... Dejo adjuntado unos estractos del datasheet del 16F628A sobre la configuracion del Preescaler y el Timer0 para que consulten.

En un rato posteo un codigo terminado que hice yo y planteo otras dudas muy grandes sobre el codigo de Mario... en el cual, insisto.... para mi hace magia XD jaja...

Saludos!

EDIT: Ahi ya adjunte los archivos que mencione.

 

Adjuntos

  • Option Register.jpg
    Option Register.jpg
    164.5 KB · Visitas: 63
  • Preescaler y diagrama de Timer0.jpg
    Preescaler y diagrama de Timer0.jpg
    143.7 KB · Visitas: 69
Última edición:
Haslick

Para adjuntar archivos, debes pulsar el boton "Ir a Avanzado", y en esa nueva pantalla abajo encontraras un nuevo boton "Gestionar Archivos Adjuntos".

Al pulsar este ultimo boton te aparecera la pantalla de gestión con botones de "Examinar" tu PC y de "Subir" el archivo seleccionado, deberas esperar que termine de subir el/los archivos y abajo de todo pulsar "Cerrar esta Ventana", ten en cuenta el tamaño maximo según el tipo de archivo a subir.

Saludos, JuanKa.-
 
Muchas gracias J2C, pero hago todo eso y no bola... :(... no se que onda.

EDIT: XD ahi ya pude cargar los archivos... no me estaban cargando los archivos por q eran un pixel mas grande de lo permitido jeje.. y no me daba cuenta.. jeje cosas q pasan.. :p GRACIAS
 
Última edición:
Bueno, vamos a los interesante del tema... las concluciones finales :).

Como dije, voy a postear el programa que logre hacer yo:

BAJENSE EL WORD QUE ADJUNTE Y VEAN EL CODIGO

Esta en MicroC, y esta todo super comentado, pero igual voy a explicar algunos detalles.

Primero y principal, funciona ;)... jaja, dato no menor.

Segundo, la base del funcionamiento es con el desbordamiento del Timer, de eso no quedaba ninguna duda.

Entonces, funciona de la misma manera que se vino hablando hasta ahora:

Primero reservo 8 localidades en la RAM general para que sirvan de dutycycle para cada canal del PWM (digamos duty1 al duty8).

Luego reservo una localidad extra que sirve como contador maestro (Tiempo_Transcurrido_del_Periodo).
Y aca es donde yo hago una diferencia, divido la frecuencia del PWM en una cantidad de partes variable con una etiqueta (Duty_Resolution) de compilador, en lugar de dejarlo fijo en 255; y ademas configuro al timer para que de una interrupcion cada una cantidad tambien variable de tiempo, tambien con otra etiqueta (Interruption_Time). (Fijense que configuro el TMR0 para que se incremente cada 1 us, es decir, cada ciclo de instruccion, ya que uso un oscilador de 4 Mhz)

Finalmente en cada interrupcion primero comparo el valor del contador maestro contra cada uno de los registros de dutycycle individual, si es igual bajo el estado del bit del canal correspondiente, al final pregunto si el contador maestro es igual al valor puesto en la etiqueta Duty_Resolution e incremento en 1 el contador maestro si no es asi, y si es asi lo reseteo, y pongo en estado alto todos los canales en los cuales el duty es distinto de 0.

¿Que dio todo esto como resultado?

Bueno, me puse a jugar un rato largo con los valores de las dos etiquetas, Duty_Resolution e Interruption_Time. Para que no quede dudas; (255 - Interruption_Time) * 1 us = es lo que va a tardar el TMR0 en desbordar. Y ese valor multiplicado por el Duty_Resolution es el periodo del PWM.

Y eh aqui las limitaciones, cuando compilo todo esto, MicroC tira muchas instrucciones assembler dentro del bloque de interrupcion... y eso limita el valor maximo que puedo poner en la etiqueta Interruption_Time... ya que no puede ser mayor el tiempo que le lleva al pic ejecutar la interrupcion que el tiempo que tarda en dar una nueva... logico no... :S.

En el codigo que subi solo uso 3 canales... lo que genera un assembler tal, que como maximo puedo cargar al TMR0 con un valor de 150... mas de eso empieza a bajar la frecuencia y hace cosas feas...

Asi que solo me queda una variable con la cual jugar, el Duty_Resolution. Mientras mas grande mayor resolucion, pero menor frecuencia... asi que encontre un equilibrio en 100. Lo cual en total todo dio una frecuencia de PWM de, aprox, 83 hz... sip.. poco... pero suficiente para dimerizar leds... y fue medida con tester fluke.... asi que confien en que es precisa, osciloscopio aun no tengo.. :(.

PERO! si quisieran usar los 8 canales... LOLA! van a tener que subir la frecuencia de oscilacion a por lo menos 20 Mhz... por que con 4 Mhz da un PWM de menos de 15 hz... :S... con 20 Mhz y 8 canales dio una frec de PWM de 86 Hz nuevamente. Eso es lo maximo que les puedo ofrecer muchachos.

Ahora vamos por otro. Tambien compile e hice funcionar el codigo que escribio Mario:


Código:
'****************************************************************
'*  Name    : PWM_DEMO.BAS                                      *
'*  Author  : Mario G. Sacco                                    *
'*  Notice  : Copyright (c) 2009 NeoTeo                         *
'*          : All Rights Reserved                               *
'*  Date    : 20/11/2009                                        *
'*  Version : 1.0                                               *
'*  Notes   : CREAMOS 8 SALIDAS PWM UTIIZANDO INTERRRUPCIONES   *
'*          : INCREMENTANDO Y DECREMENTANDO EL BRILLO DE OCHO   *
'*          : LEDs CONECTADOS A LA SALIDA DEL PUERTO B DE UN     *
'*          : ELEMENTAL 16F628A                                 *
'****************************************************************

'DECLARO EL PIC A UTILIZAR Y EL CRISTAL 
DEVICE = 16F628A
XTAL = 4
FSR_CONTEXT_SAVE = OFF 

'****************************************************************
' DECLARO LAS VARIABLES 
DIM TEMP AS BYTE
        
DIM ACC0 AS BYTE    'ACUMULADORES DE VALORES PWM
DIM ACC1 AS BYTE
DIM ACC2 AS BYTE
DIM ACC3 AS BYTE
DIM ACC4 AS BYTE
DIM ACC5 AS BYTE
DIM ACC6 AS BYTE
DIM ACC7 AS BYTE
        
DIM DUTY0 AS BYTE   'PWM DUTY CYCLES DE CADA SALIDA DEL PUERTO B
DIM DUTY1 AS BYTE
DIM DUTY2 AS BYTE
DIM DUTY3 AS BYTE
DIM DUTY4 AS BYTE
DIM DUTY5 AS BYTE
DIM DUTY6 AS BYTE
DIM DUTY7 AS BYTE  
'****************************************************************************************
' ALIAS COLOCADOS A LOS REGISTROS DEL TIMER Y EL PRESCALER
'****************************************************************************************

SYMBOL T0IE INTCON.5     ' INTERRUPCIÓN POR DESBORDE DEL TMR0 HABILITADA
SYMBOL T0IF INTCON.2     ' BANDERA QUE SE ACTIVA AL DESBORDAR LA INTERRUPCIÓN DEL TMR0
SYMBOL GIE  INTCON.7     ' INTERRUPCIONES GLOBALES HABILITADAS
SYMBOL PS0 OPTION_REG.0  ' PRESCALER RATIO BIT-0
SYMBOL PS1 OPTION_REG.1  ' PRESCALER RATIO BIT-1
SYMBOL PS2 OPTION_REG.2  ' PRESCALER RATIO BIT-2
SYMBOL PSA OPTION_REG.3  ' ASIGNACIÓN DEL PRESCALER 
                         '(1=ASIGNADO AL WDT, 0=ASIGNADO AL OSCILADOR)
SYMBOL T0CS OPTION_REG.5 ' SELECCIÓN DEL CLOCK PARA EL TMR0 
                         '(0=CLOCK INTERNO, 1=EXTERNO POR PORTA.4)
                         
'****************************************************************************************
'SALTAR HACIA LA INTERRUPCIóN
'****************************************************************************************

ON_INTERRUPT GOTO PWM_INT   'MANEJO DE LA INTERRUPCIÓN
ALL_DIGITAL = True          'TODO DIGITAL
GOTO MAIN                    'SALTO AL MANEJO DE LA INTERRUPCIÓN

'****************************************************************************************
'SUBRUTINA DE LA INTERRUPCIÓN ////// ACTUALIZAMOS CADA 8 BITS LOS PWM EN EL PUERTOB
'****************************************************************************************

PWM_INT:
ACC0 = ACC0 + DUTY0   'ACTUALIZAMOS PWM0
RLF TEMP,F  
ACC1 = ACC1 + DUTY1   'ACTUALIZAMOS PWM1
RLF TEMP,F  
ACC2 = ACC2 + DUTY2   'ACTUALIZAMOS PWM2
RLF TEMP,F  
ACC3 = ACC3 + DUTY3   'ACTUALIZAMOS PWM3
RLF TEMP,F  
ACC4 = ACC4 + DUTY4   'ACTUALIZAMOS PWM4
RLF TEMP,F  
ACC5 = ACC5 + DUTY5   'ACTUALIZAMOS PWM5
RLF TEMP,F  
ACC6 = ACC6 + DUTY6   'ACTUALIZAMOS PWM6
RLF TEMP,F  
ACC7 = ACC7 + DUTY7   'ACTUALIZAMOS PWM7
RLF TEMP,W
MOVWF PORTB              'PASAMOS LOS ESTADOS DE LOS PWM A LAS SALIDAS
T0IF = 0              'LIMPIAMOS LA BANDERA DEL OVERFLOW DEL TMR0
CONTEXT RESTORE       'EQUIVALE A LA INSTRUCCIÓN RETFIE (REESTABLECEMOS LOS REGISTROS
                      'Y SALIMOS DE LA INSTRUCCIÓN
'***********************************************************************************
'                            COMIENZA EL PROGRAMA PRINCIPAL
'***********************************************************************************

MAIN:
TEMP = 0                                   'PONEMOS TODAS LAS VARIABLES A CERO
ACC0 = 0: ACC1 = 0: ACC2 = 0: ACC3 = 0
ACC4 = 0: ACC5 = 0: ACC6 = 0: ACC7 = 0                
DUTY0 = 0: DUTY1 = 0: DUTY2 = 0: DUTY3 = 0
DUTY4 = 0: DUTY5 = 0: DUTY6 = 0: DUTY7 = 0
        
LOW PORTB                    'PONEMOS EN ESTADO BAJO TODAS LAS SALIDAS DEL PUERTO
GIE = 0                        'APAGAMOS LAS INTERRUPCIONES GLOBALES
WHILE GIE = 1:GIE = 0:WEND  'NOS ASEGURAMOS QUE SE TERMINEN DE APAGAR
PSA = 0                        'ASIGNAMOS EL OSCILADOR EXTERNO AL PRESCALER
PS0 = 1                        'SETEAMOS EL PRESCAER
PS1 = 0                        'PARA INCREMENTAR EL TMR0
PS2 = 0                        'A CADA CICLO DE INSTRUCCIÓN
T0CS = 0                    'ASIGNAMOS EL RELOJ INTERNO AL TMR0
TMR0 = 0                    'LIMPIAMOS EL TMR0
T0IE = 1                    'HABILITAMOS EL DESBORDE DE LA INTERRUPCIÓN DEL TMR0
GIE = 1                        'HABILITAMOS TODAS LAS INTERRUPCIONES 

'*********************************************************************************
'                   LAZO REPETITIVO DE LAS SALIDAS SECUENCIADAS
'*********************************************************************************

LAZO:
FOR DUTY0 = 255 TO 1 STEP -1 'DECRECE DESDE 255 A 1 POR PASOS DE A UNO
DUTY1 = ~DUTY0               'MIENTRAS SU CONTIGUO CRECE AL MISMO RITMO
DELAYMS 5                    'MOSTRANDO UNA TRANCISIÓN SUAVE DE UN LED A OTRO
NEXT
FOR DUTY1 = 255 TO 1 STEP -1
DUTY2 = ~DUTY1
DELAYMS 5
NEXT
FOR DUTY2 = 255 TO 1 STEP -1
DUTY3 = ~DUTY2
DELAYMS 5
NEXT
FOR DUTY3 = 255 TO 1 STEP -1
DUTY4 = ~DUTY3
DELAYMS 5
NEXT
FOR DUTY4 = 255 TO 1 STEP -1
DUTY5 = ~DUTY4
DELAYMS 5
NEXT
FOR DUTY5 = 255 TO 1 STEP -1
DUTY6 = ~DUTY5
DELAYMS 5
NEXT
FOR DUTY6 = 255 TO 1 STEP -1
DUTY7 = ~DUTY6
DELAYMS 5
NEXT
FOR DUTY7 = 255 TO 1 STEP -1
DUTY0 = ~DUTY7
DELAYMS 5
NEXT

GOTO LAZO                    'REINICIAMOS EL CICLO INDEFINIDAMENTE
'***************************************************************************
Como vengo insistiendo hasta el dia de la fecha.... es muy pero muy interesante este codigo... lo hice funcionar... pero aun no lo logro entender 100X100.... A ver si se prenden a analizarlo uds tambien.

Primero y principal, funciona, y segundo tambien es con base en las interrupciones del Timer... pero... a excepción eso, y de que el main solo lo usa para variar los dutys, todo lo demas es muy distinto... nada que ver a lo que estabamos haciendo hasta ahora.

Tiene variables para los 8 Duty, si, pero no tiene un contador maestro, tiene 8 acumuladores y una variable auxiliar llamada TEMP.

¿Y que hace con esto? No se... no logro entender como carambas logra el efecto de PWM.... la clave esta en esta seccion del codigo, analizemosla:
Código:
PWM_INT:
ACC0 = ACC0 + DUTY0   'ACTUALIZAMOS PWM0
RLF TEMP,F  
ACC1 = ACC1 + DUTY1   'ACTUALIZAMOS PWM1
RLF TEMP,F  
ACC2 = ACC2 + DUTY2   'ACTUALIZAMOS PWM2
RLF TEMP,F  
ACC3 = ACC3 + DUTY3   'ACTUALIZAMOS PWM3
RLF TEMP,F  
ACC4 = ACC4 + DUTY4   'ACTUALIZAMOS PWM4
RLF TEMP,F  
ACC5 = ACC5 + DUTY5   'ACTUALIZAMOS PWM5
RLF TEMP,F  
ACC6 = ACC6 + DUTY6   'ACTUALIZAMOS PWM6
RLF TEMP,F  
ACC7 = ACC7 + DUTY7   'ACTUALIZAMOS PWM7
RLF TEMP,W
MOVWF PORTB              'PASAMOS LOS ESTADOS DE LOS PWM A LAS SALIDAS
T0IF = 0              'LIMPIAMOS LA BANDERA DEL OVERFLOW DEL TMR0
CONTEXT RESTORE       'EQUIVALE A LA INSTRUCCIÓN RETFIE (REESTABLECEMOS LOS REGISTROS
                      'Y SALIMOS DE LA INSTRUCCIÓN
Va sumando los distintos dutys en los respectivos acumuladores, y cuando se desborda dicha suma, la instruccion RLF hace algo loco con la variable TEMP... RLF rota el registro TEMP y le agrega el bit del acarreo generado por la ultima operacion... y asi, va rotanto TEMP y agregandoles los acarreos de los desbordamientos de las 8 sumas y a lo ultimo manda TEMP al Puerto B... :-O WTF?!... asi, tal y como esta, funciona... aunque no lo crean eso genera una señal PWM... Si alguno lo entiende, y se le ocurre una forma bien ilustrativa de explicarlo es bienvenido.

Pero bueno, no todo es color de rosa... como mi intuicion me indicaba, medi la frecuencia a la cual trabaja dicho PWM, y saben que descubri? Que no tiene frecuencia fija... la frecuencia de cada canal depende del duty de cada uno... ajam... para un duty de 30 (el estado bajo, osea casi prendido del todo) medi 30 Hz, para un duty de 128 (la mitad) 1.9 Khz... para un duty de 250 (casi apagado) medi tambien 30 Hz... y para valores intermedios frecuencias de entre 50 Hz y 2 Khz... (Ah! detalle importante... esas mediciones las hice sin poner el preescaler como lo tiene ahi él... yo lo puse al TMR0 para que incremente cada 1 us.. es decir, sin preescaler, no como puso él, que tarda el doble... asi que en su programa todas esas frecuencias dividanlas por 2)

CONCLUCIONES?

Y... que descubrimos dos formas de generar PWMs... a frecuencia fija, o a frecuencia variable. Si quieren frecuencia fija pueden usar mi codigo, sacrificando resolucion, y con baja frecuencia... y si quieren mas resolucion usan el de mario, pero con frecuencia variable. Todo depende de la aplicacion.


¿QUE OPINAN?

.
 

Adjuntos

  • PWM con MicroC.doc
    42 KB · Visitas: 187
Última edición:
Pero estudiando un poco me di cuenta que no, que eso esta mal... Mario tiene algo mal ahi.. por que un incremento del TMR0 en cada ciclo de instruccion se logra, justamente, no asignando el preescaler al Timer, es decir, usando el Timer sin preescaler...
Y por ende, utilizando el preescaler, la config. 1 : 2 daria un incremento cada 2 ciclos de instruccion... y la 1 : 4 es cada cuatro.. etc...

Por lo que entonces, el desborde del TMR0 se podria producir en cada ciclo de instruccion... (cargando 255 en el TMR0) lo cual de todas formas seria una locura... el PIC no tendria tiempo para nada...


No habia puesto el caso del TMR0 sin prescaler por la misma razon que dices.... el PIC no tendria tiempo de nada.. de echo aun con un preescaler de 2 o incluso de 4 tampoco considero que tenga tiempo de hacer nada...

Por otro lado... este programa forzosamente se tiene que hacer en ensamblador ya que la velocidad lo es todo... no creo que usando un compilador de alto nivel puedas igualar la velocidad...
 
tuimg dijo:
descargaste el zip que esta al fnal del articulo??

Sip, es de donde saque el segundo codigo que publique. Gracias igual!

Chico3001 dijo:
No habia puesto el caso del TMR0 sin prescaler por la misma razon que dices.... el PIC no tendria tiempo de nada.. de echo aun con un preescaler de 2 o incluso de 4 tampoco considero que tenga tiempo de hacer nada...

Por otro lado... este programa forzosamente se tiene que hacer en ensamblador ya que la velocidad lo es todo... no creo que usando un compilador de alto nivel puedas igualar la velocidad...

Claro, me imagine, pero yo igual me hice lio con el tema del preescaler... me confundio lo que puso Mario en sus comentarios en el codigo :S.

Por otro lado.. sigo sin entender como Mario logra el efecto de PWM en ese codigo... la clave esta en esa porcion de codigo que copie... en realidad la clave esta solo en la instruccion RLF, la cual, leyendo el datasheet y todo, no termino de entender bien que es lo que hace...

Si alguien lo entiende por favor expliquemelo... que yo ya me frustre :( :p

Saludos y gracias por sus comentarios!
 
Amigo este tema en verdad tambien me tiene con muchas dudas de como echarlas andar gracias por postear sus conocimiento y dar un alcance del funcionamiento del PWM del codigo de Mario y haslick podrian subir el esquematico para poder ver su funcionamiento
 
Anduve pensando laaargo y tendido con este tema... creo que para ahorrar velocidad lo mejor es hacer una rutina de software y evitar el timer, ademas de precompilar los datos para dar un poco de mayor velocidad

Primero se copia un registro shadow al puerto para poder cambiar las salidas del PWM, despues se procede a decrementar y comparar cada uno de los contadores e ir actualizando poco a poco el registro shadow para el siguiente ciclo, finalmente se repite todo

Con ese procedimiento se deberia poder terminar un ciclo muy rapido, pero igual el PIC estaria enfocado a realizar el PWM y nada mas... incluso algo como subir o bajar el nivel de un canal quitaria tiempo... supongo que se podria hacer un procedimiento especial con algun puerto para subir y bajar los niveles...
 
Chico3001 dijo:
Anduve pensando laaargo y tendido con este tema... creo que para ahorrar velocidad lo mejor es hacer una rutina de software y evitar el timer, ademas de precompilar los datos para dar un poco de mayor velocidad

Vos te referis a lograrlo con frecuencia fija? Por que con su codigo, Mario, logra manejar todo el procedimiento de la interrupcion en menos de 40 instrucciones (37 instrucciones de ensamblador para ser mas preciso). Y son 8 canales PWM... que no es poca cosa.

Analicemos lo siguiente, el timer de ese codigo esta con preescaler en 1 : 2, con un cristal de 4 Mhz. Eso implica que cada 2 us se incrementa el TMR0, como no se le carga ningun valor a dicho registro en ningun momento, este se desborda generando asi una interrupcion cada 256 * 2 us = 512 us.
Si dentro de la interrupcion esta menos de 40 us, significa que el pic tiene por lo menos 472 us, es decir, 472 instrucciones para ejecutar tranquilo en el main antes de que se genere la proxima interrupcion. A mi me parece que es mas que aceptable ese numero.

Para resumir, el pic haria esto, cada 472 instrucciones se interrumpe durante 40 instrucciones y luego vuelve a los suyo, y asi sucesivamente.

Pero bueno, no olvidemos que tenemos la desventaja de que la frecuencia varia con el duty...

carlos jara dijo:
Amigo este tema en verdad tambien me tiene con muchas dudas de como echarlas andar gracias por postear sus conocimiento y dar un alcance del funcionamiento del PWM del codigo de Mario y haslick podrian subir el esquematico para poder ver su funcionamiento

Carlos, no entendi que deseas que suba, por que no hay mucho esquematico que digamos... lo estoy probando con un pic 16F628A en una protoboar... y no hay muchos componentes mas... uso oscilador interno... sin MCLR... por lo que el "esquematico" quedaria solo el pic con la alimentacion y 3 led con sus respectivas resistencias conectados desde las salidas del pic a masa... No se como mas ayudarte, preguntame cualquier cosa!
 
Haber asi como la vez?? 30 instrucciones... :D

Código:
	movf	shadow,w	;Colocamos nueva informacion de
	mofwf	portb		;PWMs en puerto B
	movlw	h'ff'		;Reiniciamos registro shadow
	movwf	shadow		;
	incf	cont0,f		;Incrementa contador
	xorwf	pwm0,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,0	;apaga PWM0
	xorwf	pwm1,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,1	;apaga PWM1
	xorwf	pwm2,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,2	;apaga PWM2
	xorwf	pwm3,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,3	;apaga PWM3
	xorwf	pwm4,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,4	;apaga PWM4
	xorwf	pwm5,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,5	;apaga PWM5
	xorwf	pwm6,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,6	;apaga PWM6
	xorwf	pwm7,w		;revisa si es tiempo de desactivar
	btfsc	status,z	;el PWM
	bcf	shadow,7	;apaga PWM7
 
Esta buena la idea, me gusta, por que seria a frecuencia fija eso, lo estuve probando.

Pero... asi como esta no me funciono a m. A vos si?

Si es asi pasame el codigo completo asi lo analizo. Si es que asi lo pudiste hacer andar, fijate que el total de instrucciones finales que quedaria no son 30, ya que cada vez que entra y sale de la interrupcion se necesitan las clasicas instrucciones para salvar y recuperar registros (w y status)... y ademas faltaria la instruccion para bajar la bandera de interrupcion (una mas es una mas :p). De todas formas siguiria siendo una ganga ;).

Ahora volviendo al analisis de ese codigo en si, por lo que veo no me parece que deberia funcionar, ya que los xorwf PWMx, w hacen w = PWMx xor w, y en w que hay en ese momento? Bueno, en el primero w = h'ff'... y en los subsiguientes w = al resultado del xorwf anterior... Habria que agregarle un movf cont0, w ensima de cada orwf PWMx, w. Me explico? Pero de todas formas agregandole eso solo tampoco me funciono. Estoy bien en lo que digo? O me estoy re confundiendo?

Mira, yo tome tu codigo como base y lo reforme un poco, me quedo algo asi:

Código:
ON INTERRUPT:

        movf  _cont0,w        //revisa si es tiempo de activar
        xorwf   _pwm0,w       //el PWM0
        btfsc   status,z     //si es asi
        bsf     portb,0       //prende PWM0
        
        movf    _cont0,w
        xorwf   _pwm1,w
        btfsc   status,z
        bsf     portb,1
        
        movf    _cont0,w
        xorwf   _pwm2,w
        btfsc   status,z
        bsf     portb,2
        
        movf    _cont0,w
        xorwf   _pwm3,w
        btfsc   status,z
        bsf     portb,3
        
        movf    _cont0,w
        xorwf   _pwm4,w
        btfsc   status,z
        bsf     portb,4
        
        movf    _cont0,w
        xorwf   _pwm5,w
        btfsc   status,z
        bsf     portb,5
        
        movf    _cont0,w
        xorwf   _pwm6,w
        btfsc   status,z
        bsf     portb,6
        
        movf     _cont0,w
        xorwf    _pwm7,w
        btfsc    status,z
        bsf      portb,7
        
        incf     _cont0,f        //Incrementa contador
        btfsc    status,z        //pregunta si ya se desbordo el contador
        clrf     portb        //si es asi baja todos los PWMs de nuevo
        
        bcf      INTCON,T0IF //bajo la bandera de interrupcion
Bueno, asi funciono.... pero algo no anda bien, por que, si, me varian los dutys, pero a 15 hertz.... :-O... no entiendo por que, son exactametne 50 instrucciones esas (contando las de salvar y recuperar registros w y status)... algo estoy haciendo mal... no puede ser que me tire solo 15 hz finales... se te ocurre que puede estar pasando?
 
Ahora volviendo al analisis de ese codigo en si, por lo que veo no me parece que deberia funcionar, ya que los xorwf PWMx, w hacen w = PWMx xor w, y en w que hay en ese momento? Bueno, en el primero w = h'ff'... y en los subsiguientes w = al resultado del xorwf anterior... Habria que agregarle un movf cont0, w ensima de cada orwf PWMx, w. Me explico? Pero de todas formas agregandole eso solo tampoco me funciono. Estoy bien en lo que digo? O me estoy re confundiendo?

Tienes toda la razon... :oops: lo que pasa es que lo hice de memoria y no lo simule ya que no tengo instalado el MPLAB (y sinceramente ni quiero hacerlo :D)

En cuanto a la frecuencia si es bastante baja :cry:, si tomamos 40 instrucciones x 255 veces que se tienen que repetir para lograr el PWM y le sacamos la inversa nos darian 98Hz considerando un cristal de 4MHz, se me ocurren algunas maneras para bajar esto... bajar el numero de pasos en el PWM a 10 (por ejemplo) e incrementar la frecuencia del cristal al maximo que es de 20MHz, con esto se lograrian en teoria 50KHz aunque en la practica deberia ser menos.... tal vez unos 5 a 10KHz, pero ya es considerable...
 
Atrás
Arriba