Example circuit for the Mizar32 PWM LED fader

Introduction

edit

PWM 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

edit

The 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".

Bus pins
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

edit

Alcor6L'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

edit

Two 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

edit

A 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

edit

In 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

edit

If 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