Увеличение частоты ШИМ
В чём проблема?
Как мы уже говорили в уроке про функции времени и про ШИМ сигнал, у микроконтроллера есть несколько таймеров, которые могут выполнять разные функции, в частности - генерировать ШИМ сигнал. Чтобы таймер генерировал ШИМ, его нужно предварительно настроить при помощи редактирования регистра таймера. Когда мы работаем в 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)
[su_spoiler title="Пины D5 и D6 (Timer 0) - 8 бит" open="no" style="fancy" icon="arrow"]
// Пины 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
[/su_spoiler][su_spoiler title="Пины D9 и D10 (Timer 1) - 8 бит" open="no" style="fancy" icon="arrow"]
// Пины 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
[/su_spoiler][su_spoiler title="Пины D9 и D10 (Timer 1) - 10 бит" open="no" style="fancy" icon="arrow"]
// Пины 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
[/su_spoiler][su_spoiler title="Пины D3 и D11 (Timer 2) - 8 бит" open="no" style="fancy" icon="arrow"]
// Пины 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
[/su_spoiler][su_spoiler title="Пример использования" open="no" style="fancy" icon="arrow"]
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() { }
[/su_spoiler]
Если очень хочется
Если очень хочется или очень нужен разогнанный ШИМ на системном (нулевом) таймере без потери функций времени, то можно их скорректировать следующим образом:
#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)
- Есть функции для перенастройки стандартного ШИМ, при котором не теряются ШИМ выходы
- Библиотека написана очень просто, можно брать из неё код кусками
- Смотрите примеры в папке с библиотекой!
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])