ШИМ с регулировкой частоты в широком диапазоне
- Войдите на сайт для отправки комментариев
Понадобилось мне в гараже сделать вентилятор. Для регулирования скорости вентилятора решил применить ШИМ. Но, порывшись в интернете, нашёл статью, где рассказывалось как можно ступенчато изменять опорную частоту ШИМ (см. 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 Гц - уже в слышимом диапазоне.
Но хотелось самому разобраться - как устроен ШИМ и как реализуется в Ардуино ИДЕ.