正确实现Timer1以产生PWM

时间:2018-08-29 18:04:15

标签: c++ timer arduino cpu-registers pwm

在Atmel ATmega328P(datasheet)上,有三个可用于产生PWM的定时器(timer0,timer1和timer2)。

我已经有了使用8位timer2的需求,我只关心使用timer2的不同计时器instad,因为在各种库中都使用了timer2,并且我想拥有更多的粒度。因此,我想使用16位timer1。

这就是我使用Timer2生成25 kHz可变占空比的原因。对于此示例,让我们考虑35%的占空比:

void setup() 
{

    /*
    16*10^6/ [prescalar] / ([OCR2A]) / 2 = [desired frequency]
    16*10^6/ 8 / [OCR2A] / 2 = 25*10^3

    Prescalar table for Timer2 (from datasheet section 17-9):
    CS22    CS21    CS20
      0     0       0       = No clock source (Timer/couter stopped)
      0     0       1       = clkT2S/(No prescaling)
      0     1       0       = clkT2S/8 (From prescaler)
      0     1       1       = clkT2S/32 (From prescaler)
      1     0       0       = clkT2S/64 (From prescaler)
      1     0       1       = clkT2S/128 (From prescaler)
      1     1       0       = clkT2S/256 (From prescaler)
      1     1       1       = clkT2S/1024 (From prescaler)
    */

    pinMode(3, OUTPUT);
    TCCR2B = _BV(WGM22) | _BV(CS21);
    TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0) | _BV(WGM20);
    OCR2A = 40; 
    OCR2B = 16; //40*0.35=16
}

void loop()
{ 
}

要使用timer1获得相同的结果必须非常简单,但是我对这些寄存器不熟悉。 我一直在寻找数据表之外的解释。我发现了这篇文章:Secrets of Arduino PWM,但是它仅涉及对timer2的使用。

我按照斯蒂芬(Stephan)的建议尝试了以下方法,但这只会导致两个输出(D9和D10)都保持高电平:

void setup() 
{

    pinMode(9, OUTPUT); //D9
    pinMode(10, OUTPUT); //D10

    // Set GPIO for timer1 output for OC1A and OC1B
    //DDRB |= (1 << DDB1) | (1 << DDB2);

    ICR1 = 0xFFFF;

    // 25% duty cycle
    OCR1A = 0x0009;

    // 75% duty cycle
    //OCR1B = 0xBFFF;

    //20.14.1, pg170
    // set none-inverting mode
    TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));

    //Table 20-6, pg171
    // Fast PWM mode
    TCCR1A |= (1 << WGM11);
    TCCR1B |= (1 << WGM12) | (1 << WGM13);

    // START the timer with no prescaler
    TCCR1B |= (1 << CS10);

}

void loop()
{
}

我尝试更改所有内容(ICR1,OCR1A,TCCR1A),但唯一做不到任何改变的组合如下:D10在D10上提供25kHz的频率,而D9保持HIGH,但无论如何,HIGH持续时间均停留在4μs寄存器。 (我只是猜测并使用OCR1A进行检查,以获得25kHz的频率。我不确定为什么会这样。)

void setup() 
{

    pinMode(9, OUTPUT);
    pinMode(10, OUTPUT);

    // Set GPIO for timer1 output for OC1A and OC1B
    //DDRB |= (1 << DDB1) | (1 << DDB2);

    ICR1 = 0xFFFF;

    // 25% duty cycle
    OCR1A = 0x0009;

    // 75% duty cycle 
    //This line causes both outputs to be held HIGH
    //OCR1B = 0xBFFF;

    //20.14.1, pg170
    // set none-inverting mode
    TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));

    //Table 20-6, pg171
    // Fast PWM mode
    TCCR1A |= (1 << WGM11);
    TCCR1B |= (1 << WGM12) | (1 << WGM13);

    // START the timer with no prescaler
    TCCR1B |= (1 << CS10);

}

void loop()
{
}

我正在使用Arduino Nano分支板进行原型制作,其中D9和D10为timer1输出引脚:

Arduino Nano pins highlighted for B1 and B2 (图片来自https://bigdanzblog.wordpress.com

我尝试过更换板子,但结果却相同。

以下是数据表中的相关信息:

atmega28p table 20-3 through 20-5

atmega328p table 20-6

atmega328p table 20-7 timer prescaler

2 个答案:

答案 0 :(得分:0)

Timer1具有2个输出,OC1AOC1B。两者都使用相同的硬件计时器运行,因此是同步的。定时器能够以三种不同的模式运行:快速PWM模式,相位校正的PWM模式以及相位和频率校正的模式。您将需要为您选择正确的模式以及适合您的应用的正确定时器预分频器。下面是一个示例。

// Timer1 Resolution 16-bit
// Timer1 A output at 25% Duty Cycle, Fast PWM Mode
// Timer1 B output at 75% Duty Cycle, Fast PWM Mode 

#include <avr/io.h>

int main(void)
{
    // Set GPIO for timer1 output for OC1A and OC1B
    DDRB |= (1 << DDB1) | (1 << DDB2);

    ICR1 = 0xFFFF;

    // 25% duty cycle
    OCR1A = 0x3FFF;

    // 75% duty cycle
    OCR1B = 0xBFFF;

    // set none-inverting mode
    TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));

    // Fast PWM mode
    TCCR1A |= (1 << WGM11);
    TCCR1B |= (1 << WGM12)|(1 << WGM13);

    // START the timer with no prescaler
    TCCR1B |= (1 << CS10);

    while (1);
}

答案 1 :(得分:0)

您的问题是寄存器的初始化顺序。您的计时器2代码

TCCR2B = _BV(WGM22) | _BV(CS21);
TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0) | _BV(WGM20);
OCR2A = 40; 
OCR2B = 16; //40*0.35=16

之所以起作用,是因为在配置TCCR2A和TCCR2A后初始化了OCR2A和OCR2B。您的计时器1代码

ICR1 = 0xFFFF;
OCR1A = 0x0009;
OCR1B = 0xBFFF;
TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));
TCCR1A |= (1 << WGM11);
TCCR1B |= (1 << WGM12) | (1 << WGM13);
TCCR1B |= (1 << CS10);

不起作用,因为配置TCCR1A和TCCR1B之前,已初始化ICR1,OCR1A和OCR1B。这里有两个不同的问题,请参见下面代码中的注释以获取解释。

#include <inttypes.h>

#include "Arduino.h"
#include <avr/io.h>

// http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf
// https://content.arduino.cc/assets/Pinout-UNOrev3_latest.pdf

void setup()
{
    uint8_t sreg = SREG;
    cli();

    // Stop timer before configuring
    TCCR1B = 0;

    // 16.11.1 TCCR1A – Timer/Counter1 Control Register A
    TCCR1A =
        0
        | (1 << COM1A1) | (0 << COM1A0)  // Clear OC1A on Compare Match, set OC1A at BOTTOM (non-inverting mode)
        | (1 << COM1B1) | (0 << COM1B0)  // Clear OC1B on Compare Match, set OC1B at BOTTOM (non-inverting mode)
        | (1 << WGM11) | (0 << WGM10)    // Fast PWM mode 14 (TOP = ICR1), part 1/2
        ;

    // 16.11.2 TCCR1B – Timer/Counter1 Control Register B
    TCCR1B =
        0
        | (1 << WGM13) | (1 << WGM12)    // Fast PWM mode 14 (TOP = ICR1), part 2/2
        ;

    // IMPORTANT NOTE ABOUT ORDER OF INITIALIZATION:
    //   "The ICR1 Register can only be written when using a Waveform
    //   Generation mode that utilizes the ICR1 Register for defining
    //   the counter’s TOP value. In these cases the Waveform
    //   Generation mode (WGM13:0) bits must be set before the TOP
    //   value can be written to the ICR1 Register."
    // Thus initializing OCR1 before TCCR1A and TCCR1B has been
    // configured with Fast PWM mode 14 is wrong.

    // Set TOP value
    ICR1 = 0xFFFF;

    // IMPORTANT NOTE ABOUT ORDER OF INITIALIZATION:
    //   "The OCR1x Register is double buffered when using any of the
    //   twelve Pulse Width Modulation (PWM) modes. For the Normal
    //   and Clear Timer on Compare (CTC) modes of operation, the
    //   double buffering is disabled."
    // If initializing OCR1A before configuring TCCR1A and TCCR1B to
    // a PWM mode the value is written to the non-buffered OCR1A
    // register and the buffered OCR1A register contains some "random",
    // unused garbage value. When later changing to PWM the buffered
    // register will be enabled, and its existing garbage value will
    // be used.
    // Thus initializing OCR1A/OCR1B before TCCR1A and TCCR1B has
    // been configured with Fast PWM is wrong.

    // 25% duty cycle - yellow scope signal
    OCR1A = 0x3FFF;

    // 75% duty cycle - blue scope signal
    OCR1B = 0xBFFF;

    // 14.4.3 DDRB – The Port B Data Direction Register
    DDRB =
        0
        | (1 << DDB1) // PB1 (aka OC1A) as output - pin 9 on Arduino Uno
        | (1 << DDB2) // PB2 (aka OC1B) as output - pin 10 on Arduino Uno
        ;

    // Start the timer with no prescaler
    TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);

    SREG = sreg;
}

void loop()
{
}

使用上面的代码,我得到以下信号

working screenshot

如果将ICR1,OCR1A和OCR1B初始化移动到TCCR1B = 0TCCR1A = ...之间,则会得到以下信息

non-working screenshot

我认为与您观察到的4μs峰值相对应。