Reverse Engineering the Arduino Nano Every to Change PWM Frequency

Which Arduino Nano Every I/O pins may be used for PWM outputs?

The Arduino Nano Every is designed for five PWM outputs using pins D3, D5, D6, D9, and D10. The PWM signals are developed using the analogWrite( ) function. For example, the command analogWrite(3, 64); configures Arduino digital output pin D3 to produce a PWM signal with a duty cycle of 25%.

This table identifies the ATmega4809 physical ports. The Arduino pin designation, and associated hardware timer will be important as we continue our reverse engineering process:

  • PB0 (D9) - TCA
  • PB1 (D10) - TCA
  • PB2 (D5) - TCA
  • PF4 (D6) - TCB
  • PF5 (D3) - TCB

This engineering brief is written assuming you have some understanding of the Arduino PWM and are looking for a way to change the default PWM frequency. For additional information about Arduino and Duty cycle please see this Arduino tutorial.

Figure 1: The Digilent Analog Discovery, operating as a logic analyzer, is used to simultaneously measure all of the Arduino Nano Every’s PWM pins.

What is the default PWM frequency for the Arduino Nano Every?

The Arduino Nano Every has a default PWM frequency of 976.5625 Hz. This may seem like an oddly specific frequency, but it is derived directly from the 16 MHz oscillator:

16 MHz / 64 / 256 = 976.5625 Hz

Where the divide by 64 is a configuration setting for the TCA0_CTRLA register and the divide by 256 is a natural division for an 8-bit PWM. In this situation, the PWM subdivides the 62,500 Hz clock (16 MHz / 64) into 256 time slots.

Why would you want to change the PWM frequency?

The default Arduino frequency of 976.5625 Hz is suitable for many simple projects such as dimming LEDs and controlling the speed of small motors. Yet, the frequency can cause an objectionable squeal when used to control devices such as high-power motors. Operating at frequencies above the limits of human hearing (about 20 kHz) eliminates the noise pollution.

The frequency may not be compatible with other systems. For example, in a future article I will demonstrate how to use Arduino to control a variable speed fan designed for 25 kHz PWM control. The 976.5625 Hz PWM is a long way from 25 kHz. As will be demonstrated in this article, we can produce a 32 kHz PWM. This is much closer to the desired 25 kHz but still some way off.

Another potential area of Arduino PWM exploration is switched mode power supplies. While the 976.5625 Hz signal may work, there is objectionable noise coupled with high expense and low efficiency. These switching power supplies typically operate with high frequencies to reduce the size of the filter components.

How can we reverse engineer the Arduino software?

Let’s begin by recognizing the wonderful contributions of Arduino and the associated community. Together, they have constructed a launching point for an entire generation of learners. In fact, I challenge you to find an electrical engineer, technologist, or technician who has not programmed the Arduino.

A large part of Arduino’s success is the hardware abstraction. The team has taken difficult tasks such as PWM and boiled it down to a simple analogWrite( ) function. That’s good for the learner, but not so good as we attempt to reverse engineer the PWM functions of the Arduino Nano Every.

Assuming we wish to continue using the Arduino abstraction, we could dig into the Arduino code base, or we could configure the microcontroller and pull values from the associated registers. While it’s a good academic exercise to explore the code, it’s overwhelming as the Arduino code contains multiple levels of abstraction allowing the same commands such as analogWrite( ) to operate on all members of the Arduino product line. On the other hand, exploring the ATmega4809 datasheet is no trivial task.

In all cases, we need to proceed carefully because we run the risk of damaging related Arduino functionality. For example, we could easily change the PWM frequency by changing the CTRLA register within the TCA (16-bit Timer/Counter Type A) peripheral. That solved one problem but causes another by breaking other Arduino functions. For example, we could increase the PWM frequency by a factor of 32, unfortunately the time tracked by millis( ) would change by a factor of 32. At best, this is an inconvenience. It’s also an open invitation for bugs as named function do not do what they are intended to do.

How did we determine the division by 64?

A full explanation requires a deep dive into the ATmega4809 datasheet with a detailed study of the underlying timer peripherals. To keep this article short, we will identify a few signals and describe how they impact the PWM.

We start with the 16 MHz oscillator which is the principal clock in the microcontroller. The peripheral clock (CLK_PER) is the next chain in the link. This is derived from the 16 MHz oscillator and used as a reference for all peripherals.

The ATmega4809 follows the tradition of implementing a prescaler as shown in Figure 2. This allows digital clock signals to be digitally divided by a factor of 2 up to 64. As stated before, we could dig through the Arduino code to determine the settings, or we could issue the following line of Arduino code to determine the actual setting in the operational microcontroller.

Serial.print("MCLKCTRLA = "); Serial.println(CLKCTRL.MCLKCTRLB, BIN); 

Figure 2: The 16 MHz clock may be divided by a prescaler to produce the peripheral clock.

The result comes back as 0. We then look up the register in the datasheet to determine the corresponding setting. The most important bit is bit 0 which tells us that the prescaler is disabled consequently CLK_PER = CLK_MAIN.

Our next stop is the TCA timer peripheral, specifically the CTRLA register which identifies the prescaler setting for the TCA module itself.

  Serial.print("TCA0_CTRLA = "); Serial.println(TCA0.SINGLE.CTRLA, BIN);

The result is binary 1011. A datasheet exploration tells us that the leading 101 indicates a division by 64. The LSB indicates that the peripheral is enabled. That takes care of TCA which are the first three PWM output identified in the opening table.

The last step is to relate the TCB output back to TCA. Skipping a few steps, we look at the CTRLA register.

  Serial.print("TCB0_CTRLA = "); Serial.println(TCB0_CTRLA, BIN);

The result is binary 101. The datasheet indicates that the TCB peripheral is using CLK_TCA from TCA0. Consequently, changing the prescale in A will change the operation of B, but not the other way round.

What are the problems when we modify the timer / counter A?

Once again, we start with the recognition that Arduino abstracts the hardware to make it easer to use. As previously mentioned, when we modify / configure the underlying hardware, we risk breaking Arduino functionality.

Without explanation we note that modifying timer A breaks the millis( ) function. In all likelihood, it breaks other time-based functions. However, modifying B does not appear to break millis( ).

How can I increase the PWM frequency of the Arduino Nano Every?

The PWM frequency can be modified by configuring the clock prescalers in the ATmega4809’s A and B timers or by changing the frequency / prescaler of the primary oscillator. However, many of these changes will break the Arduino functionality.

The remainder of this article presents a method of changing the two PWM outputs associated with TCB. The three TCA PWM outputs remain at the original 976.5625 Hz and the millis( ) function remains intact. Please know that a complete function test has not been performed. It is entirely possible that other Arduino functions will be crippled. Please leave comments below if you discover a broken functionality.

Given these constraints, we are limited to a small change in the TCB PWM frequency. We can change the CTRLA registers to use CLK_PER or CLK_PER/2 resulting in a PWM frequency of 31.25 kHz and 62.5 kHz respectively. All we need to do is set the appropriate.

For a 32.25 kHz 8-bit PWM use:

     TCB0_CTRLA = 0b00000011;  // pin D6
     TCB1_CTRLA = 0b00000011;  // pin D3

For a 62.5 kHz 8-bit PWM use:

     TCB0_CTRLA = 0b00000001;  // pin D6
     TCB1_CTRLA = 0b00000001;  // pin D3

The results are shown in Figure 3 where a Digilent Analog Discovery is used as a logic analyzer. From top to bottom, the PWM signal are associated with Arduino pins D3, D5, D6, D10, and D11. Note that all PWM signals are set for a 25% duty cycle.

Figure 3: Digilent Analog Discovery screen capture showing the two TCB PWMs at 31.25 and 62.5 kHz while the three TCA PWM signals are at at 976.5625 Hz. All signals have a 25% duty cycle.

Next steps

The ATmega4809 is capable of providing fine resolution 16-bit PWM signals from TCA. As you may have guessed from the common thread of this note, configuring the peripherals often involves the loss of Arduino abstraction and broken functions.

Perhaps it is time to put down the Arduino abstraction and learn bare-metal programming using tools such as MPLAB. This is a worthy time investment, as you learn to unlock the full potential of microcontrollers including the dazzling complexity of the newest high-performance additions.

Parting thoughts

This note provides an excellent learning opportunity for microcontrollers. We started at the top using Arduino abstraction dug deep into the ATmega4809’s timer peripherals. We learned about the PWM generators and gained a deeper appreciation for the Arduino abstraction.

As for the PWM and increasing frequency, we learned that the TCB associated PWM signals may be increased to 31 and 64 kHz without breaking functions such as millis( ). At the same time, we acknowledge that other yet untested functions could be degraded. If you discover a broken function, please leave a comment below. Also, please leave a complete code listing to help others.

Best Wishes,

APDahlen

Please follow this link for related Arduino education content.

About the Author

Aaron Dahlen, LCDR USCG (Ret.), serves as an application engineer at DigiKey. He has a unique electronics and automation foundation built over a 27-year military career as a technician and engineer which was further enhanced by 12 years of teaching (interwoven). With an MSEE degree from Minnesota State University, Mankato, Dahlen has taught in an ABET accredited EE program, served as the program coordinator for an EET program, and taught component-level repair to military electronics technicians. Dahlen has returned to his Northern Minnesota home and thoroughly enjoys researching and writing articles such as this. LinkedIn | Aaron Dahlen - Application Engineer - DigiKey

1 Like