Forum: Mikrocontroller und Digitale Elektronik RGB - LED Matrix mit Schieberegistern


von Arne P. (ob3lix)


Angehängte Dateien:

Lesenswert?

Hallo an alle,

ich bin gerade am einem Projekt dran, in dem eine RGB LED Matrix 8x8 
mittels einem AT89C51ED und 4 Stk 74HC595 angesteuert werden soll.

Ich habe aber folgendes Problem:

Es werden nicht immer die richtigen Dots angezeigt... im Bild ist ein 
Beispiel zu sehen, es soltle ein U sein, aber die Puntke rechts dürften 
nicht da sein...

Der Code ist im Anhang, vielleicht kann sich den jemand angucken?

Aufbau selber:

AT89C51ED2 Testboard von ETT, die Schieberegister sind extern aufgebaut, 
20 cm Kabel...

OE und SCLR sind auf 0V, bzw 5V.

Gibt es noch was, auf das man bei so etwas besonders achten muss?

Konstruktive Kritik zum Quellcode nehme ich gerne an ;) das letzte Mal 
Programmieren ist schon ne weile her.

Gruß
Arne Pfäffle

von Karl H. (kbuchegg)


Lesenswert?

Puh,
Das ist der komplexeste, unübersichtlichste Matrix Code, den ich je 
gesehen habe.

So macht man das nicht.
Wie macht man das richtig?
Es gibt ein (in deinem Fall drei) globales Array (für jede Farbe eines), 
welches als dein Bildspeicher fungiert.
Im Timer-Interrupt wird EINE Zeile dieses Arrays (für jede Farbe) 
ausgegeben. Beim nächsten Interrupt kommt dann die nächste Zeile drann, 
etc. etc. Immer reihum, bei jedem Interrupt die jeweils nächste Zeile.

Was du dadurch erreicht hast ist, dass du eine Art 'Bildschirmspeicher' 
im Programm hast, in dem du Bits setzen und löschen kannst, und zwar so 
wie es die Programmerfordernisse vorsehen, ohne dass sich das weitere 
Programm darum kümmern muss, wie sich das jetzt in die tatsächlich 
Ansteuerung der physikalischen Matrix niederschlägt. Diesen letzten 
Punkt erledigt die Timer-Interrupt Funktion ganz von alleine und 
eigenständig.

Das ist der grundsätzliche Aufbau, wie man eine gemultiplexte Anzeige 
realisiert: Man hat im Programm eine globale Speicherfläche, die für das 
restliche Programm 'die Ausgabe' darstellt und eine Interrupt Funktion 
kümmert sich darum, dass bei jedem Interrupt die nächste Teilinformation 
auf der physikalischen Anzeige realisiert wird. Immer reihum.


Und noch was.
Gewöhn dir Datentypen wie char, int und dergleichen ab.

char: Es gibt 3 Character Datentypen

* signed char (oder auch int8_t)
Wird verwendet, wenn man kleine Zahlen mit Vorzeichen braucht
* unsigned char (oder auch uint8_t)
Wird verwendet, wenn man kleine Zahlen ohne Vorzeichen braucht. Das ist 
genau der Datentyp, der einem Byte entspricht
* char
Wird verwendet, wenn man es mit Textverarbeitung zu tun hat

int kannst du zwar verwenden, aber auch hier ist die Verwendung von 
int16_t bzw. uint16_t besser, weil im Datentyp schon die Anzahl der Bits 
unmissverständlich ausgedrückt wird. Denn: Du willst deinem µC nicht 
mehrarbeit für nichts auflasten. Wenn eine Schleife 8 mal durchlaufen 
wird, dann wäre es töricht, dafür 16 Bit Arithmetik vorzuschreiben.

Gewöhn dir solche Dinge
  for( i = 0; i <= 7; i++ )
gleich wieder ab. Die Schleife lautet
  for( i = 0; i < 8; i++ )
Diese Schleife wird 8 mal durchlaufen. Drum steht auch die 8 im Code!
Jede For-Schleife, die nicht dem Muster folgt
  for( variable = 0; variable < Anzahl_Wiederholungen; variable++ )
ist erst mal suspekt und muss genauer untersucht werden. Enstpricht eine 
for-Schleife aber diesem Muster (variable auf 0 setzen, vergleich 
mittels kleiner auf die Anzahl, Erhöhung durch Inkrement), dann ist das 
eine normale Zählschleife, wobei die Anzahl der Wiederholungen direkt im 
Code steht. 95% aller for-Schleifen sind nach diesem Muster und 99.9% 
aller C Programmierer erkennen dieses Konstrukt nachts um halb 3 mit 
Vollrausch auch als solches innerhalb einer Zehntel Sekunde. Nur wenn da 
was anderes steht, wie zb kleiner/gleich, dann muss man hellwach sein.

von Karl H. (kbuchegg)


Lesenswert?

Und sowas
1
void Ausgabe_14_seg(char zeichen1, char zeichen2)
2
{    
3
    int gesamt;
4
5
    gesamt = zeichen1;
6
7
    seg2 = 0;
8
    for (i = 0; i <=16; i++)
9
    {

mit einem globalen i, geht schon gleich gar nicht. Da muss man fragen: 
Bist du des Wahnsinns fette Beute?

Derartige Zählvariablen sind immer lokale Variablen
1
void Ausgabe_14_seg(char zeichen1, char zeichen2)
2
{    
3
    int gesamt;
4
    uint8_t i;   // <--- dieses i existiert nur innerhalb dieser Funktion
5
6
    gesamt = zeichen1;
7
8
    seg2 = 0;
9
    for (i = 0; i <=16; i++)
10
    {


denn wenn du solche Dinge mit globalen Variablen erledigst, dann läufst 
du IMMER Gefahr, dass dir unabsichltlich dieses hier passiert
1
uint8_t i;
2
3
void foo()
4
{
5
  for( i = 0; i < 8; i++ )
6
    bar();
7
}
8
9
void bar()
10
{
11
  for( i = 0; i < 8; i++ )
12
    mach_was;
13
}

der Aufruf der Funktion bar() zerstört dir das i in foo. Und das willst 
du ganz sicher nicht. Ganz zu schweigen davon, dass du bei allem was 
komplizierter als 10 Zeilen Code ist, den Überblick verlieren wirst, 
welche Funktion welche globalen Variablen benutzt und verändert.

Variablen sollten grundsätzlich immer so lokal wie möglich sein! Aus 
praktischen Gründen muss man auf einem µC manchmal davon abweichen. Aber 
sicher nicht bei derartigen Hilfsvariablen, die man zum Beispiel für 
Schleifenzähler in einer Funktion braucht.

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Den Code komplett neuschreiben wär das Erste und das Zweite:
Schaltplan zeigen.

von Karl H. (kbuchegg)


Lesenswert?

Die Funktion hier
1
void Ausgabe_14_seg(char zeichen1, char zeichen2)
2
{    
3
    int gesamt;
4
5
    gesamt = zeichen1;
6
7
    seg2 = 0;
8
    for (i = 0; i <=16; i++)
9
    {
10
        Clock_SCK_seg = 0;
11
        Ausgang_SHR_seg = gesamt & 0x01;
12
        gesamt = gesamt >> 1;
13
        warte_ms(1);
14
        Clock_SCK_seg = 1;
15
        warte_ms(1);
16
    }
17
    
18
    Clock_RCK_seg = 0;
19
    warte_ms(1);
20
    Clock_RCK_seg = 1;
21
    seg1 = 1;
22
    warte_ms(1);
23
24
    gesamt = zeichen2;
25
26
    seg1=0;
27
    for (i = 0; i <=16; i++)
28
    {
29
        Clock_SCK_seg = 0;
30
        Ausgang_SHR_seg = gesamt & 0x01;
31
        gesamt = gesamt >> 1;
32
        warte_ms(1);
33
        Clock_SCK_seg = 1;
34
        warte_ms(1);
35
    }
36
    
37
    Clock_RCK_seg = 0;
38
    warte_ms(1);
39
    Clock_RCK_seg = 1;
40
    seg2 = 1;
41
    warte_ms(1);
42
    
43
}
ist mir überhaupt nicht klar.
Hier werden 2 mal 16 Bit ausgegeben. Laut Aufruf stammen die aus der 
Code Tabelle. Aber laut Zeichnung
1
|   Anschluss der Schieberegister und LED's:                                    |
2
|                                                                               |
3
|   |----| CL & D         |-----|       |-----|        |-----|       |-----|    |
4
|   |-µC-|---------ser(14)| SHR |Qh'----| SHR |--------| SHR |-------| SHR |    |
5
|   |----|                |-----|       |-----|        |-----|       |-----|    |
6
|                            |             |              |             |       |
7
|                            |             |              |             |       |
8
|                            |             |              |             |       |
9
|                            v             v              v             v       |
10
|                         |----|        |-----|        |-----|      |-----|     |
11
|                         |blau|        |gruen|        | rot |      | row |     |
12
|                         |----|        |-----|        |-----|      |-----|     |
13
|-------------------------------------------------------------------------------|
sind doch die ersten 8 Bits, die reinzuschieben sind, die Zeilennummer 
der Zeile, die leuchten soll.
Entweder bin ich fast blind oder ich sehe einfach nicht, wie das 
überhaupt funktionieren soll.

Nö, ich bleibe dabei: Du hast dich da in der selbst erzeugten 
Komplexität komplett verlaufen. Zurück an den Anfang und diesmal 
ordentlich gemacht. Konzentrier dich erst mal nicht darauf, möglichst 
viele verschiedene Muster erzeugen zu können, sondern auf einen 
ordentlichen und sauber ausgeführten Multiplexer samt Bildspeicher. Dann 
sind in weiterer Folge dann auch die diversen Muster nur noch ein 
Kinderspiel. So wie das jetzt gemacht ist, ist das alles nicht 
tragfähig.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:

> Hier werden 2 mal 16 Bit ausgegeben.


Korrektur ...
1
    for (i = 0; i <=16; i++)

... 2 mal 17 Bits


und wieso eigentlich
1
void Ausgabe_14_seg(char zeichen1, char zeichen2)

ein char?
Hier
1
    int gesamt;
2
3
    gesamt = zeichen1;
sind 16 Bits bei gesamt im Spiel.
Hier
1
code int    seg_14[36]  = { 0b111011110000000,  //A
2
                        0b111111110000000,  //B
3
                        0b100111000000000,  //C
sind 16 Bits im Spiel.

Nur hier beim Aufruf
1
            Ausgabe_14_seg(0,B);

wobei das B für
1
#define _B seg_14[1]
steht, werden dann beim Aufruf von den 16 Bits des Aufrufers erst mal 
(wegen char) die oberen 8 Bits gestrippt, nur um dann in der Funktion 
die 8 Bit wieder auf 16 aufzublasen.


Schmeiss den Code weg und fang von vorne an. Das taugt alles nichts. Das 
ist zu unsystematisch und von Anfang an schon mal grundsätzlich der 
komplett falsche Ansatz für eine Matrix. Wenn dein Code an allen Ecken 
und Enden mit Zugriffen auf die Schieberegister-Portpins gespickt ist, 
dann weißt du, das das alles nichts taugt. Es gibt nur eine einzige 
Funktion, die an diesen Pins rumspielen darf (ausser der 
Initialisierung) und das ist die Interrupt-Funktion, die reihum Zeile 
für Zeile ausgibt.

Pseudocode:
1
// es gibt 8 Zeilen, daher sind edie Arrays jeweils mit 8 dimensioniert
2
uint8_t Zeile_red[8] = 0;     // Jedes Bit steht für 1 LED
3
uint8_t Zeile_grn[8] = 0;     // Jedes Bit steht für 1 LED
4
uint8_t Zeile_blu[8] = 0;     // Jedes Bit steht für 1 LED
5
6
uint8_t ZeilenCode[8] = 
7
  { 0b00000001, 0b00000010, 0b00000100, 0b00001000,
8
    0b00010000, 0b00100000, 0b01000000, 0b10000000 };
9
10
....
11
12
uint8_t multiplexZeile = 0;
13
14
void ISR_Timer0 (void) interrupt 1
15
{
16
  TH0 = THB0;
17
  TL0 = TLB0;    
18
19
  // die aktuell leuchtende Zeile abschalten
20
  ShiftOutByte( 0 );
21
  ShiftOutByte( 0 );
22
  ShiftOutByte( 0 );
23
  ShiftOutByte( 0 );
24
  ActiveShiftRegisters();   // mittels RCLK durchschalten lassen
25
26
  multiplexZeile++;
27
  if( multiplexZeile == 8 )
28
    multiplexZeile = 0;
29
30
  // die nächste Zeile ausgeben
31
  ShiftOutByte( ZeilenCode[ multiplexZeile ] );
32
  ShiftOutByte( Zeile_red[ multiplexZeile ] );
33
  ShiftOutByte( Zeile_grn[ multiplexZeile ] );
34
  ShiftOutByte( Zeile_blu[ multiplexZeile ] );
35
  ActiveShiftRegisters();   // mittels RCLK durchschalten lassen
36
}
37
38
void ShiftOutByte( uint8_t wert )
39
{
40
  ... 8 Bits raustakten
41
  ... Achtung: nur die Bits raustakten. Nicht mehr!
42
43
  uint8_t i;
44
45
  for( i = 0; i < 8; i++ ) {
46
    if( wert & 0x80 )
47
      ... ein 1 Bit ausgeben
48
    else
49
      ... ein 0 Bit ausgeben
50
51
    wert <<= 1;
52
  }
53
}
54
55
void ActiveShiftRegisters()
56
{
57
  ... die Schieberegister sind gefüllt
58
  ... bringe sie jetzt dazu, dass sie ihren Inhalt auch tatsächlich
59
  ... ausgeben, indem RCK getoggelt wird
60
}

das ist im Prinzip der ganze Multiplex. (ohne die beiden 
Hilfsfunktionen). Eine LED einschalten bedeutet dann nichts anderes, als 
das man das richtige Bit in den Arrays Zeile_red bzw. Zeile_grn bzw. 
Zeile_blu auf 1 bzw auf 0 setzt.

Um also im Hautpprogramm die rote Led in der 2.ten Zeile in der Spalte 5 
einzuschalten, muss man in Zeile_red[1] das 5.te Bit auf 1 setzen.
1
   Zeile_red[1] |= ( 1 << 4 );
und dann muss auch schon die rote LED an besagter Position aufleuchten. 
Der Multiplexcode in der Interrupt Routine sorgt dafür, dass besagte LED 
dann auch aufleuchtet, wenn diese Zeile drann ist ausgegeben zu werden. 
Das Hauptprogramm muss sich nicht mehr darum kümmern. Das setzt einfach 
nur die Bits in den 3 Arrays auf 1 oder 0, je nachdem wie sie stehen 
müssen, damit sich das beabsichtigte Bild ergibt. Mit der eigentlichen 
Ausgabe hat das Hauptprogramm nichts mehr zu tun.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Karl Heinz Buchegger (kbuchegg) (Moderator)

>Das ist der komplexeste, unübersichtlichste Matrix Code, den ich je
>gesehen habe.

Das ist nur logisch, denn der Code der "Matrix" ist verschlüsselt ;-)

von Arne P. (ob3lix)


Lesenswert?

@Karl:

ok, ich werd mich mal da dran machen...

das mit der 14 seg funktion ist grad mal auf eis gelegt, also kann 
ignoriert werden...

gibt es da n kleines beispiel zum denk anstoß?

Plan ist grad in Arbeit...

von Karl H. (kbuchegg)


Lesenswert?

Arne Pfäffle schrieb:

> gibt es da n kleines beispiel zum denk anstoß?

siehe 2 Postings über deinem letzten

von Arne P. (ob3lix)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Arne Pfäffle schrieb:
>
>> gibt es da n kleines beispiel zum denk anstoß?
>
> siehe 2 Postings über deinem letzten

grad gesehen, danke ;)

wie gesagt, die 14_segment funktion fliegt aktuell raus...
hatte das nur noch im code drin...

von Karl H. (kbuchegg)


Lesenswert?

Arne Pfäffle schrieb:

> wie gesagt, die 14_segment funktion fliegt aktuell raus...
> hatte das nur noch im code drin...

Nicht 'fliegt raus'

Fang bei 0 an!

Dieses "Ich hab da so ähnlichen Code, den nehm ich erst mal als 
Grundlage und ändere ihn", ist Quatsch.

Eine Matrix ist was anderes wie eine Reihe von Segment-Anzeigen. Also 
ist das auch gerechtfertigt, da einen neuen Code anzufangen. Das man 
sich aus anderen Programmen Codeabschnitte rüberkopieren kann, wenn man 
sie brauchen kann, steht ausser Frage. Aber grundsätzlich ist das erst 
mal ein komplett neues Programm. Vor allen Dingen dann, wenn du andere 
um Hilfe bittest. Denn nichts ist unangenehmer, wenn andere dann auch 
noch erst mal rausfinden müssen, welcher Code eigentlich alt ist und 
eigentlich gar nicht dazugehört.

von Arne P. (ob3lix)


Lesenswert?

ok code ist weg...

eine Frage aber noch:

*** WARNING C233 IN LINE 145 OF C:\8051\RGB.c : using absolute registers 
together with interrupts can cause register corruptions (mixup of 
register banks)

Das kommt beim Timer 0 beim compilieren... woran köntne das liegen?

von Falk B. (falk)


Lesenswert?

@ Arne Pfäffle (ob3lix)

>*** WARNING C233 IN LINE 145 OF C:\8051\RGB.c : using absolute registers
>together with interrupts can cause register corruptions (mixup of
>register banks)

Sieht nach einem Compilerproblem aus. Oder dort ist unsachgemäßer 
Inlineassembler drin.

von Arne P. (ob3lix)


Lesenswert?

zu kompliziert ist auch scheisse....

danke!!!!! jetzt muss ich nur noch meine grafiken anpassen

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.