TLC5940 плавное затухание светодиодов.

Rimidalw
Offline
Зарегистрирован: 04.04.2012

Суть проблеммы. Понять принцип построения алгоритмов эффектов представленного на данном видео (специально с привязкой ко времени даю ссылку)

Как мы видим на видео светодиоды плавно начинают гаснуть. При этом значение яркости разное для отдельных светодиодов. (поправте если не правильно рассуждаю.)

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

#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; 
    }
  }
}

 

sds
Offline
Зарегистрирован: 29.10.2012

 

Да, для каждого led задается своя яркость, тем самым можно делать разные эффекты. 

С указанной Вами библиотекой у меня толком ничего не получилось (может, плохо старался) - частота мерцания была видна не вооруженным взглядом и была очень зависима от внешних факторов (температура, наличие рядом других эл. устройств). На каком-то этапе плюнул на эту либу и работал по документу https://sites.google.com/site/artcfox/demystifying-the-tlc5940, плюс, рекомендую посмотреть на http://www.ti.com/lit/an/slva280/slva280.pdf. После этих 2-х документов, проблемы с tlc5940 у меня закончились.

ЗЫ: Вобще, стоит почитать даташит. Хотя бы первую страницу. Функционала в этой микрухе очень много

Rimidalw
Offline
Зарегистрирован: 04.04.2012

Можете какой-нибудь свой примерчик кинуть? В даташитах ничего не понимаю.

sds
Offline
Зарегистрирован: 29.10.2012

Могу, но для понимания лучше, все-таки, почитать 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 фьюз надо поставить:

 

Using CLKO to drive the GSCLK pin of the TLC5940 allows us to achieve very high PWM rates, which translates into being able to update the grayscale values many times per second.
 
 
Rimidalw
Offline
Зарегистрирован: 04.04.2012

Спасибо за информацию буду пробовать. Заметил уже отличие.  В ардуиновской библиотеке пин DCPRG к + подтягивается, а здесь на ножку МК.

 

sds
Offline
Зарегистрирован: 29.10.2012

Да, зачем-то была комментирована следующая строка: volatile uint8_t gsUpdateFlag;

Если ее открыть, то компилируется нормально

Rimidalw
Offline
Зарегистрирован: 04.04.2012

sds пишет:

Да, зачем-то была комментирована следующая строка: volatile uint8_t gsUpdateFlag;

Если ее открыть, то компилируется нормально

Да я разобрался с этой ошибкой. Но все равно вопрос остается открытым по поводу эффектов с того видео, который я привел как пример. Как нужно построить цикл для его достижения?

sds
Offline
Зарегистрирован: 29.10.2012

Ну, это можно уже и на домашнее задание оставить :)

А там несколько эффектов, насколько я помню. У меня сделан один - эффект плавного нарастания свечения 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, то все начинает крутиться в цикле.

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

Rimidalw
Offline
Зарегистрирован: 04.04.2012

А кварц вы на 20MHz ставили или на 18. Я почитал автор пишет про эти кварцы в readme.

sds
Offline
Зарегистрирован: 29.10.2012

На 16. Его достаточно