Hallo zusammen,
hat hier schonmal jemand mit dem LED Treiber TLC59401 gearbeitet? Ich
habe ein Problem mit der Ansteuerung. Ich kann zwar einzelne LEDs wie
gewünscht ansteuern, allerdings sind sie immer auf voller Helligkeit.
Der code ist zum großteil von Adafruit für den TLC5947 kopiert, sollte
sich jedoch ohne weiteres anpassen lassen.
Warscheinlich verstehe ich das Datenblatt nicht richtig. So wie ich es
verstanden habe, shifte ich den pwmbuffer ganz normal ein. Dann kommt
ein toggel des latch und anschließend muss ich 4096x den GCLK toggeln.
Hierbei wird ein zähler inkrementiert und schaltet alle Ausgänge aus
welche im Puffer gleich dem Zähler sind. So richtig hab ichs noch nicht
verstanden aber müsste es nicht eigentlich so wie in der write Mehtode
funktionieren?
(digitalWrite(BLANK, LOW), setzt den Blank pin auf HIGH)
In einer anderen Methode schreibe ich sowas: "pwmbuffer[channel] = pwm;"
und rufe anschließend "write" auf
@ Tobias Nuss (tobias_n)
>Warscheinlich verstehe ich das Datenblatt nicht richtig. So wie ich es>verstanden habe, shifte ich den pwmbuffer ganz normal ein. Dann kommt>ein toggel des latch und anschließend muss ich 4096x den GCLK toggeln.
So in etwa. Allerdings macht man die PWM-Takterzeugung ganz sicher nicht
mit einer Funktion, sondern mit der Output Compare Funktion eines
Timers. Denn sonst blockiert die PWM-Takterzeugung die CPU.
Das ist bestenfalls für einen schnellen Test OK.
>Hierbei wird ein zähler inkrementiert und schaltet alle Ausgänge aus>welche im Puffer gleich dem Zähler sind. So richtig hab ichs noch nicht>verstanden aber müsste es nicht eigentlich so wie in der write Mehtode>funktionieren?>In einer anderen Methode schreibe ich sowas: "pwmbuffer[channel] = pwm;">und rufe anschließend "write" auf
So weit, so gut.
Ich vermisse die Ansteuerung von MODE. Ausserdem muss man zumindest am
Anfang auch das DC-Register mit Daten beschreiben, denn sowohl
DC-Register als auch PWM FUnktion wirken immer gleichzeitig.
> for (int j=11; j>=0; j--) {> digitalWrite(_clk, LOW);> if (pwmbuffer[i] & (1 << j))> digitalWrite(_dat, HIGH);> else> digitalWrite(_dat, LOW);
Rein logisch OK, praktisch aber eher langsam. So ist das deutlich
schneller.
Toby T. schrieb:> digitalWrite(BLANK, HIGH);> for(int p=0;p<4096;p++){> digitalWrite(GCLK, HIGH);> digitalWrite(GCLK, LOW);> }
Muss in einer Update Routine ständig aufgerufen werden oder?...
@ Toby Tobsn (tobias_n)
>> digitalWrite(BLANK, HIGH);>> for(int p=0;p<4096;p++){>> digitalWrite(GCLK, HIGH);>> digitalWrite(GCLK, LOW);>> }>Muss in einer Update Routine ständig aufgerufen werden oder?...
Wie ich bereits schrieb ist das bestenfalls für einen einfachen,
schnellen Test sinnvoll. Denn die PWM-Takte müssen ja gleichmäßig und
mit der richtigen Frequenz erzeugt werden.
Wenn man Z.B. den IC mit 100 Hz Updaterate betrieben will, braucht man
100Hz * 4096 = 409,6kHz PWM Takt. Die 4096 Takte werden dadurch über die
10ms gleichmäßig verteilt. Es ist NICHT sinnvoll, wenn man
beispielsweise innerhaöb von 1ms die 4096 Takte erzeugt.
Denn dann sind die LEDs die restlichen 9ms aus,
Also Timer-Interrupt + Output Compare Funktion!
Na genau so wie dus vorgeschlagen hast. Sonst hätte ich schon noch ne
Erklärung hinten dran gepackt ;)
Also TimerCompare Interrupt welcher mit der Update-Frequenz*4096
aufgerufen wird:
@Toby Tobsn (tobias_n)
>Na genau so wie dus vorgeschlagen hast. Sonst hätte ich schon noch ne>Erklärung hinten dran gepackt ;)>Also TimerCompare Interrupt welcher mit der Update-Frequenz*4096>aufgerufen wird:
Das habe ich so nicht geschrieben, oder zumindest so nicht gemeint. Denn
ein 409 kHz Interrupt ist ein "bisschen" viel CPU Last.
So macht man es nicht! Sondern man generiert mittels der Output Compare
Funktion und dem CTC-Modus den GSCLK zu 100% in Hardware, ohne
CPU-Arbeit. OCRxA, OCRnB sind deine Freunde.
Das kann in allen AVRs der Timer1, in allen "neueren" AVRs auch Timer 0
oder Timer2. Denn für 409 kHz braucht man bei 16 MHz Takt gerade mal 39
Takte, das kann auch ein 8 Bit Timer.
Parallel dazu nutzt man einen 2. Timer, der mit der normalen Update-Rate
von vielleicht 100 Hz aufgerufen wird.
Dort macht man dann das Nachladen der neuen PWM-Daten sowie den Update
mittels BLANK-Puls.
So hat ne Weile gedauert aber bin wieder dran.
Ich habe es jetzt versucht so umzusetzen wie du es beschrieben hast.
In meiner Initialisierung setzte ich die beiden Timer 0 und 2:
1
/* Set up TIMER2A, toggle GCLK with 409,5kHz */
2
TCCR2A=(1<<COM2B0)|(1<<WGM21);
3
TCCR2B=(1<<CS20);
4
OCR2A=23;
5
6
/* Set up TIMER 0, Interrupt with 100Hz */
7
//TCCR0A = (1<<COM0A0) | (1<<WGM01);
8
TCCR0A=(1<<WGM01);
9
TCCR0B=(1<<CS02)|(1<<CS00);
10
OCR0A=180;
11
TIMSK0=(1<<OCIE0A);
Der mit 100Hz aufgerufene interrupt toggelt BLANK und schreibt die neuen
Daten:
1
ISR(TIMER0_COMPA_vect)
2
{
3
tlc.write();
4
}
und:
1
voidDriver::write(void)
2
{
3
// GCLK Stop
4
//TCCR2B = 0;
5
6
// Reset grayscale counter to zero and complete grayscale pwm cycle
7
BLANK_LOW;
8
_delay_us(2);
9
BLANK_HIGH;
10
11
// Disable outputs
12
BLANK_LOW;
13
14
// Clock in data
15
LATCH_LOW;
16
// 16 channels per TLC59401
17
for(inti=16*numdrivers-1;i>=0;i--){
18
// 12 bits per channel, send MSB first
19
for(intj=11;j>=0;j--){
20
SCLK_LOW;
21
if(pwmbuffer[i]&(1<<j))
22
DATA_HIGH;
23
else
24
DATA_LOW;
25
SCLK_HIGH;
26
}
27
}
28
SCLK_LOW;
29
// end of clocking in
30
31
// latch the serial data into the grayscale register. New grayscale data immediately become valid at the rising edge of the XLAT
32
// signal; therefore, new grayscale data should be latched at the end of a grayscale cycle when BLANK is high.
33
LATCH_HIGH;
34
LATCH_LOW;
35
// end of latching in
36
37
// enable outputs
38
BLANK_HIGH;
39
40
//GCLK start
41
//TCCR2B = (1<<CS20);
42
//OCR2A = 20;
43
}
Da muss aber irgendwas beim Timing noch nicht stimmen. Die LED blinkt
recht schnell aber immer noch sichtbar und auch nicht mit der richtigen
Helligkeit (zu dunkel).
Dot Correction habe ich in der Initialisierung durchgeführt:
1
voidDriver::setDotCorrection()
2
{
3
digitalWrite(LATCH,LOW);
4
for(inti=0;i<96*numdrivers;i++)
5
{
6
digitalWrite(SCLK,LOW);
7
digitalWrite(DATA,HIGH);
8
digitalWrite(SCLK,HIGH);
9
}
10
digitalWrite(LATCH,HIGH);
11
digitalWrite(LATCH,LOW);
12
}
Als ich es wie in meinem letzten Post gemacht habe hat alles
funktioniert, allerdings mit der von dir zu recht beanstandeten CPU
Last.
Wo ist mein Denkfehler? Auf jedenfall vielen Dank für deine Hilfe bisher
:)
Gruß Tobi
@ Tobias N. (tobias_n)
> /* Set up TIMER2A, toggle GCLK with 409,5kHz */> TCCR2A = (1<<COM2B0) | (1<<WGM21);> TCCR2B = (1<<CS20);> OCR2A = 23;
Falscher Timer-Mode, du braucht Mode 5 oder 7, damit du eine PWM
ausgeben kannst.
ORC2A legt die Periodendauer fest, für die Pulsausgabe musst du aber
OCR2B nehmen.
In OCR2B sollte man die Häfte von OCR2A reinschreiben, dann hat man
einen Takt mit ca. 50% Tastverhältnis.
>Der mit 100Hz aufgerufene interrupt toggelt BLANK und schreibt die neuen>Daten:>ISR(TIMER0_COMPA_vect)>{> tlc.write();>}
Im Prinzip ist das richtig, aber
>void Driver::write(void)>{> // GCLK Stop> //TCCR2B = 0;> // Reset grayscale counter to zero and complete grayscale pwm cycle
Der Kommentar ist falsch, BLANK macht das nicht.
> BLANK_LOW;> _delay_us(2);> BLANK_HIGH;
Soweit OK.
> // Disable outputs> BLANK_LOW;
Das hier ist Unsinn. BLANK bekommt nur einen Puls.
> // Clock in data> LATCH_LOW;> // 16 channels per TLC59401> for (int i=16*numdrivers-1; i>=0 ; i--) {> // 12 bits per channel, send MSB first> for (int j=11; j>=0; j--) {> SCLK_LOW;> if (pwmbuffer[i] & (1 << j))> DATA_HIGH;> else> DATA_LOW;> SCLK_HIGH;> }> }> SCLK_LOW;> // end of clocking in
Das hier ist syntaktisch vielleicht richtig, aber nicht sonderlich
schnell. Sinnvollerweise hat man in seiner Software ein
vorausberechnetes Datenarray, das man auch per Hardware-SPI sehr schnell
senden kann. Die Eintragungen in das Array macht man mit einer
passernden Funktion. Das ist zwar langsamer, spielt dort aber keine so
große Rolle.
> LATCH_HIGH;> LATCH_LOW;
Das gehört vor den BLANK-Puls
> // enable outputs> BLANK_HIGH;
Das ist falsch.
>Da muss aber irgendwas beim Timing noch nicht stimmen. Die LED blinkt>recht schnell aber immer noch sichtbar und auch nicht mit der richtigen>Helligkeit (zu dunkel).
Es wird gar kein GCLK ausgegeben. Solche elementaren Dinge sollte man
zuerst prüfen, sei es mit dem Oszi oder Logicanalyzer.
>Wo ist mein Denkfehler? Auf jedenfall vielen Dank für deine Hilfe bisher>:)
Deine write-Methode sollte so aussehen.
1
voidDriver::write(void)
2
{
3
4
// Store new data tha was shifted in last cycle
5
6
LATCH_HIGH;
7
LATCH_LOW;
8
9
// Reset grayscale counter and start a new PWM cycle
Falk B. schrieb:> Es wird gar kein GCLK ausgegeben.
Doch, GCLK wird ausgegeben. Das BIT: COM2B0 im TCCR2A Register, lässt
OC2A bei einem Comparematch toggeln (Ja, das wurde mit dem Oszi
gemessen).
Ich werde deine Anmerkungen trotzdem mal ausprobieren. Danke schonmal.