使用 Timer1 设置 Arduino Uno (ATMEGA328P) PWM

Setting up Arduino Uno (ATMEGA328P) PWM with Timer1

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

AVR 计算器收益率:

ICR1 = 20833
OCR1A = 4167

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

下面是我的代码:

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

如果有人能帮忙,那就太好了!

谢谢,

迪伦

好的,我似乎找到了问题所在。我没有为模式 14 中的快速 PWM 正确设置寄存器(ATMEGA328P 有 15 种定时器 1 模式)。经过大量实验和进一步阅读,下面是可变频率和占空比的正确设置。 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 时,清除 Timer/Counter 控制寄存器很重要,因为它在执行 setup() 函数之前会在幕后进行一些设置。

变量"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;

Dylan144GT的回答非常好,但我想提醒的是,如果用最少的舍入误差完成方程式会更好。在他的代码中,"strobeFreq" 和 "strobeDutyCycle" 都是以频率表示的,方程式将 return 整数,如果您尝试插入小数值,这会导致一些问题,这是有道理的.让我举个例子:

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

image

我们可以修改这个等式,得到波形的周期而不是频率。为此,我们必须回忆起在第 16.9.2 节中,CTC 波形以 Toggle 模式(COMnA 模式 3)连接,因此我们必须删除 2,因为我们正在使用 Fast-PWM 模式。我们还必须用 ICRn 替换 OCRnA,因为在 Fast-PWM 模式 14 中,TOP 值由 ICRn 触发,而不是像 CTC 模式 4 中那样由 OCRnA 触发,如下所示:

image

我们可以重新排列方程式,根据周期给出 ICRn 值:

image

如果我们将 N 替换为 1024,将 f_clk_I/O 替换为时钟频率 (16.000.000Hz = 16MHz),我们将观察到对于某些周期,例如 0.128s,结果将是

image

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

image

因为 "strobePeriod" 是 10,所以 "dutyCycle" 可以是 1 到 10 之间的数字。例如:dutyCycle 30% 是 3 的 OCR1A。

更正了更高精度值的代码:

// 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 中编码并且我需要高精度的应用程序。我希望这对某人有用。