Урок 1.2. Использование цикла со счетчиком for(). ШИМ сигнал. Регулировка яркости свечения светодиода процедурой analogWrite()

Базовый курс "Программирование микроконтроллеров"
Модуль 1. Введение в Arduino. Работа с цифровым и аналоговым сигналом

RGB-светодиод

RGB — это аббревиатура из названий цветов трех светодиодов, которые размещаются внутри устройства. Чем RGB-светодиод лучше трех обычных? 

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

R

red - красный

B

blue - синий

G

green - зелёный

Матрица телевизора состоит из отдельно стоящих точек разных цветов. Если взять лупу и посмотреть через нее на включенный монитор, то эти точки можно легко увидеть. А вот на уличном экране точки размещаются не очень плотно, так что их можно различить невооруженным глазом. Но с расстояния несколько десятков метров эти точки неразличимы.

 

Получается, что чем плотнее друг к другу стоят разноцветные точки, тем меньшее расстояние требуется глазу чтобы смешивать эти цвета. Кстати, еще лучше себя показывает RGB-светодиод с матовой линзой.
Типичную распиновку RGB светодиода с общим катодом (-) Вы можете увидеть справа ⇒

Подключение RGB-светодиода

Как нам уже известно из первых уроков, любой микроконтроллер умеет хорошо работать с цифровыми сигналами. Он легко справляется с арифметическими операциями над цифровыми данными, принимает и передаёт цифровые сигналы по линиям связи. А что значит «цифровые» в данном случае?

Для того, чтобы зажечь светодиод, мы подавали на его анод высокий уровень сигнала. А чтобы погасить — низкий уровень. Получается, для управления мы использовали только два уровня напряжения: высокий и низкий. Светодиод либо будет гореть, либо не будет. Третьего — не дано. Оперируя только двумя состояниями означает, что мы работаем с цифровым сигналом.

Но что делать, если нам нужно зажечь этот самый светодиод только на половину яркости? Или запустить двигатель, на 30% его мощности? Для решения этой задачи используют подход, называемый широтно-импульсной модуляцией сигнала. О том, что такое ШИМ и как это работает, мы узнаем на сегодняшнем уроке.

Соберите следующую схему:

Широтно-импульсная модуляция — ШИМ

Разберем понятие ШИМ на примере управления скоростью вращения двигателя постоянного тока. Поставим своей целью запустить мотор на 50% от его максимальной скорости.

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

Проведем мысленный эксперимент (а кто-то может и натуральный — ничего сложного):
1. Возьмём мотор постоянного тока с массивным маховиком, закрепленным на валу (таким маховиком может служить колесо)4
2. Подадим питание от аккумулятора и мотор начнет набирать обороты;
3. Через какое-то время, мотор достигнет номинальной мощности, а его ротор максимальной скорости вращения;
4. Отключим питание, и мотор постепенно начнет замедляться вплоть до полной остановки.

Следующий опыт. Снова включим мотор, и когда его скорость достигнет половины от максимальной — выключим. Заметив, что скорость падает — снова включим. И так далее. Включая и выключая питание мотора, мы заставим ротор вращаться со скоростью, близкой к половине от максимальной!

 

Похожую аналогию можно провести с лампочкой, чем дольше держать её включенной, тем ярче нам будет казаться её свечение.

Разумеется, в силу человеческой медлительности, мотор будет удерживать заданную скорость с некоторой погрешностью. Другими словами, скорость будет «плавать» вокруг заданного значения. Чтобы минимизировать эти отклонения, нам потребуется увеличить частоту переключений. Тут уже не обойтись без автоматики.

А как заставить мотор вращаться медленнее или быстрее? Количество переданной мотору энергии будет зависеть от отношения времени когда мотор включен — tвкл к времени когда он выключен — tвыкл.

Так, для передачи мотору 50% мощности, tвкл будет равно tвыкл. Такой случай как раз изображен на графике. Чтобы мотор вращался еще медленнее, скажем с мощностью 25% от номинальной, придется время включения мотора уменьшить до этих самых 25% от общего периода управления T.

Таким образом, имея возможность менять ширину импульсов, мы можем достаточно точно управлять скоростью вращения мотора.

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

Частота ШИМ определяет период импульса — T. Требования к этой частоте диктуются несколькими факторами, в зависимости от типа управляемого устройства.

В случае управления светодиодами одним из главных факторов становится видимость мерцания. Чем выше частота, тем менее заметно мерцание излучаемого света. Высокая частота также помогает снизить влияние температурных скачков, которые светодиоды не любят. На практике для светодиодов достаточно иметь частоту ШИМ в пределах 100-300 Гц.

Обратите внимание:

 – С моторами постоянного тока дела обстоят немного иначе. С одной стороны, чем больше частота, тем более плавно и менее шумно работает мотор. С другой — на высоких частотах падает крутящий момент. Нужен баланс. Рекомендуем для большинства DIY задач использовать частоту ШИМ 2кГц.

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

 – Ещё один важный параметр — разрешение ШИМ сигнала. Этот параметр показывает, с какой точностью мы можем менять коэффициент заполнения. Чем больше разрешение, тем плавнее будет меняться мощность на управляемом устройстве. Например, у платы Ардуино с базовыми настройками, разрешение ШИМ — 256. То есть мы можем изменять сигнал от 0 до 255 — не густо, но для большинства DIY задач хватает.

 – Простейший генератор ШИМ можно собрать и без всяких микроконтроллеров, только лишь с микросхемой таймера 555. Разумеется, любой микроконтроллер тоже умеет работать с ШИМ сигналом.

 – Например, у платы Ардуино имеется 6 контактов: 3, 5, 6, 9, 10 и 11, которые можно настроить для генерации аппаратного ШИМ. По-умолчанию, на контактах 5 и 6 частота сигнала будет 1кГц, на остальных — скромные 500Гц.

 – STM32F103 — гораздо более серьёзный микроконтроллер. У него целых 20 контактов имеют возможность генерации ШИМ. Частота этого микроконтроллера — 72МГц, что делает возможным плавное и точное управление моторами постоянного тока, не говоря уже о светодиодах.

Функция analogWrite

Функция analogWrite() выдает “аналоговую” величину на порт вход/выхода. Функция может быть полезна для управления яркостью подключенного светодиода или скоростью электродвигателя. 

После вызова analogWrite() на выходе будет генерироваться постоянная прямоугольная волна с заданной шириной импульса до следующего вызова analogWrite (или вызова digitalWrite или digitalRead на том же порту вход/выхода). 

Частота ШИМ-сигнала зависит от используемых пинов:

ПиныЧастотаРазрешение
D5 и D6976 Гц8 бит (0-255)
D9 и D10488 Гц8 бит (0-255)
D3 и D11488 Гц8 бит (0-255)

На большинстве плат Arduino (на базе микроконтроллера ATmega168 или ATmega328) ШИМ поддерживают порты 3, 5, 6, 9, 10 и 11, на плате Arduino Mega порты с 2 по 13. 

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

Обратите внимание: функция analogWrite никак не связана с аналоговыми входами и с функцией analogRead.

Синтаксис

analogWrite(НОМЕР ПИНА, ЗАПОЛНЕНИЕ ШИМ СИГНАЛА);

Параметры

НОМЕР ПИНА:
порт вход/выхода на который подаем ШИМ сигнал.
ЗАПОЛНЕНИЕ ШИМ СИГНАЛА:
период рабочего цикла значение между
0 (полностью выключено) and 255 (сигнал подан постоянно).

Возвращаемые значения

нет

Обратите внимание: период ШИМ сигнала на портах вход/выхода 5 и 6 будет несколько длиннее. Это связано с тем, что таймер для данных выходов также задействован функциями millis() и delay(). Данный эффект более заметен при установке коротких периодов ШИМ сигнала (0-10).  

Управление яркостью одного светодиода

Напишем первую программу с использованием функции analogWrite! Наша программа будет проста и понятна, включим светодиод с разным уровнем ШИМ-сигнала и понаблюдаем за изменением яркости светодиода.

int led = 3; //пин подключения светодиода

void setup() 
{
  pinMode(led,OUTPUT); //конфигурация пина на выход
}

void loop() 
{
  analogWrite(led,0); //включение светодиода с уровнем "яркости" 0
  delay(500);
  analogWrite(led,100); //включение светодиода с уровнем "яркости" 100
  delay(500);
  analogWrite(led,150); //включение светодиода с уровнем "яркости" 150
  delay(500);
  analogWrite(led,200); //включение светодиода с уровнем "яркости" 200
  delay(500);
  analogWrite(led,255); //включение светодиода с уровнем "яркости" 255
  delay(500);
}

Получилось интересно, но хотелось бы бОльшей плавности в изменении яркости светодиода, но прописывать руками каждую градацию яркости идея, мягко говоря, не лучшая.

Цикл for

Конструкция for используется для повторения блока операторов, заключенных в фигурные скобки. Счетчик приращений обычно используется для приращения и завершения цикла. Оператор for подходит для любых повторяющихся действий и часто используется в сочетании с массивами коллекций данных/выводов.

Заголовок цикла for состоит из трех частей:

for (initialization; condition; increment) 
{
операторы выполняющиеся в цикле;
}

Инициализация (Initialization) выполняется самой первой и один раз. В процессе инициализации создается переменная счётчика.

Условие (condition), если оно верно, выполняется блок операторов. Иными словами, тут мы должны прописать условие продолжения выполнения цикла. Когда логическое значение условия становится ложным, цикл завершается.

Приращение (increment), закон изменения переменной счётчика. 

Изменим нашу программу так, что бы светодиод плавно изменял свою яркость и затухал, используя цикл for:

const int Red = 9; //пин подключения красного светодиода
const int Green = 10; //пин подключения зеленого светодиода
const int Blue = 11; //пин подключения синего светодиода

void setup() 
{
  pinMode(Red,OUTPUT); //конфигурация пина на выход
  pinMode(Green,OUTPUT); //конфигурация пина на выход
  pinMode(Blue,OUTPUT); //конфигурация пина на выход
}

void loop() 
{
  // инициализация переменной счётчика i=0
  // условия выполнения цикла i < 256, пока оно выполняется цикл будет выполняться
  // приращение переменной счётчика на единицу, запись i++ эквивалентна i=i+1
  for (int i = 0; i < 256; i++)
  {
    analogWrite(Red, i); //включение светодиода с уровнем "яркости" i
    delay(5);
  }
  // инициализация переменной счётчика i=255
  // условия выполнения цикла i >= (больше или равно) 0, пока оно выполняется цикл будет выполняться
  // приращение переменной счётчика на единицу, запись i-- эквивалентна i=i-1
  for (int i = 255; i >= 0; i--)
  {
    analogWrite(Red, i); //включение светодиода с уровнем "яркости" i
    delay(5);
  }
}

Переливание цветов RGB-светодиода цветами радуги

Теперь напишем достаточно сложную программу, но результат будет очень красивым! Программа будет плавно переключать цвета светодиода в следующем порядке:

const int Red = 9; //пин подключения красного светодиода
const int Green = 10; //пин подключения зеленого светодиода
const int Blue = 11; //пин подключения синего светодиода
const int t = 5; //время задержки анимации

void setup()
{
  pinMode(Red, OUTPUT); //конфигурация пина на выход
  pinMode(Green, OUTPUT); //конфигурация пина на выход
  pinMode(Blue, OUTPUT); //конфигурация пина на выход
  //при включении устройства горит красный цвет
  analogWrite(Red, 255);
  analogWrite(Green, 0);
  analogWrite(Blue, 0);
}

void loop()
{
  // красный горит, параллельно разжигаем зеленый (красный -> оранжевый -> желтый)
  for (int i = 0; i < 256; i++) //плавное изменение яркости светодиода
  {
    analogWrite(Green, i);
    delay(t);
  }
  // зеленый горит, параллельно гасим красный (желтый -> зеленый)
  for (int i = 0; i < 256; i++) //плавное изменение яркости светодиода
  {
    analogWrite(Red, 255 - i);
    delay(t);
  }
  // зеленый горит, параллельно разжигаем синий (зеленый -> голубой)
  for (int i = 0; i < 256; i++) //плавное изменение яркости светодиода
  {
    analogWrite(Blue, i);
    delay(t);
  }
  // синий горит, параллельно гасим зеленый (голубой -> синий)
  for (int i = 0; i < 256; i++) //плавное изменение яркости светодиода
  {
    analogWrite(Green, 255 - i);
    delay(t);
  }
  // синий горит, параллельно разжигаем красный (синий -> фиолетовый)
  for (int i = 0; i < 256; i++) //плавное изменение яркости светодиода
  {
    analogWrite(Red, i);
    delay(t);
  }
  // красный горит, параллельно гасим синий (фиолетовый -> красный)
  for (int i = 0; i < 256; i++) //плавное изменение яркости светодиода
  {
    analogWrite(Blue, 255 - i);
    delay(t);
  }
}
ЦВЕТRGB
красный25500
оранжевый2551250
желтый2552550
зеленый02550
голубой0255255
синий00255
фиолетовый2550255

Код программы анимации

Соберем схему и напишем код на красивую анимацию с несколькими светодиодами, будем плавно включать или выключать светодиоды в гирлянде.

const int pins[6] = {3, 5, 6, 9, 10, 11}; //массив пинов подключенных светодиодов

void setup()
{
  for (int i = 0; i < 6; i++) //конфигурация пинов подключенных светодиодов
  {
    pinMode(pins[i], OUTPUT);
  }
}

void loop()
{
  for (int i = 0; i < 6; i++) //выбор светодиода по порядковому номеру из массива подключенных пинов
  {
    for (int j = 0; j < 256; j++) //плавное увеличение яркости светодиода
    {
      analogWrite(pins[i], j);
      delay(5);
    }
    for (int j = 255; j >= 0; j--) //плавное уменьшение яркости светодиода
    {
      analogWrite(pins[i], j);
      delay(5);
    }
  }
  for (int i = 0; i < 6; i++) //выбор светодиода по порядковому номеру из массива подключенных пинов
  {
    for (int j = 0; j < 256; j++) //плавное увеличение яркости светодиода
    {
      analogWrite(pins[6 - i], j);
      delay(5);
    }
    for (int j = 255; j >= 0; j--) //плавное уменьшение яркости светодиода
    {
      analogWrite(pins[6 - i], j);
      delay(5);
    }
  }
}