Variable PWM Frequency

Hello,

I am totally new to writing Arduino or C code (or indeed any code unless you count Basic, Pascal & Cobol in my past :slight_smile: ) and am new to this forum. I wanted to create a very simple Pulse generator using the Arduino Uno/Nano and have been successful in piecing together snippets of code to output (on pin9) a variable duty cycle (using a rotary encoder). I can increment/decrement the duty cycle by 1 betwwen 5% & 95%. Despite reading through lots of threads & articles on PWM frequency control however, I cannot get figure out how to get a constantly variable frequency (between say 100Hz and 500kHz) using perhaps a potentiometer, whilst preserving a variable duty cycle with the rotary encoder.

This is the code that works quite well to give me a variable duty cycle (but always at 10Hz):

#include <rotary.h>


const int outPin =  9;// the number of the output pin
Rotary r = Rotary(2,3); // rotary encoder pins.  

int outState = LOW;             // outState used to set output HIGH/LOW
int buttonstate = 0;

unsigned long previousMillis = 0;        // will store last time output was updated
long intervalon = 50;           // interval at which to change (milliseconds)
long intervaloff =50;
long interval=1500;
void setup() {
  
  pinMode(A0,INPUT); // Connect to a button that goes to GND on push for later implemtation
  digitalWrite(A0,HIGH);
  pinMode(outPin, OUTPUT);
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();
}

void loop() {
 
  unsigned long currentMillis = millis();

   if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;

      // if the PIN 9 input is low turn it HIGH and vice-versa:
    if (outState == LOW) {
      interval=intervalon;
         outState = HIGH;
         } else {
      interval = (100-intervalon);    
      outState = LOW;
                
    }

    digitalWrite(outPin, outState);
   
     }
}

ISR(PCINT2_vect) {
 unsigned char result = r.process();
 if (result) {    
 if (result == DIR_CW){intervalon=intervalon+1;
  }
   else {intervalon=intervalon-1;
     
     if (intervalon >=95){intervalon=95;}; // UPPER WIDTH PERCENTAGE ON VALUE
     if (intervalon <=5){intervalon=5;}; // LOWER WIDTH PERCENTAGE ON VALUE
   }
   }
}

Apologies for my poor coding layout (I hope it is readable). Would be grateful if someone could advise what I would need to do (if it is possible) to be able to adjust the frequency of output on the fly. Regards

Well here’s a klunky work-in-progress I’ve been playing with off & on, might give you some ideas. Starts up with a 5 Hz freq and 50% duty cycle, change it by typing in top of serial monitor like this:
Change Hz to 20: h20[ENTER] Change duty cycle to 10%: p10[ENTER] Change both: h15,p80[ENTER].
MIN Hz = 1, MAX = ?.
Probly better to use a hardware timer but I’m not quite there yet. :confused:
Hope you have a DSO to hook to LED pin so you can see waveforms.
I couldn’t find any decent pots to try, but I’m sure you can figure out how to replace the serial entry method with pots and analogRead(). Good luck.

unsigned long cycleStart,
              onTime,              
              cycleTime,
              hz;
int val;              
const byte powerPin = LED_BUILTIN;
byte percent = 50;
void setup()
{
  Serial.begin(9600);
  Serial.println(" Hz = 5  Duty Cycle = 50%");
  hz = 5;
  cycleTime = 1000000UL / hz;
  onTime = cycleTime / 2;  
  pinMode(powerPin,OUTPUT);
}
void loop()
{
  digitalWrite(powerPin,micros() - cycleStart < onTime);  
  if(micros() - cycleStart > cycleTime)
    cycleStart += cycleTime;
  // if there's any serial available, read it:
  if(Serial.available() > 0)
  {
     val = Serial.read();
    if(val == 'h' || val == 'H')
      hz = Serial.parseInt();
    else if(val == 'p' || val == 'P')
    {
      percent = Serial.parseInt();   
    }
    else
      while(Serial.available() > 0)
        Serial.read();  
    cycleTime = 1000000UL / hz;
    percent = constrain(percent, 0, 100);
    onTime = cycleTime * 0.01 * percent;
    cycleStart = micros();
    Serial.print(" Hz = ");
    Serial.print(hz);
    Serial.print("  Duty Cycle = ");
    Serial.print(percent);
    Serial.println("%");
  }
}

Thanks for your reply 'outsider'. I do have a DSO and am therefore able to monitor the output at pin 9. The duty cycle adjustment works very well but I've tried a number of ways to change the frequency without success. From all the stuff I've read, there seems to be a number of ways to change frequency. Most, as you say, seem to involve hardware timer functions and appear to require upfront assignment of the desired, single frequency rather than changing in, at will, during program execution.

I have tried all of the methods described and none work for me alongside the other duty cycle change methods I am using (i.e Rotary encoder, millis timing and digitalWrite) to output. Perhaps I'm doing things all wrong.

I will look more closely at your code and see whether there is anything there that will give me a clue.

My original intent was to build a very basic 'Pulse generator' which would allow me to make changes duty cycle and frequency during execution. I can do this with a 555, a few capacitors and a potentiometer or two and hoped it would be just as straight forward with an Arduino :slightly_frowning_face:

I still can't make this work. I'm a bit surprised that some of the experts on this forum haven't chimed in to either give some advice/help or at least tell me what I'm trying to do, can't be done. Can anyone assist?

Advice: If you want frequencies up to 500 kHz you will need a hardware clock to generate the pulses.

Advice: Use Timer1 which is a 16-bit timer. This will give you better Frequency and Duty Cycle resolution. To set the frequency you will need to set the 'prescale' and the 'TOP' value. The PWM duty cycle can then be anywhere between 0 and TOP.

Note: The system clock is 16 MHz so at 500 kHz you will only have 32 levels of PWM. You will also be limited to what frequencies you can specify. The next frequency down from 500 kHz is 484.84 kHz (16 MHz / 33). As you make TOP higher the frequency will go down and the duty cycle resolution will go up. When you reach TOP=65535 (maximum unsigned 16-bit int) you will have to use the prescale to get lower frequencies. Pick the lowest prescale that keeps TOP below 65536.

I am curious. Why do you want variable frequency?

Here is some (untested) code that will let you set the PWM generated on Pin 9 and/or Pin 10 to any frequency from 100 Hz to 500 kHz. Both pins would have the same frequency but can have different duty cycles.

// Adjustable frequency and duty-cycle Fast PWM
const unsigned long MAX_DIVISOR = (F_CPU / 100UL) - 1; // 100 Hz minimum frequency
const unsigned long MIN_DIVISOR = (F_CPU / 500000UL) - 1; // 500 kHz maximum frequency
const unsigned long MAX_TOP = 0xFFFFUL;
const unsigned long MIN_TOP = 31;


unsigned int TOP;


void SetDivisor(unsigned long divisor)
{
  divisor = constrain(divisor, MIN_DIVISOR, MAX_DIVISOR);


  // Clear the prescale bits
  TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));


  if (divisor <= MAX_TOP)
  {
    TCCR1B |= (1 << CS10);
    TOP = divisor;
  }
  else if ((divisor / 8) <= MAX_TOP)
  {
    TCCR1B |= (1 << CS11);
    TOP = divisor / 8;
  }
  else if ((divisor / 64) <= MAX_TOP)
  {
    TCCR1B |= (1 << CS11);
    TCCR1B |= (1 << CS10);
    TOP = divisor / 64;
  }
  else if ((divisor / 256) <= MAX_TOP)
  {
    TCCR1B |= (1 << CS12);
    TOP = divisor / 256;
  }
  else if ((divisor / 1024) <= MAX_TOP)
  {
    TCCR1B |= (1 << CS12);
    TCCR1B |= (1 << CS10);
    TOP = divisor / 1024;
  }
  ICR1 = TOP;
}


float ActualFrequency(unsigned long divisor)
{
  divisor = constrain(divisor, MIN_DIVISOR, MAX_DIVISOR);
  return (float)F_CPU / (float)divisor;
}




void setup()
{
  Serial.begin(9600);
  // Stop Timer/Counter1
  TCCR1A = 0;  // Timer/Counter1 Control Register A
  TCCR1B = 0;  // Timer/Counter1 Control Register B
  TIMSK1 = 0;   // Timer/Counter1 Interrupt Mask Register
  TIFR1 = 0;   // Timer/Counter1 Interrupt Flag Register
  SetDivisor(MAX_DIVISOR); // Set TOP and Prescale for Minimum frequency
  OCR1A = 0;  // Default to 0% PWM
  OCR1B = 0;  // Default to 0% PWM


  // Set to Timer/Counter1 to Waveform Generation Mode 14: Fast PWM with TOP set by ICR1
  TCCR1A |= (1 << WGM11);
  TCCR1B |= (1 << WGM13) | (1 << WGM12) ;


  // Start 50% duty cycle on Pin 9:
  PWM16EnableA();
  PWM16A(50); // percent


  // Start 75% duty cycle on Pin 10:
  PWM16EnableB();
  PWM16B(75); // percent
  
}


void PWM16EnableA()
{
  // Enable Fast PWM on Pin 9: Set OC1A at BOTTOM and clear OC1A on OCR1A compare
  TCCR1A |= (1 << COM1A1);
  pinMode(9, OUTPUT);
}


void PWM16DisableA()
{
  // Disable Fast PWM on Pin 9
  TCCR1A &= ~(1 << COM1A1);
  pinMode(9, OUTPUT);
}


void PWM16EnableB()
{
  // Enable Fast PWM on Pin 10: Set OC1B at BOTTOM and clear OC1B on OCR1B compare
  TCCR1A |= (1 << COM1B1);
  pinMode(10, OUTPUT);
}


void PWM16DisableB()
{
  // Disable Fast PWM on Pin 10
  TCCR1A &= ~(1 << COM1B1);
  pinMode(10, OUTPUT);
}


inline void PWM16A(unsigned int PWMPercent)
{
  unsigned int PWMValue = (TOP * 100UL) / PWMPercent;
  OCR1A = PWMValue;
}


inline void PWM16B(unsigned int PWMPercent)
{
  unsigned int PWMValue = (TOP * 100UL) / PWMPercent;
  OCR1B = PWMValue;
}


void loop()
{
  float frequency = Serial.parseFloat();
  if (frequency > 1) {
    unsigned long divisor = F_CPU / frequency;
    divisor = constrain(divisor, MIN_DIVISOR, MAX_DIVISOR);
    SetDivisor(divisor);
    Serial.println(ActualFrequency(divisor));
  }
}