Returning to my exploration of the Arduino Nano Every, this post examines the use of timers and how they are used for pulse width modulation (PWM) applications.
For a detailed breakdown of the use of Timers (and the source for much of the information in this post, alongside the official Microchip documentation for the ATmega4809), see:
As I mentioned in my introductory article, the Arduino Nano Every works hard to be “pin compatible” with the original Arduino Nano. One of the reasons for the odd port mapping described is down to which pins need to support PWM. The Nano Every supports PWM on the following pins:
- D3 – PF5
- D5 – PB2
- D6 – PF4
- D9 – PB9
- D10 – PB1
Unlike the Nano, there is no PWM possible on D11. Part of the reason for the odd mapping to ATmega4809 ports and IO pins is that PWM is implemented using hardware timers and there are limitations on which pins can be used with the timers.
For the ATmega328 as used on the Nano, there are three timers which can be configured each to provide two outputs (OC0A/B, OC1A/B, OC2A/B). These are mapped onto the six PWM pins for an Uno or Nano.
The ATmega4809 has five timers in total, one “type A” which has three compare channels, and four “type B” timers with one channel channel each. The Arduino environment uses these as follows:
- TCA: 3 compare channels:
- 0 – PWM pin 9
- 1 – PWM pin 10
- 2 – PWM pin 5
- TCB0: PWM pin 6
- TCB1: PWM pin 3, also used for tone() function
- TCB2: Used with the servo library
- TCB3: Used for millis(), micros(), delay()
Note that there are many more options for the ATmega4809 itself but these are not broken out in the Arduino Nano Every.
Arduino Nano Every Timer Configuration
Which output pins are connected to which timer channel is controlled by the ATmega4809’s PORTMUX registers (see chapter 15 in the datasheet). There are six options for the three channels of TCA: they can be mapped onto pins [5:0] for each of PORTA to PORTF depending on the setting of the TCAROUTEA register. To use all six pins requires the use of “split mode” for TCA, but only pins [2:0] of PORTB are actually broken out for the Nano Every anyway, so “non-split mode” is used which only applies to bits [2:0]. There are two options for each of the single channels of TCB0, 1, 2 and 3, a default and alternative mapping, depending on the settings of the TCBROUTEA register. The Arduino library has TCA (“non-split” mode) mapped onto PORTB (pins 5, 9, 10); TCB0 to PF4 (pin 6); and TCB1 to PF5 (pin 3). None of the other output pins options from the ATmega4809 are routed out to physical pins on the Arduino Every.
A major limitation with the 4809’s timer setup is that there is only one prescalar setting and it is shared across all timers. The Arduino library configures the TCA CTRLA “CLKSEL” bits to /64 in the setup_timers() function from the relevant variant.c file:
// Use DIV64 prescaler (giving 250kHz clock), enable TCA timer
TCA0.SINGLE.CTRLA = (TCA_SINGLE_CLKSEL_DIV64_gc) | (TCA_SINGLE_ENABLE_bm);
This means that TCA is running at 250kHz, for the default system clock of 16MHz. Then in Wiring.c it sets the system TCBn timer to use the same prescalar as TCA in the CTRLA register:
/* Clock selection -> same as TCA (F_CPU/64 -- 250kHz) */
_timer->CTRLA = TCB_CLKSEL_CLKTCA_gc;
The “_timer” pointer will be set to the TCB3 due to the Nano Every having MILLIS_USE_TIMERB3 defined in the board.txt file configuration, thus placing the system timer for millis(), micros() and delay() on TCB3.
The tone() function will set up TCB1 depending on the frequency of tone required to either the TCA prescalar value (250kHz) or the system clock value (16MHz):
if(prescaler_needed){
_timer->CTRLA = TCB_CLKSEL_CLKTCA_gc;
} else {
_timer->CTRLA = TCB_CLKSEL_CLKDIV1_gc;
}
The “prescalar_needed” flag is calculated based on the requested frequency for the tone to be played.
In terms of PWM, the counters are all initialised (again in the setup_timers() function in variant.c), alongside the prescalar values, to give a base PWM frequency of 977Hz. The timers are configured to run in 8-bit mode, with a period of 255 (via the PWM_TIMER_PERIOD define in timers.h). In fact this is done for all timers apart from TCB3. Incidentally this is where the routing of timers to IO pins is setup too.
Why 977Hz? Well, 16Mhz / 64 = 250kHz as already mentioned. With a counter (period) of 255, the counters will count from 0 to 255 at 250kHz and then reset, so 250kHz / 256 = 976.5625, so this is the default “tick” for all timers apart from TCB3. The tone() function will mess with this for TCB1, which is why PWM on pin 3 will not be available if using tone().
Aside: Nano Every Main Clock Configuration
For completeness, this is a brief diversion into how the main system clock is configured. This all happens in Wiring.c which is responsible for the configuration of the timers for millis(), micros() and delay() and the implementation of these functions.
There are two main clock control registers (see chapter 10 in the ATmega4809 datasheet) that we need to worry about: MCLKCTRLA and MCLKCTRLB. There are others, but they aren’t of interest at the moment.
MCLKCTRLA contains the main system CLKSEL configuration, which is set to 0 by default, meaning the internal 16/20 MHz oscillator, whose frequency is determined by a fuse setting (OSCCFG – see section 7.8.2.3 in the datasheet). The assumed default of the Arduino environment is 16MHz to maintain compatibility with the original Arduino Nano (this is all configured in the board.txt file, via the .build.f_cpu and .bootloader.OSCCFG settings – Tom Almy’s book has details of how to run it at 20MHz).
MCLKCTRLB contains the configuration for the system prescalar. In the init() function is Wiring.c it is set according to the processor speed (as specified by F_CPU). As this is 16MHz for the Arduino environment for the Nano Every, the following code is used:
#elif (F_CPU == 16000000)
cpu_freq = 16000000;
/* No division on clock */
_PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00);
This disables the pre-scalar at the system level. The init() function eventually calls the setup_timers() function that is implemented in Variant.c (as mentioned above).
Using PWM for Audio
There are several common uses for PWM on an Arduino: controlling servos, controlling LEDs, or for audio output. The default configuration as described above, and implemented through the analogWrite() Arduino function is fine for servos and LEDs, but not for audio. To get anywhere near half-decent PWM audio output, there are usually two timer-related functions required:
- Getting a hardware timer to output a voltage level using PWM in place of having an actual digital to analog converter (basically what happens with servos and LEDs, but tailored to audio frequencies).
- Configuring a periodic timer to “play samples” by changing the PWM duty cycle value to be sent at the fixed “sample rate”.
For a detailed discussion of using PWM on an ATmega328 based Arduino, see “Secrets of Arduino PWM”. For a detailed discussion of the use of PWM for audio, see “Open Music Labs: PWM DAC”.
Typically on an ATmega328 based system, a sample rate is chosen (e.g. 16kHz or 32kHz) that relates to the size and pitch of samples to be played (for example, from a “wavetable” at a specific frequency). Also, for the actual PWM output itself, a core PWM frequency is required that is ideally above human hearing, so again something like 32kHz is ideal.
So, we need to configure a timer, with a PWM pin, able to run at the appropriate PWM base frequency, but with a PWM mode that allows a duty cycle to be configurable from a timer-driven interrupt routine itself running at a fixed playback rate.
If we choose the type A timer, that would give us a choice of three potential pins for output. But it also means that other PWM applications on those pins are not possible as they are all driven from the same timer. However type B timers only have a single 8-bit PWM mode.
Timer B for Audio PWM
In terms of a fixed PWM base frequency, the default is too slow – 977Hz will just not do it. So, the only option that doesn’t involve messing with the system clock is to override the CLKSEL mode for one of the TCB timers and set it to the system clock or the system clock / 2. This means it will run at either 16MHz or 8MHz rather than 250kHz. So then it is a question of setting up the compare registers to keep the PWM ticking over at a sensible speed. The default mode (again) is as an 8-bit timer, so for it to count the full range of 0 to 255 means the PWM frequency will either be 16MHz/256 = 62.5kHz or 8MHz/256 = 31.25kHz.
Either is fine as the starting point, but exactly how many times the “PWM” triggers will normally depend on the counter’s mode. Recall that an ATmega328 has several modes that support a PWM output. However, whilst the ATmega4809 type A timer has several PWM modes, a type B timer, has just one PWM mode – “8-bit PWM Mode” (see section 21.3.3.1.8 in the datasheet).
In 8-bit PWM mode the 16-bit compare register is treated as two 8-bit compare registers – CCMPH and CCMPL. The least-significant 8-bits (CCMPL) determines the period for the PWM cycle. The most-significant 8-bits (CCMPH) determines the duty cycle. From the datasheet:
“The period (T) is controlled by CCMPL, while CCMPH controls the duty cycle of the waveform. The counter will conitnuously count from BOTTOM to CCMPL, and the output will be set at BOTTOM and cleared when the counter reaches CCMPH.”
So by way of example, to configure Timer B1 (the one “reserved” for the arduino tone() function) for a base frequency PWM on pin D3, we have to do the following.
PORTMUX.TCBROUTEA |= PORTMUX_TCB1_bm; // Enable "alternative" pin output (PF5=D3) for TCB1
TCB1.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // SysClk and enabled
TCB1.CTRLB = TCB_CNTMODE_PWM8_gc | TCB_CCMPEN_bm; // 8-bit PWM and output enabled
TCB1.CCMPL = 255; // Default value TOP=255, duty cycle = 25%
TCB1.CCMPH = 64;
This outputs a nice 25% PWM (64 is 25% of 256) signal with a base frequency of 62.5kHz which you can see if you hook up a scope or logic analyser to D3.
So what about a timer routine to feed the PWM hardware some actual sample values? Well the simplest option would be to hang an interrupt off the same timer to trigger on each period.
On an ATmega328 we usually have several options: interrupt on overflow (i.e. counter wrap around), interrupt on compare match, and others. Unfortunately there is only one interrupt on a type B timer on the ATmega4809, the “capture event interrupt”. The datasheet says it will trigger “when the counter reaches CCML” in 8-bit PWM mode, which means it triggers on every wrap-around of the counter. So that still gives us the possibility of a 62.5kHz “tick”, so that should do the trick.
TCB1.INTCTRL |= TCB_CAPT_bm;
ISR(TCB1_INT_vect) {
// Should run at the same period as the PWM base frequency
TCB1.INTFLAGS = TCB_CAPT_bm; // Clear the interrupt flag
// Process the sample...
TCB1.CCMPL = TOP;
TCB1.CCMPH = NEW_SAMPLE; // In range 0 to TOP
}
For some reason, if I only wrote to CCMPH then it seemed to mess with the CCMPL value too, so I’ve found it best to update both in the interrupt routine.
One final note. I won’t go into the details here, but in general terms, it makes things a lot easier (when using an 8-bit microcontroller anyway) to have your sample rate a multiple that makes calculation easier. So for a typical wave-table synthesis application, where you might have 256 samples in your table you need to output in each period for your note, then if you choose a sample rate that is a multiple of 256, some optimisations are possible that means that you don’t need to get into floating point arithmetic or weird fractions of updates for your index into your table.
That is certainly possible here too. We know what with our default TOP = 255 value, we can get a sample rate of 62.5kHz. So what would it take to make it the more calculating friendly 65536 Hz? Well we know the PWM frequency is given by (21.3.3.1.8 in the data sheet: “CCMPL+1 is the period of the output pulse”):
PWM Frequency = System Clock / (TOP value + 1)
So we can also say that:
TOP value + 1 = System Clock / PWM Frequency
So substituting for 65536 Hz and a 16MHz system clock gives us:
TOP value + 1 = 16 MHz / 65536 = 244.14
So choosing a TOP value of 243 should give us our required 65536 Hz sample rate. The only complication now is that our samples have to be scaled to fit in a 0 to 243 range rather than a more typical 0 to 255 range. This can be done in two ways – either simply divide by 2 if performance is critical, giving each sample a 0 to 127 value; or multiply the same by 244/256 which may involve floating or fixed point arithmetic if you don’t want to lose accuracy (although divide by 256 is the same as shift right by 8 places).
So before I finish, how does using 65536Hz help? Well in a typical direct digital synthesis application, using a 256 byte wavetable, we need to maintain an “accumulator” to work out which sample to play from the wavetable at any one time. If the index moves on 1 step at a time, then we know that frequency of the sounding note will be:
Frequency = Sample Rate / 256 = 256 Hz
And we also know that skipping a number (so the step is 2) will double the frequency, and “playing” each sample twice will half the frequency. In general, it can be shown that the index into the wavetable is determined using the following:
Increment = frequency * 256 / Sample Rate
But to keep accuracy, the increment is often calculated using “fixed point arithmetic”. This basically means, keeping it 256 times bigger than it needs to be, then diving by 256 when it is time to add it on to the accumulator. So the actual (256 times bigger) increment is now:
Incrementx256 = frequency * 256 * 256 / Sample Rate
You can perhaps now see why having a sample rate that is a power of two is advantageous! To calculate the index to use, the simplest way to divide by 256 is to shift right by 8 bits as follows:
Increment = Increment256 >> 8
Timer A for Audio PWM
The ATmega4809 type A timer has more useful options for audio PWM but there is a caveat. It is also used to define the Arduino’s system “tick” which drives the functions: millis(), delay(), micros(). Messing with this timer will change the functionality of these timer-related functions too as well as any libraries that use them.
So is this an issue? Yes for audio PWM.
The Arduino system clock runs at 16MHz (it could go up to 20MHz with the ATmega4809, but the Arduino environment keeps it at 16MHz for backwards compatibility) and the system “tick” is configured to use TCA with a prescalar setting of /64 giving a PWM frequency of 977Hz (as described previously) which is nowhere near fast enough for an audio PWM output.
Options:
- Change the Arduino core to give more clock resolution: Tom Almy has a chapter on “Better Timer Setup” in his book “Far Inside the Arduino: Nano Every Supplement”.
- Use a different Arduino core: MegaCoreX has an option to set the PWM frequency for example.
- Change the “master configuration” for Timer A to be more audio friendly.
- There might be an alternative master clock configuration, maybe involving different external crystals or something… I’ve not looked into it…
- Go back to using the type B timers!
I’m attempting to keep to solutions that don’t involve changing the Arduino core, but the price is that I end up changing the core’s functionality, so I’m going to describe how to reconfigure TCA for use with audio and talk through the implications.
Much of the previous discussion for type B timers applies here too. However there are now several different PWM modes that we get to choose from:
- “Single-slope” PWM, with overflow triggered at the BOTTOM of the timer count.
- “Dual-slope” PWM, with overflow triggered at TOP.
- “Dual-slope” PWM, with overflow triggered at TOP and BOTTOM.
- “Dual-slope” PWM, with overflow triggered at BOTTOM.
All PWM modes are configured using the PER (period) register and there are three compare registers that can be used: CMP0, CMP1, CMP2. These map onto the three output pins for the port as specified by TCBROUTEA – which is PORTB by default for the Nano Every.
There is also a “split mode” operation which turns the timer into two 8-bit timers, each with their own three CMPn registers and each able to drive three output waveforms on output pins, so TCA0 could in principle drive 6 PWM pins. Split mode is only possible with Single-slope PWM. But the Arduino Nano Every only has three output pins broken out, so there are no advantages to split mode in this case.
Starting with dual-slope operation, the period calculation is as follows.
The frequency for dual-slope operation is given by (20.3.3.4.4):
freq = Sys Clock / (2 . Prescalar . PER)
So for a PWM frequency of 32768Hz and a prescalar of 1:
PER = Sys Clock / (2 x freq) = 16MHz / (2 x 32768) = 244
So for an 8-bit PWM value of 0..255 then the calculation referred to above is required to scale it to a value in the range 0..243.
Consequently, I initialise the TCA0 timer for use with audio as follows.
pinMode(9, OUTPUT);
PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTB_gc; // Enable "alternative" pin output PORTB[5:0] for TCA0
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; // SysClk and enabled
TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_DSTOP_gc;
TCA0.SINGLE.CTRLD = 0; // Normal (Single) mode (not split)
TCA0.SINGLE.PER = 244; // 32768 Hz "tick"
TCA0.SINGLE.CMP0 = 61; // 25% duty cycle
TCA0.SINGLE.INTCTRL |= TCA_SINGLE_OVF_bm; // Turn on interrupts
ISR(TCA0_OVF_vect) {
TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // Clear the interrupt flag
TCA0.SINGLE.CMP0BUF = pwmval; // Write out the PWM value (0 to 243)
}
This configures TCA0 for use with PORTB (which should already be the default anyway); sets the prescalar to 1, so TCA0 runs as system clock speed (16MHz); configures dual-slope PWM mode with overflow at TOP running at 32768Hz; and enables the CMP0 output for PB0 (D9).
Notes on the code:
- TCA0 is now running 64 times faster than the default, so millis(), delay(), micros() will all be running 64 times faster than before!
- The overflow interrupt routine has to clear the OVF interrupt in code.
- The pwmval will need scaling to 0..243 as described above.
- The PWM frequency (32768Hz) is also the sample update rate for the PWM output.
- Only CMP0 is being used, given a PWM audio output on PB0 which is mapped to Arduino D9.
- The PWM output is set using the CMP0 register buffer. This means it will only actually update when the counter reaches BOTTOM. This prevents out of phase updates.
- Don’t forget to set D9 as an OUTPUT pin! I’ve forgotten to do this several times and it always takes a while to work out why I’m getting no PWM output!
Here is the corresponding configuration for single-slope operation:
The frequency for single-slope operation is given by (20.3.3.4.3):
freq = Sys Clock / (Prescalar . (PER + 1))
So for a PWM frequency of 32768Hz and a prescalar of 1:
PER + 1 = Sys Clock / freq = 16MHz / 32768 = 488
PER = 488 – 1 = 487
So for an 8-bit PWM value of 0..255 then the calculation referred to above is required to scale it to a value in the range 0..487. But it could be used “as is” but it will just generate a slightly quieter signal if it isn’t using the full PWM resolution.
For this operation, I initialise the TCA0 timer for use with audio as follows.
pinMode(9, OUTPUT);
PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTB_gc; // Enable "alternative" pin output PORTB[5:0] for TCA0
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; // SysClk and enabled
TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
TCA0.SINGLE.CTRLD = 0; // Normal (Single) mode (not split)
TCA0.SINGLE.PER = 487; // 32768 Hz "tick"
TCA0.SINGLE.CMP0 = 122; // 25% duty cycle
TCA0.SINGLE.INTCTRL |= TCA_SINGLE_OVF_bm; // Turn on interrupts
ISR(TCA0_OVF_vect) {
TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // Clear the interrupt flag
TCA0.SINGLE.CMP0BUF = pwmval; // Write out the PWM value (0 to 487)
}
This configures TCA0 for use with PORTB; sets the prescalar to 1, so TCA0 runs as system clock speed (16MHz); configures single-slope PWM mode running at 32768Hz; and enables the CMP0 output for PB0 (D9).
All the same notes from above apply but naturally the PWM value now can be in the range 0..487 if we want to utilise the full range available.
Single vs Dual-slope Operation:
- Single: allows full 8-bit (“almost 9-bit”) PWM resolution. Dual: requires scaling down to 0-243.
- Dual: gives a “phase correct” update. Single: may get anomalies when updating.
So single gives more PWM resolution but at the cost of loss of phase accuracy. This probably isn’t a major issue for audio applications (is it? I don’t know!? Let me know :)).
Conclusion
It turns out that once the basic details have been worked through, it isn’t so bad creating audio-friendly PWM on an Arduino Nano Every. The choice of output pin is not as straight forward as for the original Nano. The option that has the least number of side effects and limitations (in terms of limited how other pins can be used) is to use one of the type B timers.
But TCB3 is used for the system timer, and TCB2 is used for the servo library. These also don’t have output pins available on the board, so I opted to use TCB1 as used with the tone() function. The thinking being that if you are using PWM audio, you probably aren’t interested in the tone() function anymore! But this means the audio output is limited to D3. The principles described here should apply equally well to TCB0 which would also give audio output on D6.
Using TCA0 opens up the use of D5, D9 and D10 for PWM audio output, but will interfere with the system clock and require adjustments to any use of millis(), delay() or micros().
If you have an alternative ATmega4809 microcontroller board then there are almost certainly more options in which case it would be worth investigating the unofficial MegaCoreX support, but pretty much all the theory described in this post will still apply.
At some point I’d like to investigate it myself too.
Kevin
You must be logged in to post a comment.