TLC5940 плавное затухание светодиодов.
- Войдите на сайт для отправки комментариев
Пнд, 08/04/2013 - 20:40
Суть проблеммы. Понять принцип построения алгоритмов эффектов представленного на данном видео (специально с привязкой ко времени даю ссылку)
Как мы видим на видео светодиоды плавно начинают гаснуть. При этом значение яркости разное для отдельных светодиодов. (поправте если не правильно рассуждаю.)
Пока что я понимаю принцип плавного зажигание и затухания для каждого светодиода в последовательности. Привожу свой код.
#include "Tlc5940.h"
//количество
int Leds = 16;
//маскимальная яркость
const int maxLight = 4095;
//задержка ШИМ
int delPWM = 10;
//шаг наращивания яркости
int stepPWM, stepPWMP;
void setup()
{
Tlc.init();
}
void loop()
{
for (int i=Leds-1; i>=0; i--) {
stepPWMP=1;
for (int j=0; j<=maxLight; j=j+stepPWMP) {
Tlc.set(i,j);
Tlc.update();
delay(delPWM);
stepPWMP+=2;
}
}
for (int i=Leds-1; i>=0; i--)
{
stepPWM=stepPWMP;
for (int j=maxLight; j>=0; j=j-stepPWM)
{
Tlc.set(i,j);
Tlc.update();
delay(delPWM);
stepPWM-=2;
}
}
}
Да, для каждого led задается своя яркость, тем самым можно делать разные эффекты.
С указанной Вами библиотекой у меня толком ничего не получилось (может, плохо старался) - частота мерцания была видна не вооруженным взглядом и была очень зависима от внешних факторов (температура, наличие рядом других эл. устройств). На каком-то этапе плюнул на эту либу и работал по документу https://sites.google.com/site/artcfox/demystifying-the-tlc5940, плюс, рекомендую посмотреть на http://www.ti.com/lit/an/slva280/slva280.pdf. После этих 2-х документов, проблемы с tlc5940 у меня закончились.
ЗЫ: Вобще, стоит почитать даташит. Хотя бы первую страницу. Функционала в этой микрухе очень много
Можете какой-нибудь свой примерчик кинуть? В даташитах ничего не понимаю.
Могу, но для понимания лучше, все-таки, почитать Demystifying-the-TLC5940.pdf. Там тупо, по шагам, человек все раскладывает по полочкам (лучи добра ему в поддержку). Начинает с самого простого примера и с нарастающей сложностью и новыми фичами двигается по примерам.
Я остановился на 7 примере из 9-ти. Дополнительного функционала мне было уже не нужно. Далее, адаптировал его под код компиляцию в arduino ide. Какой-то из прошлых игрушечных примеров выглядит так:
[tlc5940.ino]
#include <stdint.h> #include <avr/io.h> #include <avr/interrupt.h> #define PB0 PORTB0 #define PB1 PORTB1 #define PB2 PORTB2 #define PB3 PORTB3 #define PB5 PORTB5 #define PD4 PORTD4 #define PD7 PORTD7 #define SIN_DDR DDRB #define SIN_PORT PORTB #define SIN_PIN PB3 #define SCLK_DDR DDRB #define SCLK_PORT PORTB #define SCLK_PIN PB5 #define BLANK_DDR DDRB #define BLANK_PORT PORTB #define BLANK_PIN PB2 // The following options are configurable from the Makefile #ifndef DCPRG_DDR #define DCPRG_DDR DDRD #endif #ifndef DCPRG_PORT #define DCPRG_PORT PORTD #endif #ifndef DCPRG_PIN #define DCPRG_PIN PD4 #endif #ifndef VPRG_DDR #define VPRG_DDR DDRD #endif #ifndef VPRG_PORT #define VPRG_PORT PORTD #endif #ifndef VPRG_PIN #define VPRG_PIN PD7 #endif #ifndef XLAT_DDR #define XLAT_DDR DDRB #endif #ifndef XLAT_PORT #define XLAT_PORT PORTB #endif #ifndef XLAT_PIN #define XLAT_PIN PB1 #endif #ifndef TLC5940_N #define TLC5940_N 1 #endif #if (3 * 16 * TLC5940_N > 255) #define channel3_t uint16_t #else #define channel3_t uint8_t #endif #define setOutput(ddr, pin) ((ddr) |= (1 << (pin))) #define setLow(port, pin) ((port) &= ~(1 << (pin))) #define setHigh(port, pin) ((port) |= (1 << (pin))) #define pulse(port, pin) do { \ setHigh((port), (pin)); \ setLow((port), (pin)); \ } while (0) #define outputState(port, pin) ((port) & (1 << (pin))) #define gsData_t uint8_t //#define channel_t uint8_t #define gsDataSize ((gsData_t)24 * TLC5940_N) extern uint8_t gsData[gsDataSize]; static inline void TLC5940_SetGSUpdateFlag(void) { __asm__ volatile ("" ::: "memory"); gsUpdateFlag = 1; } uint8_t gsData[gsDataSize]; //volatile uint8_t gsUpdateFlag; void TLC5940_SetGS(channel_t channel, uint16_t value) { channel = numChannels - 1 - channel; channel3_t i = (channel3_t)channel * 3 / 2; switch (channel % 2) { case 0: gsData[i] = (value >> 4); i++; gsData[i] = (gsData[i] & 0x0F) | (uint8_t)(value << 4); break; default: // case 1: gsData[i] = (gsData[i] & 0xF0) | (value >> 8); i++; gsData[i] = (uint8_t)value; break; } } void TLC5940_SetAllGS(uint16_t value) { uint8_t tmp1 = (value >> 4); uint8_t tmp2 = (uint8_t)(value << 4) | (tmp1 >> 4); gsData_t i = 0; do { gsData[i++] = tmp1; // bits: 11 10 09 08 07 06 05 04 gsData[i++] = tmp2; // bits: 03 02 01 00 11 10 09 08 gsData[i++] = (uint8_t)value; // bits: 07 06 05 04 03 02 01 00 } while (i < gsDataSize); } void TLC5940_Init() { setOutput(SCLK_DDR, SCLK_PIN); setOutput(DCPRG_DDR, DCPRG_PIN); setOutput(VPRG_DDR, VPRG_PIN); setOutput(XLAT_DDR, XLAT_PIN); setOutput(BLANK_DDR, BLANK_PIN); setOutput(SIN_DDR, SIN_PIN); setLow(SCLK_PORT, SCLK_PIN); setLow(DCPRG_PORT, DCPRG_PIN); setHigh(VPRG_PORT, VPRG_PIN); setLow(XLAT_PORT, XLAT_PIN); setHigh(BLANK_PORT, BLANK_PIN); gsUpdateFlag = 1; // Enable SPI, Master, set clock rate fck/2 SPCR = (1 << SPE) | (1 << MSTR); SPSR = (1 << SPI2X); // CTC with OCR0A as TOP TCCR0A = (1 << WGM01); // clk_io/1024 (From prescaler) TCCR0B = ((1 << CS02) | (1 << CS00)); // Generate an interrupt every 4096 clock cycles OCR0A = 3; // Enable Timer/Counter0 Compare Match A interrupt TIMSK0 |= (1 << OCIE0A); } // This interrupt will get called every 4096 clock cycles ISR(TIMER0_COMPA_vect) { static uint8_t xlatNeedsPulse = 0; setHigh(BLANK_PORT, BLANK_PIN); if (outputState(VPRG_PORT, VPRG_PIN)) { setLow(VPRG_PORT, VPRG_PIN); if (xlatNeedsPulse == 1) { pulse(XLAT_PORT, XLAT_PIN); xlatNeedsPulse = 0; } pulse(SCLK_PORT, SCLK_PIN); } else if (xlatNeedsPulse == 1) { pulse(XLAT_PORT, XLAT_PIN); xlatNeedsPulse = 0; } setLow(BLANK_PORT, BLANK_PIN); // Below this we have 4096 cycles to shift in the data for the next cycle if (gsUpdateFlag == 1) { gsData_t i; for (i = 0; i < gsDataSize; i++) { SPDR = gsData[i]; while (!(SPSR & (1 << SPIF))); } xlatNeedsPulse = 1; gsUpdateFlag = 0; } }[ch7_1.ino]
#include <util/delay.h> #ifndef TLC5940_N #define TLC5940_N 1 #endif #define channel_t uint8_t extern volatile uint8_t gsUpdateFlag; channel_t i; #define numChannels ((channel_t)16 * TLC5940_N) void setup() { TLC5940_Init(); TLC5940_SetAllGS(0); sei(); i = 0; } int maxColor=2047, stp=230; void loop() { while(gsUpdateFlag == 1); // wait until we can modify gsData TLC5940_SetAllGS(0); int c; for (c=i; c>=0; c--) { int cl = maxColor-(i-c)*stp; if (cl < 0) { cl = 0; } if (c < numChannels) { TLC5940_SetGS(c, cl); } } TLC5940_SetGSUpdateFlag(); _delay_ms(15); i = (i + 1) % (numChannels + maxColor/stp); //i = (i + 1) % 4095; }Какой именно эффект дает, уже не вспомню :) . Но он не сложно меняется путем изменения алгоритма в методе loop. Основная функция: TLC5940_SetGS(c, cl); Задает в каком канале какая яркость должна быть. В цикле по всем каналам можно задавать любой эффект.
Но, рекомендую смотреть в доку, потому как там и схема подключения tlc обозначена. А ноги для подключения должны быть определенными (детали там же). Ну и CKOUT фьюз надо поставить:
Спасибо за информацию буду пробовать. Заметил уже отличие. В ардуиновской библиотеке пин DCPRG к + подтягивается, а здесь на ножку МК.
Да, зачем-то была комментирована следующая строка: volatile uint8_t gsUpdateFlag;
Если ее открыть, то компилируется нормально
Да, зачем-то была комментирована следующая строка: volatile uint8_t gsUpdateFlag;
Если ее открыть, то компилируется нормально
Да я разобрался с этой ошибкой. Но все равно вопрос остается открытым по поводу эффектов с того видео, который я привел как пример. Как нужно построить цикл для его достижения?
Ну, это можно уже и на домашнее задание оставить :)
А там несколько эффектов, насколько я помню. У меня сделан один - эффект плавного нарастания свечения led. Начинает зажигаться с той стороны, где сработал датчик.
В рабочем проекте код организации зажигания/гашения выглядит так:
// непосредственно, сам рабочий блок указания GS // прежние значения GS не хранятся, а вычисляются на каждой итерации цикла void ledOnOff() { // это настройки. Можно менять online через считывание analogRead с переменника int nextLedColor = getPrefValue(PREF_NEXT_LED_STEP); int toColor = getPrefValue(PREF_MAX_COLOR); int fromColor = getPrefValue(PREF_MIN_COLOR); for (byte curCnl = 0; curCnl < numChannels; curCnl++) { // текущая яркость текущего led (канала) int cl; if (ledLightMode == modeOn) { cl = loopCounter - curCnl * nextLedColor; } else { cl = toColor - loopCounter + curCnl * nextLedColor; } if (cl < fromColor) cl = fromColor; if (cl > toColor) cl = toColor; if (ledDirection == modeDownToUpper) { TLC5940_SetGS(curCnl, cl); } else { TLC5940_SetGS(numChannels - 1 - curCnl, cl); } if (curCnl == (numChannels-1)) { if ((ledLightMode == modeOn && cl == toColor) || (ledLightMode == modeOff && cl == fromColor)) { loopCounter = 0; allOk = true; if (DEBUG_MODE == 1) { // это для зацикливания процесса (режим тестирования). В обычном режиме нам это не надо ledLightMode = 1 - ledLightMode; allOk = false; } } } } loopCounter++; }Если, конечно, Вам не лень разбирать и адаптировать его под себя. В приведенной функции установкой ledLightMode (значения: 0, 1) контролируется действие - зажигание/гашение. Через ledDirection (0, 1) направление, в котором зажигаются или гасятся led. Если поставить DEBUG_MODE=1, то все начинает крутиться в цикле.
На мой взгляд, проще на какой-нибудь макетке в живую щупать и менять, оценивая результат, начиная с простых примеров
А кварц вы на 20MHz ставили или на 18. Я почитал автор пишет про эти кварцы в readme.
На 16. Его достаточно