Forum: Mikrocontroller und Digitale Elektronik TLC59401 ansteuern


von Tobias N. (tobias_n)


Angehängte Dateien:

Lesenswert?

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
1
// Constructor
2
Driver::Driver(uint8_t n, uint8_t c, uint8_t d, uint8_t l, uint8_t m) 
3
{
4
  numdrivers = n;
5
  _clk = c;  // SCLK
6
  _dat = d;  // SIN
7
  _lat = l;  // XLAT
8
  _mod = m;     // MODE
9
10
  pwmbuffer = (uint16_t *)calloc(2, 16*n);
1
void Driver::write(void) 
2
{
3
  // Disable outputs
4
  digitalWrite(BLANK, LOW);
5
  
6
  // Clock in data
7
  digitalWrite(_lat, LOW);
8
  // 16 channels per TLC59401
9
  for (int i=16*numdrivers-1; i>=0 ; i--) {
10
    // 12 bits per channel, send MSB first
11
    for (int j=11; j>=0; j--) {
12
      digitalWrite(_clk, LOW);          
13
      if (pwmbuffer[i] & (1 << j))
14
        digitalWrite(_dat, HIGH);
15
      else
16
        digitalWrite(_dat, LOW);
17
      digitalWrite(_clk, HIGH);      
18
    }
19
  }
20
  digitalWrite(_clk, LOW);
21
  // end of clocking in  
22
23
  // latch the serial data into the grayscale register. New grayscale data immediately become valid at the rising edge of the XLAT
24
  // signal; therefore, new grayscale data should be latched at the end of a grayscale cycle when BLANK is high.
25
  digitalWrite(_lat, HIGH); 
26
  digitalWrite(_lat, LOW);
27
  // end of latching in
28
  
29
  digitalWrite(BLANK, HIGH);
30
  for(int p=0;p<4096;p++){
31
    digitalWrite(GCLK, HIGH);
32
    digitalWrite(GCLK, LOW);
33
  }
34
}

von Falk B. (falk)


Lesenswert?

@ 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.
1
    int tmp = pwmbuffer[i];
2
    for (int j=11; j>=0; j--) {
3
      digitalWrite(_clk, LOW);          
4
      if (tmp & (1 << 11))
5
        digitalWrite(_dat, HIGH);
6
      else
7
        digitalWrite(_dat, LOW);
8
      tmp <<= 1;

von Tobias N. (tobias_n)


Lesenswert?

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?...

von Falk B. (falk)


Lesenswert?

@ 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!

von Tobias N. (tobias_n)


Lesenswert?

Es funktioniert :)

Danke Falk!

von Falk B. (falk)


Lesenswert?


von Tobias N. (tobias_n)


Lesenswert?

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:
1
// Interrupt to update LEDs
2
int cnt=0;
3
ISR(TIMER2_COMPB_vect)
4
{
5
  if(cnt>=4095){
6
    cnt=0;
7
    BLANK_LOW;
8
    _delay_us(2);
9
    BLANK_HIGH;
10
  }
11
  GCLK_HIGH;
12
  GCLK_LOW;
13
  cnt++;
14
}

von Falk B. (falk)


Lesenswert?

@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.

von Tobias N. (tobias_n)


Lesenswert?

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
void Driver::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 (int i=16*numdrivers-1; i>=0 ; i--) {
18
    // 12 bits per channel, send MSB first
19
    for (int j=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
void Driver::setDotCorrection()
2
{  
3
  digitalWrite(LATCH, LOW);
4
  for (int i=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

von Tobias N. (tobias_n)


Lesenswert?

Ähm ich arbeite übrigends mit einem CPU Takt von 18.432MHz! Hatte ich 
vergessen zu erwähnen.

von Falk B. (falk)


Lesenswert?

@  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
void Driver::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
10
11
  BLANK_LOW;
12
  _delay_us(2);
13
  BLANK_HIGH;
14
  
15
  // Clock in data for next cycle
16
  // 16 channels per TLC59401
17
18
  for (int i=16*numdrivers-1; i>=0 ; i--) {
19
    // 12 bits per channel, send MSB first
20
    for (int j=(1<<11); j!=0; j>>=1) {
21
      SCLK_LOW;        
22
      if (pwmbuffer[i] & j)
23
        DATA_HIGH;
24
      else
25
        DATA_LOW;
26
      SCLK_HIGH;    
27
    }
28
  }
29
  SCLK_LOW;
30
  // end of clocking in  
31
}

von Tobias N. (tobias_n)


Lesenswert?

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.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.