Forum: Mikrocontroller und Digitale Elektronik Laufschrift auf LED Matrix


von Ingo L. (corrtexx)


Lesenswert?

Hallo,

ich habe eine 16x16 LED Matrix, welche auch wunderbar schon seit einiger 
Zeit funktioniert. Der AVR spielt Snake auf der Matrix und in gewissen 
Abständen wird ein Text eingeblendet. Zeichen für Zeichen. Soweit alles 
gut.

Ich möchte nun aber nicht mehr den Text Zeichen für Zeichen nacheinander 
ausgeben, sonder als Laufschrift. Leider bekomme ich es überhaupt nicht 
geschissen. Mir gehn die Ideen aus.

Hier die Funktion der ein String übergeben wird:
1
void Matrix_Puts (char* s)
2
{
3
  while (*s != '\0' && Matrix.Mode){
4
    Matrix_Putc(*s++);
5
    _delay_ms(350);
6
  }
7
  Matrix_Putc(' ');
8
  _delay_ms(500);
9
  
10
}
11
12
void Matrix_Putc ( unsigned int c )
13
{
14
  for ( unsigned int i = 0; i < SIZE_OF_MATRIX ; i+= (SIZE_OF_ROW / 2) ){
15
    for ( unsigned char j = 0; j < 8; j++){
16
      if ( (i / 8) % 2 == 1 ){
17
        if ( j < 4 ){
18
          Matrix.Snake.Field[i+j] = ( Note[(SIZE_OF_MATRIX*(unsigned int)c+i)/16] & (1<<(3-j))) >> (3-j) ;
19
        }
20
      }else{
21
        if ( j >= 4 ){
22
          Matrix.Snake.Field[i+j] = ( Note[(SIZE_OF_MATRIX*(unsigned int)c+i)/16] & (1<<(7-j+4)) ) >> (7-j+4);
23
        }
24
      }
25
    }
26
  }
27
}

So wie es jetzt ist wird jedes Zeichen nacheinader einzeln ausgegeben.
Der Font ist 8 LEDs breit und 16 hoch. Hier mal ein Auszug für ein "A":
1
/* char = 0x41 */
2
0x00,  /* ........ */
3
0x00,  /* ........ */
4
0x18,  /* ...OO... */
5
0x18,  /* ...OO... */
6
0x3c,  /* ..OOOO.. */
7
0x24,  /* ..O..O.. */
8
0x66,  /* .OO..OO. */
9
0x6e,  /* .OO.OOO. */
10
0x7a,  /* .OOOO.O. */
11
0xe3,  /* OOO...OO */
12
0xc3,  /* OO....OO */
13
0xc3,  /* OO....OO */
14
0x00,  /* ........ */
15
0x00,  /* ........ */
16
0x00,  /* ........ */
17
0x00,  /* ........ */

Kann mir mal jemand n Tip geben wie man das am geschicktesten anstellt?
Ich glaube so trivial ist das nicht :-(

Danke

von Karl H. (kbuchegg)


Lesenswert?

Ingo Less schrieb:

> Kann mir mal jemand n Tip geben wie man das am geschicktesten anstellt?

Du musst dich von der Denkweise lösen, dass du Text ausgibst. D.h. 
natürlich gibst du Text aus, aber nur bis in eine 
Softwarezwischenschicht. Dort wird der Text als Pixel in ein 'Bild' 
gemalt und was du wirklich ausgibst, das ist dieses Bild, dass du Spalte 
für Spalte durch die Anzeige scrollst.

Ein anderer Ansatz besteht darin, an der Anzeige nicht mit fixen 
Buchstabenpositionen zu operieren (der erste Buchstabe kommt an die 
Spalte 0, der zweite kommt an die Spalte 8, der dritte an die Spalte 16, 
etc.) sondern mit Sub-Buchstaben Positionen. Im Moment seh ich aber noch 
nicht, wie man das einfach in deine Routine einbauen könnte. Dein putc 
müsste hier noch einen Offset mitkriegen, der angibt um wieviele 
Pixelspalten der Buchstabe verschoben auszugeben ist
1
void Matrix_Putc ( unsigned int c, int Offset )

und ich denke mal, dieser Offset müsste hier
1
      Matrix.Snake.Field[i+j] = ....
in irgendeiner Form mit eingehen.
1
      Matrix.Snake.Field[i+j+Offset] = ....

Beim Aufruf, dann 8 mal dieselben 2 aufeinanderfolgenden Buchstaben 
ausgeben, mit einem Offsetwert von 7 bis 0 (bzw. für den ersten 
Buchstaben von 0 bis -7), wobei du natürlich komplett ausserhalb des 
sichtbaren Bereichs liegende Spalten gar nicht ausgibst, und dann erst 
auf den nächsten Buchstaben weiterschalten.

: Bearbeitet durch User
von Max D. (max_d)


Lesenswert?

Also wenn du genug Speicher hast, dann mach dein Array mit dem 
Matrixinhalt größer als die Anzeige, rotier da mit den Indizes drüber 
und änder immer den Teil der grade nicht sichtbar ist.
Hier am Bsp. von 4x4 pixeln

0110x
1001x
1001x
0110x
\  / |------ Hier schreiben wir neu
 \/
Wird angezeigt


Noch einfach wird es wenn du doppelt so viel Platz lässt und immer den 
verdeckten Buchstaben austauschst. Gerade mit 16 px ist der wraparound 
dann praktischerweise bei 32.

von Ingo L. (corrtexx)


Lesenswert?

Ich gebs voresrt auf. Bei der Bitschieberei bekomme ich n Knoten im 
Kopf. Mit dem Offset klappt auch nicht wie ich das will. Dummerweise 
muss ich mein eigenes Programm erstmal wieder verstehen, weil ich es 
natürlich auch super dokumentiert hab, wie immer :(

: Bearbeitet durch User
von bastler (Gast)


Lesenswert?

mach das am besten so, dass du zuerst mal definierst wie lange deine 
Laufschrift maximal ist.

Dann definierst du ein Array mit Grösse = max Anzahl Buchstaben x 16.

Dann definierst du 2 Variablen ( Textlänge + Startposition)

Du brauchst dann eine Funktion, die dir deinen String quasi Buchstaben 
für Buchstaben ins Array einfüllt also dirrekt die Bitmuster

Für die Ausgabe hast du dann Halt immer ein sichtbares Fenster von 16 
Spalten welche sichtbar sind. Jetzt kommen die beiden Variablen 
Textlänge und Startposition zum zug. Du gibst dann halt immer das 
aktuelle Fenster von deinem Array aus... wenn du du mit deiner 
Laufschrift am Anfang bist ist Startposition = 0, daher du gibst die 
ersten 16 Stellen aus deinem Array auf dem Display aus. Wenn du die 
ausgegeben hast, incrementierst du Startposition. Beim nächsten aufruf 
ist ja dann Startposition nicht mehr 0 sondern 1... damit gibst du dann 
quasi die Felder 1-16 aus. Nach der Ausgabe incrementierst du 
Startposition wieder um 1... das machst du solange bis Startposition = 
Textlänge x 16 ist. Also du alle Buchstaben ausgegeben hast. Und dann 
kannst du entweder wieder den ersten Buchstaben langsam einlaufen 
lassen, oder halt direkt wieder Startposition auf 0 setzen. Ich hoffe 
meine erklärung ist verständlch.

Gruss Bastler

von Ingo L. (corrtexx)


Lesenswert?

Hallo,

danke erstmal für die vielen Antworten. Ich habe es jetzt mit einer 
zweiten Buffermatrix gelöst, in welcher ich die Zeichen speichere und 
dann in die anzuzeigende Matrix kopiere (den sichtbaren Teil). Garnnicht 
so einfach.
Aber funktioniert soweit. Was ich jetzt noch machen muss, ist den 
Zeichenabstand zu verkürzen. Jedoch hab ich noch keinen Plan wie.
1
void Matrix_Puts (char* s)
2
{
3
  uint16_t Signcounter = 0;
4
  
5
  Matrix_Putc_Into_Buffer (' ', 0);
6
  Signcounter++;
7
  while (*s != '\0' && Matrix.Mode){
8
    Matrix_Putc_Into_Buffer (*s, 1);
9
    Signcounter++;
10
    s++;
11
    if (Signcounter == 2){
12
      for (uint8_t Counter = 0; Counter < SIZE_OF_ROW; Counter++){
13
        CopyDisplay();
14
        _delay_ms(20);
15
      }
16
      Signcounter--;
17
    }
18
  }
19
  
20
  // Empty Buffer
21
  for (uint8_t Counter = 0; Counter < SIZE_OF_ROW * 2; Counter++){
22
    CopyDisplay();
23
    _delay_ms(20);
24
  }
25
}
26
27
28
void Matrix_Putc_Into_Buffer ( uint8_t Sign, uint8_t Offset)
29
{
30
  for ( unsigned int i = 0; i < SIZE_OF_MATRIX ; i+= SIZE_OF_ROW/2) {
31
    for ( unsigned char j = 0; j < 8; j++){
32
      if ( (i / 8) % 2 == 1 ){
33
        if ( j < 4 ){
34
          BigMatrix[i+j+SIZE_OF_MATRIX*(uint16_t)Offset] = ( Note[(SIZE_OF_MATRIX *(unsigned int)Sign+i)/SIZE_OF_ROW] & (1<<(3-j))) >> (3-j) ;
35
        }
36
      }else{
37
        if ( j >= 4 ){
38
          BigMatrix[i+j+SIZE_OF_MATRIX*(uint16_t)Offset] = ( Note[(SIZE_OF_MATRIX *(unsigned int)Sign+i)/SIZE_OF_ROW] & (1<<(7-j+4)) ) >> (7-j+4);
39
        }
40
      }
41
    }
42
  }
43
}
44
45
46
void CopyDisplay ( void )
47
{
48
  for ( unsigned int i = 1; i < SIZE_OF_MATRIX * MAX_CHARS ; i++) {
49
    if (i % 16 == 0 && i >= 256){
50
      BigMatrix[i-241] = BigMatrix[i];
51
    }else{
52
      BigMatrix[i-1] = BigMatrix[i];
53
    }
54
  }
55
    
56
  for ( unsigned int i = 0; i < SIZE_OF_MATRIX ; i++) {
57
    Matrix.Snake.Field[i] = BigMatrix[i];
58
  }
59
}

Bin weiterhin für jeden Tip dankbar.

von Ingo L. (corrtexx)


Lesenswert?

Nach langem probieren hab ich die Lösung gefunden:
1
void Matrix_Puts (char* s)
2
{
3
  uint16_t Signcounter = 0;
4
  
5
  Matrix_Putc_Into_Buffer (' ', Signcounter);
6
  Signcounter++;
7
  while (*s != '\0' && Matrix.Mode){
8
    Matrix_Putc_Into_Buffer (*s, Signcounter);
9
    Signcounter++;
10
    s++;
11
    if (Signcounter == 2){
12
      for (uint8_t Counter = 0; Counter < 10; Counter++){
13
        CopyDisplay();
14
        _delay_ms(30);
15
      }
16
      Signcounter--;
17
    }
18
  }
19
  
20
  // Empty Buffer
21
  for (uint8_t Counter = 0; Counter < SIZE_OF_ROW ; Counter++){
22
    CopyDisplay();
23
    _delay_ms(30);
24
  }
25
}

Warum ich allerdings mit "Counter" den Zeichenabstand einstellen kann 
ist mir im Moment noch überhaupt nicht klar... So wie es jetzt ist habe 
ich 2 Pixel Zeichenabstand, warum auch immer...

von Falk B. (falk)


Lesenswert?

@ Ingo Less (corrtexx)

>So wie es jetzt ist wird jedes Zeichen nacheinader einzeln ausgegeben.
>Der Font ist 8 LEDs breit und 16 hoch. Hier mal ein Auszug für ein "A":

>/* char = 0x41 */
>0x00,  /* ........ */
>0x00,  /* ........ */
>0x18,  /* ...OO... */
>0x18,  /* ...OO... */
>0x3c,  /* ..OOOO.. */
>0x24,  /* ..O..O.. */
>0x66,  /* .OO..OO. */
>0x6e,  /* .OO.OOO. */
>0x7a,  /* .OOOO.O. */
>0xe3,  /* OOO...OO */
>0xc3,  /* OO....OO */
>0xc3,  /* OO....OO */
>0x00,  /* ........ */
>0x00,  /* ........ */
>0x00,  /* ........ */
>0x00,  /* ........ */

Vor Ewigkeiten habe ich mal ne Propelleruhr gebaut, dort war auch eine 
Laufschrift drin, welche pixelweise scrollte. Dort habe ich aber einfach 
den Zeichensatz nicht zeilenweise, sondern spaltenweise gespeichert!
Damit ist es geradezu trivial, eine pixelweise Verschiebung vorzunehmen.
Denn man muss nicht mit Bitmanipulation hantieren, sondern die 
Spalten der Laufschrift sind ganze Bytes, bei dir wären es 16 Bit Worte.
Das ist deutlich einfacher und schneller! Ausserdem muss man keine 
Zeichen echt verschieben, sondern verschiebt nur die Anzeige. D.h. EIn 
Puffer enthält vielleicht 32 Spalten, wovon nur 16 angezeigt werden. 
Jetzt verschiebt man langsam die Startspalte der Anzeige. Wenn man in 
der Mitte angekommen ist, zwird somit das 2. Zeichen angezeigt. Jetzt 
muss man, natürlich "unsichtbar" beim nächsten Schritt die Laufschift um 
1 volles Zeichen verschieben und mit der Anzeige wieder bei Spalte 0 
anfangen.
Eine 1. Möglichkeit wäre auch, den Zeiger über den Zwischenpuffer 
überlaufen zu lassen, und damit ein Art unendlichen Puffer zu erzeugen. 
Dann muss man nicht mal das Umkopieren machen, sondern immer nur neue 
Zeichen in den Puffer schreiben, mal an Position 1, mal an Position 2.
Sinn des Ganzen ist eine Deutliche Einsparung an CPU Last, clever ist es 
ausserdem ;-)

>Kann mir mal jemand n Tip geben wie man das am geschicktesten anstellt?
>Ich glaube so trivial ist das nicht :-(

Everything is easy if you know how!

von Christian J. (Gast)


Lesenswert?

Ingo Less schrieb:

> Ich habe es jetzt mit einer
> zweiten Buffermatrix gelöst, in welcher ich die Zeichen speichere und
> dann in die anzuzeigende Matrix kopiere

Das ist die ineffiziente Variante. Eine effiziente ist den Zeiger auf 
einen Speicherinhalt für die Anzeige zu verschieben. Da vermutlich die 
Anzeige per INT abgearbeitet wird schiebt man einfach den Zeiger weiter 
und "schiebt" damit die Anzeige quasi.  Etwas Denksport aber durchaus 
interessant.

von Ingo (Gast)


Lesenswert?

Hallo,

Ne dieser zeitaufwendige Kram läuft im Hauptprogramm. Da das Ding gegen 
sich selbst Snake spielt und ab und zu mal Text über die Anzeige 
flimmern lässt ist alles OK. Ma gucken in ich nicht noch etwas optieren 
kann.



Aber danke

von Falk B. (falk)


Lesenswert?

@ Ingo (Gast)

>Ne dieser zeitaufwendige Kram läuft im Hauptprogramm.

Ist OK, aber warum soll man große Datenmengen kopieren, wenn es reicht, 
zwei Pointer zu tauschen?

https://www.mikrocontroller.net/articles/Soft-PWM#Intelligenter_L.C3.B6sungsansatz

Nach dem Democode.

" Beachtet werden sollte hier die Datenübergabe von der Funktion 
pwm_update() zur Interruptroutine. Hier werden jeweils zwei Zeiger 
verwendet, um auf Arrays zu zeigen. . . . ."

von Eric B. (beric)


Angehängte Dateien:

Lesenswert?

Probier mal den Code im Anhang.
Nicht vergessen den Testcode zu entfernen :-)

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.