使用Timer1设置Arduino Uno(ATMEGA328P)PWM

时间:2018-02-19 20:13:19

标签: arduino-uno pwm

我想用16MHz Arduino Uno(ATMEGA328P)设置自定义频率(12Hz)和占空比(20%)。

AVR计算器产量:

ICR1 = 20833
OCR1A = 4167

我已经阅读了大量的论坛和论坛,但出于某种原因,我无法让它发挥作用。

以下是我的代码:

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;
    }

如果有人可以提供帮助,那就太棒了!

谢谢,

迪伦

3 个答案:

答案 0 :(得分:0)

好的,所以我似乎找到了这个问题。我没有为模式14中的快速PWM正确设置寄存器(ATMEGA328P有15个timer1模式)。经过大量的实验和进一步的阅读,下面是可变频率和占空比的正确设置。 ICR1表示TOP值(控制频率),OCR1A表示开关值(占空比)。

// 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
}

注意:在使用Arduino IDE时清除T / C控制寄存器很重要,因为它在执行setup()函数之前会在幕后进行一些设置。

答案 1 :(得分:0)

在将变量“ strobeDutyCycle”设置为大于50的任何数字后,PWM停止工作。这是因为变量“ strobeDutyCycle”声明为uint8_t,这意味着,例如,如果我们有51,则100/51将等于1,因为unsigned int(“ uint8_t”)会舍去小数部分。因此,任何大于50的数字都将获得相同的数字,这将导致OC1A输出引脚上的输出为0。  解决该问题的方法是将变量“ strobeDutyCycle”声明为float,然后在除法完成后将其强制转换为uint16_t。

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;

答案 2 :(得分:0)

Dylan144GT 的答案非常好,但我想提醒一下,如果用最小的舍入误差完成方程,效果会更好。在他的代码中,“ strobeFreq”和“ strobeDutyCycle”全都以频率表示,方程式将返回整数,如果您尝试插入十进制值,这将引起一些问题,这将是有意义的。让我举一个例子:

完整的数据表“ ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A ”在第16.9节中提供了一些非常有用的公式。 CTC模式下的频率如下:

image

我们可以修改此方程式,以便为我们提供波形的周期而不是频率。为此,我们必须记得在16.9.2节中,CTC波形以切换模式(COMnA模式3)连接,因此我们必须删除2,因为我们正在使用快速PWM模式。我们还必须用ICRn替代OCRnA,因为在快速PWM模式14中,TOP值是由ICRn触发的,而不是像CTC模式4中那样由OCRnA触发的,就像这样:

image

我们可以重新计算等式,以根据周期为我们提供ICRn值:

image

如果用时钟频率(16.000.000Hz = 16MHz)用N代替1024,用f_clk_I / O代替N,我们将观察到在0.128s之类的时间段内,结果将是

image

这是不可能的,因为我们不能在公式中键入小数点。 如果将单位从秒更改为毫秒,反之亦然,则可以将ICRn精度提高到个位数。要获得6.25KHz的频率,我们可以选择以下值:

image

由于“ strobePeriod”为10,因此“ dutyCycle”可以为1到10之间的数字。例如:占空比30%的OCR1A为3。

更高精度的代码:

// 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 */
}

...

我不得不写这篇文章,因为我通常在Assembly中编写代码,并且我需要高精度的应用程序。我希望这已被证明对某人有用。