Генерация сигнала ШИМ переменной частоты для AVR

Я хочу сгенерировать сигнал ШИМ с переменной частотой и фиксированным рабочим циклом (50%). Частота должна варьироваться от 0 до 25 кГц. Это для микроконтроллера ATMEGA32U4, и я пишу его на C с помощью Atmel Studio. Я прочитал таблицу, но не могу понять, как проводить расчеты и какие режимы мне следует использовать. После изучения различных руководств я обнаружил, что лучше всего использовать режим CTC.

Как мне выбрать, какой прескаляр я должен использовать, поскольку частота является переменной? Мне нужно использовать прерывания? Любая помощь в настройке этих регистров таймера приветствуется.


person ADGAN    schedule 13.07.2016    source источник
comment
Вы должны попытаться спросить что-то более конкретное. Вопрос в его нынешнем виде потребует в качестве ответа другого учебника. В чем заключалась проблема с учебниками, которые вы читали? Возможно, процитируйте разделы / расчеты из учебника, который вам непонятен.   -  person Rev    schedule 13.07.2016


Ответы (2)


32U4 имеет таймеры, идентичные 328P, на которых я тестировал вашу проблему. Я использовал Таймер 1, который предлагает лучшее разрешение. Этот таймер может работать в режиме CTC, а канал A может быть привязан к фиксированному выходному выводу в режиме переключения-сравнения-сопоставления. Это делает настройку чрезвычайно простой и не требует логики прерывания. Частотой можно управлять, просто записывая в OCR1A (этот регистр имеет двойную буферизацию, поэтому изменения частоты должны происходить без сбоев) *.

В режиме CTC Таймер 1 имеет выходную частоту:

f n x = f_cpu / (2 * n * (1 + x))

где n - значение предварительного масштабирования, а x - регистр сравнения переполнения. Изучение возможных диапазонов частот на тактовой частоте 16 МГц дает:

|   N |  f-min |     f-max |     r-min |   r-max   | x-100 | x-25k |
+-----+--------+-----------+-----------+-----------+-------+-------+
|   1 | 122.1  | 8,000,000 | 4,000,000 | 0.0019    |   n/a |   319 |
|   8 |  15.3  | 1,000,000 |   500,000 | 0.00023   | 9,999 |    39 |
|  64 |   1.91 |   125,000 |    62,500 | 0.000029  | 1,249 |     4 |
| 256 |   0.49 |    31,250 |    15,625 | 0.0000073 |   311 |   n/a |
|1024 |   0.12 |     7,812 |     3,906 | 0.0000018 |    77 |   n/a |

где N - предварительная настройка масштаба, f-min и f-max - минимальная и максимальная достижимые частоты, r-min и r-max - минимальное и максимальное разрешение по частоте и, наконец, x-100 и x-25k - требуемые значения для OCR1A для Выход 100 Гц и 25 кГц соответственно.

В качестве законченного рабочего примера приведем программу, которая циклически переключает частоты 1 Гц, 2, 5, 10 ... 500 кГц с шагом в 2 секунды, что достаточно для наблюдения за работой осциллографа:

#include <avr/io.h>
#include <util/delay.h>

struct CTC1
{
    static void setup()
    {
        // CTC mode with TOP-OCR1A

        TCCR1A = 0;
        TCCR1B = _BV(WGM12);

        // toggle channel A on compare match

        TCCR1A = (TCCR1A & ~(_BV(COM1A1) | _BV(COM1A0))) | _BV(COM1A0);

        // set channel A bound pin to output mode

        DDRB |= _BV(1); // PB1 on 328p, use _BV(5) for PB5 on 32U4
    }

    static void set_freq(float f)
    {
        static const float f1 = min_freq(1), f8 = min_freq(8), f64 = min_freq(64), f256 = min_freq(256);

        uint16_t n;

        if (f >= f1)        n = 1;
        else if (f >= f8)   n = 8;
        else if (f >= f64)  n = 64;
        else if (f >= f256) n = 256;
        else                n = 1024;

        prescale(n);

        OCR1A = static_cast<uint16_t>(round(F_CPU / (2 * n * f) - 1));
    }

    static void prescale(uint16_t n)
    {
        uint8_t bits = 0;

        switch (n)
        {
            case    1:  bits = _BV(CS10);               break;
            case    8:  bits = _BV(CS11);               break;
            case   64:  bits = _BV(CS11) | _BV(CS10);   break;
            case  256:  bits = _BV(CS12);               break;
            case 1024:  bits = _BV(CS12) | _BV(CS10);   break;
            default:    bits = 0;
        }

        TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11) | _BV(CS10))) | bits;
    }

    static inline float min_freq(uint16_t n)
    {
        return ceil(F_CPU / (2 * n * 65536));
    }
};

void setup()
{
    CTC1::setup();
}

void loop()
{
    for (uint8_t x = 0; x < 6; ++x)
        for (uint8_t y = 0; y < 3; ++y)
        {
            float k = y > 0 ? (y > 1 ? 5 : 2) : 1;

            CTC1::set_freq(k * pow(10, x));
            _delay_ms(2000);
        }
}

int main()
{
    setup();
    for (;;)
        loop();
}

Сигнал наблюдается на PB1 (цифровой вывод 9 на Arduino Uno). Обратите внимание, что на 32U4 канал-A привязан к PB5.

Как любезно прокомментировал Александр З., регистр OCR1A не имеет двойной буферизации в режиме CTC. При переключении частот это может привести к серьезным сбоям, например:

введите здесь описание изображения

В зависимости от приложения это может быть легко исправлено зацикливанием занятости (хотя это может плохо работать для очень высоких частот или может вызывать неприемлемые задержки на очень низких частотах):

while (TCNT1 > x)
    ;
OCR1A = x;

Производство:

введите здесь описание изображения

person marangisto    schedule 24.07.2016
comment
Частотой можно управлять, просто записывая в OCR1A (этот регистр имеет двойную буферизацию, поэтому изменения частоты должны происходить без сбоев). Нет, OCR1A не имеет двойной буферизации в режиме CTC: таблица данных на странице 162 говорится: Примечание: изменение TOP на значение, близкое к BOTTOM, во время работы счетчика должно выполняться с осторожностью, поскольку режим CTC не обеспечивает двойной буферизации. Если новое значение, записанное в OCR1A, ниже, чем текущее значение TCNT1, счетчик пропустит совпадение при сравнении. - person Aleksander Z.; 11.08.2016
comment
Спасибо, что указали на это - я соответствующим образом обновил сообщение. Кстати, если у вас есть лучшие исправления для устранения этих глюков, пожалуйста, напишите. - person marangisto; 14.08.2016
comment
Я получил ответ: таблица данных, стр. 164: В режиме Fast PWM можно получить частотный сигнал с коэффициентом заполнения 50%, выбрав OC1A для переключения его логического уровня при каждом сопоставлении (COM1A [1: 0] = 0x1 ). Это применимо, только если OCR1A используется для определения значения TOP (WGM1 [3: 0] = 0xF). Сгенерированный сигнал будет иметь максимальную частоту f_OC1A = f_clk_I / O / 2, когда OCR1A установлен на ноль (0x0000). Эта функция аналогична переключателю OC1A в режиме CTC, за исключением того, что функция двойного буфера блока сравнения выходов включена в режиме Fast PWM. - person Aleksander Z.; 15.08.2016

Я вообще не использую ATMEGA32U4, мне больше нравится AT32UC3, извините, нет точного кода ... но почему бы вам просто не установить таймер на удвоенную частоту и не переключить контакт внутри него? так как ваша обязанность всегда составляет 50%, это должно сработать. Обновление частоты может выполняться при каждом четном или каждом n-th вызове счетчика / таймера ISR ...

что-то вроде:

const int n=10; // must be even to ensure 50% duty
volatile int ix=0;
volatile int frequency=10000;
isr_timercounter()
 {
 // clear IRQ
 // toggle pin
 ix++; if (ix>=n)
  {
  ix=0;
  // set timer/counter to 2*frequency
  }
 }

void main()
 {
 // init timer/counter
 for (;;)
  {
  //do your stuff
  frequency = ???;
  }
 }
person Spektre    schedule 15.07.2016