«Программируем светодиодное дерево» средствами платы Arduino.

Тема статьи: 
Электронные поделки и механизмы

https://youtu.be/vurtapaQvxs

В прошлой статье, посвящённой программированию схем на базе платы Arduino, мы задавали реакцию светодиодов на нажатие кнопки. Сейчас предлагаю продолжить эту тему, немного усложнив. Мы будем использовать то же устройство, которое делали в прошлый раз, состоящее из трёх светодиодов с кнопкой, и платы Aduino UNO.

ArduinoSvetodiodTree02.jpg

Схема была такая.

ArduinoSvetodiodTree01.jpg

Усложнять мы будем программный код. А также, добавим в эту схему, ради большей наглядности, светодиодное дерево.
Для его создания мы использовали по 5 диодов каждого цвета — красного, зелёного и желтого. К положительным полюсам диодов, мы припаяли по резистору на 220 КОм. Получается, что от каждого диода отходит по 2 провода — плюс и минус.

ArduinoSvetodiodTree03.jpg

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

ArduinoSvetodiodTree04.jpg

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

Для поддержки всей структуры использовали трубы (кабель-каналы 20мм в диаметре), соединенные фланцами разработанными и напечатанными на 3D принтере.

ArduinoSvetodiodTree05.jpg

В основании центральной трубы мы сделали отверстие, чтобы вывести все шесть проводов.

ArduinoSvetodiodTree06.jpg

Дети вырезали цветы из пластичной замши и, - дерево готово.

ArduinoSvetodiodTree07.jpg

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

Ниже приводится код программы для трёх диодов и кнопки.

Давайте посмотрим основные моменты.
Номера контактов Arduino, к которым подключены диоды удобно хранить в массиве.

int LEDS[3] = {2, 3, 4};

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

Например, в функции setup() происходит инициализация контактов:

for(int i = 0; i < sizeof_LEDS; i++)
{
  pinMode(LEDS[i], OUTPUT);
  digitalWrite(LEDS[i], LOW);
}

Или в функции loop() задаются значения контактов:

for(int i = 0; i < sizeof_LEDS; i++)
{
  digitalWrite(LEDS[i], current_prog.stadii[current_prog.current_stad][i]);
}

Всё это особенно удобно если диодов не три, а больше.

Программу поведения диодов будет хранить структура PROG.

//структура, хранящая параметры текущей программы для светодиодов
struct PROG {
/*текущая стадия (индекс) выполняемой программы идет по массиву stadii*/
  int current_stad = 0;
//максимальное число стадий (циклически повторяются)
  int max_stad_count = 12;
//состояния диодов на каждом шаге выполняемой программы
  int stadii[12][3] = {
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0}
  };

//задержка перед выполнением следующего шага
  int stad_delay = 500;
} current_prog;

Как видим, в этой структуре есть массив stadii, описывающий состояние каждого из 3х диодов на каждом шаге в виде нулей и единиц. 0 — диод не горит, 1 — диод горит. Задаётся максимально возможное число шагов (max_stad_count), у нас 12. По достижению 12го шага, или 12го элемента массива, мы начинаем выполнять первый — и так по-кругу. В структуре хранится индекс текущего шага (current_stad), который будет постоянно меняться. А также переменная, задающая временной интервал между шагами в миллисекундах (stad_delay).
Функция check_button_state() каждые 5 миллисекунд вызывается в цикле loop() и проверяет произошло ли нажатие кнопки. Если произошло событие и кнопка была нажата, то мы вызываем функцию, записывающую в текущий объект структуры новые данные, задающие следующую программу поведения диодов.

if( (current_but_state == true) && (but_state == false) )
{
//кнопка нажата - значит выполняем следующую программу
  current_prog_index++;
  but_state = true;
//определяем по индексу текущей программы, какую выполнять
  if(current_prog_index == 1)
    set_prog1();
  else if(current_prog_index == 2)
    set_prog2();
  else if(current_prog_index == 3)
    set_prog3();
  else
  {
    current_prog_index = 1;
    set_prog1();
  }
}

Так что программы поведения расписываются в специальных функциях. У нас это 3 функции — 3 программы (set_prog1(), set_prog2(), set_prog3()). Чтобы задать сценарии поведения диодов, достаточно в этих функциях расставить нули и единицы в нужных местах.

Приводим полный код программы:

/*Программа для трёх диодов и одной кнопки. Содержит несколько сценариев поведения диодов. При нажатии на кнопку сценарии меняются.*/
//массив, содержащий номера контактов

int LEDS[3] = {2, 3, 4};
// размер массива LEDS
int sizeof_LEDS = 3;
/* целочисленная константа с номером контакта от кнопки, этот контакт будет опрашивать состояние кнопки*/
const int BUTTON = 12;
/* переменная, хранящяя состояние кнопки, может принимать только значения true или false */
bool but_state = false;
//временной интервал, через который опрашивается состояние кнопки
int check_but_state_delay = 5;
//индекс текущей программы для светодиодов
int current_prog_index = 1;
//структура, хранящая параметры текущей программы для светодиодов
struct PROG {
/*текущая стадия (индекс) выполняемой программы идет по массиву stadii*/
  int current_stad = 0;
//максимальное число стадий (циклически повторяются)
  int max_stad_count = 12;
//состояния диодов на каждом шаге выполняемой программы
  int stadii[12][3] = {
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0}
  };

//задержка перед выполнением следующего шага
  int stad_delay = 500;
} current_prog;

// инициализация всех переменных
void setup() {
/*перебираем данные массива с номерами контактов и каждому присваиваем значение: выходной контакт, ставим все диоды в состояние: выключено*/
  for(int i = 0; i < sizeof_LEDS; i++)
  {
    pinMode(LEDS[i], OUTPUT);
    digitalWrite(LEDS[i], LOW);
  }
// для контакта кнопки задаём значение: входной контакт
  pinMode(BUTTON, INPUT);
//устанавливаем первую программу поведения диодов
  set_prog1();
}

//функция, в которой циклически повторяются заданные нами действия
void loop() {
/*локальная переменная, отсчитывающая время, через которое нужно выйти из цикла проверки состояния кнопки и выполнить переключение диодов*/
  int init = 0;
/*цикл, в котором вызывается функция проверки состояния кнопки через заданный вначале временной интервал - check_but_state_delay. По прошествии заданной в структуре PROG задержки stad_delay мы выскакиваем из цикла, чтобы переключить диоды*/
  do{
//проверяем состояние кнопки - не произошло ли событие
    check_button_state();
    delay(check_but_state_delay);
    init += check_but_state_delay;
  }while(init < current_prog.stad_delay);

/*устанавливаем значение кнопок в соответствии со следующим шагом программы*/
  for(int i = 0; i < sizeof_LEDS; i++)
  {
    digitalWrite(LEDS[i], current_prog.stadii[current_prog.current_stad][i]);
  }

/*следующий шаг делаем текущим, предварительно проверив конечный он или нет.*/
  current_prog.current_stad = check_max_current_stad(++current_prog.current_stad);
}

//функция проверяет индекс текущего шага программы
int check_max_current_stad (int param)
{
/*если шаг больше последнего - присваеваем первый, иначе - не меняем его*/
  if(param >= current_prog.max_stad_count)
    return 0;
  else
    return param;
}

/*Функция, проверяющая изменилось ли состояние кнопки*/
void check_button_state()
{
// в локальную переменную записываем текущее состояние кнопки
  bool current_but_state = digitalRead(BUTTON);

/*сравниваем с предыдущим состоянием кнопки. Если раньше кнопка была отжата, а теперь нажата, то меняем значение переменной на true для того, чтобы программа среагировала только на факт нажатия*/
  if( (current_but_state == true) && (but_state == false) )
  {
//кнопка нажата - значит выполняем следующую программу
    current_prog_index++;
    but_state = true;
//определяем по индексу текущей программы, какую выполнять
    if(current_prog_index == 1)
      set_prog1();
    else if(current_prog_index == 2)
      set_prog2();
    else if(current_prog_index == 3)
      set_prog3();
    else
    {
      current_prog_index = 1;
      set_prog1();
    }
  }
/*Если раньше кнопка была нажата, а теперь отжалась, возвращаем значение переменной в исходное, т.е. false*/
  else if( (current_but_state == false) && (but_state == true) )
  {
    but_state = false;
  }
}

/*далее идут функции, которые задают разные программы поведения диодов, записывая их в структуру программ*/
void set_prog1()
{
  current_prog.current_stad = 0;
  current_prog.max_stad_count = 12;

  int local_stadii[12][3] = {
    {1,0,0},
    {0,1,0},
    {0,0,1},
    {0,1,0},
    {1,0,0},
    {0,1,0},
    {0,0,1},
    {0,1,0},
    {1,0,0},
    {0,1,0},
    {0,0,1},
    {0,1,0}
  };

  for (int i = 0; i < 12; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      current_prog.stadii[i][j] = local_stadii[i][j];
    }
  }

  current_prog.stad_delay = 500;
}

void set_prog2()
{
  current_prog.current_stad = 0;
  current_prog.max_stad_count = 3;

  int local_stadii[12][3] = {
    {0,0,1},
    {0,1,0},
    {1,0,0},
    {0,0,1},
    {0,1,0},
    {1,0,0},
    {0,0,1},
    {0,1,0},
    {1,0,0},
    {0,0,1},
    {0,1,0},
    {1,0,0}
  };

  for (int i = 0; i < 12; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      current_prog.stadii[i][j] = local_stadii[i][j];
    }
  }

  current_prog.stad_delay = 300;
}

void set_prog3()
{
  current_prog.current_stad = 0;
  current_prog.max_stad_count = 6;

  int local_stadii[12][3] = {
    {0,0,0},
    {1,0,0},
    {1,1,0},
    {1,1,1},
    {0,1,1},
    {0,0,1},
    {0,0,0},
    {1,0,0},
    {1,1,0},
    {1,1,1},
    {0,1,1},
    {0,0,1}
  };

  for (int i = 0; i < 12; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      current_prog.stadii[i][j] = local_stadii[i][j];
    }
  }

  current_prog.stad_delay = 500;
}

Заливаем программу в микросхему Arduino и смотрим на результат. Светодиодное дерево включает и выключает диоды в соответствии с заданными программами, а нажатие кнопки переключает эти программы.

Еще что здесь можно сделать интересного — это подключить провода светодиодного дерева не к цифровым, а к аналоговым контактам. Использовать вместо функции digitalWrite(), которая принимает только 2 значения 0 и 1, функцию analogWrite(), принимающую значения от 0 до 255. Таким образом мы сможем не только управлять включением и выключением диодов, но и их яркостью.

Ниже, приводим код программы под аналоговые порты.

/*Программа для трёх диодов и одной кнопки. Содержит несколько сценариев поведения диодов. При нажатии на кнопку сценарии меняются.*/
//массив, содержащий номера контактов

int LEDS[3] = {3, 5, 6};
// размер массива LEDS
int sizeof_LEDS = 3;
/* целочисленная константа с номером контакта от кнопки, этот контакт будет опрашивать состояние кнопки*/
const int BUTTON = 12;
/* переменная, хранящяя состояние кнопки, может принимать только значения true или false */
bool but_state = false;
//временной интервал, через который опрашивается состояние кнопки
int check_but_state_delay = 5;
//индекс текущей программы для светодиодов
int current_prog_index = 1;
//структура, хранящая параметры текущей программы для светодиодов
struct PROG {
/*текущая стадия (индекс) выполняемой программы идет по массиву stadii*/
  int current_stad = 0;
//максимальное число стадий (циклически повторяются)
  int max_stad_count = 15;
//состояния диодов на каждом шаге выполняемой программы
  int stadii[15][3] = {
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0}
  };

//задержка перед выполнением следующего шага
  int stad_delay = 500;
} current_prog;

// инициализация всех переменных
void setup() {
/*перебираем данные массива с номерами контактов и каждому присваиваем значение: выходной контакт, ставим все диоды в состояние: выключено*/
  for(int i = 0; i < sizeof_LEDS; i++)
  {
    pinMode(LEDS[i], OUTPUT);
    digitalWrite(LEDS[i], LOW);
 }
// для контакта кнопки задаём значение: входной контакт
  pinMode(BUTTON, INPUT);
//устанавливаем первую программу поведения диодов
  set_prog1();
}

//функция, в которой циклически повторяются заданные нами действия
void loop() {
/*локальная переменная, отсчитывающая время, через которое нужно выйти из цикла проверки состояния кнопки и выполнить переключение диодов*/
  int init = 0;
/*цикл, в котором вызывается функция проверки состояния кнопки через заданный вначале временной интервал - check_but_state_delay. По прошествии заданной в структуре PROG задержки stad_delay мы выскакиваем из цикла, чтобы переключить диоды*/
  do{
//проверяем состояние кнопки - не произошло ли событие
    check_button_state();
    delay(check_but_state_delay);
    init += check_but_state_delay;
  }while(init < current_prog.stad_delay);

/*устанавливаем значение кнопок в соответствии со следующим шагом программы*/
  for(int i = 0; i < sizeof_LEDS; i++)
  {
    analogWrite(LEDS[i], current_prog.stadii[current_prog.current_stad][i]);
  }

/*следующий шаг делаем текущим, предварительно проверив конечный он или нет.*/
  current_prog.current_stad = check_max_current_stad(++current_prog.current_stad);
}

//функция проверяет индекс текущего шага программы
int check_max_current_stad (int param)
{
/*если шаг больше последнего - присваеваем первый, иначе - не меняем его*/
  if(param >= current_prog.max_stad_count)
    return 0;
  else
    return param;
}

/*Функция, проверяющая изменилось ли состояние кнопки*/
void check_button_state()
{
// в локальную переменную записываем текущее состояние кнопки
  bool current_but_state = digitalRead(BUTTON);

/*сравниваем с предыдущим состоянием кнопки. Если раньше кнопка была отжата, а теперь нажата, то меняем значение переменной на true для того, чтобы программа среагировала только на факт нажатия*/
  if( (current_but_state == true) && (but_state == false) )
  {
//кнопка нажата - значит выполняем следующую программу
    current_prog_index++;
    but_state = true;
//определяем по индексу текущей программы, какую выполнять
    if(current_prog_index == 1)
      set_prog1();
    else if(current_prog_index == 2)
      set_prog2();
    else if(current_prog_index == 3)
      set_prog3();
    else
    {
      current_prog_index = 1;
      set_prog1();
    }
  }
/*Если раньше кнопка была нажата, а теперь отжалась, возвращаем значение переменной в исходное, т.е. false*/
  else if( (current_but_state == false) && (but_state == true) )
  {
    but_state = false;
  }
}

/*далее идут функции, которые задают разные программы поведения диодов, записывая их в структуру программ*/
void set_prog1()
{
  current_prog.current_stad = 0;
  current_prog.max_stad_count = 15;

  int local_stadii[15][3] = {
    {50,0,0},
    {100,0,0},
    {150,0,0},
    {200,0,0},
    {255,0,0},
    {0,50,0},
    {0,100,0},
    {0,150,0},
    {0,200,0},
    {0,255,0},
    {0,0,50},
    {0,0,100},
    {0,0,150},
    {0,0,200},
    {0,0,255}
  };

  for (int i = 0; i < 15; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      current_prog.stadii[i][j] = local_stadii[i][j];
    }
  }

  current_prog.stad_delay = 500;
}

void set_prog2()
{
  current_prog.current_stad = 0;
  current_prog.max_stad_count = 10;

  int local_stadii[15][3] = {
    {0,100,200},
    {50,50,150},
    {100,0,100},
    {150,50,50},
    {200,100,0},
    {255,150,50},
    {200,200,100},
    {150,255,150},
    {100,200,200},
    {50,150,255},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0},
    {0,0,0}
  };

  for (int i = 0; i < 15; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      current_prog.stadii[i][j] = local_stadii[i][j];
    }
  }

  current_prog.stad_delay = 300;
}

void set_prog3()
{
  current_prog.current_stad = 0;
  current_prog.max_stad_count = 15;

  int local_stadii[15][3] = {
    {0,0,0},
    {25,25,25},
    {50,50,50},
    {75,75,75},
    {100,100,100},
    {125,125,125},
    {150,150,150},
    {175,175,175},
    {200,200,200},
    {225,225,225},
    {255,255,255},
    {200,200,200},
    {100,100,100},
    {50,50,50},
    {0,0,0}
  };

  for (int i = 0; i < 15; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      current_prog.stadii[i][j] = local_stadii[i][j];
    }
  }

  current_prog.stad_delay = 500;
}

На сегодня всё, до новых встреч!