Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC12F675 PWM Working But not Perfect

Status
Not open for further replies.

wuchy143

Member
Hi All,

I'm performing PWM to drive 5V LED's. Essentially the output of the chip is hooked up to a PFET which turns on/off a bank of ~LED's. The PWM on and off times are controlled by a pot which swings from 0--->5V. I read the A/D reading from the pot. From that reading I then load up Timer0, bang the port bit to turn off the PWM output. Then once that's done I load Timer0 again to achieve my On time.

Here is my code. Be gentle. I'm learning...haha j/k. Be rough I don't care. Just want to get better at this stuff.

Code:
void PWM (unsigned int On_Time);
 void Read_AD_Pin(void);
 void Gather_AD_Reading(void);
 void Transform_AD_Reading(void);

 unsigned int On_Time = 0x00;
 unsigned int TMR0_MIRROR = 0x00;
 unsigned int On_Time_Trans = 0x00;
 unsigned int AD_10_Bit = 0x00;

void main() {

ADCON0     = 0b10000001;          // a/d on
CMCON      = 0x00;                // Disable comparators
ANSEL      = 0b00000001;          //assigning digital i/o
TRISIO     = 0b00000001;          //ports are all outputs
OPTION_REG = 0b01011000;
T0IF_bit   = 0;                   //clear timer0 overflow flag

for(;;)
        {
        Read_AD_Pin();
        Gather_AD_Reading();
        Transform_AD_Reading();
        PWM(On_Time);
        }
}

void PWM(unsigned int On_Time)
     {
     T0IF_bit = 0;
     TMR0 = AD_10_Bit;
     TMR0_MIRROR = 0xFF - AD_10_Bit;
         while(T0IF_bit != 1)
                    {
                    GP2_bit = 0;
                    }
                    T0IF_bit = 0;

        TMR0 = TMR0_MIRROR;
        while(T0IF_bit != 1)
                   {
                   GP2_bit = 1;
                   }
                   T0IF_bit = 0;
     }

void Read_AD_Pin(void)
     {
     NOT_DONE_bit = 1;
     while(NOT_DONE_bit = 1)
                           {;}
     }

void Gather_AD_Reading(void)
     {
     AD_10_Bit = (unsigned int)ADRESH;
     AD_10_Bit <<= 8;
     AD_10_Bit |=(unsigned int)ADRESL;
     }

void Transform_AD_Reading(void)
     {
     AD_10_Bit = AD_10_Bit/4;
     }

The problem I am having is that when the LED's are stepping up in brightness just when they turn on my resoution stinks. It's very choppy from on brightness to the other levels. Currently each of my steps are 5uS but I want to make that smaller. That way if my steps are much smaller down at lower brightness it will give the human eye a much smoother change. I'm limited by the amount of steps I can have due to timer0 being a byte(256 steps).

Any guidance would be much appreciated. Thanks
 
Hi there Wuchy,

Usually for software PWM I'll do something like the following:

Have a timer interrupt which interrupts at 256*(PWM Frequency).
Have a register which holds your 8-bit duty cycle value.
Every timer interrupt you increase a register.
Compare your timer register with your duty cycle register.
If the timer register is higher than the duty cycle register, turn the output off. Else (the timer register is lower than the duty cycle register) turn the output on.

The register which your timer increments is an 8-bit register which is just free-running. To change your duty cycle just change the register which holds your 8-bit duty cycle.

You can also use 16-bit registers to give you 16-bit resolution however your timer now has to interrupt at 65536*(PWM freq).

Hope this made sense, if you have any questions just ask.
 
Hi Gobbledok,

Thanks for the reply.

I'm trying to understand your PWM algorithm and have a couple questions. I'm trying to break it down into smaller chunks:

1.
Have a timer interrupt which interrupts at 256*(PWM Frequency).

I would like my frequency to be 125Hz. So, from what you say I should have an interrupt occur every 31.25uS?(aka Timer0 goes off every 31.25uS)
256 * 125Hz = 32,000----> 1/32,000 = 31.25uS period

From what I know about timers it appears that with the PIC12F675 I would only be able to do this by pre-loading the Timer0 register to a high value so it goes off at the specified rate of 31.25uS?

2.
Have a register which holds your 8-bit duty cycle value.

In my case this means that I take my A/D reading(0-5V) and put it into a register which is my duty cycle?

Also, just so I understand why you multiply the 256 * 125Hz....i Really don't understand that. Why are you doing that. Could you explain that a little more?

Thanks

wuchy
 
Hi Wuchy the reason you need to interrupt at 256*PWMFcy is because that becomes your resolution. In any one of those interrupts, depending on your PWM register value and your duty cycle value, you could be turning the output on or off.

Yep your maths is right for the PWM period.

Have a look below, it explains the rest better than before.

Code:
/*  PsuedoCode  */

Main:
  Read A/D;
  Place A/D result into PWM register;


Timer Interrupt:
  Load Timer register for next interrupt;
  Clear interrupt flag;
  Increment PWM register;
  Compare PWM register with duty cycle register;
  If PWM register is lower than duty cycle register (i.e the PWM register rolled over) then turn the output on;
  If PWM register is higher than duty cycle register (i.e. your preset duty cycle has been reached) then turn the output off;

I hope this makes a bit more sense this way, sometimes it's better to visualise.
 
Last edited:
Here's one I did for someone else

Code:
#include<htc.h>
#define _XTAL_FREQ 4000000
#define abs(n) ((n) >= 0 ? (n) : -(n)) 

char A,B,tog;

void main()
	{
	OPTION_REG = 0;
	TMR0IE = 1;
	TRISB2 = 0;
	A = B = 34;
	TMR0 = A;
	GIE = 1;
	while(1)
		{
		if(RA0 && (A < 62))
			{
			A++;
			B--;
			__delay_ms(50);
			}
		if(RA1 && (A > 6))
			{
			A--;
			B++;
			__delay_ms(50);
			}
		}
	}

void interrupt ISR()
	{
	if(TMR0IF)
		{
		tog = ~tog;
		if(tog)
			TMR0 = 0xff - A;
		else
			TMR0 = 0xff - B;
		RB2 = tog;
		TMR0IF = 0;
		}	
	}

Uses interrupts and tmr0 frequency is about 1khz the OPTION_REG needs to be prescaled for different frequencies (68 steps)
 
Thanks for the help both of you!

I have been able to get an interrupt driven PWM to work on the PIC12F675 but with interrupt latency I haven't been able to get the frequencies I want with the internal crystal. Though, it works fine as an example of how to do software PWM using Gobbledok's aproach. I posted it in case anyone wants to use or make better. Let me know if there are ways of improving it.

Again thanks guys/gals. I learned a lot from doing this.

ps I'm using mikroelectronika's IDE/compiler.


Code:
  void Read_AD_Pin(void);
 void Gather_AD_Reading(void);
 void Transform_AD_Reading(void);
 
 unsigned char PWM_Register = 0;
 unsigned int On_Time_Trans = 0x00;
 unsigned int AD_10_Bit     = 0x00;
 unsigned int On_Time       = 0x00;
 unsigned int TMR0_MIRROR   = 0x00;
 unsigned char Duty_Cycle   = 0x05;


void main() {
INTCON   = 0b11100000;          //turn on timer0 interrupt
ADCON0   = 0b10000001;          // a/d on
CMCON    = 0x00;                // Disable comparators
ANSEL    = 0b00000001;          //assigning digital i/o
TRISIO   = 0b00000001;          //ports are all except of GP0
OPTION_REG = 0b01010000;
T0IF_bit = 0;                   //clear timer0 overflow flag

for(;;)                         //loop forever
        {
        Read_AD_Pin();
        Gather_AD_Reading();
        Transform_AD_Reading();
        }
}

void Read_AD_Pin(void)
     {
     NOT_DONE_bit = 1;
     while(NOT_DONE_bit = 1)
                           {;}
     }

void Gather_AD_Reading(void)
     {
     AD_10_Bit = (unsigned int)ADRESH;
     AD_10_Bit <<= 8;
     AD_10_Bit |=(unsigned int)ADRESL;
     }


void interrupt(void)
     {
     TMR0 = 0xC8;                          //load timer for next interrupt
     T0IF_bit = 0;                         //clear timer 0 interrupt flag bit
     PWM_Register++;

     if(PWM_Register <= AD_10_Bit)
                     {
                     GP2_Bit = 1;
                     }
     else
         {
         GP2_Bit = 0;
         }
     }
     
     
void Transform_AD_Reading(void)
     {
     AD_10_Bit = AD_10_Bit/4;


     }
 
The GPIO #3 cannot be used as an output.
To get precise PWM freq control u need to run a 20Mhz crystal for best resolution and use asm programming. Hi lvl languages won't give u the precision control u need.

BTW u are using the 8 bit timer0 which isn't a good idea for tight freq. control.

The 16 bit Timer1 is required for that.
 
Last edited:
Status
Not open for further replies.

Latest threads

Back
Top