I saw that the Arduino Uno Q manual specifies a PWM frequency of 500Hz, which isn't exactly ideal for creating automatic controls.
I need two PWM signals to control a voice coil.
How can I adjust the resolution and frequency of the PWM signals?
Thanks
As the Arduino UNO Q header signals are compatible with the Arduino UNO R3, except for the logic levels, the PWM signals are available on the following pins:
- Pins 3 and 11 β 490 Hz
- Pins 5 and 6 β 980 Hz
- Pins 9 and 10 β 490 Hz
To obtain PWM with adjustable frequency and resolution, you must work with register-level code. This can be done quite easily on the UNO R3; whereas, accessing the registers of the UNO Q MCU is not as straightforward.
While the UNO-Q tries to have similar functionality on the main pins, it is a 32 bit STM32 (ARM) based board whereas the UNO-R3 is a 8 bit AVR based board.
A lot of the default information about how the Q is configured, is defined within the Device tree. As for PWM Pins:
Two of the tables defined in the Q's device tree:
/* PWM pin mapping - Digital pins with their timer channels */
/* Currently only the pins marked with ~ on the pin headers are enabled */
pwm-pin-gpios =
/*<&gpiob 7 0>,*/ /* D0/PB6 - TIM4_CH2 */
/*<&gpiob 6 0>,*/ /* D1/PB6 - TIM4_CH1 */
<&gpiob 3 0>, /* D2/PB3 - TIM2_CH2 */
<&gpiob 0 0>, /* D3/PB0 - TIM3_CH3 */
<&gpioa 11 0>, /* D5/PA11 - TIM1_CH4 */
<&gpiob 1 0>, /* D6/PB1 - TIM3_CH4 */
<&gpiob 2 0>, /* D7/PB2 - TIM8_CH4N */
<&gpiob 4 0>, /* D8/PB4 - TIM3_CH1 */
<&gpiob 8 0>, /* D9/PB8 - TIM4_CH3 */
<&gpiob 9 0>, /* D10/PB9 - TIM4_CH4 */
<&gpiob 15 0>, /* D11/PB15 - TIM1_CH3N */
<&gpiob 14 0>, /* D12/PB14 - TIM1_CH2N */
<&gpiob 13 0>, /* D13/PB13 - TIM1_CH1N */
<&gpiob 11 0>, /* D20/PB11 - TIM2_CH4 */
<&gpiob 10 0>, /* D21/PB10 - TIM2_CH3 */
<&gpioh 10 0>, /* LED3_R - TIM5_CH1 */
<&gpioh 11 0>, /* LED3_G - TIM5_CH2 */
<&gpioh 12 0>; /* LED3_B - TIM5_CH3 */
...
pwms =
/*<&pwm4 2 PWM_HZ(500) PWM_POLARITY_NORMAL>,*/ /* D0/PB7 β TIM4_CH2 */
/*<&pwm4 1 PWM_HZ(500) PWM_POLARITY_NORMAL>,*/ /* D1/PB6 β TIM4_CH1 */
<&pwm2 2 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D2/PB3 β TIM2_CH2 */
<&pwm3 3 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D3/PB0 β TIM3_CH3 */
<&pwm1 4 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D5/PA11 β TIM1_CH4 */
<&pwm3 4 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D6/PB1 β TIM3_CH4 */
<&pwm8 4 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D7/PB2 β TIM8_CH4N */
<&pwm3 1 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D8/PB4 β TIM3_CH1 */
<&pwm4 3 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D9/PB8 β TIM4_CH3 */
<&pwm4 4 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D10/PB9 β TIM4_CH4 */
<&pwm1 3 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D11/PB15 β TIM1_CH3N */
<&pwm1 2 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D12/PB14 β TIM1_CH2N */
<&pwm1 1 PWM_HZ(500) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>, /* D13/PB13 β TIM1_CH1N */
<&pwm2 4 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D20/PB11 β TIM2_CH4 */
<&pwm2 3 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D21/PB10 β TIM2_CH3 */
<&pwm5 1 PWM_HZ(500) PWM_POLARITY_INVERTED>, /* LED3_R/PH10 β TIM5_CH1 */
<&pwm5 2 PWM_HZ(500) PWM_POLARITY_INVERTED>, /* LED3_G/PH11 β TIM5_CH2 */
<&pwm5 3 PWM_HZ(500) PWM_POLARITY_INVERTED>; /* LED3_B/PH12 β TIM5_CH3 */
EDIT: edit I should mention that the main table entries refer to additional objects like:
<&pwm2 2 PWM_HZ(500) PWM_POLARITY_NORMAL>, /* D2/PB3 β TIM2_CH2 */
Is using Timers2 2nd channel:
&timers2 {
status = "okay";
st,prescaler = <4>;
pwm2: pwm {
status = "okay";
zephyr,deferred-init;
pinctrl-0 = <>;
pinctrl-1 = <>;
pinctrl-2 = <&tim2_ch2_pb3 &tim2_ch3_pb10 &tim2_ch4_pb11>;
pinctrl-names = "default", "sleep", "arduino";
};
};
the item in the pinctrl... &tim2_ch2_pb3 defines that Timer2 channel2 is on the pin pb3...
Shows you some of the information. The first table shows an ArduinoCore-zephyr added table to do mapping, but most of the real information is in the second one, which also defines the default HZ defined for these pins. Which as you see, is 500, but that is not
the full PWM capabilities of the chip.
For example the GIGA which uses a different STM32 chip has a PWM setup to drive the Camera clock at 12mhz.
pwms = <&pwm1 3 PWM_HZ(12000000) PWM_POLARITY_NORMAL>,
<&pwm2 4 PWM_HZ(500) PWM_POLARITY_NORMAL>,
Internal to the call to analogWrite, you will see it uses the Zephyr call:
(void)pwm_set_pulse_dt(&arduino_pwm[idx], pulse);
If you do an internet search like: zephyr pwm_set_pulse_dt
You can find the PWM documentation information in zephyr:
Pulse Width Modulation (PWM) β Zephyr Project Documentation
If you search for: zephyr pwm can I change the frequency at run time
You might see AI results like:
#include <zephyr/drivers/pwm.h>
static const struct pwm_dt_spec pwm1 = PWM_DT_SPEC_GET(DT_ALIAS(pwm1));
// Change frequency to 1 kHz (period = 1000 ns)
pwm_set_cycles(&pwm1.dev, 0, 1000, 500, PWM_POLARITY_NORMAL);
Edit2: to change the resolution, use the same call you do on other Arduinos:
void analogWriteResolution(int bits)
Of course you need to figure out how to get the handle to the correct PWM object and the like, but hopefully this gives you a few hints.
And now for something completely hacking...
@ptillisch @Merlin513 - Actually hoping that we can add some cleaner ways for doing some things like this. That is ways to map from Arduino world into Zephyr World to allow us to extend using Zephyr stuff when necessary.
Here is a simple sketch, most of the code is in the wiring_analog.cpp
//-----------------------------------------------------------------------------
// Copy of the Arduino Zephyr analog code - to map from Pin Number to zephyr
// object and channel
//-----------------------------------------------------------------------------
#include "wiring_private.h"
#define PWM_PIN 2
#define PWM_DT_SPEC(n, p, i) PWM_DT_SPEC_GET_BY_IDX(n, i),
#define PWM_PINS(n, p, i) \
DIGITAL_PIN_GPIOS_FIND_PIN(DT_REG_ADDR(DT_PHANDLE_BY_IDX(DT_PATH(zephyr_user), p, i)), \
DT_PHA_BY_IDX(DT_PATH(zephyr_user), p, i, pin)),
const struct pwm_dt_spec arduino_pwm[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), pwms, PWM_DT_SPEC)
};
/* pwm-pins node provides a mapping digital pin numbers to pwm channels */
const pin_size_t arduino_pwm_pins[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), pwm_pin_gpios, PWM_PINS)
};
size_t pwm_pin_index(pin_size_t pinNumber) {
for (size_t i = 0; i < ARRAY_SIZE(arduino_pwm_pins); i++) {
if (arduino_pwm_pins[i] == pinNumber) {
return i;
}
}
return (size_t)-1;
}
void setup() {
Serial.begin(115200);
// Start of the PWM...
analogWrite(PWM_PIN, 64);
delay(100);
// Quick and dirty to set the period and pulse in nano seconds
size_t idx = pwm_pin_index(PWM_PIN);
pwm_set(arduino_pwm[idx].dev, arduino_pwm[idx].channel, 1000, 500, PWM_POLARITY_NORMAL);
}
void loop() {
// put your main code here, to run repeatedly:
}
If I uplaod the following sketch (taken from your post #4) into UNO Q, what will be the output at DPIn-2 (PWM_PIN)?
//-----------------------------------------------------------------------------
// Copy of the Arduino Zephyr analog code - to map from Pin Number to zephyr
// object and channel
//-----------------------------------------------------------------------------
#include "wiring_private.h"
#define PWM_PIN 2
#define PWM_DT_SPEC(n, p, i) PWM_DT_SPEC_GET_BY_IDX(n, i),
#define PWM_PINS(n, p, i) \
DIGITAL_PIN_GPIOS_FIND_PIN(DT_REG_ADDR(DT_PHANDLE_BY_IDX(DT_PATH(zephyr_user), p, i)), \
DT_PHA_BY_IDX(DT_PATH(zephyr_user), p, i, pin)),
const struct pwm_dt_spec arduino_pwm[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), pwms, PWM_DT_SPEC)
};
/* pwm-pins node provides a mapping digital pin numbers to pwm channels */
const pin_size_t arduino_pwm_pins[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), pwm_pin_gpios, PWM_PINS)
};
size_t pwm_pin_index(pin_size_t pinNumber) {
for (size_t i = 0; i < ARRAY_SIZE(arduino_pwm_pins); i++) {
if (arduino_pwm_pins[i] == pinNumber) {
return i;
}
}
return (size_t)-1;
}
void setup() {
Serial.begin(115200);
// Start of the PWM...
analogWrite(PWM_PIN, 64);
delay(100);
// Quick and dirty to set the period and pulse in nano seconds
size_t idx = pwm_pin_index(PWM_PIN);
pwm_set(arduino_pwm[idx].dev, arduino_pwm[idx].channel, 1000, 500, PWM_POLARITY_NORMAL);
}
void loop() {
// put your main code here, to run repeatedly:
}
Yeah - thought crossed my mind about how to change PWM frequency at runtime as I was working on Lidar app which uses PWM to control the drive motor. Noticed when doing that having issues getting the motors RPM to what I set it at when using a PID control. Was wondering at the time whether a "analogWriteFrequency(pin, Hz)" could be included like we did on the Teensy 4 and I believe ESP32 supports. But have been still playing with things to get it working.
PS. Think if memory serves me we have been talking about this type of stuff for awhile.
Now to play with your PWM stuff
Since you know I can not leave well enough alone, I made it a little more generic - new analogWriteFreq function:
//-----------------------------------------------------------------------------
// Copy of the Arduino Zephyr analog code - to map from Pin Number to zephyr
// object and channel
//-----------------------------------------------------------------------------
#include "wiring_private.h"
#define PWM_PIN 2
#define PWM_DT_SPEC(n, p, i) PWM_DT_SPEC_GET_BY_IDX(n, i),
#define PWM_PINS(n, p, i) \
DIGITAL_PIN_GPIOS_FIND_PIN(DT_REG_ADDR(DT_PHANDLE_BY_IDX(DT_PATH(zephyr_user), p, i)), \
DT_PHA_BY_IDX(DT_PATH(zephyr_user), p, i, pin)),
const struct pwm_dt_spec arduino_pwm[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), pwms, PWM_DT_SPEC)
};
/* pwm-pins node provides a mapping digital pin numbers to pwm channels */
const pin_size_t arduino_pwm_pins[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), pwm_pin_gpios, PWM_PINS)
};
size_t pwm_pin_index(pin_size_t pinNumber) {
for (size_t i = 0; i < ARRAY_SIZE(arduino_pwm_pins); i++) {
if (arduino_pwm_pins[i] == pinNumber) {
return i;
}
}
return (size_t)-1;
}
void setup() {
Serial.begin(115200);
delay(2000);
// Start of the PWM...
pinMode(PWM_PIN, OUTPUT);
analogWrite(PWM_PIN, 64);
delay(100);
analogWriteFrequency(PWM_PIN, 4000, 50);
}
void loop() {
}
void analogWriteFrequency(int pin, float freq, float duty)
{
//Step 1. calculate period
float T = 1.0/freq;
//Step 2 convert to nanoseconds
int Tns = (int)(T * 1000000000.0);
Serial.println(Tns);
//Step 3. Calculate Pulse width
int PW = (int) ((duty / 100.0) * Tns);
Serial.println(PW);
// Quick and dirty to set the period and pulse in nano seconds
size_t idx = pwm_pin_index(pin);
pwm_set(arduino_pwm[idx].dev, arduino_pwm[idx].channel, Tns, PW, PWM_POLARITY_NORMAL);
}
Looks good!
I was thinking about potentially trying to migrate this into cores/arduino/wiring_analog.cpp
(ArduinoCore-zephyr)
Where you would pass in the frequency and it would either have stashed or ask zephyr what is the current duty and then scale it to the new frequency.
And ditto change analogWrite to scale to the current frequency...
Not sure what probability they would pull in such change, but maybe worth a chance as I know Teensy does this and probably some others like ESP32..
I have not gotten it directly into the core yet, but I have a mocked up version with modified versions of the APIs...
zephyr_change_frequency-260523a.zip (2.5 KB)
Sketch + .cpp and .h... Sketch looks now like:
//-----------------------------------------------------------------------------
// Copy of the Arduino Zephyr analog code - to map from Pin Number to zephyr
// object and channel
//-----------------------------------------------------------------------------
#include "wiring_analog_extended.h"
#define PWM_PIN 2
void setup() {
Serial.begin(115200);
pinMode(3, OUTPUT);
// Start of the PWM...
analogWriteResolution(10);
XanalogWrite(PWM_PIN, 64);
delay(100);
// Quick and dirty to set the period and pulse in nano seconds
//size_t idx = pwm_pin_index(PWM_PIN);
//pwm_set(arduino_pwm[idx].dev, arduino_pwm[idx].channel, 1000, 500, PWM_POLARITY_NORMAL);
XanalogWriteFrequency(PWM_PIN, 1000000);
}
uint16_t duty = 0;
void loop() {
delay(10);
digitalWrite(3, !digitalRead(3));
XanalogWrite(PWM_PIN, duty);
duty += 64;
if (duty == 1024) duty = 1023;
else if (duty > 1024) duty = 0;
}
The X functions are in the two other files, where the XanalogWrite is modified... the frequency one is new...
You can see where it cycled through both 0 and 1023...
I decided to simply the changes. The analogWriteFrequency function only takes pin and frequency. computes the nanoseconds and saves it into an array of frequencies and change the analogWrite function to use this array instead of the value built in the table from the device tree, which is read only...
zephyr_change_frequency-260523a.zip (2.3 KB)
Thanks everyone for your quick and effective work, now the PWM outputs can be used to their full potential.
I did issue a Pull Request for PWM updates to Arduino
core: analog: add analogWriteFrequency by KurtE Β· Pull Request #493 Β· arduino/ArduinoCore-zephyr
I don't give it a high probability, but ![]()
It might help know what you are doing - controlling voice coil ?
There may be other ways to do what you want if you provide a bit more info - eg an external hardware oscillator .




