View Categories

ШИМ сигнал

В прошлых уроках мы рассмотрели чтение и вывод цифрового сигнала (GPIO) и чтение аналогового сигнала (ADC). А есть ли у МК возможность выдать аналоговый сигнал? То есть не 0 или 5 Вольт, а что-то среднее между ними. Например для воспроизведения звука динамиком или управления яркостью светодиода. Например так, как мы управляли яркостью светодиода при помощи потенциометра, подключив его как делитель напряжения - он выдавал напряжение от 0 до 5V с плавной регулировкой.

На некоторых МК есть ЦАП - цифро-аналоговый преобразователь (DAC, Digital-to-Analog Converter), он преобразует численное значение из программы в соответствующее напряжение в Вольтах и выводит его на пин. Простейший ЦАП можно собрать из десятка резисторов - R2R ЦАП, рассмотрим его в отдельном уроке.

Для большинства задач ЦАП на самом деле и не нужен - его заменяет ШИМ сигнал.

"Ручной" ШИМ #

Давайте рассмотрим его работу на примере классического мигания светодиодом, но возьмём короткий период: например 100 мс - по 50 на включенное и выключенное состояние:

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

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(50);
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
}

Теперь изменим задержки так, чтобы суммарное время (100 мс) не изменилось, например 90 и 10 мс:

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

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(90);
    digitalWrite(LED_BUILTIN, LOW);
    delay(10);
}

Светодиод начал гореть дольше (вспышки стали длиннее) и "средняя" яркость визуально увеличилась. Поменяем местами - 10 на активное и 90 на неактивное состояние:

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

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(10);
    digitalWrite(LED_BUILTIN, LOW);
    delay(90);
}

Теперь светодиод даёт меньше света!

Также он ощутимо мерцает, поэтому уменьшим задержку в 10 раз: попробуйте запустить скетч с 5/5, 1/9 и 9/1 мс задержками - получится управление яркостью светодиода, причём мерцания почти не будет заметно из-за высокой частоты переключения, а суммарно получится 11 возможных значений яркости (от 0 до 10 с шагом 1). Такой эффект достигается из-за инертности зрения: светодиод всё так же мерцает, но глаз не успевает засечь эти вспышки точно так же, как увидеть лопасти вращающегося вентилятора.

Давайте ещё сильнее увеличим частоту и перейдем к задержкам в микросекундах. Задержкой будем управлять при помощи потенциометра, подключенного к пину A0. Функция analogRead вернёт значение от 0 до 1023, так что просто используем его следующим образом - первая задержка по значению, а вторая - 1023 - значение:

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

void loop() {
    int v = analogRead(0);
    digitalWrite(LED_BUILTIN, HIGH);
    delayMicroseconds(v);
    digitalWrite(LED_BUILTIN, LOW);
    delayMicroseconds(1023 - v);
}

То есть сумма задержек всегда будет равна 1023. Загрузите скетч и покрутите рукоятку - яркость светодиода меняется и мерцания уже не видно, но аналоговым сигналом тут и не пахнет. Мы сейчас "на коленке" реализовали ШИМ сигнал - Широтно-Импульсная Модуляция (PWM, Pulse-Width Modulation), это цифровой сигнал, имеющий две основные характеристики: частота и ширина импульса.

Частота ШИМ #

Частота измеряется в Герцах, Гц (Hertz, Hz), 1 Гц равен одному периоду колебаний в секунду (T на графике). Период и частота - взаимно обратные величины, период измеряется в секундах: частота = 1 / период. Периодом считается время между одинаковыми точками сигнала:


T - период, Ti - ширина импульса, Vm - напряжение сигнала, Va - среднее напряжение

Чем выше частота, тем более незаметны импульсы для потребителя, например со светодиодом при частоте 10 Гц мы увидим мерцание, а 100 Гц - уже нет. Светодиод загорается практически мгновенно, а вот глаз не способен уловить короткие вспышки, он их "интегрирует" из-за инертности зрения. Если подавать ШИМ на нагреватель, то можно регулировать мощность нагрева. Если нагреватель большой и инертный - частота может быть 1 Гц и ниже, например у электрической плиты или мультиварки в некоторых моделях отчётливо слышно включение и выключение нагревательного элемента. Если подавать ШИМ на электромотор - то из-за неидеальности конструкции у него начнут вибрировать катушки с частотой ШИМ, частоты от 100 Гц до ~16 кГц замечательно слышны человеческим ухом как гудение и высокий писк соответственно, поэтому для управления моторами частота обычно выбирается в районе 20 кГц.

Ширина импульса #

Ширина или длительность импульса (Ti на графике) измеряется в секундах в диапазоне от 0 до периода колебаний T. Чем длиннее импульс, тем выше среднее напряжение сигнала (Va на графике) - оно пропорционально отношению длины импульса к периоду. Например период 1с, импульс 0.25с, среднее напряжение сигнала при питании 5V (Vm на графике) будет равно Va = Vm * (Ti / T) = 5 * (0.25 / 1) = 1.25 V.

Отношение длины импульса к периоду (Ti / T) также называется коэффициентом заполнения ШИМ - это более удобная безразмерная величина, которая уже не зависит от частоты и периода - имеет значение от 0.0 до 1.0 (или заполнение ШИМ 0.. 100%). Чтобы получить результирующее напряжение, нужно просто умножить коэффициент заполнения на напряжение питания ШИМ. Величина, обратная заполнению, называется скважностью ШИМ - на практике используется редко.

Аппаратный ШИМ #

В примерах выше мы сделали программный ШИМ сигнал, то есть вручную засекали время и переключали состояние пина. Также в нашей реализации "на задержках" микроконтроллер сможет заниматься только одной этой задачей - генерировать один ШИМ сигнал. У многих МК есть возможность генерировать ШИМ аппаратно - при помощи периферии, вообще не используя ресурсы процессора. Обычно это делается при помощи аппаратного таймера - универсального блока, который может засекать время и самостоятельно дёргать пином.

analogWrite #

В фреймворке Arduino предусмотрена готовая функция для запуска аппаратного ШИМ сигнала на пине analogWrite(pin, value):

  • pin - номер GPIO, который поддерживает ШИМ сигнал. См. описание к конкретной плате/МК, также на распиновке такие пины помечены символом ~. Например на Arduino Nano/UNO это пины 3, 5, 6, 9, 10, 11 - выводы аппаратных таймеров
  • value - заполнение от 0 до 2^разрядность - 1, где разрядность (разрешение) ШИМ в битах, см. документацию на конкретную плату/МК. В большинстве случаев по умолчанию это 8 бит - значения от 0 до 255. Значения value от 0 до максимума соответствуют заполнению ШИМ 0.. 100%

Среднее напряжение сигнала в Вольтах будет равно Vcc * value / 255 для 8 бит ШИМ - роли ширины импульса и периода в данном случае играют значение и разрядность

Дополнительная информация:

  • Генерация происходит автоматически - достаточно один раз вызвать analogWrite и ШИМ будет генерироваться самостоятельно и асинхронно, на него не влияют delay-задержки
  • Частота ШИМ через analogWrite не стандартизирована и может отличаться на разных платформах или даже на разных пинах одного МК, см. описание к конкретной модели
  • На некоторых платформах доступны функции analogWriteFrequency - настроить частоту ШИМ и analogWriteResolution - разрешение
  • Перед вызовом analogWrite не нужно вручную переключать пин в режим OUTPUT, это сделается автоматически
  • При вызове digitalWrite запущенный на пине ШИМ отключается и пин переходит в обычный GPIO режим с указанным уровнем
  • При вызове analogWrite со значением 0 или максимальным ШИМ отключается, пин переходит в обычный режим выхода с соответствующим сигналом
  • На AVR Arduino (Nano, UNO, Mega...) запуск ШИМ через analogWrite на некоторых пинах может конфликтовать с некоторыми библиотеками и функцией tone (о ней позже), т.к. для этих задач используется один и тот же таймер. См. описание к конкретной плате

Пример 1 #

Давайте поуправляем яркостью светодиода при помощи analogWrite. Подключите RGB светодиод к пинам 3, 5, 6.

Для начала просто плавно помигаем красным цветом, для чего используем цикл и задержки. Пример демонстрирует независимость работы ШИМ от наличия задержек - программа ждёт задержку, а ШИМ продолжает генерироваться с установленным заполнением внутри delay:

#define LED_R 3

void setup() {
}

void loop() {
    for (int i = 0; i < 255; i += 10) {
        analogWrite(LED_R, i);
        delay(20);
    }
    for (int i = 255; i > 0; i -= 10) {
        analogWrite(LED_R, i);
        delay(20);
    }
}

Пример 2 #

Добавим потенциометр для ввода данных в программу:

В Arduino Nano analogRead возвращает от 0 до 1023 (10 бит), а analogWrite принимает от 0 до 255 (8 бит), поэтому просто поделим результат чтения потенциометра на 4, чтобы получить полный диапазон ШИМ. В программу для наглядности добавим задержку:

#define LED_R 3
#define POT_PIN 0

void setup() {
}

void loop() {
    analogWrite(LED_R, analogRead(POT_PIN) / 4);
    delay(100);
}

Теперь потенциометр управляет яркостью красного светодиода.

Пример 3 #

Сделаем чтобы один цвет перетекал в другой, вместо того чтобы просто гаснуть:

#define LED_R 3
#define LED_G 5
#define POT_PIN 0

void setup() {
}

void loop() {
    int v = analogRead(POT_PIN) / 4;
    analogWrite(LED_R, v);
    analogWrite(LED_G, 255 - v);
    delay(100);
}

Теперь вращение рукоятки приводит к тому, что цвет перетекает из красного в зелёный через жёлтый (смесь зелёного и жёлтого на средней яркости). Другие примеры с RGB светодиодом можно найти в уроке про RGB светодиод и цветовые алгоритмы.

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

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх