Урок 6. Что за ШИМ?
(продолжение, начало см. Урок 5. Нажми на кнопку!)
(продолжение, начало см. Урок 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:
В этом скетче длительность импульса - чуть более 300 микросекунд. При этом в цикле мы постепенно уменьшаем и увеличиваем относительную ширину импульса, добиваясь эффекта циклического плавного изменения яркости. При этом надо избегать "крайних" значений ширины импульса около 0 и 300, иначе никакого эффекта плавного мерцания не будет (этим управляет константа "zazor").
Ура, мы изобрели диммер! (именно этот скетч я заливаю готовые платы перед отправкой, в качестве одного из элементов ОТК ;)
Торжество разума омрачает только одно - наш Ардуино оказался загруженным исключительно одной задачей. А если надо заставить скетч выполнять и другие операции? Как сделать генерацию PWM-сигнала фоновой и незаметной задачей?
Вот именно в этом случае нужно воспользоваться функцией аппаратного ШИМ в Arduino. Правда, работает она лишь на избранных пинах (они помечены звездочкой или надписью pwm), зато можно один раз выполнить analogWrite(pin, value), и спокойно заниматься другими делами. Вот так можно переписать скетч с использованием новой функции:
(на самом деле это скетч из примеров к ArduinoIDE - Analog|Fading).
Полюбоваться на его работу можно, если воткнуть светодиод длинной плюсовой ножкой в digital9, а короткой минусовой - в GND. Для счастливых владельцев Arduino Mega можно просто исправить значение константы ledPin в самом начале скетча с 9 на 13, поскольку в отличие от Duemilanova, Mega поддерживает аппаратный ШИМ на digital13.
Как видите - проще некуда. В данном случае используется аппаратная возможность atmega, связанная с работой таймеров. Но производится она внутри ядра, поэтому снаружи все выглядит так просто. И это хорошо, особенно для начинающих ;)
Что такое 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, связанная с работой таймеров. Но производится она внутри ядра, поэтому снаружи все выглядит так просто. И это хорошо, особенно для начинающих ;)
Замечательная статья, спасибо! Однако, для окончательного перехода от теории к "железной практике", желательно, как мне кажется, рассмотреть PWM в паре с "детекцией перехода фазы через ноль" а заодно и про использование прерываний(этим самым фазовым переходом:) рассказать.
ОтветитьУдалитьСпасибо за уроки! Очень хочется продолжения.
ОтветитьУдалитьВ статье есть два сертьезных недостатка:
ОтветитьУдалить1. Неверно посчитан ток, протекающий через светодиод с токоограничительным резистором 1к - он будет не 5/1000=5 мА, а (5-2)/1000 = 3 мА, где 2 В - падение напряжения на светодиоде.
2. Я бы рекомендовал не следовать написанному в предпоследнем абзаце статьи и не включать светодиод непосредственно между 9 выводом контроллера и землей без токоограничительного резистора. Конечно, в данном конкретном случае ничего страшного не произойдет, но в дидактических целях это, думаю, недопустимо.
Сергей, благодарю за комментарий - точно, ошибка, причем грубая. Поправлю обязательно.
Удалить