Mizar32/PWM
Introduction
editPWM stands for Pulse Width Modulation, which is a way of generating square-wave output signals with different frequencies and a different proportion of each waveform cycle being a low output or a high output.
When you set up a PWM output, you specify the frequency of the output (how many times it goes high and low per second) and its duty cycle (what percentage of each cycle is spent as a high output).
The most common way that PWM outputs are used is with a fixed frequency and varying the duty cycle, thereby creating a crude kind of digital-to-analog converter to control the brightness of a light or the power delivered by a motor. If the output signal is amplified to control high-power devices, this results in a more efficient system because if an amplifier is fully on for half the time and fully off for the other half, this wastes less energy than being on at half power all the time.
Another use is to produce simple sound effects or organ notes by using a fixed duty cycle and changing the frequency to generate sounds with a square waveform. In this case, changing the duty cycle changes the tonal quality of the sound that is produced.
Lastly, the PWM system can be used to generate interrupts at fixed time intervals by enabling the PWM interrupt. This way, each time one cycle of PWM output is completed, the processor stops what it is doing, runs a special piece of code called an interrupt routine and when this is done it goes back to continue what it was doing before the interrupt occurred. Though the hardware is capable of this, PWM interrupts are not implemented in eLua yet.
Hardware view
editThe Mizar32 has seven independent PWM outputs, though on the circuit diagram only PWM0 to PWM5 are labelled as such. PWM6, if enabled, appears on the pin labelled "GPIO50".
PWM | AVR32 pin | Bus pin | eLua name | PicoLisp | Notes |
---|---|---|---|---|---|
PWM0 | PB19 | BUS4 pin 7 | pio.PB_19 |
'PB_19 |
|
PWM1 | PB20 | BUS4 pin 8 | pio.PB_20 |
'PB_20 |
Also connected to JTAG pin 7 "EVT0" |
PWM2 | PB21 | BUS1 pin 8 | pio.PB_21 |
'PB_21 |
|
PWM3 | PB22 | BUS6 pin 1 | pio.PB_22 |
'PB_22 |
|
PWM4 | PB27 | BUS6 pin 2 | pio.PB_27 |
'PB_27 |
|
PWM5 | PB28 | BUS6 pin 3 | pio.PB_28 |
'PB_28 |
|
PWM6 | PB18 | BUS5 pin 9 | pio.PB_18 |
'PB_18 |
On Mizar32 bus, the pin is called "GPIO50" |
Software view
editAlcor6L's PWM
module is used to program the PWM pins.
PWM6 is not available to users as it it used internally to provide the system timer (tmr.SYS_TIMER
in eLua, *tmr-sys-timer*
in PicoLisp).
Output frequency and duty cycle
editTwo functions are used get some PWM output on a pin.
Language | Example |
---|---|
eLua | pwm.setup(channel, frequency, duty_cycle)
|
PicoLisp | (pwm-setup channel frequency duty_cycle)
|
sets the output frequency and duty cycle, where
channel
, from 0 to 6, is the PWM channel you wish to use,
frequency
, from 1 to 1000000, determines the frequency of the output waveform in cycles per second and
duty_cycle
, a value from 0 to 100, determines what percentage of each cycle
the output value of the waveform will spend at at the high level.
Language | Example |
---|---|
eLua | pwm.start(channel)
|
PicoLisp | (pwm-start channel)
|
sets the oscillator running to produce a cyclic output waveform.
If the setup()
function is called before calling the start()
function, as we have just described, the corresponding pin on the bus becomes an output pin outputting zero volts when setup
has completed and then, when start()
is called, the output waveform goes high, remains high for the specified percentage of the cycle, then goes low for the rest of the cycle, repeating until stop()
is called for the same channel.
Alternatively, if start()
is called first, the pin remains an input until setup()
is called, at which point it becomes an output and starts producing the waveform.
When stop()
is called, the PWM output always completes the current cycle; the "stopped" state means that when the current cycle completes, it will not start a new one, so you can get exactly one complete cycle of output like this:
In eLua:
pwm.setup(0, 10, 50) pwm.start(0) pwm.stop(0)
In PicoLisp:
(pwm-setup 0 10 50) (pwm-start 0) (pwm-stop 0)
This program code will complete before the whole cycle has been output, and note that, when it is "stopped", the pin continues outputting 0 volts.
Clock frequency
editA further function
Language | Example |
---|---|
eLua | pwm.setclock(id, freq)
|
PicoLisp | (pwm-setclock id freq)
|
allows you to set the PWM clock frequency, which is a higher frequency than the output frequency and determines the time-granularity of the output waveform. In effect, the PWM hardware only decides whether to change
the output value of each PWM pin once every 1/freq
of a second, so a higher clock frequency gives better precision in time and frequency.
The PWM clock frequency also determines the lowest and highest possible frequencies of the PWM output waveforms: a lower clock frequency allows the output frequency to be lower.
Possible values for the clocking frequency on Mizar32 are from 63Hz to 16500000Hz. If you ask for values outside this range, it will set the lowest or highest of these two, accordingly. Within this range, not all frequencies are available; of the available frequencies it sets the one that is closest to what you asked for, and both setclock()
and getclock()
return an integer which is the actual frequency that has been set into the PWM clock.
Although Alcor6L's setclock()
and getclock()
functions accept a
first parameter to say which channel you want to set the clock for,
in reality the hardware only has one clock for all the channels,
so setting the clock frequency for any one channel will change the clock
frequency for all of them, as well as changing the current output frequency of any that are running. For this reason setclock()
is usually only called once before setting up the individual channels.
Range and precision of PWM output frequencies
editIn Alcor6L, the setup()
function's parameters only take notice of the whole part of numbers, ignoring any fractional part, so the lowest frequency you can ask for is one cycle per second and the maximum precision is one hertz.
Furthermore, the hardware can only generate certain frequencies (the ones that are exact divisors of the clock frequency) and setup()
returns an integer value which is the closest whole number to the actual output frequency that was set, which may be different from the frequency that you asked for.
For example, with the default clock frequency of one megahertz, you can set any integer frequency from 1 to 1037, but 1038 gives you 1039, then more and more values become more and more inaccurate until we come to the highest available frequencies of 250000Hz, 333333Hz, 500000Hz and 1000000Hz, which are the clock frequency divided by 4, 3, 2 and 1.
At the highest clock rate of 16500000Hz instead, every integer frequency from 16Hz to 4105Hz can be obtained and above this frequency a wider range of values are available, which would be more suitable for applications such as an electric organ, where accuracy in the frequency of high notes is more important than generating extremely low notes.
Using PWM pins as PIO pins
editIf a particular PWM output is not being used for PWM output, you can use it as a generic PIO pin simply by calling the functions in the pio
module on that pin.
You would also use this if you wanted a pin that you had used for PWM output to stop outputting a voltage at all: you would call
Language | Example |
---|---|
eLua | pio.pin.setdir(pio.INPUT, pio.PB_xx)
|
PicoLisp | (pio-pin-setdir *pio-input* 'PB_xx)
|
where xx
is the pin number of that PWM channel in the table above.
Example code (eLua)
edit-- Make a LED slowly fade up and down forever -- Connect a LED in series with a -- 330 ohm resistor between the PWM0 pin -- (BUS4 pin 7) and GND (BUS4 pin 1) local pwmid = 0 -- Which channel to use? local speed = 3000 -- PWM frequency in Hz local fadetime = 1 -- How many secs to fade up? local tmrid = 0 -- Which timer for the delay? local nsteps = 100 -- How many steps in the fade? -- Calculate the delay for each steps, in microseconds local delay = pwm.getclock( tmrid ) * fadetime / nsteps pwm.start( pwmid ) while true do -- Fade the LED up for duty = 0, nsteps do pwm.setup( pwmid, speed, duty ) tmr.delay( tmrid, delay ) end -- Fade the LED back down again for duty = nsteps, 0, -1 do pwm.setup( pwmid, speed, duty ) tmr.delay( tmrid, delay ) end end
Example code (PicoLisp)
edit# Make the LED slowly fade up and down forever # Connect a LED in series with a # 330 ohm resistor between PWM0 pin # (BUS4 pin 7) and GND (BUS4 pin 1) (setq pwmid 0 # Which channel to use? speed 3000 # PWM frequency in Hz fadetime 1 # How many secs to fade up? nsteps 100 ) # How many steps in the fade? (setq delay (/ (* (pwm-getclock tmrid) fadetime) nsteps ) ) (pwm-start pwmid) (loop # Fade the LED up (for duty nsteps (pwm-setup pwmid speed duty) (tmr-delay *tmr-sys-timer* delay) ) # Fade the LED down (for (i nsteps (ge0 i) (dec i)) (pwm-setup pwmid speed i) (tmr-delay *tmr-sys-timer* delay) ) )
Further reading
edit- The eLua PWM module reference manual;
- The Atmel AT32UC3A datasheet Chapter 32: Pulse Width Modulation Controller (PWM).