Change timer frequency

Hey, I’m trying to create a variable frequency PWM signal. I don’t understand a lot about the registers, but I managed to get something basic by reading the documentation. I can adjust the duty cycle quite nicely, but I don’t know how to adjust the frequency. I did a different design, and I was able to adjust the prescaler, but that only gave me 8 different frequencies, whereas I’d like something much smoother.

I also saw something about adjusting the ICR1 register, but I can’t get that working…

I also have an issue with the OCR0B register. Changing that adjusts the duty cycle, but I can’t seem to get a duty cycle of 0. It goes to about 1%, but that’s it.

I’m using an ATmega2560. Any help would be great.

Here’s what I have so far.

int pot_1 = 0;

void setup() {
  pinMode(4,OUTPUT);
  pinMode(13,OUTPUT);
  TCCR0A |= (1<<WGM00)|(1<<WGM01)|(1<<COM0B1);
  TCCR0B |= (1<<CS01)|(1<<CS00)|(1<<FOC0B);


}

void loop() {
  int duty_1 = analogRead(pot_1);
  if(duty_1<5) duty_1 = 0;
  if(duty_1>511) duty_1 = 511;
  OCR0B = duty_1/2;

}

Here is an example I wrote:

// Generating Variable-Frequency PWM on Timer1 of an Arduino UNO (Pins 9 and/or 10)
// Good for integer frequencies from 1 Hz to 65535 Hz.
// Written June 19th, 2020 by John Wasser


uint16_t TimerTOP = 0xFFFF;
byte PercentPWM_A = 50;  // Default to 50% duty cycle
byte PercentPWM_B = 50;  // Default to 50% duty cycle


void FPWMBegin()
{
  digitalWrite(9, LOW);
  pinMode(9, OUTPUT);
  digitalWrite(10, LOW);
  pinMode(10, OUTPUT);


  // Stop Timer/Counter1
  TCCR1A = 0;  // Timer/Counter1 Control Register A
  TCCR1B = 0;  // Timer/Counter1 Control Register B
  TIMSK1 = 0;   // Timer/Counter1 Interrupt Mask Register


  // Set Timer/Counter1 to Waveform Generation Mode 8: Phase and Frequency correct PWM with TOP set by ICR1
  TCCR1B |= (1 << WGM13);  // WGM=8
  TCCR1A |= (1 << COM1A1);  // Normal PWM on Pin 9
  TCCR1A |= (1 << COM1B1);  // Norml PWM on Pin 10


  FPWMSetFrequency(1000);  // Default to 1 kHz
}


bool FPWMSetFrequency(uint16_t frequency)
{
  byte prescaleBits; // 1, 2, 3, 4, 5
  uint16_t prescaleFactor;  // 1, 8, 64, 256, 1024
  uint32_t top32;


  // Clear the three clock select bits to stop the timer
  TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));


  // Find the smallest prescale factor that will fit the TOP value within 16 bits.
  // frequency = F_CPU / (2 * prescale *  TOP)
  // TOP = F_CPU / (2UL * prescale * frequency);


  prescaleBits = 1;
  prescaleFactor = 1;  // Used for 123-65535 Hz
  top32 = F_CPU / (2UL * prescaleFactor * frequency);
  if (top32 > 65535UL) // Too many clocks to count in 16 bits?
  {
    prescaleBits = 2;
    prescaleFactor = 8;  // Used for 16-122 Hz
    top32 = F_CPU / (2UL * prescaleFactor * frequency);
    if (top32 > 65535UL) // Too many clocks to count in 16 bits?
    {
      prescaleBits = 3;
      prescaleFactor = 64;  // Used for 2-15 Hz
      top32 = F_CPU / (2UL * prescaleFactor * frequency);
      if (top32 > 65535UL) // Too many clocks to count in 16 bits?
      {
        prescaleBits = 4;
        prescaleFactor = 256; // Only used for 1 Hz
        top32 = F_CPU / (2UL * prescaleFactor * frequency);
        if (top32 > 65535UL) // Too many clocks to count in 16 bits?
        {
          prescaleBits = 5;
          prescaleFactor = 1024;
          top32 = F_CPU / (2UL * prescaleFactor * frequency);
          if (top32 > 65535UL) // Too many clocks to count in 16 bits?
          {
            return false;
          }
        }
      }
    }
  }


  //  Serial.print("Freq: ");
  //  Serial.print(frequency);
  //  Serial.print(" prescale: ");
  //  Serial.print(prescaleFactor);
  //  Serial.print(" TOP: ");
  //  Serial.println(top32);


  if (top32 < 16)
    return false; // Require at least 16 levels of PWM


  TimerTOP = top32;


  OCR1A = (PercentPWM_A * (uint32_t)TimerTOP) / 100;
  OCR1B = (PercentPWM_A * (uint32_t)TimerTOP) / 100;


  ICR1 = TimerTOP;


  TCCR1B |= prescaleBits; // Set clock prescale bits to start the timer
  return true;
}


void FPWMSetDutyCyclePercentageA(byte percent)
{
  PercentPWM_A = min(percent, 100);
  OCR1A = (PercentPWM_A * (uint32_t)TimerTOP) / 100;
}


void FPWMSetDutyCyclePercentageB(byte percent)
{
  PercentPWM_B = min(percent, 100);
  OCR1A = (PercentPWM_B * (uint32_t)TimerTOP) / 100;
}


void setup()
{
  Serial.begin(115200);


  FPWMBegin();  // Start output at 1000 Hz, 50% duty cycle
}


void loop()
{
  // Call FPWMSetFrequency(uint16_t frequency) any time to change the output frequency
  // Call FPWMSetDutyCyclePercentageA(byte percent) at any time to change duty cycle
  // Call FPWMSetDutyCyclePercentageB(byte percent) at any time to change duty cycle
}

Hey,

Thank you for your reply. That program worked perfectly once I changed the pins to 11 and 12 for the ATMega2560 chip. I'm going to reverse engineer the code and make sure I understand it. Thank you for sharing!