PWM mit 74HC595 Schieberegister

Diese Experimentierplatine habe ich Ende 2007 entwickelt um mich ein bisschen mit Portexpandern und PWM auseinanderzusetzen. Ein Bekannter kam zu diesem Zeitpunkt zu mir mit 2 großen Laufschrift-Displays (110x12 LEDs) und fragte mich, ob ich ihm eine Ansteuerung hierfür bauen kann. Dummerweise sind die Programmiertastaturen für diese Laufschriften nämlich verloren gegangen.

74HC595 Experimentierplatine - klicken zum Vergrößern

Da ich mich zu diesem Zeitpunkt noch nie mit Schieberegistern oder PWM auseinandergesetzt hatte, wollte ich natürlich nicht gleich auf die Ansteuerung los, sondern habe mir eben erst einmal diese Experimentierplatine mit 2 Spalten á 14 LEDs entwickelt. Jetzt weiß ich, dass das eine gute Idee war...

Über den Attiny2313 schiebe ich jeweils 16 Bit in die Schieberegister, wobei die ersten beiden Ausgänge des IC1 die Spalten über die beiden Transistoren Q1 und Q2 schalten, die Ausgänge 3 bis 8 steuern die LEDs 1 bis 6 an - und die Ausgänge 1 bis 8 des zweiten 74HC595 steuern die LEDs 7 bis 14. Also werden genau 16 Bit benötigt.

Zunächst hatte ich die Ansteuerung komplett in Bascom geschrieben - was nicht unbedingt voll zufriedenstellend war. Also habe ich mich mal einen Tag hingesetzt und schnell mal ein bisschen Assembler gelernt - und am gleichen Tag die Ansteuerungsroutinen dorthin umgebaut. Das Ganze ist jetzt schön schnell - für Aufbereitung und Ausgabe beider Spalten werden 1086 clocks bei einer Zeit von ca. 0,054ms benötigt. Für 127 Helligkeitswerte bedeutet das: ein Durchlauf benötigt ca. 7ms - also liegt die Anzeigefrequenz bei ca. 140Hz.

Ich bin mir sicher, dass man noch ein bisschen was am Code drehen kann - aber dafür, dass ich am Tag zuvor noch kein Assembler konnte ist die Funktion richtig zufriedenstellend.

So, generell funktioniert also alles schon einmal wie es soll - die Helligkeit aller 28 LEDs können einzeln ohne flackern gedimmt werden. Aber ein Problem besteht noch: Warum um Himmels Willen leuchtet eine ausgeschaltene LED trotzdem noch ein bisschen, wenn die LED in der anderen Spalte an ist?

die LEDs links sollten eigentlich aus sein die LEDs links sollten eigentlich aus sein

Auf der Platine oben kann man es nur schwer erkennen: Die ersten 5 LEDs der linken Spalte leuchten mit einer Helligkeit von 0/127 bis 4/127. Die oberste LED der rechten Spalte ist - wie die oberste der linken Spalte - wie es sich gehört komplett aus. Aber LEDs 2 bis 5 der rechten Spalte glimmen proportional zur Helligkeit der LEDs der linken Spalte.

Links eine Detailaufnahme einer ähnlichen Ansteuerung. Die LEDs in der linken Spalte leuchten jeweils mit einer Helligkeit von 0 bis 20 in 5er-Schritten (also 0 - 5 - 10 - 15 - 20). Normalerweise sind die LEDs schon ein Stück dunkler - aber die Kamera lässt das Ganze gerne mal ein bisschen heller aussehen... Die 5 LEDs der linken Spalte sind eigentlich alle aus - aber wie man hier schön sieht, leuchten sie trotzdem proportional zu der Helligkeit der gegenüberliegenden LED.

Auf dem rechten Bild ebenfalls eine andere Ansteuerung. Die rechten LEDs leuchten mit der größten Helligkeit 127 - die LEDs in der linken Spalte sollten eigentlich aus sein.

WARUM? Das versuche ich jetzt verzweifelt herauszufinden. Ein Königreich für ein Oszilloskop, das mir leider immer noch in meiner Werkstatt fehlt - ist erst mal einfach zu teuer...

Im Moment bin ich so weit zu denken, dass die Schieberegister den "hohen" Takt nicht ganz mitmachen und die Ausgänge daher doch noch ein kleines bisschen von der Spalte vorher offen sind - kann ich mir aber irgendwie nicht ganz vorstellen... Oder die Transistoren schließen bei der Frequenz noch nicht komplett? Glaub ich aber auch nicht - die sollten noch mehr aushalten... Aber irgendwo muss ja ein kleiner Strom fließen, die die LEDs (übrigens Low-Current-LEDs) zum Leuchten bringen.

Aber es spricht auch etwas für die Theorie, dass Schieberegister oder Transistor nicht richtig schließen: Wenn ich eine Pause zwischen der Ausgabe der beiden Spalte setze, also Ausgabe Spalte 1 - Pause - Spalte 2 - Pause - usw., dann leuchten die LEDs, die aus sein sollen, nicht! Auch kann man hier gut erkennen, dass sich da kein Fehler ins Programm geschmuggelt hat, der die LED eventuell kurz aufleuchten lässt - aber dafür flackert die Ausgabe dann auch saumäßig!

Nun ja, jetzt werde ich erst einmal in Foren nachfragen, ob jemand hier eine Lösung weiß - vielleicht ist es ja nur eine Kleinigkeit...?

Bisher versuchte Änderungen an der Hardware:

Änderung
Ergebnis
Transistor BC558 statt TIP125 eingesetzt
keine Änderung
Widerstände R21 und R22 zwischen Basis und Emitter auf 2,2k gesetzt
keine Änderung

Bisher versuchte Änderungen an der Software:

Änderung
Ergebnis
Zeilen kommentiert
Besser verständlich ;)
Zwischen der Ausgabe der einzelnen Spalten werden die Spalte erst einmal wieder gelöscht
LED-aus-Problem erst einmal beseitigt, dafür sind die anderen LEDs aber auch dunkler

Vorschläge zu Änderungen an der Hardware (noch nicht durchgeführt):

  • TIP125 ändern in P-FETs

Hier zur Vervollständigung noch der zu Grunde liegende Schaltplan von der ganzen Sache (klicken für Großansicht):

Neuer Schaltplan (bisher nicht aufgebaut/getestet)
Schaltplan der 74HC595 Experimentierplatine - klicken zum Vergrößern

Bisheriger Schaltplan
Schaltplan der 74HC595 Experimentierplatine - klicken zum Vergrößern

Und zu guter Letzt noch der komplette Quelltext (Inline-Assembler in BASCOM):

$regfile = "attiny2313.DAT"
$crystal = 20000000
$baud = 9600                                                ' use baud rate
$hwstack = 28                                               ' default use 32 for the hardware stack
$swstack = 16                                               ' default use 10 for the SW stack
$framesize = 40
$prog &HFF , &H64 , &HDF , &H00                             ' generated. Take care that the chip supports all
                                                             ' fuse bytes.
Dim Outbyte As Integer
Dim Led_pwm(32) As Byte
Dim X As Byte , Y As Byte
Dim S As Byte

For X = 3 To 32                                             'PWM-Werte mit versch. Helligkeit vorbelegen
   Led_pwm(x) = X - 3
   Led_pwm(x) = Led_pwm(x) * 4
   Led_pwm(x) = Led_pwm(x) + 11
Next
Led_pwm(3) = 0
Led_pwm(4) = 0
Led_pwm(5) = 0
Led_pwm(6) = 0
Led_pwm(7) = 0

Led_pwm(19) = 0
Led_pwm(20) = 5
Led_pwm(21) = 10
Led_pwm(22) = 15
Led_pwm(23) = 20

'Led_pwm(10) = 127
Led_pwm(19) = 127
'Led_pwm(26) = 0
'Led_pwm(32) = 127

Led_pwm(1) = 0                                              'Spalte 2 aus
Led_pwm(2) = 255                                            'Spalte 1 an
Led_pwm(17) = 255                                           'Spalte 2 an
Led_pwm(18) = 0                                             'Spalte 1 aus

$asm
   .def Temp = R16
   .def Pwmcnt = R17
   .def Cnt = R18
   .def Pwmwert = R19

   .equ Schiebe_ddr = Ddrb
   .equ Schiebe_port = Portb
   .equ Rck = Pb4
   .equ Sck = Pb7
   .equ Seri = Pb5
   .equ Poe = Pb3

   'PCK, SCK, SERI, POE als Ausgänge schalten
   LDI temp,184                                             'PB3 OR PB4 OR PB5 OR PB7
   Out Schiebe_ddr , Temp

   CBI SCHIEBE_PORT,poe                                     'Schieberegister "aktivieren"
   RCALL Leeren                                             'Schieberegister komplett leeren

   CLR pwmcnt                                               'Zähler für PWM auf 0 setzen
   '---------------------------------------------------------------------------
   'Hauptprogramm
Beginn:
   INC PwmCnt                                               'PWM-Zähler um 1 erhöhen
   CPI Pwmcnt,128                                           'PWM-Zähler mit 128 vergleichen
   BRNE WorkPWM                                             'Wenn PWM-Zähler nicht 128 ist, dann zu "Workpwm"
   CLR pwmcnt                                               'ansonsten PWM-Zähler auf 0 zurücksetzen
Workpwm:
   Loadadr Led_pwm(32) , Z                                  'Zeiger auf PWM-Werte-Array setzen (letzes
                                                            '  Element, da Ausgabe "von hinten nach vorne")
   LDI cnt,2                                                '2 Spalten sollen ausgegeben werden
S_loop:
   RCALL Leeren                                             'Schieberegister leeren
   RCALL Spalte_out                                         'Spalte (16 Bit) in Schieberegister laden
   RCALL Schiebe_out                                        'Schieberegister-Daten in Ausgang übernehmen
   DEC cnt                                                  'Spalten-Zähler um 1 verringern
   BRNE S_Loop                                              'Wenn Spalten-Zähler nicht 0, dann zu "S_Loop"
   rjmp Beginn                                              'zu "Beginn" springen => nächste PWM-Stufe

Leeren:
   '16 Bit in das Schieberegister, dass alles aus ist
   Push temp                                                'Register sichern
   ldi temp,16                                              '16 Bits sollen übermittelt werden
Leeren_1:
   SBI SCHIEBE_PORT,SERI                                    '1 ins Schieberegister schreiben (LED aus)
   RCALL Schiebe_CLOCK                                      'Schiebepuls geben => Schieberegister shiften
   dec temp                                                 'Bit-Zähler um 1 verringern
   brne Leeren_1                                            'Wenn Bit-Zähler nicht 0 ist, dann zu "Leeren_1"
   RCALL Schiebe_out                                        'Schieberegister-Daten in Ausgang übernehmen
   POP temp                                                 'Register wiederherstellen
   RET

Schiebe_out:
   'Daten im Schieberegister in Ausgang übernehmen
   sbi SCHIEBE_PORT,RCK                                     'RCK am Schieberegister high => Daten an Ausgang
   Cbi SCHIEBE_PORT,RCK                                     'RCK wieder auf low setzen
   ret

Schiebe_clock:
   'Daten im Scheiberegister shiften
   SBI SCHIEBE_PORT,SCK                                     'SCK am Schieberegister auf high => Daten shiften
   CBI SCHIEBE_PORT,SCK                                     'SCK wieder auf low setzen
   RET

Spalte_out:
   'Spalte ausgeben
   PUSH cnt                                                 'Register sichern
   LDI cnt,16                                               '16 Bits sollen übermittelt werden
Spalte_out_1:
   LD PwmWert,Z                                             'aktuellen PWM-Wert aus Array holen
   LDI temp,1                                               '  und Zeiger
   Sub Zl , Temp                                            '  um 1 verringern
   LDI temp,0                                               '  Zeiger wird somit auf das
   SBC ZH,temp                                              '  vorherige Element gesetzt
   CPI Pwmwert,0                                            'PWM-Wert für die LED mit 0 vergleichen
   BREQ Ledaus                                              'Wenn gleich (PWM-Wert=0), dann zu "Ledaus"
   CP pwmwert,Pwmcnt                                        'PWM-Wert mit PWM-Zähler vergleichen
   BRLO LedAus                                              'wenn PWMWert<PWM-Zähler, dann zu "Ledaus"
Ledan:
   CBI SCHIEBE_PORT,SERI                                    '0 ins Schieberegister schreiben (LED an)
   rjmp Schieben                                            'zu "Schieben" springen
Ledaus:
   SBI SCHIEBE_PORT,SERI                                    '1 ins Schieberegister schreiben (LED aus)
Schieben:
   RCALL Schiebe_CLOCK                                      'Schiebepuls geben => Schieberegister shiften
   DEC cnt                                                  'Bit-Zähler um 1 verringern
   BRNE Spalte_Out_1                                        'Wenn Bit-Zähler != 0, dann zu "Spalte_Out_1"
   POP cnt                                                  'Register wiederherstellen
   ret
$end Asm