CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

PIC16F877: PWM with less than 250Hz possible?
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
doke



Joined: 09 Feb 2009
Posts: 6

View user's profile Send private message

PIC16F877: PWM with less than 250Hz possible?
PostPosted: Sun Feb 15, 2009 12:21 pm     Reply with quote

Hello,

I use an PIC16F877 @ 4 MHz and want to control two servo motors with it. I'm afraid I can't use the PWM module at less than ca. 250 Hz (244.14 Hz when PR2=0b11111111)
However, I need a PWM signal at 50 Hz (20ms period) which is 1-2ms high and low the rest of the period (18-19ms).

To come over this problem, I tried to use the delay_us/delay_ms functions. However, I discovered two problems:

    - my (old) CCS compiler ignores the upper byte from int values in the delay functions => no variables bigger 255 can be used :-(
    Therefore I wrote a (very ugly) "workaround" (see code below) and split my int variable in 4 parts (4 if statements)

    - The delay function probably blocks my PIC and i can't do other stuff while the delay is active. Is that true?
    If yes, I could only use one servo :-(

I'm not sure, what the common way is to control servos. Maybe there is a way to use the PWM modules at 50Hz?

Code:
  unsigned int   duty = 50 ;     // 0..100 (%) (=0..1000µs)
  ...

      output_high(PIN_C2);
      delay_us(1000);

      IF (duty<25) {
        delay_us(duty*10);
        output_low(PIN_C2);
        delay_us(250-(duty*10));
        delay_us(750);
        }
      IF ((duty>24)&&(duty<50)) {
        delay_us(250);
        delay_us(duty*10-250);
        output_low(PIN_C2);
        delay_us(250-(duty*10));
        delay_us(500);
      }
      IF ((duty>49)&&(duty<75)) {
        delay_us(500);
        delay_us(duty*10-500);
        output_low(PIN_C2);
        delay_us(250-(duty*10));
        delay_us(250);
      }
      IF (duty>74) {
        delay_us(750);
        delay_us(duty*10-500);
        output_low(PIN_C2);
        delay_us(250-(duty*10));
      }
      delay_us(18000);
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Sun Feb 15, 2009 12:42 pm     Reply with quote

Here are two functions for a long delay with a variable parameter (16-bit).
These routines are only needed for vs. 3 and earlier. If you have vs. 4,
it allows a 16-bit variable as the parameter in delay_us() and delay_ms().

long_delay_us():
http://www.ccsinfo.com/forum/viewtopic.php?t=16656&start=1

long_delay_ms():
http://www.ccsinfo.com/forum/viewtopic.php?t=375&start=1

----------
To do PWM below 244 Hz, you can use software PWM.

See this thread for links:
http://www.ccsinfo.com/forum/viewtopic.php?t=35160
doke



Joined: 09 Feb 2009
Posts: 6

View user's profile Send private message

PostPosted: Tue Feb 17, 2009 10:59 am     Reply with quote

Thank you very much for you answer PCM programmer!

I tried the long_delay_us function and it works really fine! However, if I'll be able get the soft PWM running, I can write a program without the delay functions.

I implemented a program similar to the one you posted here: http://www.ccsinfo.com/forum/viewtopic.php?t=20050 (see also code below).

I didn't get yet, how to calculate/set the frequency of the PWM signal. As I understand LOOPCNT is proportional to the signal period and width to the "high period" of the signal. But why does the code generate a 100Hz signal with LOOPCNT = 39?
Would it be a 50 Hz signal if LOOPCNT was 78? Then I would only have a resolution of 78 different widths, which would be too small for the control of servos.

Code:
#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)

#define PWM_PIN  PIN_B1

#define LOOPCNT 39

int8 width;

//-------------------------------
#INT_RTCC
void tick_interrupt(void);

//====================================
main()
{
width = 10;

setup_counters(RTCC_INTERNAL, RTCC_DIV_1);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
 
while(1);
}

//====================================
#INT_RTCC
void tick_interrupt(void)
{
static int8 loop = LOOPCNT;
static int8 pulse;

if(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }

}


-----

edited: small changes in values (20->78)
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Feb 17, 2009 3:29 pm     Reply with quote

Here is an improved program that will give a 50 Hz pwm update rate,
and allow 100 pwm steps.
Code:
#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)

#define PWM_PIN  PIN_B1

//#define TIMER0_PRELOAD 163  // Gives 10 KHz interrupt rate
#define TIMER0_PRELOAD 63     // Gives  5 KHz interrupt rate

#define LOOPCNT 100           // Gives 50 Hz PWM update rate

int8 width;

//-------------------------------
// Function prototypes

#INT_RTCC 
void tick_interrupt(void);

//====================================
main()
{
width = 10;

setup_timer_0(RTCC_INTERNAL | RTCC_DIV_1);
set_timer0(TIMER0_PRELOAD);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
 
// The following code is used to test the program:
// Increase the PWM pulse width from 0 to 100%
// with one step change every 100 ms.  Watch the
// pulse width increase on an oscilloscope.
while(1)
  {
   for(width = 0; width <= LOOPCNT; width++)
       delay_ms(100);
  }
 
}

//====================================
#INT_RTCC
void tick_interrupt(void)
{
static int8 loop = LOOPCNT;
static int8 pulse;

// To test the interrupt rate, uncomment the next line.
// Pin B0 will have a 2.5 KHz squarewave on it, if the
// interrupt rate is 5 KHz.
//output_toggle(PIN_B0); 

// This line compensates for interrupt latency.
set_timer0(get_timer0() + TIMER0_PRELOAD);


if(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }

}
doke



Joined: 09 Feb 2009
Posts: 6

View user's profile Send private message

PostPosted: Thu Feb 19, 2009 12:43 pm     Reply with quote

Thanks again very much for the answer!

I tried the code again and it works fine, however I'm not yet satisfied with the resolution of the PWM signal. With a 10 KHz interrupt rate and a 50 Hz period of the PWM signal with pulse width 1-2ms I only have 10 different values to control my servo (width 10..20). With the delay function i had a resulution from ca. 1µs (1000 different values between 1ms and 2ms).

I tried to increase the TIMER0_PRELOAD value, but values higher than 178 don't result in a good square wave signal on my pin (checked with an oscillocope).

Is it possible to get a higher resolution?
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Thu Feb 19, 2009 4:55 pm     Reply with quote

Possibly the increased resolution could be done in CCP Compare mode.
I just don't want to write the code for you.

For examples, look at this CCS driver file:
Quote:
c:\Program Files\PICC\Drivers\Servos.c

Here is a test program that calls the Servos.c driver:
http://www.ccsinfo.com/forum/viewtopic.php?t=34560&start=22

More on using CCP compare mode:
http://www.ccsinfo.com/forum/viewtopic.php?t=30607

I don't guarantee that any of this is quickly usable as a template to solve your problem.
oh1jty



Joined: 08 Mar 2010
Posts: 3

View user's profile Send private message

PostPosted: Mon Mar 08, 2010 3:45 pm     Reply with quote

PCM programmer wrote:
Here is an improved program that will give a 50 Hz pwm update rate,
and allow 100 pwm steps.
Code:
#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)

#define PWM_PIN  PIN_B1

//#define TIMER0_PRELOAD 163  // Gives 10 KHz interrupt rate
#define TIMER0_PRELOAD 63     // Gives  5 KHz interrupt rate

#define LOOPCNT 100           // Gives 50 Hz PWM update rate

int8 width;

//-------------------------------
// Function prototypes

#INT_RTCC 
void tick_interrupt(void);

//====================================
main()
{
width = 10;

setup_timer_0(RTCC_INTERNAL | RTCC_DIV_1);
set_timer0(TIMER0_PRELOAD);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
 
// The following code is used to test the program:
// Increase the PWM pulse width from 0 to 100%
// with one step change every 100 ms.  Watch the
// pulse width increase on an oscilloscope.
while(1)
  {
   for(width = 0; width <= LOOPCNT; width++)
       delay_ms(100);
  }
 
}

//====================================
#INT_RTCC
void tick_interrupt(void)
{
static int8 loop = LOOPCNT;
static int8 pulse;

// To test the interrupt rate, uncomment the next line.
// Pin B0 will have a 2.5 KHz squarewave on it, if the
// interrupt rate is 5 KHz.
//output_toggle(PIN_B0); 

// This line compensates for interrupt latency.
set_timer0(get_timer0() + TIMER0_PRELOAD);


if(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }

}


This code doesn`t work on my 18F4620 design.
But it works fine on my old 16F877 design.
Any ideas ?
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Mon Mar 08, 2010 3:59 pm     Reply with quote

Timer0 is 8 bits wide in the 16F877. It can optionally be 8 or 16 bits
wide in the 18F4620. Look in the Timer0 section of the 18F4620.h
header file to see the constant for setup_timer_0() that configures
it to operate in 8-bit mode.
Mike Walne



Joined: 19 Feb 2004
Posts: 1785
Location: Boston Spa UK

View user's profile Send private message

Resolution
PostPosted: Mon Mar 08, 2010 4:39 pm     Reply with quote

Do you know how much resolution you need?

Is 4us good enough? (i.e. 250 different values from 1ms to 2ms)
oh1jty



Joined: 08 Mar 2010
Posts: 3

View user's profile Send private message

PostPosted: Mon Mar 08, 2010 5:57 pm     Reply with quote

PCM programmer wrote:
Timer0 is 8 bits wide in the 16F877. It can optionally be 8 or 16 bits
wide in the 18F4620. Look in the Timer0 section of the 18F4620.h
header file to see the constant for setup_timer_0() that configures
it to operate in 8-bit mode.


I tried with RTCC_8_BIT, but no difference.
Measured the test interrupt rate from B6.
With 16F877 it was around 1.6KHz and with 18F4620 around 5Hz
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Mon Mar 08, 2010 6:11 pm     Reply with quote

It works for me. I took the program you quoted above, and I changed
the #include line for the PIC to 18F4620. I edited the setup_timer_0()
statement to 'OR' in the RTCC_8_BIT constant. I then programmed it
into the 18F4620 chip on my PicDem2-Plus board. I then looked at
pin B1 with my oscilloscope. It has a rectangular waveform on it, and
the scope's frequency counter says 47.85 Hz. The duty cycle slowly
increases from 0 to 100%, and then repeats. I tested with with compiler
vs. 4.105.
Code:

#include <18F4620.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)

#define PWM_PIN  PIN_B1

//#define TIMER0_PRELOAD 163  // Gives 10 KHz interrupt rate
#define TIMER0_PRELOAD 63     // Gives  5 KHz interrupt rate

#define LOOPCNT 100           // Gives 50 Hz PWM update rate

int8 width;

//-------------------------------
// Function prototypes

#INT_RTCC 
void tick_interrupt(void);

//====================================
main()
{
width = 10;

setup_timer_0(RTCC_INTERNAL | RTCC_DIV_1 | RTCC_8_BIT);
set_timer0(TIMER0_PRELOAD);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
 
// The following code is used to test the program:
// Increase the PWM pulse width from 0 to 100%
// with one step change every 100 ms.  Watch the
// pulse width increase on an oscilloscope.
while(1)
  {
   for(width = 0; width <= LOOPCNT; width++)
       delay_ms(100);
  }
 
}

//====================================
#INT_RTCC
void tick_interrupt(void)
{
static int8 loop = LOOPCNT;
static int8 pulse;

// To test the interrupt rate, uncomment the next line.
// Pin B0 will have a 2.5 KHz squarewave on it, if the
// interrupt rate is 5 KHz.
//output_toggle(PIN_B0); 

// This line compensates for interrupt latency.
set_timer0(get_timer0() + TIMER0_PRELOAD);


if(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }

}
oh1jty



Joined: 08 Mar 2010
Posts: 3

View user's profile Send private message

PostPosted: Tue Mar 09, 2010 12:32 pm     Reply with quote

Thanks, i found the problem.

I tried with simpler code and it worked without any problems.
So i went thru the code and found this line:
"setup_counters(RTCC_INTERNAL, RTCC_DIV_8);" which the wizard had added Laughing
Brian



Joined: 29 Jun 2010
Posts: 10

View user's profile Send private message

PostPosted: Tue Aug 31, 2010 1:27 pm     Reply with quote

I am using the PIC18f2480, and this code generates a 11.95 Hz PWM frequency. Simiarily, uncommenting the debug code shows that the interrupt rate is 598 Hz, not 2.5 Khz. Not sure why?

Also, I'm confused how the interrupt function works.
Specifically:
Code:

f(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }


1) How would (--loop) every equal 0 if its initially 100 (the value of loopcount)? Wouldn't --loop just be 99?
2) What is the initial value for pulse? Initially its just declared as static int8 pulse.

I'm just a little confused how that logic works.

Thanks.


PCM programmer wrote:
It works for me. I took the program you quoted above, and I changed
the #include line for the PIC to 18F4620. I edited the setup_timer_0()
statement to 'OR' in the RTCC_8_BIT constant. I then programmed it
into the 18F4620 chip on my PicDem2-Plus board. I then looked at
pin B1 with my oscilloscope. It has a rectangular waveform on it, and
the scope's frequency counter says 47.85 Hz. The duty cycle slowly
increases from 0 to 100%, and then repeats. I tested with with compiler
vs. 4.105.
Code:

#include <18F4620.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock = 4000000)

#define PWM_PIN  PIN_B1

//#define TIMER0_PRELOAD 163  // Gives 10 KHz interrupt rate
#define TIMER0_PRELOAD 63     // Gives  5 KHz interrupt rate

#define LOOPCNT 100           // Gives 50 Hz PWM update rate

int8 width;

//-------------------------------
// Function prototypes

#INT_RTCC 
void tick_interrupt(void);

//====================================
main()
{
width = 10;

setup_timer_0(RTCC_INTERNAL | RTCC_DIV_1 | RTCC_8_BIT);
set_timer0(TIMER0_PRELOAD);
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
 
// The following code is used to test the program:
// Increase the PWM pulse width from 0 to 100%
// with one step change every 100 ms.  Watch the
// pulse width increase on an oscilloscope.
while(1)
  {
   for(width = 0; width <= LOOPCNT; width++)
       delay_ms(100);
  }
 
}

//====================================
#INT_RTCC
void tick_interrupt(void)
{
static int8 loop = LOOPCNT;
static int8 pulse;

// To test the interrupt rate, uncomment the next line.
// Pin B0 will have a 2.5 KHz squarewave on it, if the
// interrupt rate is 5 KHz.
//output_toggle(PIN_B0); 

// This line compensates for interrupt latency.
set_timer0(get_timer0() + TIMER0_PRELOAD);


if(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }

}
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Aug 31, 2010 1:51 pm     Reply with quote

It worked for me. I got 47.85 Hz on pin B1. The duty cycle slowly
increases until it's 100%, and then repeats. This was with vs. 4.111.
I copied and pasted the code (in your post above) into an MPLAB project.
I don't have an 18F2480, so I used an 18F2580, which is very similar.
I used it with a 4 MHz crystal, as specified in the code. I tested it on a
PicDem2-Plus board. I used an oscilloscope with a built-in frequency
counter to see the results.
Brian



Joined: 29 Jun 2010
Posts: 10

View user's profile Send private message

PostPosted: Tue Aug 31, 2010 2:11 pm     Reply with quote

Perhaps its because I'm not using an external crystal, just the internal clock..?..

Also, could you explain how the interrupt logic works here?

Code:

if(--loop == 0)
  {
   loop = LOOPCNT;
   pulse = width;
  }

if(pulse)
  {
   output_high(PWM_PIN);
   pulse--;
  }
else
  {
   output_low(PWM_PIN);
  }


1) How would (--loop) every equal 0 if its initially 100 (the value of loopcount)? Wouldn't --loop just be 99?
2) What is the initial value for pulse? Initially its just declared as static int8 pulse.

I'm just a little confused how that logic works.

Thanks!
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group