View previous topic :: View next topic |
Author |
Message |
doke
Joined: 09 Feb 2009 Posts: 6
|
PIC16F877: PWM with less than 250Hz possible? |
Posted: Sun Feb 15, 2009 12:21 pm |
|
|
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
|
|
|
doke
Joined: 09 Feb 2009 Posts: 6
|
|
Posted: Tue Feb 17, 2009 10:59 am |
|
|
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
|
|
Posted: Tue Feb 17, 2009 3:29 pm |
|
|
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
|
|
Posted: Thu Feb 19, 2009 12:43 pm |
|
|
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
|
|
|
oh1jty
Joined: 08 Mar 2010 Posts: 3
|
|
Posted: Mon Mar 08, 2010 3:45 pm |
|
|
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
|
|
Posted: Mon Mar 08, 2010 3:59 pm |
|
|
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
|
Resolution |
Posted: Mon Mar 08, 2010 4:39 pm |
|
|
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
|
|
Posted: Mon Mar 08, 2010 5:57 pm |
|
|
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
|
|
Posted: Mon Mar 08, 2010 6:11 pm |
|
|
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
|
|
Posted: Tue Mar 09, 2010 12:32 pm |
|
|
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 |
|
|
Brian
Joined: 29 Jun 2010 Posts: 10
|
|
Posted: Tue Aug 31, 2010 1:27 pm |
|
|
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
|
|
Posted: Tue Aug 31, 2010 1:51 pm |
|
|
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
|
|
Posted: Tue Aug 31, 2010 2:11 pm |
|
|
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! |
|
|
|