ШИМ с регулировкой частоты в широком диапазоне
- Войдите на сайт для отправки комментариев
Понадобилось мне в гараже сделать вентилятор. Для регулирования скорости вентилятора решил применить ШИМ. Но, порывшись в интернете, нашёл статью, где рассказывалось как можно ступенчато изменять опорную частоту ШИМ (см. http://mypractic.ru/urok-37-shirotno-impulsnaya-modulyaciya-v-arduino.html). По этой статье можно было задать следующую опорную частоту: 62 500 Гц, 7 812,5 Гц, 976,56 Гц, 244,14 Гц, 61,04 Гц.
На 62,5 кГц мой MOSFET не захотел работать, на 7,8 кГц и ниже – двигатель начинал громко петь, что меня не устраивало. Нужно было сместить частоту за слышимый диапазон, т.е. за 20 кГц. Поэтому пришлось читать RTFM.
Рассмотрим реализацию ШИМ на примере Arduino Uno с контроллером Atmega328P.
Все выходы ШИМ привязаны к определённым таймерам:
Таймер 0: OC0A -> PD6 (на микроконтроллере) à Вывод 6 на Arduino Uno
Таймер 0: OC0B -> PD5 (на микроконтроллере) à Вывод 5 на Arduino Uno
Таймер 1: OC1A -> PB1 (на микроконтроллере) à Вывод 9 на Arduino Uno
Таймер 1: OC1B -> PB2 (на микроконтроллере) à Вывод 10 на Arduino Uno
Таймер 2: OC2A -> PB3 (на микроконтроллере) à Вывод 11 на Arduino Uno
Таймер 2: OC2B -> PD3 (на микроконтроллере) à Вывод 3 на Arduino Uno
Каждый таймер можно запрограммировать на следующие функции:
1. Normal Mode
- Clear Timer on Compare Match (CTC) Mode
- Fast PWM Mode
- Phase Correct PWM Mode
Эти 4 режима ещё подразделяются на несколько режимов, в зависимости от того, как сконфигурить биты WGMx2:0 в регистре TCCRx:

Выяснилось следующее: стандартный ШИМ в Arduio IDE реализован через ШИМ с фазовой коррекцией, но используется режим 1. В этом режиме таймер постоянно считает от нуля до 255, затем считает обратно – от 255 до нуля. В Arduino IDE для реализации ШИМ используется команда
analogWrite(pin, value)
где,
· pin: порт вход/выхода на который подаем ШИМ сигнал.
· value: период рабочего цикла значение между 0 (полностью выключено) and 255 (сигнал подан постоянно).
Так вот изменяя величину value мы задаём значение в регистр OCRA или OCRB, и при превышении таймером этой величины – включается наш выход, при понижении – выход отключается.
Я же решил использовать режим 5, в котором можно задавать верхнее значение таймера, задавая значенеи в регистр OCRA. Отсюда сразу же вылазит ограничение на данный метод, а именно: ДЛЯ ДАННОЙ РЕАЛИЗАЦИИ ШИМ МОЖНО БУДЕТ ИСПОЛЬЗОВАТЬ ТОЛЬКО ПИНЫ, ПОДКЛЮЧЕННЫЕ К OCRB!!!
Получается вот такая программная реализация:
//================================================================
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
//================================================================
int sensorPin = A0; // select the input pin for the potentiometer
const int LedPin = 5; // select the pin for the LED
int sensorValue = 0; // variable to store the value coming from the sensor
int fill_factor = 0; // 100%=OCR0A
void setup() {
// declare the ledPin as an OUTPUT:
pinMode(LedPin, OUTPUT);
/*
Table 17-8. Waveform Generation Mode Bit Description
Mode | WGM2 | WGM1 | WGM0 | Timer/Counter Mode | TOP | Update of | TOVn Flag
| | | | of Operation | | OCRnx at | Set on
------------------------------------------------------------------------------------------------
5 1 0 1 PWM, Phase Correct OCRA TOP BOTTOM
*/
TCCR0A = ((0 << COM0A1) | (0 << COM0A0) | (1 << COM0B1) | (0 << COM0B0) |
(0 << WGM01) | (1 << WGM00));
/*
* CS02 CS01 CS00 Description
* 0 0 0 No clock source (Timer/Counter stopped)
* 0 0 1 clkI/O / 1 (No prescaling)
* 0 1 0 clkI/O / 8 (From prescaler)
* 0 1 1 clkI/O / 64 (From prescaler)
* 1 0 0 clkI/O / 256 (From prescaler)
* 1 0 1 clkI/O / 1024 (From prescaler)
*/
TCCR0B = ((0 << FOC0A) | (0 << FOC0B) | (1 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00));
/* for Prescaler = clkI/O / 8
* OCR0A = 10 - 100 kHz
* OCR0A = 20 - 50 kHz
* OCR0A = 25 - 40 kHz
* OCR0A = 30 - 33.33 kHz
* OCR0A = 40 - 25 kHz
* OCR0A = 50 - 20 kHz
* OCR0A = 64 - 15.625 kHz
* OCR0A = 80 - 12.5 kHz
* OCR0A = 100 - 10 kHz
* OCR0A = 125 - 8 kHz
* OCR0A = 160 - 6.25 kHz
* OCR0A = 200 - 5 kHz
* OCR0A = 250 - 4 kHz
*/
OCR0A = 40;
}
void loop() {
// read the value from the sensor:
sensorValue = analogRead(sensorPin);
fill_factor = map(sensorValue, 0, 1023, 0, OCR0A);
// Добавим мёртвую зону для полного открытия/закрытия MOSFET-а
if (fill_factor <= 2)
{
digitalWrite(LedPin, LOW);
}
else if (fill_factor >= (OCR0A-2))
{
digitalWrite(LedPin, HIGH);
}
else
{
sbi(TCCR0A, COM0B1);
OCR0B = fill_factor; // set pwm duty
}
}
Частота рассчитывается как CTC-методе, описанном в параграфе 14.7.2 Clear Timer on Compare Match (CTC) Mode:

В итоге остановился на частоте ШИМ в 25 кГц, при котором двигатель не гудит и не пищит.
Если использовать таймер 1, то можно сделать 8, 9 или 10 разрядный ШИМ. Каждый лишний бит разрядности уменьшит частоту в 2 раза.
Ну да, как вариант можно было бы использовать таймер 1 с 9-ти разрядным ШИМом, было бы 31250 Гц. 10-ти разрядный - это 15625 Гц - уже в слышимом диапазоне.
Но хотелось самому разобраться - как устроен ШИМ и как реализуется в Ардуино ИДЕ.