19.08.2010

Уроки Wiring (6)

Урок 6. Что за ШИМ?

(продолжение, начало см. Урок 5. Нажми на кнопку!)

Пожалуй, самая загадочная для новичка надпись на плате Arduino - PWM. На самом деле, это сокращение от Pulse Width Modulation, по-русски "Широтно Импульсная Модуляция" или ШИМ.

Что такое PWM?

На секунду вернемся к картинке из предыдущего урока:



Когда говорят о PWM, подразумевают примерно такой прямоугольный повторяющийся импульс, у которого известно соотношение времени присутствия нулевого и единичного уровня или - как можно сказать глядя на экран осциллографа - относительная ширина импульса.

Собственно, именно эту ширину и предполагается модулировать - то есть изменять определенным образом. Зачем все это может понадобиться? Да вот - хотя бы для изменения яркости свечения светодиодов.

Так выглядит график зависимости свечения от тока для самого ординарного светодиода:


Получается, что чем больше ток через светодиод, тем ярче он будет светиться. Логично, если вспомнить, что светодиод - это полупроводниковый прибор, управляемый током. Так что всего-то остается - понять, как изменять величину тока в цепи питания светодиода.

Возьмем для примера  светодиод L, присутствующий на всех современных Arduino-совместимых платах:


Светодиод загорится только в том случае, когда к его выводам будет приложена достаточная разность потенциалов. В данном случае одна ножка подключена к земле, а к другой - через токоограничительный резистор 1 кОм - вывод digital13. Следовательно, светодиод будет загораться по команде digitalWrite(13, HIGH).

Резистор включен последовательно со светодиодом с единственной целью - ограничить ток, протекающий в цепи. Обычно его значение прикидывают следующим образом: берут напряжение во всей цепи (для Arduino это 5 Вольт) и вычитают из него рабочее падение напряжения на светодиоде (указано в технических характеристиках светодиода, в простейшем случае зависит от цвета - для красного около 2 Вольт), получая падение напряжение на резисторе (в данном случае 5-2=3В). И далее, по закону Ома: R = U/I, где вместо I подставляется ток в цепи - который мы и хотим ограничить, скажем 3 мА. Получаем R = 3В/0.003А = 1000 Ом. При таком раскладе светодиод будет светиться, хоть и немного тускло, но вполне приемлемо. Чтобы добиться максимума яркости, можно увеличить ток до 30 мА - изменив номинал резистора R2 на 100 Ом.

Что же получается? Если мы хотим изменить яркость свечения светодиода, надо менять значение токоограничительного резистора. В принципе, нет ничего невозможного - существуют специальные резисторы с цифровым управлением, которые управляются через шину SPI или I2C, получают от микроконтроллера значение и меняют свое сопротивление в соответствии с ним. Но едва ли это оправдано с точки зрения стоимости и удобства (пойди еще найди и купи такой компонент, не пользующийся широким спросом). Другой вариант - это простейший ЦАП на основе R-2R цепочки, но он требует использования дополнительных выходов Arduino и тоже выглядит чересчур внушительным решением для такой простой задачи. Вот тут-то, как вы уже догадались, и приходит на помощь ШИМ.

Говоря по-простому, если цифровой выход быстро-быстро переключать между единицей и нулем, можно управлять интегральным значением тока и, в конечном итоге - яркостью:


Вот так выглядит скетч, который плавно гасит и зажигает светодиод L:

void setup() {
  pinMode(13, OUTPUT);
}

word cycle = 300;
byte stepdelay = 20;
byte zazor = 5;

void loop() {
  for (word intensity=zazor;intensity<(cycle-zazor);intensity++) {
    for (byte k=0;k<stepdelay;k++) {
      digitalWrite(13,HIGH);
      delayMicroseconds(intensity);
      digitalWrite(13,LOW);
      delayMicroseconds(cycle-intensity);
    }
  }
  for (word intensity=(cycle-zazor);intensity>zazor;intensity--) {
    for (word k=0;k<stepdelay;k++) {
      digitalWrite(13,HIGH);
      delayMicroseconds(intensity);
      digitalWrite(13,LOW);
      delayMicroseconds(cycle-intensity);
    }
  }
}

В этом скетче длительность импульса - чуть более 300 микросекунд. При этом в цикле мы постепенно уменьшаем и увеличиваем относительную ширину импульса, добиваясь эффекта циклического плавного изменения яркости. При этом надо избегать "крайних" значений ширины импульса около 0 и 300, иначе никакого эффекта плавного мерцания не будет (этим управляет константа "zazor").

Ура, мы изобрели диммер! (именно этот скетч я заливаю готовые платы перед отправкой, в качестве одного из элементов ОТК ;)

Торжество разума омрачает только одно - наш Ардуино оказался загруженным исключительно одной задачей. А если надо заставить скетч выполнять и другие операции? Как сделать генерацию PWM-сигнала фоновой и незаметной задачей?

Вот именно в этом случае нужно воспользоваться функцией аппаратного ШИМ в Arduino. Правда, работает она лишь на избранных пинах (они помечены звездочкой или надписью pwm), зато можно один раз выполнить analogWrite(pin, value), и спокойно заниматься другими делами. Вот так можно переписать скетч с использованием новой функции:

/*
 Fading
 
 This example shows how to fade an LED using the analogWrite() function.
 
 The circuit:
 * LED attached from digital pin 9 to ground.
 
 Created 1 Nov 2008
 By David A. Mellis
 Modified 17 June 2009
 By Tom Igoe
 
 http://arduino.cc/en/Tutorial/Fading
 
 */


int ledPin = 9;    // LED connected to digital pin 9

void setup()  { 
  // nothing happens in setup void loop()  { 
  // fade in from min to max in increments of 5 points:
  for(int fadeValue = 0 ; fadeValue <= 255; fadeValue +=5) { 
    // sets the value (range from 0 to 255):
    analogWrite(ledPin, fadeValue);         
    // wait for 30 milliseconds to see the dimming effect    
    delay(30);                            
  } 

  // fade out from max to min in increments of 5 points:
  for(int fadeValue = 255 ; fadeValue >= 0; fadeValue -=5) { 
    // sets the value (range from 0 to 255):
    analogWrite(ledPin, fadeValue);         
    // wait for 30 milliseconds to see the dimming effect    
    delay(30);                            
  } 
}

(на самом деле это скетч из примеров к ArduinoIDE - Analog|Fading).

Полюбоваться на его работу можно, если воткнуть светодиод длинной плюсовой ножкой в digital9, а короткой минусовой - в GND. Для счастливых владельцев Arduino Mega можно просто исправить значение константы ledPin в самом начале скетча с 9 на 13, поскольку в отличие от Duemilanova, Mega поддерживает аппаратный ШИМ на digital13.

Как видите - проще некуда. В данном случае используется аппаратная возможность atmega, связанная с работой таймеров. Но производится она внутри ядра, поэтому снаружи все выглядит так просто. И это хорошо, особенно для начинающих ;)

4 комментария:

  1. Замечательная статья, спасибо! Однако, для окончательного перехода от теории к "железной практике", желательно, как мне кажется, рассмотреть PWM в паре с "детекцией перехода фазы через ноль" а заодно и про использование прерываний(этим самым фазовым переходом:) рассказать.

    ОтветитьУдалить
  2. Спасибо за уроки! Очень хочется продолжения.

    ОтветитьУдалить
  3. В статье есть два сертьезных недостатка:
    1. Неверно посчитан ток, протекающий через светодиод с токоограничительным резистором 1к - он будет не 5/1000=5 мА, а (5-2)/1000 = 3 мА, где 2 В - падение напряжения на светодиоде.
    2. Я бы рекомендовал не следовать написанному в предпоследнем абзаце статьи и не включать светодиод непосредственно между 9 выводом контроллера и землей без токоограничительного резистора. Конечно, в данном конкретном случае ничего страшного не произойдет, но в дидактических целях это, думаю, недопустимо.

    ОтветитьУдалить
    Ответы
    1. Сергей, благодарю за комментарий - точно, ошибка, причем грубая. Поправлю обязательно.

      Удалить