Осиливаем ШИМ в теплой компании Arduino и RGB светодиода. Изменение частоты ШИМ (PWM) Ардуино

Скачать на Телефон 12.07.2019
Скачать на Телефон

Широтно-импульсная модуляция (pulse width modulation, PWM) часто используется в цифровой технике. В Arduino есть специальная функция для формирования этого сигнала.
Временная диаграмма ШИМ-сигнала:

analogWrite

Скважность это отношение периода следования импульсов к их длительности. На рисунке – чем больше мгновенное значение управляющего сигнала, тем меньше скважность импульсов ШИМ-сигнала. Этот способ модуляции широко применяется в технике ввиду того, что позволяет произвольное среднедействующее значение средствами цифровых устройств. Этот способ применим в тех случаях, когда необходимо, например, плавно регулировать яркость свечения лампы, температуры нагревателя, скорость вращения мотора постоянного тока и так далее.
Arduino имеет встроенную функцию для формирования ШИМ-сигнала – analogWrite(pin,value) . У этой функции два параметра: pin – номер вывода, на котором формируется сигнал; value – относительное значение скважности импульсов.
Value может принимать значения от 0 до 255. Причем максимальная скважность получается при 0 (а среднедействующее значение минимально), а минимальная при 255 (среднедействующее максимально). Функция analogWrite может работать только с выводами 9, 10 и 11 (на нашей плате с микроконтроллером Atmega8). Соответственно, только такие значения может принимать переменная pin .
Если использовать эту функцию для управления другим выводом (0-8, 12, 13), то при значения скважности от 0 до 127 на выводе будет низкий уровень напряжения, а при 128-255 – высокий.

Подготовка к работе

Мы будем очень рады, если вы поддержите наш ресурс и посетите магазин наших товаров .

И попробуем выполнить новую задачу. Думаю, что все видели новогодние витринные гирлянды, в которых плавно мигают светодиоды. Допустим, что мы хотим сделать нечто подобное.
Мы уже рассматривали функцию digitalWrite() и знаем, что значение, которое она записывает, может быть двух вариантов - высокий или низкий уровень. В данном случае нам поможет функция analogWrite(). "Формулировки" функций различаются только начальными приставками, поэтому их легко запомнить.

Функция analogWrite(), так же как и digitalWrite(), содержит в скобках два аргумента и работает по тому же словесному принципу: "куда, что". Главным различием является возможность записи широкого диапазона значений вместо привычного LOW или HIGH. Это и позволит нам регулировать яркость светодиода. Главное замечание, которое необходимо учитывать, это то, что данная функция работает только на определенных контактах. Эти контакты обозначены символом "~". Этот символ означает, что это PWM-контакт. PWM (pulse-width modulation) звучит по-русски как ШИМ (широтно-импульсная модуляция). Принцип работы основан на изменении длительности импульса. Графически это можно изобразить так:

Давайте попробуем разобраться как это работает, рассмотрев простой пример. Для этого необходимо подключить светодиод к PWM-контакту через резистор номиналом 150 Ом и "зашить" в Arduino простенькую программу. Схема подключения и код скетча представлены ниже:


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

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


Думаю, что в целом код понятен, но необходимо уделить немного внимания циклу for(). Существует такое понятие как разрешение. Поскольку мы работаем с 8-битным разрешением (это будет рассмотрено несколько позднее), то минимальному значению будет соответствовать 0, а максимальному - 255. В конце каждой итерации мы установили временную задержку в 10мс.

Давайте вернемся к схеме из предыдущего урока и попробуем сделать аналогичную гирлянду с использованием функции analogWrite().


int buttonPin = 2;
int pins = {3,5,6,9,10,11};

boolean lastButton = LOW;
boolean currentButton = LOW;
boolean enable = false;

void setup()
{
pinMode(buttonPin, INPUT);
for(int mode = 0; mode <= 5; mode++) pinMode(pins, OUTPUT);
}

boolean debounce(boolean last)
{
boolean current = digitalRead(buttonPin);
if(last != current)
{
delay(5);
current = digitalRead(buttonPin);
}
return current;
}

void loop()
{
currentButton = debounce(lastButton);
if(lastButton == LOW && currentButton == HIGH)
{
enable = !enable;
}

If(enable == true)
{
for (int i=0; i<=5; i++)
{
for (int brightness = 0; brightness <= 255; brightness++)
{
delay(1);
}
delay(40);
}
for (int i=0; i<=5; i++)
{
for (int brightness = 255; brightness >= 0; brightness--)
{
analogWrite(pins[i], brightness);
delay(1);
}
delay(40);
}
}

If(enable == false)
{
for(int i = 0; i <= 5; i++) digitalWrite(pins[i], LOW);
}

LastButton = currentButton;
}


Визуально скетч стал несколько сложнее. На самом деле здесь все просто и давайте в этом разберемся. Нам необходимо идентифицировать все подключенные светодиоды, но вместо привычного int led мы используем массив, каждый элемент которого является PWM-контактом на Arduino. В теле функции void setup() мы тоже поступили хитрым образом. "Перечислять" все контакты мы доверили циклу for(), с каждой итерацией которого производится конфигурация соответствующего контакта на OUTPUT. Переходим к функции void loop(). Функция debounce() и начальное условие if() остается без изменений. У нас по-прежнему идет проверка уровней двух переменных: предыдущее значение (изначально LOW) и текущее состояние кнопки. При выполнении этих условий значение переменной enable инвертируется. Учитывая это, мы добавили еще два простых условия if(). Если enable = true, то гирлянда включается, плавностью "перетекания" которой управляет цикл for(). Если же enable = false, то все светодиоды выключены. По окончанию условий переменная lastButton принимает текущее состояние кнопки.
Тестируя нашу программу, мы заметили, что все работает не должным образом. Помните, в прошлом уроке мы сделали поправку, что при большом значении временной задержки кнопка срабатывает по её истечению? В прошлом примере, при включенной гирлянде, суммарная задержка в теле функции void loop() составляла 85мс. Это давало нам возможность успеть "попасть" в определенной отрезок времени. В данном скетче, при том же условии, задержка отличается в несколько раз. Возможно, при желании выключить гирлянду напрашивается слово "прервать". Это и будет являться решением данной задачи!

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

Например);

  • резистор номиналом 190…240 Ом (вот отличный набор резисторов самых распространённых номиналов);
  • персональный компьютер со средой разработки Arduino IDE.
  • Инструкция по использованию ШИМ в Arduino

    1 Общие сведения о широтно-импульсной модуляции

    Цифровые выводы Arduino могут выдавать только два значения: логический 0 (LOW, низкий уровень) и логическую 1 (HIGH, высокий). На то они и цифровые. Но есть у Ардуино «особые» выводы, которые обозначаются PWM . Их иногда обозначают волнистой чертой "~" или обводят кружочками или ещё как-то выделяют среди прочих. PWM расшифровывается как Pulse-width modulation или широтно-импульсная модуляция , ШИМ .

    Широтно-импульсно модулированный сигнал - это импульсный сигнал постоянной частоты, но переменной скважности (соотношение длительности импульса и периода его следования). Из-за того, что большинство физических процессов в природе имеют инерцию, то резкие перепады напряжения от 1 к 0 будут сглаживаться, принимая некоторое среднее значение. С помощью задания скважности можно менять среднее напряжение на выходе ШИМ.

    Если скважность равняется 100%, то всё время на цифровом выходе Arduino будет напряжение логическая "1" или 5 вольт. Если задать скважность 50%, то половину времени на выходе будет логическая "1", а половину - логический "0", и среднее напряжение будет равняться 2,5 вольтам. Ну и так далее.


    В программе скважность задаётся не в процентах, а числом от 0 до 255. Например, команда analogWrite(10, 64) скажет микроконтроллеру подать на цифровой PWM выход №10 сигнал со скважностью 25%.

    Выводы Arduino с функцией широтно-импульсной модуляции работают на частоте около 500 Гц. Значит, период следования импульсов - около 2 миллисекунд, что и отмеряют зелёные вертикальные штрихи на рисунке.

    Получается, что мы можем сымитировать аналоговый сигнал на цифровом выходе! Интересно, правда?!

    Как же мы можем использовать ШИМ? Применений масса! Например, управлять яркостью светодиода, скоростью вращения двигателя, током транзистора, звуком из пьезоизлучателя и т.д.…

    2 Схема для демонстрации широтно-импульсной модуляции в Arduino

    Давайте рассмотрим самый базовый пример - управление яркостью светодиода с помощью ШИМ. Соберём классическую схему.


    3 Пример скетча с ШИМ

    Откроем из примеров скетч "Fade": Файл Образцы 01.Basics Fade .


    Немного изменим его и загрузим в память Arduino.

    Int ledPin = 3; // объявляем пин, управляющий светодиодом int brightness = 0; // переменная для задания яркости int fadeAmount = 5; // шаг изменения яркости void setup() { pinMode(ledPin, OUTPUT); } void loop() { analogWrite(ledPin, brightness); // устанавливаем яркость brightness на выводе ledPin brightness += fadeAmount; // изменяем значение яркости /* при достижении границ 0 или 255 меняем направление изменения яркости */ if (brightness == 0 || brightness == 255) { fadeAmount = -fadeAmount; // изменяем знак шага } delay(30); // задержка для большей видимости эффекта }

    4 Управление яркостью светодиода с помощью PWM и Arduino

    Включаем питание. Светодиод плавно наращивает яркость, а затем плавно уменьшает. Мы сымитировали аналоговый сигнал на цифровом выходе с помощью широтно-импульсной модуляции.


    Посмотрите приложенные видео, где наглядно показано изменение яркости светодиода, на подключённом осциллографе видно, как при этом меняется сигнал с Arduino.

    И попробуем выполнить новую задачу. Думаю, что все видели новогодние витринные гирлянды, в которых плавно мигают светодиоды. Допустим, что мы хотим сделать нечто подобное.
    Мы уже рассматривали функцию digitalWrite() и знаем, что значение, которое она записывает, может быть двух вариантов - высокий или низкий уровень. В данном случае нам поможет функция analogWrite(). "Формулировки" функций различаются только начальными приставками, поэтому их легко запомнить.

    Функция analogWrite(), так же как и digitalWrite(), содержит в скобках два аргумента и работает по тому же словесному принципу: "куда, что". Главным различием является возможность записи широкого диапазона значений вместо привычного LOW или HIGH. Это и позволит нам регулировать яркость светодиода. Главное замечание, которое необходимо учитывать, это то, что данная функция работает только на определенных контактах. Эти контакты обозначены символом "~". Этот символ означает, что это PWM-контакт. PWM (pulse-width modulation) звучит по-русски как ШИМ (широтно-импульсная модуляция). Принцип работы основан на изменении длительности импульса. Графически это можно изобразить так:

    Давайте попробуем разобраться как это работает, рассмотрев простой пример. Для этого необходимо подключить светодиод к PWM-контакту через резистор номиналом 150 Ом и "зашить" в Arduino простенькую программу. Схема подключения и код скетча представлены ниже:


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

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


    Думаю, что в целом код понятен, но необходимо уделить немного внимания циклу for(). Существует такое понятие как разрешение. Поскольку мы работаем с 8-битным разрешением (это будет рассмотрено несколько позднее), то минимальному значению будет соответствовать 0, а максимальному - 255. В конце каждой итерации мы установили временную задержку в 10мс.

    Давайте вернемся к схеме из предыдущего урока и попробуем сделать аналогичную гирлянду с использованием функции analogWrite().


    int buttonPin = 2;
    int pins = {3,5,6,9,10,11};

    boolean lastButton = LOW;
    boolean currentButton = LOW;
    boolean enable = false;

    void setup()
    {
    pinMode(buttonPin, INPUT);
    for(int mode = 0; mode <= 5; mode++) pinMode(pins, OUTPUT);
    }

    boolean debounce(boolean last)
    {
    boolean current = digitalRead(buttonPin);
    if(last != current)
    {
    delay(5);
    current = digitalRead(buttonPin);
    }
    return current;
    }

    void loop()
    {
    currentButton = debounce(lastButton);
    if(lastButton == LOW && currentButton == HIGH)
    {
    enable = !enable;
    }

    If(enable == true)
    {
    for (int i=0; i<=5; i++)
    {
    for (int brightness = 0; brightness <= 255; brightness++)
    {
    delay(1);
    }
    delay(40);
    }
    for (int i=0; i<=5; i++)
    {
    for (int brightness = 255; brightness >= 0; brightness--)
    {
    analogWrite(pins[i], brightness);
    delay(1);
    }
    delay(40);
    }
    }

    If(enable == false)
    {
    for(int i = 0; i <= 5; i++) digitalWrite(pins[i], LOW);
    }

    LastButton = currentButton;
    }


    Визуально скетч стал несколько сложнее. На самом деле здесь все просто и давайте в этом разберемся. Нам необходимо идентифицировать все подключенные светодиоды, но вместо привычного int led мы используем массив, каждый элемент которого является PWM-контактом на Arduino. В теле функции void setup() мы тоже поступили хитрым образом. "Перечислять" все контакты мы доверили циклу for(), с каждой итерацией которого производится конфигурация соответствующего контакта на OUTPUT. Переходим к функции void loop(). Функция debounce() и начальное условие if() остается без изменений. У нас по-прежнему идет проверка уровней двух переменных: предыдущее значение (изначально LOW) и текущее состояние кнопки. При выполнении этих условий значение переменной enable инвертируется. Учитывая это, мы добавили еще два простых условия if(). Если enable = true, то гирлянда включается, плавностью "перетекания" которой управляет цикл for(). Если же enable = false, то все светодиоды выключены. По окончанию условий переменная lastButton принимает текущее состояние кнопки.
    Тестируя нашу программу, мы заметили, что все работает не должным образом. Помните, в прошлом уроке мы сделали поправку, что при большом значении временной задержки кнопка срабатывает по её истечению? В прошлом примере, при включенной гирлянде, суммарная задержка в теле функции void loop() составляла 85мс. Это давало нам возможность успеть "попасть" в определенной отрезок времени. В данном скетче, при том же условии, задержка отличается в несколько раз. Возможно, при желании выключить гирлянду напрашивается слово "прервать". Это и будет являться решением данной задачи!

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

    Месяца 3 назад, как и многие горе-электроники, купил себе на мой тогдашний взгляд самую навороченную микропроцессорную плату из семейства Arduino, а именно Seeeduino Mega, на базе процессора Atmega1280. Побаловавшись всласть вращающимся сервоприводом и моргающим светодиодом, встал вопрос: «зачем же я её купил?».

    Я работаю одним из ведущих конструкторов на одном крупном военном Зеленоградском заводе, и в данный момент веду проект по разработке метрологического средства измерения. В данной задаче существует бесконечное множество проблем, которые требуют индивидуального решения. Одной из таких задач является управление шаговым двигателем без шумов и с шагом не 1.8 градуса, как сказано в документации шагового двигателя, а до 0.0001 градуса. Казалось бы, задача сложна и нерешабельна, но, повозившись немного со схемами управления, пришёл к выводу, что всё реально и возможно. Требуется только генерация двух сигналов специфичной формы и со сдвигом фаз и частотой изменения напряжения до 1 МГц. (Подробное исследование шагового мотора и раскрытие всех тайн управления напишу в следующей статье) Сразу же в голове стали появляться проблески надежды, что я не зря потратил 1500 рублей на свою красненькую Seeeduino, и я, набравшись энтузиазма, начал разбираться.

    Первоначальный ужас:

    Подключив микропроцессорную плату к осцилографу, и написав цикл digitalWrite(HIGH), и ниже digitalWrite(LOW), на осцилографе обнаружил довольно унылый меандр с частотой 50Гц. Это кошмар. Это крах, подумал я, на фоне требуемых 1Мгц.
    Далее, через осцилограф, я изучил еще несколько скоростей выполнения:
    AnalogRead() - скорость выполнения 110 мкс.
    AnalogWrite() - 2000 мкс
    SerialPrintLn() - при скорости 9600 около 250мкс, а при максимальной скорости около 3мкс.
    DigitalWrite() - 1800мкс
    DigitalRead() - 1900мкс

    На этом я, всплакнув, чуть не выкинул свою Seeeduino. Но не тут-то было!

    Глаза боятся, руки делают!

    Не буду рассказывать свои душевные муки и описывать три долгих дня изучения, лучше сразу скажу всё как есть!
    Подняв всю возможную документацию на Arduino и на процессор Atmega1280, исследовав опыт зарубежных коллег , хочу предложить несколько советов, как заменять чтение/запись:
    Улучшаем AnalogRead()
    #define FASTADC 1

    // 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

    Void setup() {
    int start ;
    int i ;

    #if FASTADC
    // set prescale to 16
    sbi(ADCSRA,ADPS2) ;
    cbi(ADCSRA,ADPS1) ;
    cbi(ADCSRA,ADPS0) ;
    #endif

    Serial.begin(9600) ;
    Serial.print("ADCTEST: ") ;
    start = millis() ;
    for (i = 0 ; i < 30000 ; i++)
    analogRead(0) ;
    Serial.print(millis() - start) ;
    Serial.println(" msec (30000 calls)") ;
    }

    Void loop() {
    }

    Результат: скорость 18,2 мкс против бывших 110 мкс .
    Кстати, максимальная скорость АЦП Атмеги как раз 16мкс. Как вариант - использовать другую микросхему, заточенную именно под АЦП, которая позволит уменьшить скорость до 0,2мкс (читать ниже, почему)

    Улучшаем digitalWrite()
    Каждая Arduino/Seeeduino/Feduino/Orduino/прочаяduino имеет порты. Каждый порт - 8 бит, которые сначала надо настроить на запись. Например, на моей Seeeduino PORTA - c 22 по 30 ножку. Теперь всё просто. Управляем с 22 по 30 ножки с помощью функций
    PORTA=B00001010 (битовая, ножки 23 и 25 - HIGH)
    или
    PORTA=10 (десятичная, всё так же)
    Результат = 0,2мкс против 1800мкс , которые достигаются обычным digitalWrite()
    Улучшаем digitalRead()
    Практически то же самое, что и в улучшении с digitalWrite(), но теперь настраиваем ножки на INPUT, и используем, например:
    if (PINA==B00000010) {...} (если на ножке 23 присутствует HIGH, а на 22 и 24-30 присутствует LOW)
    Результат выполнения этого if() - 0.2мкс против 1900мкс , которые достигаются обычным digitalRead()
    Улучшаем ШИМ модулятор, или analogWrite()
    Итак, есть данные, что digitalRead() исполняется 0,2мкс, и ШИМ модулятор имеет дискретность 8 разрядов, минимальное время переключения ШИМ 51,2мкс против 2000 мкс.
    Используем следующий код:
    int PWM_time=32; //Число, которое мы как бы хотим записать в analogWrite(PIN, 32)
    for (int k=0;kFor (int k=0;k<256-PWM_time) PORTA=B00000000;

    Вот и получили ШИМ с частотой 19кГц против 50Гц.

    Подведём итоги

    digitalWrite() было 1800мкс , стало 0,2мкс
    digitalRead() было 1900мкс , стало 0,2мкс
    analogWrite() было 2000мкс , стало 51,2мкс
    analogRead() было 110мкс , стало 18,2мкс , а можно до 0,2мкс

    Рекомендуем почитать

    Наверх