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

Наверное не надо объяснять, что на месте светодиода может быть любое устройство, двигатель к примеру, которое управляется посредством ШИМ. В нашем примере, как и на занятии, мы будем говорить про светодиод.

Для плавного включения светодиода необходимо постепенно увеличивать скважность импульса управляющего яркостью светодиода.

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

Рис. 1. Цикл, обеспечивающий плавное включение свтодиода.

Для начала мы определяем длительность данного процесса. Возьмём, к примеру 1.5 секунды, т.е. 1500 миллисекунд. Число шагов должно быть не менее 25 в секунду, что бы не видно было мигания. Возьмём, к примеру, 50.

Увеличение числа шагов делает процесс более плавным, но при этом забирает ресурсы контроллера.

Диапазон управляющих значений ШИМ 0 – 255 определён функцией контроллера analogWrite().

Таким образом, мы можем решить задачу следующим образом: организовываем цикл, количество итераций которого равно числу шагов (в данном случае 50) и выдаем на предварительно выбранный порт входа/выхода значение, пропорциональное номеру итерации, в диапазоне от 0 до 255. При этом число шагов у нас ограничено 50. Для поиска соответствия будем использовать функцию Arduino map.

st = map(n, 0, Steps, 0, 255);

где n – номер итерации, а Steps – число шагов. Т.е. n меняется в диапазоне от 0 до Steps (50), а выходное значение функции, меняется пропорционально от 0 до 255.

Значение st определяет скважность управляющего импульса на выходе контроллера. Включим выбранный порт с данной скважностью:

analogWrite(pin, st);

Каждая итерация цикла должна включать задержку. Длительность задержки, грубо, можно рассчитать исходя из заданной длительности процесса и числа шагов. t = 1500 / 50 = 30 мс.

Рис. 2. Итерации цикла включающего светодиод.

В примитивном варианте цикл выглядел бы так:

for(int n = 0; n < N; ++n){
int st = map(n, 0, Steps, 0, 255);
analogWrite(pin, st);
delay(30);
}

Обратите внимание на функцию delay(). Данная функция ОСТАНАВЛИВАЕТ выполнение программы. Т.е. каждые 30 миллисекунд, наша программа, выполнив несколько операций будет останавливаться. Такое решение допустимо и часто используется в демонстрационных примерах учебных программ, но для реальных устройств подобное решение нерационально.

Мы уже знаем решение, давайте опишем его ещё раз, что бы наш “пазл” полностью сложился.

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

#include "timer.h"
int n = 0;      //номер итерации
int Steps = 50; //число итераций (шагов)
int dT = 30;    //длительность задержки
timer *Timer;
int levelLight = 0;
static const int pin = 11;

void setup() {
  pinMode(pin, OUTPUT);
  Timer = new timer();
}

void loop() {
  while(n < Steps){
    if(!Timer->getTimer()){
      Timer->setTimer(dT); //запускаем таймер на следующий период
      levelLight = map(n, 0, Steps, 0, 255); 
      analogWrite(pin, levelLight);
      ++n;
    } else break;
  }
 if(n == Steps) n = 0;
}

Давайте попробуем сделать полезное “виртуально-аппаратное устройство”, которое, как и таймер, сможет пригодиться в наших проектах.

Задача устройства: плавно включать и выключать аппаратное устройство по внешней команде (вызове соответствующей функции).

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

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

//++++++++++++++++++++++++++++++++++++++++++++++
class softLed{
  private:
    int n;        //текущая итерация
    int pin;      //номер порта ввода/вывода
  protected:
    int steps;    //число шагов
    int stat;     //состояние светодиода
    unsigned long dt; //длительность задержки
    timer *Timer; //указатель на класс таймера
  public:
    softLed(int, int, bool, unsigned long);    //конструктор
    ~softLed();   //деструктор
    bool ledOn(); //включение
    bool cycle();   //цикл в цепи
};

Часть членов класса объявлена как private, т.е. к классам имеют доступ только функции – члены класса. Часть объявлена как protected, т.е. доступ к ним имеют функции данного класса и функции дочернего класса. Часть функций доступна всем – public. Разделение доступа называется термином «Инкапсуляция». В дальнейшем мы рассмотрим это более подробно.

Класс имеет всего 4 функции:

softLed(int, int, bool, unsigned long); //конструктор
~softLed(); //деструктор
bool ledOn(); //включение
bool cycle(); //жизненный цикл

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

Ниже привожу весь код демо-программы.

#include "timer.h"

static const int pin = 11;
//++++++++++++++++++++++++++++++++++++++++++++++
class softLed{
  private:
    int n;        //текущая итерация
    int pin;
  protected:
    int steps;    //число шагов
    int stat;     //состояние светодиода
    unsigned long dt; //длительность задержки
    timer *Timer;
  public:
    softLed(int, int, bool, unsigned long);    //конструктор
    ~softLed();   //деструктор
    bool ledOn(); //включение
    bool cycle();   //цикл в цепи
};
//***********************
softLed::softLed(int pin, int steps = 30, bool stat = false, unsigned long dt = 50){
  this->pin = pin;
  this->n = 0;
  this->steps = steps;
  this->stat = stat;
  this -> dt = dt;
  Timer = new timer();
  pinMode(this->pin, OUTPUT);
  digitalWrite(this->pin, this->stat);
}
//***********************
softLed::~softLed(){
  delete Timer;
}
//***********************
bool softLed::ledOn(){
  this -> n = this -> steps;
  this->stat = !this->stat;
  Serial.println("dt = " + (String)this->dt);
  return this->n > 0;
}
//***********************
bool softLed::cycle(){
  if(this->n && !this->Timer->getTimer()){
      Timer->setTimer(this->dt); //запускаем таймер на следующий период
      int levelLight = map(this->n-1, 0, this->steps-1, 0, 255); 
      levelLight = this->stat? 255 - levelLight: levelLight;
      analogWrite(this->pin, levelLight);
      this->n--;
  }
  return this->n > 0;
}
//---------------------------------
softLed *sLed;
timer *Tm;
//***********************
void setup() {
  sLed = new softLed(pin);
  Tm = new timer();
  sLed->ledOn();
}

//***********************
void loop() {
  sLed->cycle();
  if(!Tm -> getTimer()){
    Tm -> setTimer(3000);
    sLed->ledOn();
  }
}

Смотрится немного страшновато… НО, если убрать описание класса (в другой файл, после отладки) и просто пользоваться этим классом, то ….

#include "timer.h"
static const int pin = 11;
//---------------------------------
softLed *sLed;
timer *Tm;
//***********************
void setup() {
  sLed = new softLed(pin);
  Tm = new timer();
  sLed->ledOn();
}

//***********************
void loop() {
  sLed->cycle();
  if(!Tm -> getTimer()){
    Tm -> setTimer(3000);
    sLed->ledOn();
  }
}

Опа! Абсолютно понятная, законченная программа.