2

I want to setup a custom frequency(12Hz) and duty cycle (20%) with a 16MHz Arduino Uno (ATMEGA328P).

AVR Calculator yields:

ICR1 = 20833
OCR1A = 4167

I have read a tonne of forums and tuts but for some reason I cannot get this to work.

Below is my code:

void setup()
{
  // PB1 is now an output (Pin9 Arduino UNO)
  DDRB |= (1 << DDB1);
  // PB2 is now an output (Pin10 Arduino UNO)
  DDRB |= (1 << DDB2);

      // Set PWM frequency/top value
      ICR1 = 20833;

      // Set PWM duty cycle
      OCR1A = 4167;

      // Set inverting mode (start low, go high)
      TCCR1A |= (1 << COM1A1);
      TCCR1A |= (1 << COM1B1);
      TCCR1A |= (1 << COM1A0);
      TCCR1A |= (1 << COM1B0);

      // Set fast PWM Mode
      TCCR1A |= (1 << WGM11);
      TCCR1B |= (1 << WGM12);
      TCCR1B |= (1 << WGM13);

      // Set prescaler to 64 and starts PWM
      TCCR1B |= (1 << CS10);
      TCCR1B |= (1 << CS11);
    }

    void loop() {
      // Refresh PWM frequency
      OCR1A = 4167;
    }

If someone can help, that would be great!

Thanks,

Dylan

Dylan144GT
  • 93
  • 1
  • 8

3 Answers3

5

Okay, so I seemed to find the issue. I was not setting up the registers correctly for fast PWM in mode 14 (ATMEGA328P has 15 timer1 modes). After a lot of experimentation and further reading, below is the correct setup for variable frequency and duty cycle. ICR1 denotes the TOP value (controls frequency) and OCR1A gives the switching value (duty cycle).

// ADJUSTABLE VARIABLES
// Strobe frequency
uint16_t timer1Prescaler = 64;
uint8_t strobeFreq = 20,
        strobeDutyCycle = 20;

void setup
{
  // Set PB1 to be an output (Pin9 Arduino UNO)
  DDRB |= (1 << PB1);

  // Clear Timer/Counter Control Registers
  TCCR1A = 0;
  TCCR1B = 0;

  // Set non-inverting mode
  TCCR1A |= (1 << COM1A1);

  // Set fast PWM Mode 14
  TCCR1A |= (1 << WGM11);
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << WGM13);

  // Set prescaler to 64 and starts PWM
  TCCR1B |= (1 << CS10);
  TCCR1B |= (1 << CS11);

  // Set PWM frequency/top value
  ICR1 = (F_CPU / (timer1Prescaler*strobeFreq)) - 1;
  OCR1A = ICR1 / (100 / strobeDutyCycle);
}

void loop()
{
  // main loop code
}

NOTE: Clearing the Timer/Counter Control Registers is important when using the Arduino IDE as it does do a little setup behind the scenes before executing the setup() function.

Dylan144GT
  • 93
  • 1
  • 8
0

After variable "strobeDutyCycle" has been set to any number greater than 50, PWM stoped working. This is because variable "strobeDutyCycle" is declared as uint8_t, which means if we have, for instance, 51, then 100/51 will be equal to 1 because unsigned int ("uint8_t") cuts the decimal part. And hence any number greater than 50 will gain the same one, which leads to 0 output on the OC1A output pin. The solution for the problem is to declare the variable "strobeDutyCycle" as float and then cast it to uint16_t after the division is done.

float strobeDutyCycle = 60;
uint16_t timer1Prescaler = 1024;
uint16_t strobeFreq= 2;
...
float pwmFrequency = (F_CPU / (timer1Prescaler*strobeFreq)) - 1;
float dutyCycleDivisor = 100 / strobeDutyCycle;
float pwmValueWithDutyCycle = pwmFrequency  / dutyCycleDivisor;

ICR1 = (uint16_t)pwmFrequency;
OCR1A = (uint16_t)pwmValueWithDutyCycle;
0

Dylan144GT's answer is very nice, but I want to remind that equations are better if done with the least rounding errors. In his code the "strobeFreq" and "strobeDutyCycle" are all in terms of frequency, and the equations will return integers, which will cause some problems if you try to insert decimal values, which would make sense. Let me give you an example:

The full datasheet "ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A" gives us some very useful equations in section 16.9. The frequency in CTC mode is the following:

image

We can modify this equation to give us the period of the waveform instead of the frequency. To do this we have to recall that in Section 16.9.2, the CTC waveform is wired in Toggle mode (COMnA mode 3), so we have to remove the 2 because we're working with Fast-PWM mode. We'll also have to substitute OCRnA by ICRn because in Fast-PWM mode 14, the TOP value is triggered by ICRn and not OCRnA like in CTC mode 4, like this:

image

We can rearrange the equation to give us the ICRn value based on the period:

image

If we substitute N by 1024 and the f_clk_I/O by the clock frequency (16.000.000Hz = 16MHz), we'll observe that for some periods like 0.128s, the result would be

image

which we know is not possible because we cannot type decimals floats in the equation. If we change units from seconds to milliseconds, or vice versa , we can increase the ICRn precision up to single digits. To obtain frequency of 6.25KHz we can select the following values:

image

Since the "strobePeriod" is 10, the "dutyCycle" can be numbers between 1 to 10. For example: dutyCycle 30% is an OCR1A of 3.

Corrected code for higher precision values:

// ADJUSTABLE VARIABLES
// Strobe frequency
uint16_t timer1Prescaler = 64; /* 1, 8, 64, 256, 1024 */
uint8_t strobePeriod = 50, /* milliseconds */
        strobeDutyCycle = 20; /* percent */

...

  // Set PWM frequency/top value
  ICR1 = (F_CPU*strobePeriod / (timer1Prescaler*1000) ) - 1; /* equals 12499 */
  OCR1A = ICR1 / (100 / strobeDutyCycle); /* equals 2499 */
}

...

I had to write this because I usually code in Assembly and I require applications with high precision. I hope this has proven useful for someone.

StickySli
  • 81
  • 8