Help with Timer1 Fast PWM on Arduino - Vary Frequency (25-42kHz) and Duty Cycle (10-90%) Using Two Potentiometers

Hi everyone,

I'm working on a project where I’m using Timer1 in Fast PWM mode (Mode 14) on an Arduino to generate a square wave output on pins 9 and 10. My goal is to:

  • Vary the frequency of the PWM signal between 25 kHz to 42 kHz
  • Vary the duty cycle between 10% to 90%

I want to use two separate potentiometers to control each parameter:

  • One for frequency
  • One for duty cycle

However, the issue I'm facing is that only one parameter (either frequency or duty cycle) updates at a time. I need help understanding how to properly read both potentiometers and update both parameters simultaneously in the code without conflicts.

Has anyone done something similar or can suggest the best approach for this? I’d appreciate code examples or ideas to achieve smooth dual control with Timer1.pasting the code for review.in this code only duty cycle varies not frequency!

#define F_CPU 16000000UL
#define PRESCALER 1
  
const int freqPin = A0;     // Potentiometer for frequency
const int dutyPin = A1;     // Potentiometer for duty cycle

const int oc1aPin = 9;      // OC1A - Pin 9 (non-inverting)
const int oc1bPin = 10;     // OC1B - Pin 10 (inverting)

void setup() {
  pinMode(oc1aPin, OUTPUT);
  pinMode(oc1bPin, OUTPUT);
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  // Fast PWM, mode 14 (TOP = ICR1), COM1A = non-inverting, COM1B = inverting
  TCCR1A = (1 << COM1A1) |  (1 << COM1B1) | (1 << COM1B0) | (1 << WGM11);
  TCCR1B = (1 << WGM13) |  (1 << CS10);  // Prescaler = 1

  ICR1 = 640;
  OCR1A = 320;
  OCR1B = 330; // Initial duty + dead time
}

void loop() {
  int freqInput = analogRead(freqPin);
  int dutyADC = analogRead(dutyPin);

 // float freq = 25000 + (freqADC / 1023.0) * (42000 - 25000);
  uint16_t top = map(freqInput, 0, 1023, 380, 639);
  ICR1 = top;

  //ICR1 = top;

  float dutyCycle = 10 + (dutyADC / 1023.0) * 80;  // 10%–90%
  uint16_t ocrValue = (uint16_t)((dutyCycle / 100.0) * top);

  const uint8_t deadTimeTicks = 10;  // Adjust as needed (in timer ticks)

  OCR1A = ocrValue;  // Non-inverting: high for ocrValue ticks
  OCR1B = (ocrValue + deadTimeTicks < top) ? (ocrValue + deadTimeTicks) : top;

  delay(5);
}

Thanks in advance!

That's impossible :frowning:

Frequency and duty cycle are controlled by different registers which can not be changed at the same time.

Changing the frequency immediately changes the duty cycle as well. Preserving the duty cycle across a frequency change is impossible.

is it possible to use switch here and one potentiometer? switch on varying frequency off then duty cycle ?

As I understand it, the OP has two potentiometers and lets say that one is marked with a range 25kHz to 42kHz and the other is marked with a range 10% duty cycle to 90% duty cycle. He/She want to regularly (at discrete intervals) read these two potentiometers in the loop and then calculate the appropriate control register values for timer1 and set it to achieve the desired frequency and duty cycle.

Are you sure that this impossible with reasonable levels of precision ?

It depends on the strength of

simultaneously

Correct! Could you help?!

I'd guess that this is running on a (single core) AVR, probably Uno class running at 16MHz so that plus/minus a few milliseconds would suffice but the OP can confirm.

Can you get a simpler program without potentiometers (or 4 separate programs) to work for the 4 edge cases, that is

  1. 25kHz @ 10% duty cycle
  2. 25kHz @ 90% duty cycle
  3. 42kHz @ 10% duty cycle
  4. 42kHz @ 90% duty cycle

I'm a bit reluctant to do a complete solution for what appears to be a school type assignment but the challenge could be irresistible if you say that ChatGPT (or similar AI) can't deliver a working solution.

I mentioned ChatGPT because I tried using it to understand the Timer1 configuration, but the solutions it gave either didn’t produce the correct frequency/duty cycle or weren’t stable. That’s why I reached out here, hoping to get insights from people who have actual experience with low-level timer settings.

I’ve been experimenting with Fast PWM mode to generate a square wave between 25kHz and 42kHz, and I want to learn how to control both frequency and duty cycle using separate inputs (like potentiometers) — eventually for an inverter-related application.

Thanks again for considering!

First you control the frequency, then the duty cycle depending on the actual TOP value.

But how?!

Not in this thread you didn't but the code structure made it clear that AI had been used.

You seem to already be determining the frequency first, then the duty cycle which is the correct procedure.

The comment does not match the configuration:

for Fast PWM Mode 14 you need also to set TCCR1B with the WGM12 bit:
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); // Prescaler = 1

How are you measuring the frequency and duty cycle ? With a logic analyser or oscilloscope ?

Oscilloscope

yes ,i already mentioned i used AI and provided solution by AI didn't works.

yes,i have set the WGM12 bit

implemented similar requirement using an ESP32 - may give you some ideas

code

// ESP32 - variable pulse period and duty cycle using timer interrupt
// timer interrupt used to count pulse HIGH and LOW times

// UPDATE to use 20MHz clock

// range 100000000uSec (0.005Hz) to 5uSec (100000Hz)
// note: without delay() in loop() pulse width jitter in particular at high frequencies

// ideas from https://deepbluembedded.com/esp32-timers-timer-interrupt-tutorial-arduino-ide/

#define LED 18  // pulse output

hw_timer_t *Timer1_Cfg = NULL;  // timer object

volatile long int period = 200, pulseON = 100, pulseOFF = 100, dutyCycle = 50;
void IRAM_ATTR Timer1_ISR() {  // timer ISR called on every timer event
  static long int lastPulseON = -1, pulse = 100;
  // if pulseON value changed reset counts and set output to HIGH
  if (pulseON != lastPulseON) {
    pulse = lastPulseON = pulseON;  // note current pulseON
    digitalWrite(LED, 1);           // set output high
  } else
    digitalWrite(LED, !digitalRead(LED));  // invert pulse HIGH or LOW
  // setup timer interrupt
  timerAlarm(Timer1_Cfg, pulse, true, 0);  // API 3.0 change timer count
  // if change HIGH/LOW pulse count
  if (pulse == pulseON) pulse = pulseOFF;
  else pulse = pulseON;
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\n\nESP32 - variable pulse period and duty cycle using hardware timer");
  Serial.println("period increment > (* by 10) decrement < (/ by 10)");
  Serial.printf(" duty cycle enter 1 (for 10%) to 9 (for 90%)\n");
  pinMode(LED, OUTPUT);
  /* API 2.x
  Timer1_Cfg = timerBegin(0, 80, true);                 // setup timer prescaler value and counting direction up
  timerAttachInterrupt(Timer1_Cfg, &Timer1_ISR, true);  // attach callback fundtion
  timerAlarmWrite(Timer1_Cfg, 5, true);                 // setup timer count and execute periodically
  timerAlarmEnable(Timer1_Cfg);                         // enable timer
   API 3.0 */
  if ((Timer1_Cfg = timerBegin(2000000)) == NULL) {  // API 3.0 setup timer for 1uSec
    Serial.println("timerBegin Failed!!");
    while (1) delay(100);
  }
  Serial.println(period);
  Serial.print("timerBegin() OK frequency ");
  Serial.println(timerGetFrequency(Timer1_Cfg));
  timerAttachInterrupt(Timer1_Cfg, &Timer1_ISR);  // API 3.0 attach callback fundtion
  timerAlarm(Timer1_Cfg, 100, true, 0);           // setup timer count, execute periodically and enable
  Serial.println(period);
}

void loop() {
  static long int unit = 200;
  static unsigned long /*period = 100UL, */ oldPeriod = 2UL;  // pulse width multipliers - start 5uSec
  while (Serial.available()) {                                // if characters entered read them
    char ch = Serial.read();
    if (ch == '>') {
      period += unit;
      if (period / unit >= 10) unit *= 10;
    }
    // < decrement value by 1 unit  - minimum is 1 ?
    if (ch == '<' && period != 1) {
      if (period == unit) unit = unit / 10;
      period -= unit;
    }
    // * increment value by 10 ?
    if (ch == '*') {
      period = unit = unit * 10;
    }
    // / decrement value by 10 - minimum is 1
    if (ch == '/' && period != 1) {
      if (period == unit) unit = unit / 10;
      period = unit;
    }
    if ((ch >= '1') && (ch <= '9')) dutyCycle = (ch - '0') * 10;  // set duty cycle
  }
  // calculate pulseON and pulseOFF counts from period and duty cycle
  pulseON = (period)*dutyCycle / 100;
  pulseOFF = (period) * (100 - dutyCycle) / 100;
  // if period/duty cycle has changed display new value
  if (pulseON != oldPeriod) {
    Serial.printf("period = %lduSec frequency = %.3fHz\n", period/2, 2000000.0 / (period));
    Serial.printf("dutyCycle %ld pulseON %ld pulseOFF %ld\n", dutyCycle, pulseON, pulseOFF);
    oldPeriod = pulseON;
  }
  // check Serial every second
  delay(1000);
}

50KHz 60% duty cycle

serial monitor output

ESP32 - variable pulse period and duty cycle using hardware timer
period increment > (* by 10) decrement < (/ by 10)
 duty cycle enter 1 (for 10) to 9 (for 90)
200
timerBegin() OK frequency 2000000
200
period = 100uSec frequency = 10000.000Hz
dutyCycle 50 pulseON 100 pulseOFF 100
period = 70uSec frequency = 14285.714Hz
dutyCycle 50 pulseON 70 pulseOFF 70
period = 30uSec frequency = 33333.333Hz
dutyCycle 50 pulseON 30 pulseOFF 30
period = 20uSec frequency = 50000.000Hz
dutyCycle 50 pulseON 20 pulseOFF 20
period = 20uSec frequency = 50000.000Hz
dutyCycle 60 pulseON 24 pulseOFF 16

Thanks for sharing @horace .but i am still confuse how to do the variation in frequency and duty cycle using potentiometer.

That was not obvious from the code in #1. Have you just changed it? Does the program now work as you require ? If not, then post your latest code.

I have got your code to work with the correction to TCCR1B

in the code of post 17 the period and duty cycle are modified by keyboard input via the serial monitor - change the code use potentiometers or a QEI encoder

seem to remember implementing a version of the code of post 17 to use the ESP32 RMT

yes,i've changed it .but still not works !

#define F_CPU 16000000UL
#define PRESCALER 1

const int freqPin = A0;     // Potentiometer for frequency
const int dutyPin = A1;     // Potentiometer for duty cycle

const int oc1aPin = 9;      // OC1A - Pin 9 (non-inverting)
const int oc1bPin = 10;     // OC1B - Pin 10 (inverting)

void setup() {
  pinMode(oc1aPin, OUTPUT);
  pinMode(oc1bPin, OUTPUT);

  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  // Fast PWM, mode 14 (TOP = ICR1), COM1A = non-inverting, COM1B = inverting
  TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << COM1B0) | (1 << WGM11);
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);  // Prescaler = 1

  ICR1 = 640;
  OCR1A = 320;
  OCR1B = 330; // Initial duty + dead time
}

void loop() {
  int freqInput = analogRead(freqPin);
  int dutyADC = analogRead(dutyPin);

 // float freq = 25000 + (freqADC / 1023.0) * (42000 - 25000);
  uint16_t top = map(freqInput, 0, 1023, 380, 639);
  ICR1 = top;

  float dutyCycle = 10 + (dutyADC / 1023.0) * 80;  // 10%–90%
  uint16_t ocrValue = (uint16_t)((dutyCycle / 100.0) * top);

  const uint8_t deadTimeTicks = 10;  // Adjust as needed (in timer ticks)

  OCR1A = ocrValue;  // Non-inverting: high for ocrValue ticks
  OCR1B = (ocrValue + deadTimeTicks < top) ? (ocrValue + deadTimeTicks) : top;

  delay(5);
}

[quote="6v6gt, post:19, topic:1384753"]
I have got your code to work with the correction to TCCR1B
[/quote] I noticed you mentioned that the version I posted doesn’t work as required, but you were able to get it working on your end. I’ve tried several adjustments myself, but it’s still not functioning as expected in my case.

If possible, could you please share how exactly you implemented your working version? It would be really helpful if you could also provide the code that worked for you. That way, I can better understand the changes and learn from your approach.

Looking forward to your response.
Thanks in advance!