Контроллер для светодиодной подсветки

Описанный в статье светодиодный контроллер позволяет управлять RGB светодиодами или RGB лентой, даёт возможность хранить в памяти 4 заданных пользователем цвета, а также имеет два динамических режима, в которых цвета изменяются хаотически и по круговому циклу, то есть по всей гамме. Особенностью этого контроллера является способ управления - режимы переключаются и программируются одной кнопкой, что позволяет его использовать, например, для подсветки мотоцикла (потому что не очень удобно пользоваться сенсорной панелью в крагах :D или на ходу в пульт тыкать).

Алгоритм работы RGB контроллера

При подаче питания контроллер делает вспышку и переходит в режим, который был установлен до этого, то есть на котором его выключили. Нажатием на кнопку происходит переключение режимов по кругу. Всего режимов 6: первые 4 режима - это цвета, заданные пользователем, режим 5 - это хаотическое переливание цветов + периодические вспышки. Режим 6 - это просто перебор цветов по кругу, цвета берутся из выбранной при компиляции гаммы.
Для задания пользователем цветов (входа в режим программирования) необходимо удерживать кнопку при подаче питания. После того, как питание подалось и контроллер мигнул лентой, кнопку можно отпускать - вход в режим программирования выполнен.
После входа в режим программирования можно выбрать интересующий цвет поворотом переменного резистора. Цвета выбираются из запрограммированной гаммы.
Для сохранения установленного цвета в памяти контроллера необходимо нажать кнопку один или несколько раз. Количество нажатий будет означать режим, в который записывается цвет. Пауза между нажатиями не должна превышать одной секунды.

Схемное решение очень простое.

Схема устройства

Рис. 1. Схема RGB контроллера.

В схеме были использованы следующие элементы:
Контроллер: ATmega48 – 1шт.
Конденсаторы: чипы 1206 0.1мкф – 4шт.
Резисторы: чипы 1206 200ом – 4шт.
Переменный резистор 4.7к – 1шт.
Транзисторы: IRF7807 – 3шт.

Я схему нагружал примерно на 2 Ампера суммарной нагрузки, но так-то он прокачает гораздо больше.

Активная защита от дребезга кнопки

Любая кнопка подвержена дребезгу в той или иной степени, и с этим приходится бороться. Дребезг в кнопках происходит в момент замыкания и размыкания контактов из-за разрыва постоянного тока в цепи кнопки (такое небольшое искрообразование), а также из-за образовавшихся на контактах окислов, которые препятствуют хорошему контакту. Дребезг контактов не только влечёт нестабильную работу программы, но и также уменьшает срок службы самих кнопок.
Поэтому было решено сделать защиту от дребезга кнопок следующим образом:
Никаких подтягивающих резисторов, чтобы не создавать в цепи кнопки постоянного тока, то есть по умолчанию на кнопке находится 0В, поэтому замыкание и размыкание происходит без искрообразования. В те моменты, когда нам необходимо узнать состояние кнопки, переводим пин кнопки на выход и даём кратковременный импульс буквально на 1-2 такта. Это позволяет как бы прошить окислы. Дальше подключаем подтягивающий резистор, считываем состояние кнопки, и снова переводим пин в состояние 0.
Пример кода обработки кнопки:
sbi       DDRC,5         ;переводим пин на выход
sbi       PORTC,5       ;подаём жесткую 1
nop                            ;пропускаем один такт - на пин сигнал
                                  ;выходит с запозданием на 1 такт
cbi       DDRC,5        ;переключаем пин на вход. Получается, пин настроен на вход и подключён внутренний подтягивающий резистор.
….. тут делаем опрос кнопочки
cbi       PORTC,5        ;убираем подтяжку.

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

Пример кода:
lsl      temp                ;сдвигаем регистр накопления результата
sbic    PINC,5          ;проверяем бит кнопки если там 0 то пропускаем
sbr     temp,1            ;если там 1 то добавляем устанавливаем нулевой бит в 1.

Дальше проверяем: если temp = 0, значит, кнопка нажата. Если 255 - значит, отпущена. Если что-то другое, то состояние нестабильное и верить ему нельзя.

Алгоритм получения псевдослучайного числа.
Иногда бывает необходимо получить случайное число, и, как обычно, за минимальное количество тактов. Я не раз задавался этим вопросом и постепенно выработал один очень неплохой, на мой взгляд, алгоритм генерации случайных чисел. Он очень прост и использует всего 3 регистра.

Пример кода программы:
.def     rnd_a   =r7;                ;это 3 регистра, которые используются подпрограммой
.def     rnd_b  =r8;
.def     rnd_c   =r9;

rnd:     mov     rnd_a,rnd_b    ;A=B   первым делом перемещаем регистры по кругу
           mov     rnd_b,rnd_c               ;B=C
           lsl        rnd_b                         ;B<<1  сдвигаем регистр влево на один бит
           brcs     null                             ;если бит7 был 0, то надо впихнуть в первый бит 1
                                              ;это не даёт перейти всем регистрам в ноль.
           inc       rnd_b                          ;добавляем 1
                                              ;поскольку нет команды C=A+B делаем это за 2 такта
null:     mov     rnd_c,rnd_a     ;С=A
           add      rnd_c,rnd_b                ;C=C+B
           ret                                          ;выход

Этот алгоритм я проверял в processing - очень приятный язык программирования, хорошая оболочка, примеры на каждую команду. Хорошо расписано всё. Он основан на яве, что позволяет компилировать проект и под Винду, и под Линукс, и под МакОС.
Написал 2 програмульки. В одной сделал вывод на дисплей, во второй подсчитал период повторяемости случайного числа, чтоб хоть как-то оценить случайность.
И вот что получилось - на рисунке 2 приведены скриншоты, сделанные в программе.

Рис. 2. Результат тестирования алгоритма с тремя переменными.

Как видно, алгоритм имеет зависимость. При тестировании его в программе, измеряющей период, тот составил 11352 такта.
Пробовал дорабатывать данный алгоритм введением дополнительных математических функций: инверсий, ксоров - период сильно не меняется. Но введение дополнительной переменной даёт очень хороший результат. Алгоритм получается следующий:
.def     rnd_a   =r7;                 ;это 4 регистра, которые используюся подпрограммой
.def     rnd_b  =r8;
.def     rnd_c   =r9;
.def     rnd_d  =r10;

rnd:     mov     rnd_a,rnd_d      ;A=D - первым делом перемещаем регистры по кругу
           mov     rnd_d,rnd_b                 ;D=B
          mov     rnd_b,rnd_c                 ;B=C
          lsl        rnd_b                           ;B<<1  сдвигаем регистр влево на один бит
          brcs     null                               ;если бит7 был 0, то надо впихнуть в первый бит 1
                                                ;это не даёт перейти всем регистрам в ноль.
          inc       rnd_b                            ;добавляем 1
                                                ;поскольку нет команды C=A+B делаем это за 2 такта
null:    mov     rnd_c,rnd_a        ;С=A
          add      rnd_c,rnd_b                  ;C=C+B
          ret                                             ;выход

Алгоритм несильно усложнился, добавилась одна строчка, но результат оказался очень неплохим. На рисунке 3 видно, что пропала зависимость - по крайней мере, её не заметно - и период составил уже 14 956 124 такта. Думаю, это уже сильно :)

Рис. 3. Результат теста алгоритма с четырьмя переменными.

По рисунку можно отметить равномерное заполнение всей поверхности и разброс оттенков, то есть нет зависимости между цветами и координатами.
Введение дополнительных регистров увеличивает период повторяемости. :) можно это наглядно проверить, исходники всех программ имеются ниже в разделе «Материалы».

Реализация работы АЦП.

Для перебора цветов было решено использовать внутреннее АЦП контроллера. Это более удобно и понятно, чем нажимать кнопочки. Но переменные резисторы, да и само АЦП довольно сильно шумят, особенно когда контроллер выдаёт шимы. Чтобы получить стабильное значение, которое не будет дёргаться, я реализовал накопление 10-битного результата и усреднение.

Пример кода работы с АЦП без прерываний.
Для включения и использования внутреннего АЦП надо задать делитель, чтобы частота АЦП попала в диапазон 50-200кГц (это прописано в даташите на контроллер), выбрать режим опорного напряжения, указать разрядность и установить бит включения АЦП. Чем меньше частота АЦП, тем точнее будет измерение.
Частота контроллера - 1МГц. Берём делитель 16, получаем 62.5кГц. Согласно таблице из даташита «Table 24-5. ADC Prescaler Selections», ADPS2=1, ADPS1=0, ADPS0=0.
Опорное напрядение у нас Vcc. Согласно даташиту «Table 24-3. Voltage Reference Selections for ADC», Refs1=0, Refs0=1.
Поскольку нам необходимо использовать 10-битное значение, ADLAR=0. Результат будет в младших 10 битах.

Тело самой процедуры инициализации АЦП
ADC_init:      
   ldi        temp,(0<<ADLAR)+(1<<REFS0)+2   ;выбираем канал С2. опорное напряжение – Vcc т.е +5в в нашем случае.
   sts        ADMUX,temp
   ldi        temp,0x00
   sts        ADCSRB,temp
   ldi        temp,(1<<ADEN)+(1<<ADPS2)+(0<<ADPS0)+(0<<ADPS0)    ;задаём делитель. И включаем АЦП.
   sts        ADCSRA,temp
   ret

Процедура снятия показаний и усреднение:
Чтобы считать показания с АЦП, надо:

  • запустить преобразование (это делается установкой бита ADSC)
  • сбросить флаг окончания обработки АЦП (это делается записью 1 в бит ADIF)
  • дождаться окончания обработки АЦП (ждать, пока не установится бит ADIF).

Кусок из программы:
ADC_result:
           load     x,0                              ;регистр для накопления обнуляем
           ldi        temp,64                      ;счётчик измерений
no_calc:
;сбрасываем флаг прерывания, запускаем измерение, и задаём делитель.
           ldi        temp2,(1<<ADIF)+(1<<ADSC)+(1<<ADEN)+(1<<ADPS2)+(1<<ADPS0)
           output ADCSRA,temp2
           rcall     halt_adc                      ;подпрограмма ожидающая конца измерения
           input    zl,ADCL                     ;чтение регистров АЦП
           input    zh,ADCH
           add      xl,zl                            ;суммирование с результатом
           adc      xh,zh
           dec      temp                           ;уменьшаем счётчик числа выполненных измерений
           brne     no_calc                       ;если не прошло 64 измерения переходим на начало
           ret

;ожидание конца измерения АЦП
halt_adc:
           input    temp2,ADCSRA        ;считываем состояние АЦП
           sbrs      temp2,ADIF              ;проверяем флаг конца измерения.
           Rjmp   halt_adc
           ret

В этом примере используется накопление 64 значений не случайно, так как у нас разрешение АЦП составляет 10 бит, то есть 1024 значений. Умножаем на 64 -  получается 65536, то есть полноценное 16-битное число, старшая часть которого дёргается в 64 раза меньше, чем реальный результат АЦП, и, соответственно, она используется в программе дальше.

Цветовая гамма

Я попробовал 3 вида формирования цветов - они выбираются перед компиляцией проекта. В самом низу программы подключается один из нужных файлов, каждый файл содержит информацию по 256 цветам (хотя цвет получается в итоге с разрешением 24 бита). Просто использовать 256 оттенков всей гаммы. Так было проще. Да и не было необходимости.
В общем, там 3 вида цветовой гаммы.
Первая - «прогрессивная» (не знаю, почему, просто так назвалась). Это не что иное, как 3 фазы, смещённые на 120 градусов.

Рис. 4. Прогрессивная цветовая гамма.

Эта гамма была первая, которую я попробовал. Она даёт довольно сочные цвета. НО! Допустим, выбрать чисто зелёный не выйдет, так как максимальная его амплитуда находится при значении цвета (смотрим по нижней шкале) 64, но при этом цвете также присутствуют синий и красный. В итоге получаются всё какие-то оттенки. Хотя переливается красиво, да.
Тогда было решено попробовать другую гамму, где цвета пересекаются таким образом, чтобы получался однозначно чистый цвет. И получилась «пропорциональная» характеристика. Она отличается тем, что одновременно  могут сочетаться только 2 цвета - тогда появляются моменты с чистым цветом.

Рис. 5. Пропорциональная цветовая гамма.

С пропорциональной гаммой цвета получились более приятные. Но :) пришла идея сделать характеристику не линейную, а полусинусами. Получилось «полусинусная» гамма, что-то среднее между прогрессивной и пропорциональной цветовой гаммой.

Рис. 6. Цветовая гамма "полусинус".

Такая гамма дала более насыщенные цвета, чем пропорциональная. И в тоже время все достоинства её остались.

Для создания своей гаммы в проекте есть файл, созданный в Excel. Там можно применить любые формулы и задать любые формы гаммы как угодно. И спокойно скопировать прямо в текст программы или подключить отдельным файлом, как это сделано сейчас.

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

Работа с шимами

Для изменения интенсивности цветов в программе использовано 3 канала шимов. Для этого пришлось задействовать 2 таймера: для удобства использовал 8-битные таймеры Timer0 и Timer2 в режиме FastPWM.
Чтоб получить шим на выходе надо:

  • настроить соответствующий пин на выход (в данном случае пины D3, D5, D6)
  • задать для таймера режим (в данном случае, Fast PWM)
  • задать режим пина регистра сравнения (я выбрал Clear on Compare Match, то есть обнуление, когда счётчик больше)
  • записать значение в регистр сравнения (он задаёт скважность)
  • указать предделитель

После выполнения этих пунктов на выходе контроллера появится шим.
При выборе частоты шима я исходил из следующих данных. Частота шима должна быть больше 100гц, чтоб не было заметно мигание светодиодов. Тактовая частота контроллера выбрана 1 Мгц, поэтому делитель выбрал 8, и частота шима получилась 488 Гц округлённо - следующий делитель 64, а это уже много.

Программирование контроллера

При программировании контроллера необходимо выставить фъюзы как показано на рисунке 7. В Еепром хранятся цвета по умолчанию, поэтому можно для проверки прошить не только Флеш, но и Еепром.

Рис. 7. Пример установленных фъюзов при программировании.

Внешний вид готового устройства получился вот такой:

 

Рис. 8. Внешний вид контроллера

Печатная плата сделана в формате P-CAD. Единственное, при монтаже уже заметил, что стабилизатор разместил на другом слое - хотя это не помешало мне его установить зеркально, благо места с обратной стороны предостаточно. Печатная плата получилась без перемычек. Нагружал его на RGB ленту по 60 светодиодов на метр, в общей сложности было 5 метров ленты.

 

Материалы:

  1. Исходник программы контроллера
  2. Исходники программ для тестирования алгоритма случайного числа, написаны в Processing.
  3. Processing можно скачать отсюда
  4. Схема и печатная плата
  5. Файл для формирования гаммы

 

 

Автор: Сергей Меньшиков, e-mail для связи -  

Обсуждение на форуме