РадиоКот :: Генератор световых эффектов для новогодней елки
Например TDA7294

РадиоКот >Схемы >Светотехника >Бегущие огни и световые эффекты >

Теги статьи: Добавить тег

Генератор световых эффектов для новогодней елки

Автор: Bullet
Опубликовано 23.11.2015
Создано при помощи КотоРед.

Давным давно в далекой галактике… Ну на самом деле не так давно и далеко, где то в начале 90-х, мой отец, всю жизнь отходивший в море судовым врачом, после очередного полугодового плаванья привез из Китая новогоднюю елку со световодами. По тем временам диво невиданное. Состояло это чудо китайской промышленности собственно из самой елки и подставки, внутри которой располагалась галогеновая лампа и цветной диск из оргстекла, закрепленный на валу электродвигателя. Свет от лампы проходит через диск и далее по световодам, заставляя иголки елки светится, а благодаря вращению диска, достигается переливание цветов. Но, как и все продукты под товарной маркой Made in China, устройство оказалось недолговечным. Через пару-тройку лет приказал долго жить блок питания. С виду обычный адаптер на 12 вольт, но попытки заменить его другим адаптером не приводили к положительному результату. Лампа светилась, но двигатель не вращался. Покрутив с пол часика в руках, выяснили что БП, оказывается, переменного тока. В результате удалив из нового адаптера диодный мост, устройство заработало в штатном режиме. Так оно проработало еще пару лет, пока не перегорела лампочка. На удивление, в те времена, найти замену для лампы оказалось не сложно, но как вы наверное уже догадались, лампы перегорали с завидной регулярностью. И что самое удивительное, сейчас, не смотря на огромнейший ассортимент всевозможных ламп в любом магазине электрики, найти подходящую так и не удалось. В результате пришлось колхозить. Была взята сгоревшая лампа, с помощью молотка, зубила и такой то матери, лампа было отделена от отражателя, и вышеупомянутыми средствами, в отражатель была инсталлирована лампа без отражателя, но подходящая по габаритам и цоколю. Конструкция хоть и жуткая, но работоспособная, правда эффект от такой лампы уже не тот, яркость заметно упала, да и проблему перегоревших лампочек это не решает. И вот после очередной перегоревшей лампочки, было принято решение заменить источник света на светодиодный. логичным решением было бы удалить галогенку и на ее место поставить CREE светодиод на 1-3 Вт. Но мы не ищем легких путей, по этому, вместе с лампой был удален и двигатель с диском. Установить один большой RGB светодиод, или несколько маленьких и рулить ими с помощью готового контроллера, тоже мало интересно, да и в этом случае вся елка будет иметь один цвет, а хотелось, чтобы разные участки, светились разными цветами.

В итоге, концепция выглядела следующим образом: разместить максимально возможное количество RGB светодиодов и рулить ими с помощью ATMega16 используя BAM(Binary Angle Modulation). Т.к. отверстие в подставке около 3 см в диаметре, на эту площадь удалось вместить 18 светодиодов в 5050 корпусе. 18х3=54 канала, многовато для 40-ногой меги, по этому светодиоды были объединены в линейки, по 3 в каждой. Итого 18 каналов, плюс, изначально планировалось управлять общей яркостью через общий анод, а это еще 6 каналов, но после того, как развел плату, пришло осознание, что этот вариант работать не будет. Т.к. разводить плату по новой уже не хотелось, эти ноги просто остались незадействованными (правда, плату все равно пришлось разводить по новой, т.к. она не лезла в корпус, но к тому моменту, прошивка была полностью готова и что-то менять в схеме не было ни какого желания). В итоге 3 порта МК задействованы на светодиоды и еще один порт оставлен для подключения матричной клавиатуры 4х4.

Схема устройства в принципе ни чем не примечательная, каждый канал МК через ограничительный резистор подключен на базу NPN транзистора, эмиттер на землю, коллектор, через резистор, на катод первого светодиода в линейке. Светодиоды в линейке включены последовательно, а на последнем аноды объединены и подключены к +12в.

После травления платы и запайки компонентов получилось вот такое устройство

Т.к. о схеме особенно рассказывать нечего, а написать что-то нужно, то далее поговорим о программной реализации. Собственно вся задача сводится к тому, чтобы определенным образом менять яркость каждого светодиода (имеется в виду, что RGB светодиод состоит из 3-х светодиодов красного, зеленого и синего цвета). Обычно, для этих целей используют ШИМ (Широтно-импульсная модуляция, англ. pulse-width modulation (PWM)), но аппаратных возможностей МК не достаточно для управления 18 каналами. Программный ШИМ, тоже не самая удачная идея для этого проекта. По этому, как я уже упоминал ранее, для управления светодиодами был использован BAM(Binary Angle Modulation). О том, что такое BAM и как его реализовать на Си, можно прочесть по ссылке №1. в конце статьи. Но т.к. я пишу на ОЧЕНЬ высокоуровневом языке, название которого, в приличных кругах, и произносить то не принято, алгоритм пришлось выдумывать самому, но суть осталась той же. Собственно по этой же причине я не привожу исходники, а весь алгоритм постараюсь объяснить на пальцах (правда пока писал статью, пришел к выводу, что часть кодов все таки стоит показать, для наглядности, и чтобы они были понятны широкому кругу читателей, я их напишу на Си). Постараюсь сделать это как можно подробней, чтобы даже котятам было понятно, а профессионалы, я надеюсь, способны своять такое устройство и без моих объяснений.

И так, для организации BAM, нам понадобится таймер и прерывание по достижению таймером определенного значения. Настраиваем таймер Т0 и прерывания:

Обнуляем таймер Т0, установив в регистр TCNT0 значение 0;

Устанавливаем в регистр сравнения OCR0 значение 128 (10000000 в двоичной системе);

Разрешаем прерывание по совпадению таймера Т0 установив регистр TIMSK = 2 (или OCIE0=1);

Запускаем таймер с делителем на 64 и вызовом прерывания по совпадению, что соответствует значению регистра TCCR0 = 11;

Разрешаем глобальные прерывания #asm("sei")

Все настройки выполняются до основного цикла программы.

Теперь, в обработчике прерывания по вектору $026 (OC0addr) сдвигаем OCR0 на один бит вправо (OCR0 >> 1 или делим на 2, что одно и то же), а при достижении 0 опять устанавливаем 128. Таким образом, каждое последующее прерывание, будет вызываться в два раза раньше, чем предыдущее, что нам собственно и нужно. Осталось только дописать установку значения портов (об этом ниже), и наш алгоритм BAM готов.

Теперь попробуем разобраться с портами. В первую очередь следует настроить порты на выход, как это сделать надеюсь объяснять не нужно (для тех кто в танке, в регистр DDRx записать значение 255 (для тех кто совсем не в теме, вместо х подставить букву соответствующего порта)). И так, порты мы настроили, теперь определимся с тем, что же мы будем в них выводить. Как уже упоминалось ранее, все управление сводится к изменению яркости каждого светодиода (канала). Соответственно, нам нужны переменные, в которых мы будем хранить значения яркости для каждого канала. Возится с таким количеством переменных не самая удачная идея, поэтому лучше организовать их в массив, а так как мы имеем дело с 3 портами МК, то удобнее будет создать 3 массива по8 байт каждый. Таким образом у нас каждый элемент массива, будет соответствовать своему выводу порта МК. Назовем эти массивы ch1, ch2, ch3. Меняя значения элементов этих массивов, мы можем получить необходимые цвета светодиодов. Вроде все просто, но есть одна маленькая загвоздка, значения должны выводится в порт побитно, при каждом прерывании. Т.к. при OCR0 равном 1 у нас будет только 64 такта МК, на то, чтобы установить значения в порты, наша маленькая загвоздка, может обернуться большой проблемой. Компиляторы высокоуровневых языков не всегда генерируют оптимальный код для битовых операций (не всегда, это мягко сказано). Поэтому данные надо подготовить заранее, а в прерывании скормить 3 байта в 3 порта. Учитывая то, что оперативной памяти в нашем МК как гуталина у дяди кота Матроскина, экономить ее мы не будем, и просто создадим еще 3 массива той же размерности (назовем их pb, pc, pd) и с помощью нехитрой процедуры, поместим в них значения яркости, предварительно развернув на 900. Что я имел этим в виду, и как это сделать я расскажу чуть позже. А пока в обработчике прерывания нам необходимо выяснить, какое у нас по счету прерывание и присвоить порту соответствующее значение из массива. Делается это следующим образом:

В процедуре обработки прерывания объявляем 2 локальные переменные «i» и «j»;

Присваиваем им значения i=1, j=0;

Запускаем цикл с условием пока i меньше OCR0(цикл необходимо запускать до изменения значения OCR0);

В теле цикла сдвигаем i на один бит влево (i<<1) и увеличиваем j на 1 (j++ или j=j+1)

По окончании цикла, присваиваем порту значение элемента массива с индексом j.

Вот так это будет выглядеть на Си:

interrupt [TIM0_COMP] void timer0_comp_isr(void)
{
    char j, i;
    i=1;
    j=0;
    while (i<OCR0)
    {
      i=i<<1;
      j=j+1;
    }
   OCR0 = OCR0 >> 1;
   if (OCR0 == 0)
    {
      OCR0 = 128;
    };

   PORTB=pb[j];
   PORTC=pc[j];
   PORTD=pd[j];
}

Теперь поговорим о том самом развороте массива на 900. Начнем с того, что я имел в виду. Каждый элемент нашего массива имеет размер 1 байт. Байт в свою очередь можно представить как массив из 8 бит. Таким образом, наш массив является двумерным битовым массивом 8х8 бит. Для наглядности приведу таблицу:

 

7

6

5

4

3

2

1

0

 

ch1[0]

0

0

0

0

0

0

0

1

1

ch1[1]

0

0

1

0

0

0

0

1

35

ch1[2]

0

1

0

0

0

0

0

1

65

ch1[3]

0

1

1

0

0

0

0

1

97

ch1[4]

1

0

0

0

0

0

0

1

129

ch1[5]

1

0

1

0

0

0

0

1

161

ch1[6]

1

1

0

0

0

0

0

1

193

ch1[7]

1

1

1

0

0

0

0

1

225

При первом прерывании мы должны вывести в порт старшие (7) биты каждого байта из массива ch1 и это будет 0b11110000, при втором прерывании (6) 0b11001100 и т.д. То есть, нам нужно поменять местами порядковый номер элемента массива, с порядковым номером бита в байте и в результате получить:

 

7

6

5

4

3

2

1

0

 

pb[0]

1

1

1

1

1

1

1

1

255

pb[1]

0

0

0

0

0

0

0

0

0

pb[2]

0

0

0

0

0

0

0

0

0

pb[3]

0

0

0

0

0

0

0

0

0

pb[4]

0

0

0

0

0

0

0

0

0

pb[5]

1

0

1

0

1

0

1

0

170

pb[6]

1

1

0

0

1

1

0

0

204

pb[7]

1

1

1

1

0

0

0

0

240

Небольшое лирическое отступление:

К сожалению, Си, в отличие от моего компилятора, не позволяет напрямую обращаться к битам в переменной. По этому, специально для этой статьи, пришлось написать еще маленькую процедуру, которая копирует бит из одной переменной в другую. За основу был взят код из статьи по ссылке №1. Но перед тем, как показать вам эту процедуру, нужно сделать небольшое пояснение (для котят):

При объявлении функции или процедуры, в круглых скобках мы указываем входные параметры, которые являются локальными переменными для функции. При вызове функции, указав в скобках имя глобальной переменной, в функцию передается не сама переменная, а только ее значение, и любые изменения этого значения в теле функции, не приведут к изменению значения самой глобальной переменной. Если же нам необходимо, чтобы в ходе выполнения функции менялась сама переменная, то в функцию нужно передавать ссылку на адрес этой переменной. В разных языках это делается по разному, поэтому читайте документацию к своему компилятору. В Си это делается следующим образом. При объявлении функции, перед именем параметра ставится знак * (звездочка, он же умножение). Знак звездочка ставится как при объявлении параметра, так и в теле самой функции. А при вызове функции, перед именем переменной, ставится знак & (амперсанд). Нам это понадобится в нашей процедуре копирования битов. Собственно вот и она:

void COPYBIT(char *dst,char bit_d ,char src,char bit_c)
{
(*dst) &=~(1<<(bit_d));
(*dst) |=(( (src) & (1<<bit_c))?1:0)<<(bit_d);
}

О том как работает эта вязкая синтаксическая конструкция, читайте в вышеупомянутом первоисточнике. А выполняет она следующее: в бит с номером bit_d переменой dst помещается значение бита с номером bit_s переменой src. Как вы уже наверное заметили, в переменную dst передается не значение переменной а ссылка, таким образом, переменная, указанная при вызове процедуры будет изменена.

Вернемся к нашей процедуре поворота массива, назовем ее angle (угол). Объявим 2 локальные переменные i и j. Запустим цикл от j=0 до 7, инкремент j при каждом повторе. В этом цикле запустим второй цикл, с аналогичными параметрами, но уже для i. В теле цикла, с помощью нашей процедуры COPYBIT, присваиваем i-тому биту байта j массива порта j-тый бит i-того байта из массива канала. Ну и код процедуры для наглядности:

void angle(void)
{
   char i,j;

   for (j=0; j<=7; j++)
  {
      for (i=0; i<=7; i++)
    {
        COPYBIT(&pb[j],i,ch1[i],j);
        COPYBIT(&pc[j],i,ch2[i],j);
        COPYBIT(&pd[j],i,ch3[i],j);
    }
  }

}

Вызывать процедуру angle, стоит только при OCR0 равном 64 (if (OCR0==64)  angle();), почему это так, описано все в той же статье по ссылке №1. Так же, дабы не гонять ее по сто раз по кругу, я использовал переменную-флаг, который поднимается при вызове процедуры и сбрасывается при OCR0 равном 0 в обработчике прерывания. Так что еще одним условием для вызова процедуры angle является опущенный флаг.

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

Шарясь по интернету, в поисках идей для эффектов, наткнулся на статью (ссылка №2), которая натолкнула меня на мысль использовать вместо цветовой схемы RGB, цветовую схему HSV (англ. Hue, Saturation, Value — тон, насыщенность, значение). Покурив пару часов формулы перевода HSV в RGB, пришел к выводу, что реализовывать их на МК в первозданном виде весьма сомнительное удовольствие, да и компилятор не сильно обрадовался такому количеству чисел с плавающей точкой. Первое, от чего я отказался, это значение насыщенности. Мы все-таки делаем новогоднюю елку, а не телевизор, поэтому примем это значение за максимум. Далее я расширил диапазон значения тона с 3600 до 1535, это позволило более точно обозначать цвет и использовать целочисленное деление без потерь. Что же касается значения  Value, суть которого яркость цвета, его я ограничил диапазоном от 0 до 16. В принципе этот диапазон можно установить и до 255, но для моих целей 16 было удобнее. В итоге родилась следующая математическая функция:

Для любых оттенков H € [0, 1535] и яркости V € [0, 16]:

x=(V* (H mod 256))/16

x1=V*(not (H mod 256))/16

v=(V*255)/16

H

R

G

B

H € [0, 255]

v

x

0

H € [256, 511]

x1

v

0

H € [512, 767]

0

v

x

H € [768, 1023]

0

x1

v

H € [1024, 1279]

x

0

v

H € [1280, 1535]

v

0

x1

 

Теперь попробую объяснить, что же тут написано. Имея значения H и V, рассчитываем значения для переменных x, x1 и v. Функция mod означает остаток от целочисленного деления, но так как переменные  x и x1 имеют размер 1 байт, ее можно опустить. Далее, в зависимости от того, в каком диапазоне значений лежит H, присваиваем значения переменных x, x1 и v переменным яркости цветов R, G и B согласно таблице. А вот как это будет выглядеть на Си:

void HSVtoRGB(int H, int V , char *r, char *g, char *b)

{
   char x, x1, v;
   if (H >=1536)
   {
      H=H-1536;
   }
   if (H <0)
   {
      H=H+1536;
   }
   x=H;
   x=(V*x)/16;
   x1=H;
   x1=V*(~x1)/16;
   v=(V*255)/16;

   if ((0<=H) && (H<256))
   {
      *r=v;
      *g=x;
      *b=0;
   }
   if ((256<=H) && (H<512))
   {
      *r= x1;
      *g=v;
      *b=0;
   }
   if ((512<=H) && (H<768))
   {
      *r=0;
      *g=v;
      *b=x;
   }
   if ((768<=H) && (H<1024))
   {
      *r=0;
      *g= x1;
      *b=v;
   }
   if ((1024<=H) && (H<1280))
   {
      *r=x;
      *g=0;
      *b=v;
   }
   if ((1280<=H) && (H<1536))
   {
      *r=v;
      *g=0;
      *b=x1;
   }
}

И так, задачу управления цветом и яркостью светодиодной линейки мы упростили. Объявим 2 массива для хранения значений цвета и яркости int H[6]; char V[6]; и переменную общей яркости char VO=16. Так же, для удобства, сделаем макрозамену имен элементов массивов каналов с помощью директивы #define (в моем компиляторе она называется и описывается по другому, по этому если вы, как и я, пишите не на Си, читайте документацию и ищите аналог).

#define B1 ch1[0]
#define R1 ch1[1]
#define G1 ch1[2]
#define A1 ch1[3]
#define B2 ch2[4]
#define R2 ch2[5]
#define G2 ch2[6]
#define A2 ch2[7]
#define B3 ch2[0]
#define R3 ch2[1]
#define G3 ch2[2]
#define A3 ch2[3]
#define B4 ch3[4]
#define R4 ch3[5]
#define G4 ch3[6]
#define A4 ch3[7]
#define B5 ch3[0]
#define R5 ch3[1]
#define G5 ch3[2]
#define A5 ch3[3]
#define B6 ch1[4]
#define R6 ch1[5]
#define G6 ch1[6]
#define A6 ch1[7]

(А1-А6 это те самые общие аноды, от которых пришлось отказаться)

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

Вызовем процедуру HSVtoRGB в основном цикле программы:

HSVtoRGB(H[0],V[0],&R1,&G1,&B1);
HSVtoRGB(H[1],V[1],&R2,&G2,&B2);
HSVtoRGB(H[2],V[2],&R3,&G3,&B3);
HSVtoRGB(H[3],V[3],&R4,&G4,&B4);
HSVtoRGB(H[4],V[4],&R5,&G5,&B5);
HSVtoRGB(H[5],V[5],&R6,&G6,&B6);

А дальше начинается самое интересное. Вот где раздолье для фантазии. Включаем спецэффекты!!!

И так, наш самый первый и самый простой эффект, который напрашивается сам собой, назовем его «Вращающаяся радуга». H[0] увеличиваем на единицу(можно и больше, тут кому как нравится), V[0] устанавливаем равным VO. Проверяем, не вышла ли переменная H[0] за границы диапазона (0<=H[0]<1536) и если вышла, то корректируем. А далее в цикле от i=0 до 5 присваиваем элементу массива H[i] значение элемента массив H[i-1]-shift и V[i]=V[0]. Значение shift определено макрозаменой #define shift 256 (в принципе может быть любым на ваш выбор).

H[0]=H[0]+1;
V[0]=VO;
if (H[0] >=1536)
   H[0]=H[0]-1536;
for (i=1; i<=5; i++)
  {
      H[i]=H[i-1]-shift;
      V[i]=V[0];
   }

Сохраняем, компилируем и наслаждаемся.

Эффекты стоит просчитывать при OCR0>64. Так же не лишним будет ограничить скорость смены цветов, при выполнении эффектов. Для этого объявим еще 2 глобальные переменные f1(считает количество проходов OCR0 через 0) и sp(делитель скорости смены цветов). Поместим код нашего эффекта в констукцию if:

if ((f1==0)||((f1/sp)>1))
   {
      …//код эффекта
      f1=1;
   }

А в процедуру обработки прерывания добавим инкремент переменной f1 при проходе OCR0 через 0. Число, с которым сравнивается значение f1/sp, подбирается эмпирическим путем, и зависит только от ваших предпочтений, в моем случае, для первого эффекта, это 1. Значение переменной sp можно менять в ходе выполнения программы и тем самым ускорять или замедлять эффекты. Значение по умолчанию я установил равным 3. Сравнение f1 с 0 необходимо для исключения ситуации, при которой f1/sp не сможет превысить заданное число, например условие (f1/sp)>25 не будет выполнятся при sp больше 10.

 

Далее переходим к следующему эффекту. Назовем его «Сегментный переход». Суть его в том, чтобы менять цвет каждого сегмента по очереди. Здесь мы так же воспользуемся глобальной переменной i. При вызове эффекта i-тому элементу массива H, присваиваем значение 0-го элемента массива и выполняем инкремент i. При i>5, сбрасываем i и увеличиваем цвет H[0] на значение shift, не забываем при этом выполнить проверку на соответствие диапазону значений. Код эффекта ниже:

if ((f1==0)||(f1/sp)>25)
   {
      if (i>5)
         {
            i=0;
            H[0]=H[0]+shift;
               if (H[0] >=1536)
               {
                  H[0]=H[0]-1536;
               }
         }
      H[i]=H[0];
      V[i]=VO;
      i++;
      f1=1;
   }

В третьем эффекте, назовем его заморским словом «Блинк», попробуем покрутить яркость. Суть эффекта в плавном зажигании и погасании светодиодов.  Для него нам понадобится еще одна глобальная переменная v1, при объявлении сразу присвоим ей значение 0. При вызове эффекта увеличиваем v1 на единицу. Далее проверяем не превышает ли v1 значения общей яркости VO в два раза и если превышает, сбрасываем v1 и меняем цвет. После осуществляем проверку на превышение VO. Если v1 больше VO то V[0]=VO-(v1-VO) иначе V[0]=v1. Далее в цикле присваиваем значения цвета и яркости всем элементам соответствующих массивов.

if ((f1==0)||((f1/sp)>10))
   {
      v1++;
       if (v1>(VO*2))
         {
            v1=0;
            H[0]=H[0]+shift;
               if (H[0] >=1536)
               {
                  H[0]=H[0]-1536;
               }
         }
      if (v1>VO)
         {
            V[0]=VO-(v1-VO);
         }
      else
         {
            V[0]=v1;
         }
      for (i=1; i<=5; i++)
         {
            H[i]=H[0];
            V[i]=V[0];
         }
    f1=1;
   }

Следующие 2 эффекта будут производными от предыдущего.

И так 4-й эффект «Треугольный блинк», в нем светодиоды будут загораться и гаснуть через один. Код этого эффекта практически не отличается от предыдущего, но здесь мы задействуем еще одну глобальную переменную j. Добавим строку j++; в условие v1>32. Далее в цикле присвоения значений цвета и яркости добавим проверку на четность if (((j+i)% 2) ==0) и если условие выполняется V[i]=V[0], если нет то V[i]=0. По окончании цикла установим значение V[0]=0, если j нечетное.

if ((f1==0)||((f1/sp)>10))
   {
      v1=v1+1;
      if (v1>32)
        {
            v1=0;
            H[0]=H[0]+shift;
            j++;
               if (H[0] >=1536)
               {
                  H[0]=H[0]-1536;
               }
         }
         if ((v1>VO) && (v1<=VO*2))
         {
            V[0]=VO-(v1-VO);
         }
         else
         {
            V[0]=v1;
         }
         for (i=1; i<=5; i++)
         {
            H[i]=H[0];
               if (((j+i)% 2) ==0)
               {
                  V[i]=V[0];
               }
               else
               {
                  V[i]=0;
               }
          }
         if ((j % 2) ==1)
         {
            V[0]=0;
         }
      f1=1;
   }

Следующий эффект «Шестиугольный блинк». В нем светодиоды будут загораться и гаснуть не по очереди, а одни гаснут, пока другие загораются. Его реализация усложнена тем, что цвет нужно менять только в момент полного погасания светодиодов, по этому увеличивать значение цвета H[0] мы будем на shift*2, а в цикле установки цвета, при проверке четности добавим проверку значения яркости if (v1>VO), при выполнении которой установим значение H[i]=H[0]+shift, и в противном случае H[i]=H[0]-shift. Таким образом, все четные светодиоды имеют цвет равный H[0], а всем нечетным присваивается цвет в зависимости от того, загораются они или гаснут. Это необходимо по тому, что при максимальном свечении нечетных светодиодов, нечетные полностью погаснут и произойдет смена цвета H[0].

if ((f1==0)||((f1/sp)>10))
   {
      v1++;
       if (v1>32)
         {
            v1=0;
            H[0]=H[0]+shift*2;
               if (H[0] >=1536)
               {
                  H[0]=H[0]-1536;
               }
         }
      if (v1>VO)
         {
            V[0]=VO-(v1-VO);
         }
      else
         {
            V[0]=v1;
         }
      for (i=1; i<=5; i++)
         {
               if ((i % 2) ==1)
               {
                  V[i]=VO-V[0];
                       if (v1>VO)
                     {
                        H[i]=H[0]+shift;
                     }
                       else
                     {
                        H[i]=H[0]-shift;
                     }
               }
                 else
               {
                  V[i]=V[0];
                  H[i]=H[0];
               }
          }
      f1=1;
   }

Ну и последний эффект, бегущий огонь или «Капля». Суть его в том, что по кругу бежит огонек, а следующие за ним 2 светодиода имеют яркость ниже чем у предыдущего и визуально напоминают каплю. Первое условие if (j>6), при его выполнении обнуляем j и производим смену цвета. Далее в цикле от i=0 до i<=5 устанавливаем цвет всех светодиодов равным H[0] и яркость равной 0: H[i]=H[0] и V[i]=0. По окончании цикла устанавливаем яркость бегущего огня V[j]=VO, при условии что j<6(дабы не выйти за пределы массива).  Яркости последующих светодиодов V[j-1]=(VO / 4)*3 и V[j-2]=VO / 2 устанавливаем при условии j<7.

if ((f1==0)||((f1/sp)>25))
   {
      if (j>6)
         {
            j=0;
            H[0]=H[0]+shift;
               if (H[0] >=1536)
               {
                  H[0]=H[0]-1536;
               }
         }
      for (i=0; i<=5; i++)
         {
            H[i]=H[0];
            V[i]=0;
         }
      if (j<6)
         {
            V[j]=VO;
         }
      if (j<7)
         {
            V[j-1]=(VO / 4)*3;
            V[j-2]=VO / 2;
         }
      j++;
      f1=1;
   }

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

На этом позвольте откланяться. Надеюсь эта статья позволит вам порадовать своих родных и близких новой гирляндой. С наступающим Новым годом!

И напоследок небольшое видео работы елки. «И вот она нарядная на праздник к нам пришла».

 

P.S.

 

Я прекрасно понимаю, что далеко не все коты являются счастливыми обладателями такой елки, поэтому схема и печатная плата приводятся с целью ознакомления. Так же хочу предупредить, что код на Си проверялся только в симуляторе и я не могу гарантировать корректную работу в железе (еще раз повторю, что рабочая прошивка писалась на другом языке), но по идее проблем быть не должно. Файлы со схемой, платой, прошивкой и кодом на Си в архиве. Для запуска симуляции в Proteus, необходимо установить модель Led_3Color.

 

Источники:

1. https://easyelectronics.ru/upravlenie-bolshim-kolichestvom-svetodiodov-cherez-binary-angle-modulation.html

2. https://bsvi.ru/mood-lamp-lampochka-s-xarakterom/


Файлы:
Архив RAR


Все вопросы в Форум.




Как вам эта статья?

Заработало ли это устройство у вас?

27 7 6
1 0 0