ШИМ с регулировкой частоты в широком диапазоне

KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

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

  1. Clear Timer on Compare Match (CTC) Mode
  2. Fast PWM Mode
  3. 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 кГц, при котором двигатель не гудит и не пищит.

dezz0riented
dezz0riented аватар
Offline
Зарегистрирован: 27.07.2014

Если использовать таймер 1, то можно сделать 8, 9 или 10 разрядный ШИМ. Каждый лишний бит разрядности уменьшит частоту в 2 раза.

KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

Ну да, как вариант можно было бы использовать таймер 1 с 9-ти разрядным ШИМом, было бы 31250 Гц. 10-ти разрядный - это 15625 Гц - уже в слышимом диапазоне.

Но хотелось самому разобраться - как устроен ШИМ и как реализуется в Ардуино ИДЕ.