PIC12F675 con Entrada Analógica y PWM

Hola, quiero hacer un control por PWM con frecuencia fija y ciclo de trabajo variable según la entrada analógica del puerto AN0 del PIC12F675.

Este microcontrolador no tiene modulo CCP por lo cual debe hacerte por software.

Ya tengo digitalizada la lectura de la señal análoga, pero tengo algunas dudas con la implementación del PWM.

He pensado hacerlo de la siguiente manera: trabajar con una frecuencia de 1 kHz (T=1ms); después dividir la señal digitalizada entre 1024 (teniendo en cuenta que esta en 10 Bits) y guardarla en una variable llamada ciclo; luego activar el puerto de salida en estado Alto durante un tiempo (en ms) igual a la variable ciclo; por ultimo activar el puerto de salida en un estado Bajo durante un tiempo (en ms) igual a la 1-ciclo. Hacer este ciclo indefinidamente.

Dejo adjunto el archivo en C CCS y la simulación en Proteus 8. Escucho opiniones y sugerencias. Gracias.
 

Adjuntos

  • pwm12f675.zip
    24.2 KB · Visitas: 93
el PWM a software es tan simple como dificil de entender.

primero que nada debes tener una base de tiempo digamos que quieres una frecuencia fija supongamos 50hz o 20ms que es lo que se usa en servos y como dimer en un led.


ahora para hacer la frecuencia dividimos 20ms /2 por que necesitamos el doble de interrupciones para hacer una señal cuadrada.

lo que nos daria 10ms.

ahora esos 10ms los dividiremos en 255 partes que es la que nos da un byte.

lo que nos da 39.21 microsegundos de interrupcion.

como veras no es un chiste hacer PWM a software.

una vez obtenido el calculo haces los calculos para el desbordamiento del timer del pic.

unsignded char contador;

interrucpion
{

contador++;

//tu interrucion calculada.
}


void main()
{
// declara tus interrupciones

}


ahora lo que sigue es hacer que la base de tiempo nos de una frecuencia fija es decir generar los 20ms!


unsignded char contador;

interrucpion
{

contador++;


if(contador>=255)
{
contador=0;
}



//tu interrucion calculada.
}


lo que hise ahi fue fijar el contador que siempre cuente 255 partes que nos da 20ms.

en este caso al ser 1 byte sin signo no pasa nada si se desborda por que empieza nuevamente en 0 pero si hubiesemos usado una variable de 16 bits y no se reinicia en 0 tendriamos un problema pues la frecuencia del contador seria de 0xFFFF o sea una fruecuencia largisima.

ahora ya que tenemos la frecuencia fijada el paso siguiente es agregar una variable que se encargue de hacer el PWM y una simple pregunta if else que nos hace un verdadero PWM a software.

unsignded char contador;
unsigned char duty;


interrucpion
{

contador++;


if(contador>=255)
{
contador=0;
}

if(duty>= contador)
{
// prende led
}

else
{
//apaga led
}




//tu interrucion calculada.
}


como veras es muy simple y pudes hacer eso con todos los pines libres del micro y si se te acaban puedes usar registros de corrimiento y usar aun mas pines con PWM totalmente a software.

yo e usado este algoritmo con 32 pines con registros de corrimiento.
 
Yo diría que mejor:
Código:
unsignded char contador;
 unsigned char duty;
 interrucpion
 {
 contador++;

 if(contador>=255)
 {
    contador=0;
    // prende led
 }

 if(duty >= contador)
 {
    //apaga led
 }

 //tu interrucion calculada.
 }

Así reducimos las instrucciones innecesarias, recomendaría incluso solo if(duty = contador) para evitar tener que estar dando instrucciones de apagado consecutivas, se supone que cuando la condición se iguala se apaga y ya, nada lo volverá a encender hasta que se reinicie la cuenta.

Me enviaron ese PIC por error cuando pedí el 615 que si tiene PWM :/ igual lo solucionare por software, solo que en mi caso usaría el XC8 (no me da ganas pagar por compiladores cuando casi no uso PIC en realidad).
 
Última edición:
fijate Nuyel

te diste cuenta de esos errores menos de mi error de ortografia unsignded

el if else es por si quieres agregar mas leds en el patillaje del micro.

ejemplo:

if(duty1 >= contador)
{
//prende led
}
else
{
//apaga led

if(duty2 >= contador)
{
//prende led 2
}
else
{
//apaga led 2
}

if(duty3 >= contador)
{
//prende led 3
}
else
{
//apaga led 3
}

tambien hay que recordar que en el ensamblador el else es codigo resesivo del if.

aunque en realidad no consume muchos ciclos este algoritmo del PWM a software.

bueno cuando se conecta el osciloscopio y se mide es poquito o casi nada el daño.
 
Busco eficiencia por sobre todo, aunque luego lo rellenaran con delay y me parece que usar delay() causaba conflicto bajo ciertas condiciones.

La acción de apagar o encender (dependiendo de como se mire) se puede colocar en la condicional para reiniciar la cuenta, as se ejecuta todo casi al mismo tiempo. Lo único que faltaría es un condicional para cambiar de estado en cada led independiente, ahorras espacio, instrucciones y tiempo para hacer otras cosas.

Buena programación es cosa de practica, es mejor acostumbrarse a ella cuanto antes y asi se sufre menos depues cuando necesite líneas extras de código y tenga el espacio restringido.
 
bueno no se como mires el algoritmo

yo lo que velo es que en PWM tenemos 2 tiempos

en el flanco de subida va estar prendido lo que diga en DUTY y en el flanco de bajada estara apagado una vez que se termino el DUTY.

puede que funcione como dices.

talvez si es cierto lo que dices de la eficiencia de codigo pero ese algoritmo que puse funciono bastante bien en regiostros de corrimiento para tener 32 PWM.

digo le puse control remoto IR , bluetooth , audio y para acabarla de amolar 32 PWM con mi pesimo algoritmo.

me gustaria ver como creaste tu algoritmo aver si aprendo un poco. ;)
 
Aún no he podido hacer correctamente el PWM por medio del Timer0, lo máximo que hice fue leer el valor analógico y convertirlo a digital (10 Bits) y luego activar/desactivar el pin_a1 según la lectura y una comparación prefijada.

Código:
#include <12f675.h>
#device ADC=10
#fuses NOMCLR,INTRC_IO
#use DELAY (INTERNAL=4MHz)

void main (void)
{
   int16 lectura;
   setup_adc_ports(AN0_ANALOG);
   setup_adc(ADC_CLOCK_INTERNAL);
   set_adc_channel(00);
   while(true)
   {
      delay_us(20);                 //Si quito esta linea me sale error en la simulacion, ¿Por que sucede esto?
      lectura=read_adc();
      if (lectura<=512)             //Si la entrada es menor o igual a VCC/2
      {
         output_high (pin_a1);
      }
      else
      {
         output_low (pin_a1);
      }  
   }
}


Me pueden dar una mano con el programa para hacer PWM por Timer, cuyo ciclo de trabajo sea proporcional al valor de la entrada analógica.
 
a mira estas completamente perdido

lo que explique es que debemos tener una base de tiempo que segun los calculos fue de 39us
¿aqui todo exelente no?

la base de tiempo debe incrementar una variable la cual llame contador podemos ponerle el nombre que se nos ocurra gato, perro , cuenta, puntos ,etc.

esa variable incrementara nadamas

contador++;

este contador debemos reiniciarlo cada 255 cuentas que un byte sin signo nos da 255 que nos va perfecto.

if(contador >= 255)
{
contador =0;
}


cuando nosotros reiniciamos contador hacemos que nuestro perido mida 20ms.

como dijimos que debemos generar PWM para eso yo llame una variable llamada DUTY puede tener cualquier nombre, fulano, perro, perico, cuenta, pwm , etc.

esta variable igual debe ser sin signo y de 1 byte como la variable contador.

esta variable nosotros la vamos a cambiar , al cambirarle el valor a esta variable nos generara autentico PWM a software

if(duty >= contador)
{
//prende led
}
else
{
//apaga led
}


asi de simple

¿que problema tiene tu codigo?

pues que se ve que no comprendiste como funciona

trata de corregir la falla y me cuentas como te fue
 
algo parecido a esto:

duty=read_adc();

asi de facil debes leer el ADC y cargar el byte con lo que leyo el ADC.

recuerda que deben ser variables globales pues vas a usar una interrupcion.

yo diria que antes de usar una interrupcion intenta con un delay de 39us dentro del main. si funciono es hora de usar la interrupcion.
 
Hola, ya cree el código, lo simule en Proteus y funciona pero el periodo no cuadra al deseado, toma un valor de 65ms, cuando debe ser de 20ms. ¿Como puedo mejorar esto?

Dejo el código que escribí con la asesoría de los colegas de este foro. También la simulación.

Código:
/*******************************************************************************
* Programa: 12F675 PWM
* Versión: 1.0
* Autor: yorsk2004
* Descripción: Implementación de control por PWM (por software, mediante el 
*              Timer0, sin CCP) de ciclo variable proporcionala una entrada
*              analogica.
*******************************************************************************/

#include <12f675.h>
#device adc=8              //Lo selecciono de 8 bits por simplicidad e igual escala que el Timer0.
#fuses nomclr,intrc_io
#use delay (internal=4MHz)

unsigned char contador;
unsigned char duty;

#int_timer0                //Uso la interrupción por Timer0, este usa 8 bits.
void timer0()
{
   contador++;
   if(contador>=255)       //Si se desborda el Timer0, entonces:
   {
   contador=0;             //Reseteo el Timer0.
   }
   if(contador<=duty)      //Si el Timer0 esta contando y aun no ha llegado hasta el ciclo activo...
   {                       //... que tiene que generar, entonces:
   output_high (pin_a1);   //Habilito la salida, en este caso GP1.
   }
   else                    //Si ya hizo su ciclo activo, entonces:
   {
   output_low (pin_a1);    //Deshabilito la salida, por el resto de tiempo que queda del periodo completo.
   }
}

void main ()
{
   setup_adc_ports(an0_analog);              //Configuro el puerto GP0 como entrada analoga.
   setup_adc(adc_clock_internal);
   set_adc_channel(00);
   delay_us(50);                
   setup_timer_0(rtcc_internal|rtcc_div_1);  //Preescaler=1:1.
   set_timer0(178);                          //Valor de configuración calculado para f= de acuerdo a la formula.
   enable_interrupts(int_timer0);            //Habilito interrupcion por Timer0.
   enable_interrupts(global);                //Habilito todas las interrupciones.
   while(true)                               //Ciclo indefinido, hacer siempre.
   {
      duty=read_adc();                       //Lectura del la entrada analogica, este sera el ciclo de trabajo...
      delay_us(50);                          //... este sera el control del PWM.
   }
}
 

Adjuntos

  • 12F675 PWM.zip
    27.2 KB · Visitas: 56
Prueba de esta forma que es más sencillo.
PHP:
#include <12f675.H>
#fuses   NOMCLR
#use     delay(internal = 4MHz)

#define pin_pwm   PIN_A4
#define timer_max 0xD9

int8 ciclo_activo = 1;


#INT_TIMER0
void timer0_isr() 
{
   int8 contador;
   
   set_timer0(timer_max);           // Recargar el Timer 0
   
   if(--contador == 0)              // Si al ir bajando "contador" llega a 0...
   {
      contador = 0xFF;              // Se regresa "contador" a 255
      output_low(pin_pwm);          // Se pone en bajo el pin de salida PWM
   }
   
   if(contador == ciclo_activo)     // Si "contador" es igual a "ciclo_activo"...
   {
      output_high(pin_pwm);         // Se pone en alto el pin de salida PWM
   }
}


void main()
{
   setup_timer_0(T0_INTERNAL|T0_DIV_2);
   enable_interrupts(INT_TIMER0);
   set_timer0(timer_max);
   enable_interrupts(GLOBAL);
   
   setup_adc_ports(sAN0);
   setup_adc(ADC_CLOCK_INTERNAL);
   set_adc_channel(0);
   delay_us(50);
   
   while(true)
   {
      // Se ajusta el ciclo activo conforme a la lectura del ADC (Max = 255 a 8 bits)
      ciclo_activo = read_adc();    // Leer el canal ADC (Canal 0)
      delay_us(50);                 // Esperar conversión.
   }
}
Tal vez necesites cambiar las constantes del Timer por la versión de tu compilador.

Nota:
#device adc = 8 Es el valor por defecto, por eso no está incluido en la cabecera del programa.
 
D@rkbytes, ¿Como calculo el valor de timer_max para otra frecuencia? por ejemplo un para un PWM de 5 kHz. ¿Se debe colocar en hexadecimal?

¿Con esto?

FormulaT.jpg

Gracias.
 
Si. También en la hoja de datos está la fórmula.
No es necesario que escribas los valores en hexadecimal.

Ya que llegue a casa adjunto sobre este mismo mensaje un programa que hice para encontrar los valores del Timer 0 y el Timer 1

---------- Actualizado ----------

Archivo adjunto.
 

Adjuntos

  • Timers Calc.rar
    18 KB · Visitas: 72
Última edición:
dario

¿en CCS hay que volver a cargar el timer con el valor a cargar o no es necesario?

en otros compiladores veo que si es necesario pero en CCS no lo se.

otra cosa

el algoritmo que propusiste es igual al que propuse solo que haces una cuenta negativa y la cuenta la haces dentro del if.

solo que los hise asi para que se vea mas didactio el PWM a software
 
En cualquier compilador es necesario recargar el Timer 0 dentro de la interrupción. Esto no lo puede hacer el compilador.

Y sí, haciendo la cuenta negativa e interna al "if", se simplifica el programa.
También los flancos están invertidos para que se ajuste el ciclo activo a la lectura del conversor.
No soy dario, mi nick es D@rkbytes :cool:
 
D@rkbytes y TRILO-BYTE: La señal obtenida con el set_timer0 (217) con pre de 1:2 es de 30ms de periodo, ¿como puedo calcularla para otra frecuencia? ¿Cual es el rango de frecuencias para hacer PWM por este método?

Gracias.
 
Hola a todos, he leido este interesante articulo, y al descargar el archivo con Timer Calc, queria probarlo y me dice que hay problema de dependencia con el file .ocx, alguien me podria gentilmente ayudar, Muchas Gracias.
 
asi es es que es el problema de los programas escritos en BASIC.
los time run y los archivos raros.

hay que cargar manualmente el archivo para que pueda funcionar, ami me costo hacerlo correr.

yorsk2004 a lo que te refieres si puedes buscar una calculadora de timer para pic como la que mencionan con el error de .ocx

esa calculadora la pego aqui:

Ver el archivo adjunto 109681

si tambien tienes problemas con el mismo archivo pues es de practicar algo de GOGLING y buscar
no recuerdo bien pero creo que habia que montarlo manualmente desde el regedit.

algo sencillo pero dificil de corregir.

y respecto al PWM yo lo he trabajado a 10ms para evitar el efecto blinking en un LED RGB

cuando haces PWM en un led se vera como que medio parpadea pero cuando usas un led RGB se hacen mezclas de colores pero empiezan los parpadeos , con 10 ms es aceptable pero trabaja un poco mejor con 5ms.

bueno depende que quieras hacer.
 
Atrás
Arriba