|
|
View previous topic :: View next topic |
Author |
Message |
Guest
|
generating a 20ms pwm signal |
Posted: Sat Mar 29, 2008 10:24 pm |
|
|
Hi everyone, I've been working on a pwm code and have troubles trying to get a 20ms pwm signal with a 0.01ms interrupt resolution. My PIC is 16F877A and this is part of my code on how i wrote for my pwm routine (got the idea on this routine thru PCM and thanks man ):
Code: |
#define PERIOD 2000
int16 width,pulse,loop;
void Initialize_RTC(void)
{
setup_timer_2(T2_DIV_BY_1,50,1);
set_timer2(0);
enable_interrupts(INT_TIMER2);
enable_interrupts(GLOBAL);
}
void main()
{
Initialize_RTC();
width = 150;
while(1);
}
#INT_TIMER2
void tick_interrupt(void)
{
if(--loop == 0 )
{
loop = PERIOD;
pulse = width;
}
if(pulse1)
{
output_high(PWM_PIN1);
pulse1--;
}
else
{
output_low(PWM_PIN1);
}
|
It is supposed to generate a 0.01ms overflow and countdown the loop for 2000 times to generate a 20ms signal. And the duty cycle is the "pulse". I'm getting some signals but it isn't correct. Can anyone help me pls? |
|
|
Douglas Kennedy
Joined: 07 Sep 2003 Posts: 755 Location: Florida
|
|
Posted: Sun Mar 30, 2008 12:21 am |
|
|
pulse1 is not defined in the snippet you show. Again it is better for you to break your code down to the smallest number of lines that will exhibit your issue. The lines should represent a compilable program. You should try compiling it before posting it. Small should be under twenty lines. Often just by doing this you will solve the issue yourself.
When you choose snippets of code you will have a bias. Remember you are not getting your own code to work so your judgment when it comes to your own code is imperfect so this judgment might extend to the snippet you present. If your snippet compiles and exhibits your issue then your judgment is replaced with a real demonstrable fact. |
|
|
Guest
|
|
Posted: Sun Mar 30, 2008 1:35 am |
|
|
Oh yea it's a working code. Hold on i post it up the whole thing.
Code: |
#include <16F877A.h>
#fuses HS, NOLVP
#use delay(clock = 20000000)
#define PWM_PIN1 PIN_C0
#define PERIOD 2000
int16 width;
static int16 loop = PERIOD;
static int16 pulse;
//***************************************************************************
void Initialize_RTC(void)//////////*********************************************
{
setup_timer_2(T2_DIV_BY_1,50,1);
set_timer2(0);
enable_interrupts(INT_TIMER2);
enable_interrupts(GLOBAL);
}
//=========================================================================================
void main()
{
width = 150; //1.5 ms duty cycle
Initialize_RTC();
while(1);
}
//====================================================================================
#INT_TIMER2
void tick_interrupt(void)
{
if(--loop == 0 )
{
loop = PERIOD;
pulse = width;
}
if(pulse>0)
{
output_high(PWM_PIN1);
pulse--;
}
if(pulse<=0)
{
output_low(PWM_PIN1);
}
}
//******************************************************************************
|
|
|
|
Ttelmah Guest
|
|
Posted: Sun Mar 30, 2008 3:59 am |
|
|
Big problem here is that you are just calling the interrupt too fast.
Interrupts have a very significant overhead, involved in being called, tiding up all the registers that need to be saved, checking what interrupt is involved, calling the handler, returning from this, clearing the interrupt flag, restoring the registers, and then finally returning to the caller. You can reckon on a _minimum_ of about 55 instructions for all this. Then your code performs a minimum of a decrement, and two tests (perhaps 20 instructions), and a 'worst case' of something more like 50 instructions.
Now you are setting the timer to run straight off the master 'instruction' clock (oscillator/4), and asking for an interrupt every 51 instructions (remember that the count includes '0', so the count will be 0 to 50 as programmed). This means that you are trying to interrupt much faster than the code can possibly handle. Hence your odd times....
This is the primary reason for hardware PWM's. Implementing a PWM, of any accuracy, at speed in the main code, is hard.
If you are prepared for the chip to just do this one job (and not have to do anything else), the software PWM, can be made to work, by _not_ calling an interrupt handler. Instead _poll_the interrupt flag, in the main loop, clear it immediately, and do all the timings here.
Best Wishes |
|
|
Guest
|
|
Posted: Wed Apr 02, 2008 4:14 am |
|
|
Wow, Ttelmah, i didn't know i can't implement PWM like that. But for my case here, i need the controller to generate the PWM signals at a rate of 0.01ms per interrupt. Besides that, i will do other things with the PIC like needing it to handle serial communications with the PC.
So, in your explanation, i couldn't quite understand on how to implement the method you're suggesting. Can you please write me a sample code on this?
Best Regards. |
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Wed Apr 02, 2008 7:07 am |
|
|
If you could relax your requirement for 1 part in 2000 control of the PWM, and make it just 1 part in 1000, you could do the following:
Set the operating frequency of the PIC way slow, at 800KHz.
Use the maximum prescaler setting for TMR2, at 16.
Set the comparator for TMR2 at 250.
The repitition rate is now 800000/(4*16*250) or 50Hz, i.e. 20msec.
Now you can use the PIC's built-in 10-bit PWM generator, and it'll give you resolution of 1 part in 1000. With no processor action required!
I think what Ttelmah was suggesting was something like this, but I'd do it with all unsigned variables:
Code: |
[Set up your timer to repeat at 100000/sec, but don't enable it as an interrupt.]
while (1)
{
if (!bit_test(TMR2IF)) // Has timer ticked?
continue; // No, do nothing
bit_clear(TMR2IF); // Yes, now we have to clear the interrupt manually
if (--loop == 0 )
{
loop = PERIOD;
pulse = width;
}
if (pulse)
{
output_high(PWM_PIN1);
pulse--;
}
else
output_low(PWM_PIN1);
if (bit_test(RCIF)) // Serial character arrived?
{ // Process incoming serial data here, but it has to be fast!
}
}
|
|
|
|
RLScott
Joined: 10 Jul 2007 Posts: 465
|
|
Posted: Wed Apr 02, 2008 7:17 am |
|
|
I suggest using the Output Compare Mode of a CCP module to implement your 20 msec. period PWM. When you use Output Compare Mode, you have to calculate the timer value for the next edge and load the PR register with that time. When the timer reaches that value it will automatically toggle the output pin (provided you have set it up to do that). This puts a lot less stress on your software because they don't need to get involved at each 10 usec. tick of the clock. The only difficultly with this approach is if you need to generate extremely low or extremely high duty cycles (close to 0% or 100%). That is because in such a case there may not be enough time between one edge and the next to set up the PR register with the appropriate value. If you can avoid these extremes, then there is no problem. If you have to go to these extremes in duty cycle, you could generate some special case software to handle these short pulses with instruction timing. It is complex, but it can be done.
Robert Scott
Real-Time Specialties |
|
|
Ttelmah Guest
|
|
Posted: Wed Apr 02, 2008 7:24 am |
|
|
Bodged into your own code:
Code: |
#define PERIOD 2000
int16 width,pulse,loop;
void Initialize_RTC(void)
{
setup_timer_2(T2_DIV_BY_1,50,1);
set_timer2(0);
}
#bit TMR2IF=0xC.1
void main()
{
Initialize_RTC();
width = 150;
TMR2IF=0;
while(1) {
while(TMR2IF==0) ;
TMR2IF=0;
//This waits for the timer interrupt.
if(--loop == 0 ) {
loop = PERIOD;
pulse = width;
}
if(pulse) {
output_high(PWM_PIN1);
pulse--;
}
else {
output_low(PWM_PIN1);
}
}
}
|
Provided the whole loop takes less than 50 instructions, this should work.
You can save two instructions straight away, by switching to fast, or fixed I/O.
Best Wishes |
|
|
Guest
|
|
Posted: Fri Apr 04, 2008 1:51 am |
|
|
The code by Ttelmah worked! But i'm gonna need to output more than one pwm output pin. I've got 5 to work on, which means the amount of instructions exceed the 50 limit. I've tried to duplicate it to work for 4 pins but it produced the same problem i faced in earlier stages, the timing is off the mark.
So is there a way for me to do it besides linking 2 PICs to work? |
|
|
Matro Guest
|
|
Posted: Fri Apr 04, 2008 2:36 am |
|
|
If you increase the clock frequency to 25/30 MHz, the following code should be OK :
Code: |
#include <16f877a.h>
#define PERIOD 2000
#use fast_io(b)
unsigned int16 width1,width2,width3,width4,width5,loop;
void Initialize_RTC(void)
{
setup_timer_2(T2_DIV_BY_1,50,1);
set_timer2(0);
}
#bit TMR2IF=0xC.1
void main()
{
set_tris_b(0x00);
Initialize_RTC();
TMR2IF=0;
loop =PERIOD;
while(1) {
while(TMR2IF==0) ;
TMR2IF=0;
//This waits for the timer interrupt.
if(--loop == 0 ) {
loop = PERIOD;
}
if(loop>=width1) {
output_high(PIN_B1);
}
else {
output_low(PIN_B1);
}
if(loop>=width2) {
output_high(PIN_B2);
}
else {
output_low(PIN_B2);
}
if(loop>=width3) {
output_high(PIN_B3);
}
else {
output_low(PIN_B3);
}
if(loop>=width4) {
output_high(PIN_B4);
}
else {
output_low(PIN_B4);
}
if(loop>=width5) {
output_high(PIN_B5);
}
else {
output_low(PIN_B5);
}
}
}
|
Matro. |
|
|
Matro Guest
|
|
Posted: Fri Apr 04, 2008 2:39 am |
|
|
Of course, you have to initialize all widthX values and to adapt the timer2 setup according to the Xtal frequency. ;-)
Matro. |
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Fri Apr 04, 2008 12:36 pm |
|
|
OK, I think this will do it:
You have to make up a set of 6 "state bytes" and 6 "intervals". The intervals are the times when one or more of the PWM outputs go from high to low; the states are the settings, at that moment, of the PWM outputs.
Call the PWM outputs by the names A-E. So let's say the PWM outputs all go high together at time 0, then A goes low at 100, C goes low at 550, B and E go low at 1200, and D goes low at 1550.
So the state bytes would be 00011111, 00001111, 00001011, 00000010, 00000000, and the intervals would be 0, 100, 550, 1200, 1550. In this case one state byte and one interval would be left unused, because two outputs make a transition together.
At time 0 (always the first interval) you take the first state and send it to the output port, in broadside mode, not as individual bits. You set the count of intervals to 1, ready for the first transition.
At every clock tick, you don't check anything for each output individually. Instead you check the current tick count against the next interval, and if it matches, send out the next state, and increment the selection counter for another pass. This greatly reduces the amount of work that has to be done each time a bit needs to change.
I'm not sure how you'd load up the arrays while the loop is running. Could the system tolerate a glitch once in a while, when new commands come in? Alternatively, you could look for the longest time period with no transitions, and figure out a way to do the work then, while periodically updating the count. Or maybe not check the flag at all, but use another timer to measure a period in which a fixed number of interrupts should have occurred, and add that many counts when it expires. That's a bit complicated, but it ought to work.
Note that since you're counting up to 2000, the intervals[] array has to be a set of int16 variables.
Code: |
void main()
{
set_tris_b(0x00);
Initialize_RTC();
TMR2IF=0;
loop =PERIOD;
while(1) {
{
if (TMR2IF == 0) //This waits for the timer interrupt.
continue;
TMR2IF=0;
if(--loop == 0 )
{
loop = PERIOD;
portb = state[0];
interval_select = 1;
}
if (loop != intervals[interval_select]) // Ready for a transition?
continue; // No, just loop
portb = state[interval_select]; // Yes, send data to port
interval_select++; // Ready for next interval
}
|
|
|
|
|
|
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
|