Arduino Fast PWM: Faster

Although you can change the Arduino runtime’s default PWM clock prescaler, as seen there, the default Phase-correct PWM might not produce the right type of output for the rest of your project’s circuitry.

I needed a fixed-width pulse to drive current into a transformer primary winding, with a variable duty cycle (hence, period) to set the power supply’s output voltage. The simplest solution is Fast PWM mode: the output goes high when the timer resets to zero, goes low when the timer reaches the value setting the pulse width, and remains low until the timer reaches the value determining the PWM period.

The best fit for those requirements is Fast PWM Mode 14, which stores the PWM period in ICRx and the PWM pulse width in OCRxA. See page 133 of the Fine Manual for details on the WGMx3:0 Waveform Generation Mode bits.

I needed a 50 μs pulse width, which sets the upper limit on the timer clock period. Given the Diecimila’s 16 MHz clock, the timer prescaler can produce these ticks:

  • 1/1 = 62.5 ns
  • 1/8 = 500 ns
  • 1/64 = 4 μs
  • 1/256 = 16 μs
  • 1/1024 = 64 μs

So anything smaller than 1/1024 would work. For example, three ticks at 1/256 works out to 48 μs, which is close enough for my purposes. A 1/8 prescaler produces an exact match at 100 ticks and gives a nice half-microsecond resolution for pulse width adjustments.

The overall PWM period can vary from 200 μs to 10 ms, which sets the lower limit on the tick rate. The timer is 16 bits wide: 65535 counts must take more than 10 ms. The 1/1 prescaler is too fast at 4 ms, but the 1/8 prescaler runs for 32 ms.

So I selected the 1/8 prescaler. The table on page 134 gives the CSx2:0 Clock-Select mode bits.

Define the relevant values at the top of the program (uh, sketch)

#define TIMER1_PRESCALE 8	// clock prescaler value
#define TCCR1B_CS20 0x02	// CS2:0 bits = prescaler selection

#define BOOST_PERIOD_DEFAULT	(microsecondsToClockCycles(2500) / TIMER1_PRESCALE)
#define BOOST_ON_DEFAULT	(microsecondsToClockCycles(50) / TIMER1_PRESCALE)

#define BOOST_PERIOD_MIN	(microsecondsToClockCycles(200) / TIMER1_PRESCALE)
#define BOOST_PERIOD_MAX	(microsecondsToClockCycles(10000) / TIMER1_PRESCALE)

The microsecondsToClockCycles() conversion comes from the Arduino headers; just use it in your code and it works. It’ll give you the right answer for 8 MHz units, too, but you must manually adjust the timer prescaler setting; that could be automated with some extra effort.

Then, in the setup() routine, bash the timer into its new mode

analogWrite(PIN_BOOSTER,1);	// let Arduino setup do its thing
TCCR1B = 0x00;			// stop Timer1 clock for register updates

TCCR1A = 0x82;			// Clear OC1A on match, Fast PWM Mode: lower WGM1x = 14
ICR1 = BOOST_PERIOD_DEFAULT;	// drive PWM period
OCR1A = BOOST_ON_DEFAULT;	// ON duration = drive pulse width
TCNT1 = BOOST_ON_DEFAULT - 1;	// force immediate OCR1A compare on next tick
TCCR1B = 0x18 | TCCR1B_CS20;	// upper WGM1x = 14, Clock Sel = prescaler, start running

The Arduino analogWrite() function does all the heavy lifting to set the PWM machinery for normal use, followed by the tweakage for my purposes. All this happens so fast that the first normal PWM pulse would still be in progress, but turning the PWM timer clock off is a nice gesture anyway. Forcing a compare on the first timer tick means the first pulse may be a runt, but that’s OK: the rest will be just fine.

What you don’t want is a booster transistor drive output stuck-at-HIGH for very long, as that will saturate the transformer core and put a dead short across the power supply: not a good state to be in. Fortunately, the ATmega168 wakes up with all its pins set as inputs until the firmware reconfigures them, so the booster transistor stays off.

The PWM machinery is now producing an output set to the default values. In the loop() routine, you can adjust the timer period as needed

noInterrupts();			// no distractions for a moment
TCCR1B &= 0xf8;			// stop the timer - OC1A = booster may be active now

TCNT1 = BOOST_ON_DEFAULT - 1;	// force immediate OCR1A compare on next tick
ICR1 = BasePeriod;		// set new PWM period

TCCR1B |= TCCR1B_CS20;		// start the timer with proper prescaler value
interrupts();			// allow distractions again

The ATmega168 hardware automagically handles the process of updating a 16-bit register from two 8-bit halves (see page 111 in the Manual), but you must ensure nobody else messes with the step-by-step process. I don’t know if the compiler turns off interrupts around the loads & stores, but this makes sure it works.

Once again, setting the TCNTx register to force a compare on the next timer tick will cause a runt output pulse, but that’s better than a stuck-HIGH output lasting an entire PWM period. You can get fancier, but in my application this was just fine.

You can update the PWM pulse width, too, using much the same hocus-pocus.

And that’s all there is to it!

Memo to Self: always let the Arduino runtime do its standard setup!