On Thu, 20 Dec 2001, Martin Peach wrote: > ----- Original Message ----- > From: "Martin Buehler" > To: > Sent: Thursday, December 20, 2001 9:20 AM > Subject: [PIC]: slow pwm > > > > i need to generate a pwm signal with variable duty cycle, with a period of > > about 1s. > > as far as i understand, with the 16f872 it is possible to get a period of > > up to 65ms, when running the pic at 4mhz. > > what is the best way to get a slower period? > > Try this: > Set up Timer0 with a period equal to or better than the resolution you > need and count clock cycles, and a counter in software that consists of as > many registers as you need to count one second worth of overflows. Then get > the timer to generate an interrupt when it overflows. > Write an interrupt service routine that reloads the timer and increments > the software counter. The ISR also sets/resets the port pin after comparing > the value of the counter with the trigger value, and resets the counter at > the end of the PWM period. > In the main loop outside of interrupt space you can set the trigger > point for the pulse by adjusting the trigger value of the software counter. I agree. To get a low frequency PWM on a PIC you either have to lower the oscillator frequency or resort to a software algorithm as suggested by Martin. You can check out some software techniques here: http://www.dattalo.com/technical/theory/pwm.html As a slight variation of Martin's suggestion, I recommend that TMR0 be configured to generate an interrupt at a constant rate and that the software algorithm be tweaked to determine on which interrupt the I/O pin changes states. For example, suppose you decide that you want a 1Hz carrier frequency and an 8-bit PWM. I'd program TMR0 to interrupt 256 times a second and then use software to decide on which one of the 256 edges the I/O changes states. There are two benefits of this approach. First, the isochronous programming is decoupled from the PWM programming. Second, if you wish to implement several PWMs simultaneously then it's trivial to do so. The drawback with this approach is that for a single PWM it's possible to achieve a MUCH higher precision using an approach like the one Martin suggests. A multiple output software PWM can be found here: http://www.dattalo.com/technical/software/pic/pwm8.asm This code be put directly in the interrupt routine. ------ One subtle detail that I glossed over is how you program an interrupt routine to interrupt exactly 256 times per second. If you're running off a 20Mhz crystal (or 20/4 = 5Meg instructions per second), there are 19531.25 instruction cycles between each 1/256'th step (i.e. 5e6/256 = 19531.25). How do you generate an interrupt every 19531.25 cycles? Well you can't exactly (without resorting to extra hardware). However, you can get darned close using phase accumulators. For example, suppose you program TMR0 to generate an interrupt every 256 cycles. After 19531.25/256 = 76.294 interrupts, you'd run through the PWM routine. E.g. Interrupt_routine: save w,status, etc. if tmr0 interrupt: clear pending tmr0 interrupt phase_accumulator += DELTA_PHASE if phase_accumulator rolled over then do a PWM step; . . . restore w,status, etc retfie Now, if you choose to implement an all-software phase accumulator then you'd want to choose a 16-bit wide one. The value for DELTA_PHASE is determined by: DELTA_PHASE = size of phase accumulator / roll over rate = 2^16 / ( 5e6/2^16) = 2^32 / 5e6 = 858.993 ~= 859 = 0x35b Look what happens when you repeatedly add 859 to a counter: iteration phase_accumulator ------------------------------ 0 0x0000 1 0x035b 2 0x06b6 3 0x0a11 74 0xf84e 75 0xfba9 76 0xff04 77 0x025f <- rolled over. In summary, a 16-bit phase accumulator with a delta phase of 859 counts rolls over after about 76 iterations. If this is part of a TMR0 interrupt routine that is programmed to interrupt every 256 cycles, then every ~76 iterations we'd run through the software pwm and will run through it approximately 256 times per second if we're using a 20Mhz oscillator. You probably have realized that there's jitter introduced by this method. True, but it's relatively small. However, it's possible to reduce the jitter to nearly zero by tinkering with TMRO. In other words, you can make TMR0 be part of your phase accumulator. In the simplest case, you'd let TMR0 roll over 3 times and on the 4'th time you'd make it count to only 5b cycles. This will give you 35b cycles after 4 interrupts. A more practical approach is to have a phase accumulator for TMR0. For example, if at every tmr0 interrupt we program the next interrupt to occur 35b/4 ~= 0xd6 cycles later then we'd accumulate 358 cycles every 4 interrupts. If we increase this to a 16-bit accumulator then we can reduce this error to almost zero. Scott -- http://www.piclist.com hint: The PICList is archived three different ways. See http://www.piclist.com/#archives for details.