Page 1 of 1

Change PWM frequency on the fly

Posted: 11 July 2011, 13:06 PM
by coopecb1
According to the Library Reference, I should be able to change the PWM frequency with calls to OpenPWM() with Mode = 1:
The Phase/Frequency Correct PWM mode has a maximum frequency of one-quarter of the CPU clock frequency and may be used when the PWM frequency will be changed in the midst of PWM signal generation. Frequency changes are effected by making additional calls to OpenPWM() and the change is synchronized so that it takes effect at the beginning of a cycle.
However, when I try it, the call to OpenPWM fails and no change occurs. Adding a Call PWM() line makes no difference. A call to ClosePWM first works but that's not what I want to do:

Code: Select all

Option TargetDevice ZX24x
Sub Main()
dim pwmval as integer
dim pwmchn as byte
dim pwmfrq as single
dim stat as Boolean
pwmchn = 1
'output on Pin 26
pwmval = 50

pwmfrq = 2000.0

' this call succeeds
Call OpenPWM(pwmchn,pwmfrq,1,stat)
Debug.Print pwmfrq;" ";stat
Call PWM(pwmchn,pwmval)
Call sleep(1.0)

pwmfrq = 1000.0

' this call fails
Call OpenPWM(pwmchn,pwmfrq,1,stat)
Debug.Print pwmfrq;" ";stat
Call sleep(1.0)

Call ClosePWM(pwmchn)
Call sleep(1.0)

' this call succeeds
Call OpenPWM(pwmchn,pwmfrq,1,stat)
Debug.Print pwmfrq;" ";stat
Call PWM(pwmchn,pwmval)

End Sub
Debug output:
ZBasic v3.3.1
2000.0 True
1000.0 False
1000.0 True


I'm using an oscilloscope connected to pin 26 to verify the result.

Re: Change PWM frequency on the fly

Posted: 11 July 2011, 16:42 PM
by dkinzer
coopecb1 wrote:However, when I try it, the call to OpenPWM fails and no change occurs.
This occurs because of an error in the ZBasic Library implementation for xmega-based devices. Normally, calls to OpenPWM() don't check to see if the timer is busy but you can force it to check for the timer being busy (and failing if it is) by adding &H80 to the mode value. The xmega implementation has this backward, requiring &H80 in the mode value to avoid checking the timer busy flag.

We have fixed this internally and an update will be released some time in the future. In the mean time, you can just add &H80 to the mode value to have skip the timer busy check thus allowing the second call to OpenPWM() to succeed. (You should add a note to your code indicating that this is a temporary workaround.)

As an aside, are you aware that changing the frequency of an active PWM channel will also change the duty cycle? In your example code the duty cycle will be 25% when the frequency changes to 1KHz if it is 50% at 2KHz.

Re: Change PWM frequency on the fly

Posted: 12 July 2011, 6:59 AM
by coopecb1
dkinzer wrote:adding &H80 to the mode value
Using &H81 as the mode value works. Thanks for the help.
dkinzer wrote:As an aside, are you aware that changing the frequency of an active PWM channel will also change the duty cycle? In your example code the duty cycle will be 25% when the frequency changes to 1KHz if it is 50% at 2KHz.
So it does. This could be exploited as a "feature", in that this implements pulse-frequency modulation (PFM). PFM is useful in some types of switching power supplies and communications.

Apparently the "on time" is not recalculated. Adding a call to PWM forces recalculation, seamlessly as far as I can determine from my oscilloscope.

Re: Change PWM frequency on the fly

Posted: 12 July 2011, 8:17 AM
by dkinzer
coopecb1 wrote:Apparently the "on time" is not recalculated.
That is correct. A given timer supports up to four different PWM outputs all sharing the same base frequency. The period of the base frequency is set by one register (together with the prescaler) and the duty cycle is set by a separate register (one per output) as a percentage of the period-setting value.
coopecb1 wrote:Adding a call to PWM forces recalculation, seamlessly as far as I can determine from my oscilloscope.
Perhaps. You'll need a logic analyzer or storage scope to observe the transition. On my logic analyzer I see a pulses of 250uS, 375uS and 500uS around the transition.

In any event, if your application has multiple tasks you should probably disable interrupts during the changeover to avoid having a task switch in the middle. Even if you don't have multiple tasks this is probably a good idea because handling of RTC and software UART interrupts could add latency between changing the frequency and adjusting the duty cycle.

Code: Select all

Sub Main()
    Dim pwmval as integer
    Dim pwmchn as byte
    Dim pwmfrq as single
    Dim stat as Boolean

    pwmchn = 1
    'output on Pin 26
    pwmval = 50

    pwmfrq = 2000.0

    Call OpenPWM(pwmchn, pwmfrq, 1, stat)
    Debug.Print pwmfrq;" ";stat
    Call PWM(pwmchn, pwmval)
    Call sleep(1.0)

	' disable interrupts to avoid a task switch while PWM frequency is changed
    pwmfrq = 1000.0
    Dim intStat as Byte
    intStat = DisableInt()
    Call OpenPWM(pwmchn, pwmfrq, &H81, stat)
    Call PWM(pwmchn, pwmval)
    Call EnableInt(intStat)
    Debug.Print pwmfrq;" ";stat
End Sub