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!
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 ?
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
25kHz @ 10% duty cycle
25kHz @ 90% duty cycle
42kHz @ 10% duty cycle
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.
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);
}
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
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
[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!