Правильная реализация Timer1 для генерации ШИМ

На Atmel ATmega328P ( таблица данных) есть три таймера, доступных для генерации ШИМ (timer0, timer1 и timer2).

У меня уже есть то, что мне нужно, используя 8-битный таймер 2, я просто обеспокоен использованием другого таймера вместо таймера 2, потому что таймер 2 используется в различных библиотеках, и я хотел бы иметь больше детализации. Таким образом, я хотел бы использовать 16-битный таймер1.

Вот то, что я использую, чтобы сгенерировать переменный коэффициент заполнения 25 кГц, используя timer2. Для этого примера давайте рассмотрим 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, должно быть невероятно просто, однако я не знаком с этими регистрами. Я искал объяснения за пределами таблицы. Я нашел этот пост: Секреты Arduino PWM, однако он охватывает только использование timer2.

Я попробовал следующее в соответствии с предложением Стефана, однако это просто приводит к тому, что оба выхода (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), но единственной комбинацией, которая сделала что-то другое, была следующая, которая дает частоту 25 кГц на D10, и D9 удерживал HIGH, но длительность HIGH застряла на 4 мкс независимо от регистров. (Я только что догадался и проверил с OCR1A, чтобы получить 25 кГц. Я не уверен, почему это работает.)

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 контакты выделены для B1 и B2(Изображение с https://bigdanzblog.wordpress.com)

Я пытался сменить доску, но у меня тот же результат.

Вот соответствующая информация из таблицы:

таблица atmega28p с 20-3 по 20-5

таблица atmega328p 20-6

atmega328p Таблица 20-7 Таймер Прескалер

1 ответ

Ваша проблема - порядок инициализации регистров. Ваш код таймера 2

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

работает, потому что OCR2A и OCR2B инициализируются после настройки TCCR2A и TCCR2A. Ваш код таймера 1

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

не работает, потому что ICR1, OCR1A и OCR1B инициализируются до настройки TCCR1A и TCCR1B. Здесь есть две разные проблемы, объяснения см. В комментариях в приведенном ниже коде.

       #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()
{
}

С помощью приведенного выше кода я получаю следующие сигналы

Если я перемещу инициализации ICR1, OCR1A и OCR1B между TCCR1B = 0 и TCCR1A = ... Я получаю следующее

что, как я полагаю, соответствует тому всплеску длительностью 4 мкс, которое вы наблюдали.

Таймер 1 имеет 2 выхода, OC1A а также OC1B, Оба запускают один и тот же аппаратный таймер и поэтому синхронизируются. Таймер может работать в трех различных режимах: режим быстрого ШИМ, режим ШИМ с коррекцией фазы и режим с коррекцией фазы и частоты. Вам нужно будет выбрать правильный режим для вас, а также правильный прескалер таймера, который подходит для вашего приложения. Ниже приведен пример.

// 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);
}
Другие вопросы по тегам