Code für TLC5940 - PWM-IC von TI

  • Moin!


    Wie angekündigt hier nun der Code, den ich zurzeit für die Ansteuerung des TLC5940 nutze.


    Er ist noch nicht kommentiert, aber dem geneigten Leser wird es nicht schwerfallen, ihn zu verstehen.


    Gruß
    Jan


    [code:1]'###################################################################################################################################
    '##### #######
    '##### Ansteuerung des TLC5940 PWM-ICs von Texas Instruments (http://focus.ti.com/docs/prod/folders/print/tlc5940.html) #######
    '##### #######
    '##### Projekt: tlc5940.bas #######
    '##### Erstellt: 08.02.2008 #######
    '##### Version: 0.3 #######
    '##### Erstellt durch: Jan #######
    '##### #######
    '##### Change history: #######
    '##### 08.02.2008 -- initial release #######
    '##### 12.02.2008 -- Adding variable dot correction and usage of timer interrupt for pwm-clock #######
    '##### 14.02.2008 -- code cleanup #######
    '##### #######
    '##### #######
    '###################################################################################################################################


    '################################
    '##### #######
    '##### Gen.-Settings #######
    '##### #######
    '################################


    $regfile = "m32def.DAT"
    $crystal = 16000000
    $hwstack = 64
    $swstack = 64
    $framesize = 64
    $baud = 9600


    '################################
    '##### #######
    '##### Timer-Konfig. #######
    '##### #######
    '################################


    On Timer0 Pwm
    Config Timer0 = Timer , Prescale = 1024
    Enable Timer0
    Enable Interrupts


    '################################
    '##### #######
    '##### Port-Konfig. #######
    '##### #######
    '################################


    Config Portc = Output


    Vprg Alias Portc.0
    Si Alias Portc.1
    Sclk Alias Portc.2
    Xlat Alias Portc.3
    Blank Alias Portc.4
    Dcprog Alias Portc.5
    Gsclk Alias Portc.6


    '################################
    '##### #######
    '##### Deklarationen #######
    '##### #######
    '################################


    Declare Sub Setdc


    Dim Pulse As Integer
    Dim A As Integer , B As Integer



    Dim Led As Byte , Channels As Byte
    Dim Gsout As Integer , Dcout As Byte



    '################################
    '##### #######
    '##### Initialisierung #######
    '##### #######
    '################################


    Const Tlcs = 1 'Anzahl der TLCs


    #if Tlcs = 1
    Dim Channel(16) As Integer , Channeldc(16) As Byte
    #elseif Tlcs = 2
    Dim Channel(32) As Integer , Channeldc(32) As Byte
    #endif
    #if Tlcs = 3
    Dim Channel(48) As Integer , Channeldc(48) As Byte
    #endif
    #if Tlcs = 4
    Dim Channel(64) As Integer , Channeldc(64) As Byte
    #endif



    Channels = Tlcs * 16




    '################################
    '##### #######
    '##### DotCorrection #######
    '##### #######
    '################################


    For A = 1 To Channels
    Channeldc(a) = 63 'Keine DotCorrection, volles Rohr
    Next


    Call Setdc


    '################################
    '##### #######
    '##### Hauptschleife #######
    '##### #######
    '################################


    Do


    For A = 1 To Channels
    For B = 0 To 4095
    Channel(a) = B
    Waitus 25
    Next
    Next



    For A = Channels To 1 Step -1
    For B = 4095 To 0 Step -1
    Channel(a) = B
    Waitus 25
    Next
    Next


    Loop


    End


    '################################
    '##### #######
    '##### Methoden und #######
    '##### Funktionen #######
    '##### #######
    '################################



    Sub Setdc 'DotCorrection
    Set Dcprog
    Set Vprg


    Set Blank
    Reset Si
    Reset Xlat


    Dim Dotcorrection As Byte


    For Dotcorrection = Channels To 1 Step -1
    Dcout = Channeldc(dotcorrection)
    Shiftout Si , Sclk , Dcout , 0 , 6
    Next


    Set Xlat
    Reset Xlat
    Reset Vprg


    'Lt. Datenblatt notwendig, funzt aber genausogut ohne
    'Set Sclk
    'Reset Sclk


    Reset Blank
    End Sub



    '###############################
    '##### #######
    '##### PWM-Timer #######
    '##### Interrupt #######
    '##### #######
    '###############################


    Pwm:
    For Led = Channels To 1 Step -1
    Gsout = Channel(led)
    Rotate Gsout , Right , 12
    Shiftout Si , Sclk , Gsout , 0 , 12
    Next


    Set Xlat
    Reset Xlat


    For Pulse = 0 To 4095
    Set Gsclk
    Reset Gsclk
    Next


    Set Blank
    Reset Blank
    Return
    [/code:1]

  • Ein bisschen konstruktive Kritik von mir:


    Der Interrupt wird viel zu selten aufgerufen. Mit dem gewählten Prescale und bei 16 MHz wird der Timer0-Überlauf (immer nach 256 Zählschritten) 'nur' rund 61 Mal in der Sekunde aufgerufen.
    Das hat zur Folge, dass die eigentliche PWM-Frequenz auch 'nur' 61 Hz beträgt, wobei die maximale On-Phase (bei einem PWM-Wert von 4095) einer PWM-Periode bei deiner Schleife für die 4096 GSCLK-Schritte wohl zwischen 1 und 4 ms (man müsste die Schleifenzeit im BASCOM-Simulator mal durchmessen, um es genau sagen zu können) dauern dürfte. Bei einer PWM-Frequenz von 61 Hz ist die Dauer einer Periode ca. 16 ms. Wenn nur maximal 1 - 4 ms davon On sind, dann verschenkst du unnötigerweise sehr viel an maximaler Helligkeit der LEDs, abgesehen davon, dass eine PWM-Frequenz von 61 Hz wirklich alles andere als berauschend ist.


    "Rotate Gsout , Right , 12" -> Das Gleiche erreichst du mit "Rotate Gsout, Left, 4" oder "Shift Gsout, Left, 4", resultiert aber in weniger Assembler-Code und dauert somit auch weniger lange.


    Das ist jetzt nur mal das Ergebnis einer kurzen, oberflächlichen Analyse.


    Gruss
    Neni

  • So ich habe nun mal im BASCOM-Simulator die Interrupt-Routine genau durchgemessen, und bin zu folgendem Ergebnis gekommen:


    Die Gsclk-Schleife braucht viel zu viel Zeit, nämlich 9,47 ms. Damit wäre nur schon wegen dieser Schleife alleine die PWM-Frequenz auf ca. 100 Hz begrenzt (ohne Zeit für den Hauptloop bereitzustellen). Das Problem liegt vor allem darin, dass eine 2-Byte-Variable (Pulse als Integer) als Schleifenzähler dient. Das macht den resultierenden Schleifencode langsam. Wenn man die Schleife in zwei verschachtelte Byte-Schleifen Pulse und Outer_pulse (beide als Byte dimensioniert) aufteilt, wie man es unten im auskommentierten Code sieht, dann braucht die ganze Doppelschleife 'nur' noch 5,14 ms. Aber auch das fand ich zu viel, also habe ich flugs diese zeitkritische Schleife in Assembler gemacht. Damit benötigt sie jetzt nur noch 2,05 ms. Der Code zum Befüllen des TLC5940 braucht ca. 0,33 ms pro Chip. Wenn man sich also an eine Maximalzahl von 5 - 6 TLC5940-Chips hält, dann bräuchte der Interrupt jetzt im Maximum (mit der Maximalanzahl Chips) inkl. der automatischen Registersicherung auf dem Stack so ca. 4 ms. Wenn man nun noch ca. 2 ms pro Durchlauf für's Hauptprogramm reserviert, dann könnte man das Ganze so konfigurieren, dass der Interrupt alle 6 ms aufgerufen wird, was einer PWM-Frequenz von 166 Hz gleichkommt, also durchaus akzeptabel.


    Man wird sich aber auch deutlich der Limitierungen gewahr, die der TLC5940 dadurch, dass man seinen PWM-Takt per Software generieren muss, mit sich bringt.


  • Hi Neni,


    danke für die Verbesserungen, werde ich dieses WE mal einbauen. Ich habe leider von Assembler keine Ahnung, daher nehme ich ja Bascom ;)


    Gibt es nicht auch vielleicht die Möglichkeit, den bzw. die TLC(s) extern zu takten? Also ohne dass das der µController machen muss.

  • Prinzipiell müsste es schon gehen, den PWM-Clock extern zu generieren. Das Problem ist eher, dass die Daten nicht vollkomen asynchron zum PWM-Clock reingeladen werden können. Das Reinclocken geht ja noch asynchron, aber das Laden der Latches muss am Ende eines PWM-Zyklus vor dessen Neustart erfolgen. D.h. man kann den PWM-Teil nicht ganz unbeachtet für sich laufen lassen.


    Eine Lösung gibt's aber trotzdem: Man verwendet einen mindestens 13-Bit-Binär-Zähler/Teiler (aus der 74xx oder 40xx Logik-Reihe), den man z.Bsp. mit 1 MHz clockt (auch die TLC5490 mit demselben Takt; ergibt eine PWM-Frequenz von 244 Hz). Den Q12-Pin des Zählers führt man zum externen Interrupt des AVR. So löst der Zähler beim Erreichen von 4096 einen Interrupt im µC aus. In diesem Interrupt zieht man dann BLANK (der auch zum Reset des Zählers geht) hoch, zieht dann XLAT hoch, zieht XLAT wieder runter und dann auch BLANK wieder runter. Dann clockt man weiter im Interrupt alle 'neuen' Daten in die TLCs. Der Vorteil ist jetzt, dass das Reinclocken der Daten parallel zum ja wieder laufenden PWM-Clock ist und man somit keine zusätzliche Zeit durch die sequentielle Abarbeitung der Tasks verliert. Man sollte nur beachten, dass das Reinclocken der Daten nicht länger als ca. 2,5 ms dauert, da der externe Interrupt ja ca. alle 4 ms ausgelöst wird. Mit der Shiftout-Routine von oben könnte man so rund ca. 7 TLC5940 kaskadieren. Wenn man noch mehr will, muss man eben die PWM-Clock runterdrehen. Wenn einem eine PWM-Frequenz von z.Bsp. 122 Hz reicht, kann man einen PWM-Clock von 500 kHz (anstatt 1 MHz) nehmen und könnte dann schon ca. 14 - 16 TLC5940 kaskadieren.


    Ein riesiger Vorteil ist hier auch, dass du die maximale Helligkeit der LEDs voll nutzen kannst, da die Totzeit zwischen den PWM-Phasen kaum mehr ins Gewicht fällt (ist ja nur noch die Zeit für die Manipulation von BLANK und XLAT).


    Gruss
    Neni

  • Hmm das is ja mal echt komisch mit dem PWM-Takt. Also unpraktisch.
    Aber mit synvox Lösung wäre es in der tat gut machbar. Dann könnte man ja auch noch gut im PWM-Takt höher gehen - mit nem 14 oder mehr Bit-Zähler, oder?


    Und wie sehr sieht man es, wenn man einfach stuhr BLANK auf High zieht, neue Daten reinschiebt und dann BLANK wieder freigibt? Flackert das sehr? Dürfte wahrscheinlich Helligkeitsschwankungen geben, weil sich dann der PWM Cycle immer verschiebt... hmm nicht gut :(

  • Du kannst BLANK nicht einfach fest auf einen bestimmten Pegel setzen, da es zum Resetieren des internen Zählers des TLC5940 benötigt wird. Und dieser MUSS von aussen resettiert werden, da er einfach nach 4096 GSCLK-Takten stehen bleibt (er läuft nicht automatisch über oder sowas). Damit ist dann nach 4096 Zyklen Schluss mit PWM. Wenn nicht kurz darauf der Reset des Zählers erfolgt, dann bleiben die LEDs in der Folge einfach dunkel, da kann der GSCLK-Takt noch so munter weiterlaufen wie er will. Je länger man sich also mit dem Reset des TLC5940-Zählers Zeit lässt nach seiner Erreichnung von 4096, um so länger ist die Totzeit zwischen den PWM-Zyklen.


    Ich kann noch verstehen, dass das mit dem externen Reset etc. wegen der Sychronisation mit dem Einlatchen (XLAT-Pin) der Daten implementiert worden ist, aber das Fehlen einer Ausgabeleitung, die einem per Pegelwechsel mitteilen würde, wenn der interne Zähler 4096 erreicht hat, empfinde ich als echtes Design-Manko beim TLC5940.


    So braucht man jetzt eben einen externen Zähler auf 4096, wenn man den TLC5940 unabhängig takten möchte, oder man muss wie eben im Code oben genau diese 4096 Takte im µC (mit allen erwähnten Nachteilen) erzeugen.


    Ein Binärzähler mit höherer Auflösung (14 bit und mehr) bringt auch nichts, da man ihn sowieso nur für's Zählen bis 4096 braucht, denn wie gesagt, intern hört beim TLC5940 die PWM-Generierung nach 4096 Clocks einfach auf. Es bringt also nichts, weiter zu zählen bzw. dann später zu resetieren, ausser dass man sich eine längere Totzeit einhandelt.


    EDIT:
    Sorry, ich habe deine Vorschläge zuerst falsch verstanden gehabt. Wenn du den BLANK stur während des ganzen Daten-Einclockens hoch ziehst und danach (nach dem Einlatchen) wieder runter, dann hast du auf jeden Fall eine Totzeit während dieser ganzen Phase. Noch schlimmer ist allerdings, wenn man den Zählerstand des TLC5940 nicht beachtet und BLANK völlig unabhängig davon bedient. Dann hat man u.U. wechselnd lange PWM-Zyklen, da man den Zähler möglicherweise vor Erreichen von 4096 resetiert.
    Das mit dem höheren PWM-Takt mit Binärzähler mit 14 bit oder mehr würde allerdings gehen (gute Idee übrigens :) ). Mann müsste allerdings den Interrupt trotzdem mit der Q12-Leitung (4096) erzeugen und eine höhere Leitung (Vielfaches von 4096) dabei auch noch monitoren. Wenn der Interrupt ausgelöst wird und die höhere Leitung ist dabei LOW, dann resetiert man mit BLANK nur den internen Zähler des TLC5940. Ist die höhere Leitung HIGH, dann resetiert man den externen Zähler mit einer separaten Leitung, latcht mit XLAT die zuletzt übertragenen Daten ein, resetiert mit BLANK den TLC5940-Zähler und löst eine neue Daten-Einclockung aus. Der Shiftout müsste dann allerdings aus der Interrupt-Routine verschwinden und irgendwo ins Hauptprogramm rein. Er dürfte aber nur dann ausgeführt werden, wenn in der Interrupt-Routine ein entsprechendes Flag (die Auslösung der Daten-Einclockung) gesetzt worden ist. Mit dieser Lösung dürfte die Datenübertragung dann eben maximal das gewählte Vielfache des PWM-Zyklus dauern.


    Hmmm, da kommt mir gerade die Idee, dass es mit der Flag-Lösung gar keinen höheren (mehr als 13 bit) Zähler braucht. Im Hauptprogramm macht man den Shiftout nur bei gesetztem Flag und setzt den Flag aber erst wieder nach Beendigung des Shiftouts zurück. Die Interrupt-Routine ihrerseits latcht die Daten nur ein und setzt den Flag, wenn der Flag nicht schon gesetzt ist, ansonsten führt sie nur einen BLANK-Reset und Reset des externen Zählers aus (d.h. startet einen neuen PWM-Zyklus). Damit wäre das Daten-Einclocken prinzipiell unabhängig vom PWM-Zyklus, das Einlatchen würde aber trotzdem immer synchron erfolgen.


    Gruss
    Neni

  • Also dann hier konkret der angepasste Code zum Ausprobieren:
    Verwendet wird der INT0-Pin für den externen Interrupt.
    Als Zähler kommt zum Bsp. der 74xx4020 in Frage, wobei dann
    Q13 (Bezeichnug der Zähler-Pins beginnt mit Q1 und nicht Q0)
    an INT0 geht und der BLANK-Ausgang vom µC an BLANK vom
    TLC und Reset vom 4020 angeschlossen wird.


    Gruss
    Neni


  • Ja, das ginge zur Not.
    Man kann meine Assemblerschleife von oben zur Generierung der 4096
    GSCLK-Pulse im Tiny2313 im Hauptprogramm implementieren (natürlich
    andere Portpins wählen). Nach diesen 4096 Pulsen zieht man einen
    Ausgangspin (welchen man mit INT0 vom Mega32 verbindet) hoch und
    wieder runter.
    Der INT0-Pin vom Tiny2313 wird mit dem BLANK-Ausgang vom Mega32
    verbunden und im Tiny eine Interruptroutine ähnlich der im Mega32
    gemacht, die ein Flag-Bit setzt. Der INT0 wird so konfiguriert, dass er bei
    einer fallenden Flanke ausgelöst wird.
    Vor der GSCLK-Puls-Routine im Tiny wird eine Warteschleife implementiert
    (Do - Loop - Until), welche so lange wartet, bis das Flag-Bit (wird als
    ungesetzt, also 0, initialisiert) gesetzt ist. Dann startet ein neuer 4096-
    GSCLK-Zyklus. Am Ende des Zyklus muss man neben dem oben erwähnten
    Pulsen eines Ausgangspins auch noch das Flag-Bit wieder rücksetzen.


    Das wäre dann auch schon die ganze Applikation gewesen :wink: .


    Man sollte nur darauf achten, dass man den Tiny hoch genug taktet. Mit
    beispielsweise 16 MHz erreicht man eine GSCKL-Frequenz von rund 2 MHz,
    also eine PWM-Frequenz von 500 Hz.


    Gruss
    Neni

  • OK, ich habe hier noch ne AppNote gefunden, wie den TLC5940 nur mit diskreten Teilen in DC-Mode bekommt: http://focus.ti.com/lit/an/slva259/slva259.pdf. Man kann dadurch sogar den BLANK extern als PWM-Pin - ist zwar auf dem Niveau von "Lampe an / Lampe aus", aber zumindest hat man ne KSQ.


    Edit:
    Wenn man auf die eigentlich PWM verzichtet und nur mit DotCorrection arbeitet, dann könnte man das Szenario natürlich auch mit dem bisherigen Aufbau machen. Werde ich nachher mal testen. Man muss die DC ja dann anders reinclocken, damit sie im EEP bleibt, oder?

  • Hallo,


    habe gestern den Code auf einem Atmega8 ausprobiert. Problem ist das die Leds am TLC nicht sehr hell leuchten. Auch wenn ich den Widerstand für die Stromstärke der LEDs ändere passiert nicht viel. Hängt der Widerstand in der Luft bzw. wird er angefasst so leuchten die LEDs wild durcheinander. Wo kann der Fehler liegen?

  • tja, wenn du den genau den ersten Code (Original von Jan, unmodifiziert) genommen hast, dann steht gleich im Post darunter von mir, weshalb dieser Code eine Menge an maximaler LED-Helligkeit 'verschenkt' und somit eigentlich gänzlich ungeeignet und unbrauchbar für eine sinnvolle Nutzung des TLC5940 ist.


    Du solltest entweder meine etwas darunter gepostete Anpassung der Interrupt-Routine (mit Assembler-Code) verwenden und die Interruptaufruffrequenz auf einmal alle 4 - 6 ms einstellen (Anpassen des Prescalers und eines eventuellen Reload-Wertes für Timer0), oder aber du entscheidest dich idealerweise für die unterste Code-Variante mit externem PWM-Takt und externem Zählerbaustein. Diese ist zwar von der benötigten Hardware her mit etwas mehr Aufwand verbunden, bringt aber bei weitem die besten Ergebnisse in Kombination mit dem TLC5940.


    Gruss
    Neni

  • Moin!


    Vielleicht liest das ja noch mal jemand :) Habe nach Jahren der Abstinenz mal wieder Lust, was mit dem TLC zu machen, allerdings habe ich das mit dem externen Zähler nicht ganz gepeilt. Die Frage ist, woher er denn den Takt bekommt? Hänge ich ihn parallel an GSCLK und den dann an den 16MHz-Quarz oda wat?


    Wär schick, wenn das mal jemand aufmalen könnte. Und noch besser wäre es, wenn jemand den Code für den Tiny posten könnte, den habe ich nämlich griffbereit.


    Danke und Gruß


    Jan

  • Uiuiui Jan, das ist ja echt ne Ewigkeit her, dass ich mich hier mal mit dem Teil befasst habe.


    Als externen Oszi kannst du eigentlich so ziemlich alle Möglichkeiten ausschöpfen, also einen RC- oder Quarz-Oszi mit TTL- oder CMOS-Chips, einen kompletten Quarz-Oszillator in SMD- oder DIL-Form. Auch können gewisse AVRs ihren internen System-Clock an einem Pin ausgeben, oder aber man verwendet einen internen Timer zu Generierung des Taktes etc. etc.


    Ich würde dir aber ehrlich gesagt eher vom TLC5940 abraten, da es mittlerweile deutlich bessere Alternativen mit internem Oszi etc. gibt. Ein guter Kandidat wäre hier sicher der TLC5947, da er ansonsten die gleiche Schnittstelle zum Reinclocken der GS-Daten hat. Auch sehr gut bewährt hat sich der TLC59116, allerdings hat dieser eine i2c-Schnittstelle.


    Ich habe im Moment zu wenig Zeit für solche Spielereien, als dass ich dir da eine genaue Zeichnung oder gar fertigen Code anbieten könnte.


    Gruss
    Neni

  • Moin Neni!


    Danke für die Antwort, wusste gar nicht, da es da was schickeres seitens TI gibt. Der 5947 hört sich nett an (24Ch), kann aber nur 30mA und die PWM-Frequenz ist ja uahc nicht grad der Burner. Da gefällt mir der 59116 schon wesentlich besser mit seinen 25MHz. Außerdem pumpt der mir auch 120mA, genau wie der 5940. Nachteil (für mich) ist, dass er per I²C angeschnackt wird, und da hab ich so meine Probleme mit.


    Wie ist Deine Einschätzung dbzgl.. Ist das effizient mit Bascom zu realisieren oder für mich als I²C-Noob zu heftig?


    Danke und Gruß
    Jan