Увеличение частоты ШИМ

В чём проблема?


Как мы уже говорили в уроке про функции времени и про ШИМ сигнал, у микроконтроллера есть несколько таймеров, которые могут выполнять разные функции, в частности - генерировать ШИМ сигнал. Чтобы таймер генерировал ШИМ, его нужно предварительно настроить при помощи редактирования регистра таймера. Когда мы работаем в Arduino IDE, таймеры настраиваются без нашего ведома в библиотеке Arduino.h, и собственно получают настройки, которые пожелали разработчики. И вот настройки эти не очень хорошие: частота ШИМ по умолчанию низкая, возможности таймеров не используются в полной мере. Посмотрим на стандартный ШИМ у ATmega328 (Arduino UNO/Nano/Pro Mini):

Таймер Пины Частота Разрешение
Timer 0 D5 и D6 976 Гц 8 бит (0-255)
Timer 1 D9 и D10 488 Гц 8 бит (0-255)
Timer 2 D3 и D11 488 Гц 8 бит (0-255)

На самом деле все таймеры спокойно могут выдавать 64 кГц ШИМ сигнал, а таймер 1 - он вообще 16 битный, и на той частоте, которую ему дали Arduino, мог бы работать с разрешением 15 бит вместо 8, а это, на минуточку, 32768 градаций заполнения вместо 256!!! Так к чему такая несправедливость? Таймер 0 занимается отсчётом времени, и настроен так, чтобы миллисекунды тикали точно. А остальные таймеры просто причёсаны к нулевому под одну гребёнку, чтобы у ардуинщика не возникло лишних проблем. Такой подход в целом можно понять, но сделали бы хоть пару стандартных функций для более высокой частоты, ну серьёзно! Ладно, если они не сделали, то сделаем мы.

Настройка частоты ШИМ через регистры


Как мы обсуждали в предыдущем уроке, микроконтроллер настраивается на низком уровне через регистры, так вот генерация ШИМ настраивается через регистры таймеров. Далее вы найдёте несколько готовых "кусков" кода, которые достаточно вставить в setup(), и частота ШИМ будет перенастроена (меняется предделитель и режим работы таймера). Работать с ШИМ сигналом можно будет всё так же при помощи функции analogWrite(), управляя заполнением ШИМ на стандартных пинах.

Меняем частоту ШИМ на ATmega328 (Arduino UNO/Nano/Pro Mini)


Пины D5 и D6 (Timer 0) - 8 бит
// Пины D5 и D6 - 62.5 кГц
TCCR0B = 0b00000001;	// x1
TCCR0A = 0b00000011;	// fast pwm

// Пины D5 и D6 - 31.4 кГц
TCCR0B = 0b00000001;	// x1
TCCR0A = 0b00000001;	// phase correct

// Пины D5 и D6 - 7.8 кГц
TCCR0B = 0b00000010;	// x8
TCCR0A = 0b00000011;	// fast pwm

// Пины D5 и D6 - 4 кГц
TCCR0B = 0b00000010;	// x8
TCCR0A = 0b00000001;	// phase correct

// Пины D5 и D6 - 976 Гц - по умолчанию
TCCR0B = 0b00000011;	// x64
TCCR0A = 0b00000011;	// fast pwm

// Пины D5 и D6 - 490 Гц
TCCR0B = 0b00000011;	// x64
TCCR0A = 0b00000001;	// phase correct

// Пины D5 и D6 - 244 Гц
TCCR0B = 0b00000100;	// x256
TCCR0A = 0b00000011;	// fast pwm

// Пины D5 и D6 - 122 Гц
TCCR0B = 0b00000100;	// x256
TCCR0A = 0b00000001;	// phase correct

// Пины D5 и D6 - 61 Гц
TCCR0B = 0b00000101;	// x1024
TCCR0A = 0b00000011;	// fast pwm

// Пины D5 и D6 - 30 Гц
TCCR0B = 0b00000101;	// x1024
TCCR0A = 0b00000001;	// phase correct
Пины D9 и D10 (Timer 1) - 8 бит
// Пины D9 и D10 - 62.5 кГц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00001001;	// x1 fast pwm

// Пины D9 и D10 - 31.4 кГц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00000001;	// x1 phase correct

// Пины D9 и D10 - 7.8 кГц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00001010;	// x8 fast pwm

// Пины D9 и D10 - 4 кГц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00000010;	// x8 phase correct

// Пины D9 и D10 - 976 Гц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00001011;	// x64 fast pwm

// Пины D9 и D10 - 490 Гц - по умолчанию
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00000011;	// x64 phase correct

// Пины D9 и D10 - 244 Гц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00001100;	// x256 fast pwm

// Пины D9 и D10 - 122 Гц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00000100;	// x256 phase correct

// Пины D9 и D10 - 61 Гц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00001101;	// x1024 fast pwm

// Пины D9 и D10 - 30 Гц
TCCR1A = 0b00000001;	// 8bit
TCCR1B = 0b00000101;	// x1024 phase correct
Пины D9 и D10 (Timer 1) - 10 бит
// Пины D9 и D10 - 15.6 кГц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00001001;	// x1 fast pwm

// Пины D9 и D10 - 7.8 кГц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00000001;	// x1 phase correct

// Пины D9 и D10 - 2 кГц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00001010;	// x8 fast pwm

// Пины D9 и D10 - 977 Гц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00000010;	// x8 phase correct

// Пины D9 и D10 - 244 Гц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00001011;	// x64 fast pwm

// Пины D9 и D10 - 122 Гц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00000011;	// x64 phase correct

// Пины D9 и D10 - 61 Гц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00001100;	// x256 fast pwm

// Пины D9 и D10 - 30 Гц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00000100;	// x256 phase correct

// Пины D9 и D10 - 15 Гц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00001101;	// x1024 fast pwm

// Пины D9 и D10 - 7.5 Гц 10bit
TCCR1A = 0b00000011;	// 10bit
TCCR1B = 0b00000101;	// x1024 phase correct
Пины D3 и D11 (Timer 2) - 8 бит
// Пины D3 и D11 - 62.5 кГц
TCCR2B = 0b00000001;	// x1
TCCR2A = 0b00000011;	// fast pwm

// Пины D3 и D11 - 31.4 кГц
TCCR2B = 0b00000001;	// x1
TCCR2A = 0b00000001;	// phase correct

// Пины D3 и D11 - 8 кГц
TCCR2B = 0b00000010;	// x8
TCCR2A = 0b00000011;	// fast pwm

// Пины D3 и D11 - 4 кГц
TCCR2B = 0b00000010;	// x8
TCCR2A = 0b00000001;	// phase correct

// Пины D3 и D11 - 2 кГц
TCCR2B = 0b00000011;	// x32
TCCR2A = 0b00000011;	// fast pwm

// Пины D3 и D11 - 980 Гц
TCCR2B = 0b00000011;	// x32
TCCR2A = 0b00000001;	// phase correct

// Пины D3 и D11 - 980 Гц
TCCR2B = 0b00000100;	// x64
TCCR2A = 0b00000011;	// fast pwm

// Пины D3 и D11 - 490 Гц - по умолчанию
TCCR2B = 0b00000100;	// x64
TCCR2A = 0b00000001;	// phase correct

// Пины D3 и D11 - 490 Гц
TCCR2B = 0b00000101;	// x128
TCCR2A = 0b00000011;	// fast pwm

// Пины D3 и D11 - 245 Гц
TCCR2B = 0b00000101;	// x128
TCCR2A = 0b00000001;	// phase correct

// Пины D3 и D11 - 245 Гц
TCCR2B = 0b00000110;	// x256
TCCR2A = 0b00000011;	// fast pwm

// Пины D3 и D11 - 122 Гц
TCCR2B = 0b00000110;	// x256
TCCR2A = 0b00000001;	// phase correct

// Пины D3 и D11 - 60 Гц
TCCR2B = 0b00000111;	// x1024
TCCR2A = 0b00000011;	// fast pwm

// Пины D3 и D11 - 30 Гц
TCCR2B = 0b00000111;	// x1024
TCCR2A = 0b00000001;	// phase correct
Пример использования
void setup() {
  // Пины D5 и D6 - 7.8 кГц
  TCCR0B = 0b00000010;  // x8
  TCCR0A = 0b00000011;  // fast pwm

  // Пины D3 и D11 - 62.5 кГц
  TCCR2B = 0b00000001;  // x1
  TCCR2A = 0b00000011;  // fast pwm

  // Пины D9 и D10 - 7.8 кГц 10bit
  TCCR1A = 0b00000011;  // 10bit
  TCCR1B = 0b00000001;  // x1 phase correct

  analogWrite(3, 15);
  analogWrite(5, 167);
  analogWrite(6, 241);
  analogWrite(9, 745);    // да, диапазон 0-1023
  analogWrite(10, 345);   // да, диапазон 0-1023
  analogWrite(11, 78);
}

void loop() {
}
Важно! При изменении частоты на пинах D5 и D6 вы потеряете функции времени (millis(), delay(), pulseIn(), setTimeout() и прочие), они будут работать некорректно. Также перестанут работать библиотеки, которые их используют!

Если очень хочется


Если очень хочется или очень нужен разогнанный ШИМ на системном (нулевом) таймере без потери функций времени, то можно их скорректировать следующим образом:

#define micros() (micros() >> CORRECT_CLOCK)
#define millis() (millis() >> CORRECT_CLOCK)
void fixDelay(uint32_t ms) {
  delay(ms << CORRECT_CLOCK);
}

Дефайны нужно размещать перед подключением библиотек, чтобы они залезли в код и подменили функции. Единственное, скорректировать delay внутри другой библиотеки таким образом не получится, для себя можно использовать fixDelay() как написано выше. Самое главное - CORRECT_CLOCK. Это целое число, равное отношению делителя таймера по умолчанию и нового установленного (для разгона ШИМ). Например ставим ШИМ 8 кГц. Из списка выше видим, что по умолчанию делитель 64, а 7.8 кГц будет 8, то есть в 8 раз меньше. CORRECT_CLOCK ставим соответствующий.

#define CORRECT_CLOCK 8
void fixDelay(uint32_t ms) {
  delay(ms << CORRECT_CLOCK);
}

void setup() {
  pinMode(13, 1);
  // Пины D5 и D6 - 4 кГц
  TCCR0B = 0b00000010;  // x8
  TCCR0A = 0b00000001;  // phase correct
}

void loop() {
  digitalWrite(13, !digitalRead(13));
  fixDelay(1000);
}

Библиотеки для работы с ШИМ


Помимо ковыряния регистров в ручную существуют готовые библиотеки, позволяющие изменить частоту ШИМ Ардуино. Рассмотрим некоторые из них:

  • Библиотека PWM (GitHub) - мощная библиотека, позволяющая менять частоту ШИМ на микроконтроллерах ATmega48 / 88 / 168 / 328 / 640 / 1280 / 1281 / 2560 / 2561, из них 328 стоит на UNO/Nano/Mini, а 2560 - это Arduino Mega.
    • Позволяет установить любую частоту ШИМ, предделитель, TOP
    • При работе с 8-битными таймерами доступен только один канал (например на ATmega328 останутся D3, D5, D9 и D10)
    • Позволяет работать с 16-битными таймерами на более высоком разрешении (16 бит вместо стандартных 8)
    • Библиотека написана очень сложно, по кускам её растащить не получится
    • Смотрите примеры в папке с библиотекой!
  • Библиотека GyverPWM (GitHub) - библиотека, которую мы написали вместе с Егором Захаровым. Библиотека позволяет очень гибко работать с ШИМ на микроконтроллере ATmega328 (позже добавим Мегу):
    • Позволяет установить любую частоту ШИМ в диапазоне 250 Гц - 200 кГц
    • Выбор разрядности: 4-8 бит для 8 бит таймеров, 4-16 бит для 16-бит таймеров (при разрядности 4 бита частота ШИМ составляет 1 МГЦ 😀 )
    • Выбор режима работы ШИМ: Fast PWM или Phase-correct PWM (благоприятен для электродвигателей)
    • Генерация меандра на пине D9 с частотой от 2 Гц до 8 МГц с максимальной точностью
    • При работе с 8-битными таймерами доступен только один канал (например на ATmega328 останутся D3, D5, D9 и D10)
    • Есть функции для перенастройки стандартного ШИМ, при котором не теряются ШИМ выходы
    • Библиотека написана очень просто, можно брать из неё код кусками
    • Смотрите примеры в папке с библиотекой!

Видео


Полезные страницы


4.3/5 - (11 голосов)
5 1 голос
Рейтинг статьи
Подписаться
Уведомить о
guest

25 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх