Forum: Mikrocontroller und Digitale Elektronik Datenstruktur für ein Displaybuffer in C


von Sven W. (swi)


Lesenswert?

Hi,
in einem Projekt möchte ích mit einem AT89S8051 Prozessor eine 
LED-Matrix ansprechen die durch Multiplexing insgesamt 20 Spalten und 14 
Spalten besitzt (eine Matrix besteht aus 7 Zeilen und 5 Spalten)

Als Programmiersprache dient C. Den Inhalt des Displays soll der 
Prozessor im Ram halten und per Interrupt (Timer ca. alle 20ms) auf das 
Display Spaltenweise schreiben.

Mein Problem besteht darin die Datenmenge im Ram zu halten. Ich dachte 
an ein Array der Dimension 20x14. Leider unterstützt C jedoch kein 
Bit-Array.

Könnt ihr mir sagen wie man solch einen "Display-Buffer" effizient 
programmieren kann?

Viele Grüße,

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Sven W. schrieb:
> Mein Problem besteht darin die Datenmenge im Ram zu halten. Ich dachte
> an ein Array der Dimension 20x14. Leider unterstützt C jedoch kein
> Bit-Array.

Nicht direkt, aber es geht trotzdem in C, siehe Bitmanipulation.
1
#define N_LINES    20
2
#define N_COLUMNS  14
3
4
uint16 display[NUM_LINES];

Da passen dann 20 x 16 Bit rein, wovon Du aber nur 20 x 14 brauchst. Der 
Speicherverbrauch liegt also bei 40 Bytes.

Den Rest zur Speicherung/Abfrage entnimmst Du dem oben genannten 
Artikel.

von Dominik S. (dasd)


Lesenswert?

Warum nicht einfach ein ein Array aus 20x uint16_t?

von Peter D. (peda)


Lesenswert?

Du wirst ja die 7 Zeilen multiplexen. Dann sollte man das für die 
Anzeige optimieren, also 7 * 40 Bit = 7 * 5 Byte.
1
uint8_t disp_buff[7][5];

von Le X. (lex_91)


Lesenswert?

Es ist natürlich toll, wenn man den Displayinhalt in möglichst wenig 
Speicher quetschen kann.
Aber wenn du nicht grad mit einem Tiny unterwegs bist (wovon ich mangels 
genug I/O Pins ausgehe) hast du den Speicher.

Beispiel: Ich könnte bei einem LED-Cube (8*8*8) mit 64 Byte speicher 
auskommen.
Aber ich definiere mir mein Array als uint8_t cube[8][8][8];
Mein ATmega1284P hat 16k RAM, da drück ich die 512 Byte doch locker ab.

Vorteile:   1) höchstwahrscheinlich schnellerer Code da Bitgeschiebe und 
-verknüpfungen entfallen
      2) Die Schnittstellen ändern sich nicht (kaum) wenn ich mal auf 
RGB LEDs umsteigen wollte. Dann hab ich z.B. 1 Byte für 
Farbinformationen.

Wenn du das Display nicht gerade kommerziell vertreibst dann zahl den 
Euro mehr den der ATmega1284P gegenüber einem alten 16er kostet.

von Karl H. (kbuchegg)


Lesenswert?

Sven W. schrieb:

> Mein Problem besteht darin die Datenmenge im Ram zu halten. Ich dachte
> an ein Array der Dimension 20x14. Leider unterstützt C jedoch kein
> Bit-Array.

Kann man ja auch selber machen.

Wenn du (nur so als Beispiel) 40 Bits nebeneinander brauchst, dann 
kannst du die in 5 Bytes unterbringen.
Warum?
Weil 5*8 die 40 ergeben

Also:
uint8_t Line[5];

In welchem Byte befindet sich daher das Bit mit der Nummer 12?
Na, wenn in jedem Byte jeweils 8 Bit sind, dann ist das Bit mit der 
Nummer 12 in Line[1].
Warum?
Weil  12/8 gleich 1 ergibt.

In diesem Byte, an welcher Bitposition ist denn da das 12-te Bit?

(mal kurz eine Skizze machen

   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 absolute Bitnummer
 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--
 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--

   7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0  Bit im Byte
 |                       |                       |
 +--- Line[0] -----------+------ Line[1] --------+

so, nachdem das soweit klar ist, zurück zur Frage: das wievielte Bit ist 
denn das Bit Nummer 12, gesehen aus der Sicht von Line[1]. Aus der 
Zeichnug sehen wir: das ist Bit 3)

Warum ist das so?
Weil 12 % 8 gleich 4 ergibt, und 7 - 4 die 3 macht.

Wenn das Display die Bits genau anders rum haben will, so dass Bit 0 
links ist, anstatt rechts, dann ist es noch einfacher. Dann ist 12 % 8, 
also 4 die Bitnummer.


D.h. wird ein Pixel gesetzt, dann setzt man in

  Line[ xPos / 8 ]

das Pixel mit der Nummer

  ( 1 << ( 7 - xPos%8 );

oder, wenn das Bit 0 links ist (wie meistens), dann eben das Bit

  ( 1 << (xPos%8) );

Alles zusammengenommen:

void setPixel( uint8_t xPos )
{
  Line[xPos/8] |= ( 1 << xPos%8 );
}


Und das ganze hat dann auch noch den Vorteil, dass du wahrscheinlich 
diese ganzen 8 Bits in dem Byte als ganzes zum LCD schicken kannst, ohne 
dass du erst mal die Einzel-Bits aus 8 Einzelvariablen zusammensetzen 
musst. D.h. deine ISR hat die auszugebenden Bytes schon fix fertig 
vorliegen und muss sie nur ans LCD weitergeben. Und das ist gut so. Denn 
deine ISR ackerte früher x mal in der Sekunde, um die Einzelbits zum im 
wesentlichen immer gleichen Byte zusammenzusetzen, weil sich der Display 
Inhalt viel seltener ändert, als ihn die ISR benötigt. D.h. du 
verschiebst zwar ein wenig Aufwand in den Funktionsblock Pixel-Setzen. 
Dafür entlastest du aber im Gegenzug die ISR massiv. Und im Summe bringt 
dir das mehr, weil die ISR ja dauernd läuft, während du im Vergleich 
dazu eher selten ein Pixel setzt oder löscht.


Sind die Pixel am LCD nicht zeilenweise in Bytes zusammengefasst, 
sondern spaltenweise (also immmer 8 Pixel übereinander in einem Byte), 
dann ist das genau so einfach. Einfach mal aufmalen und überlegen, wie 
man aus der x/y Position des zu setzenden Pixels, auf das Byte und auf 
die Bitnummer in diesem Byte kommt.

Jaaa, man kann sich Formeln auch selber herleiten. Das wird auf dieser 
Ebene selten komplizierter als eine Multiplikation und/oder Division 
und/oder Division mit Rest und vielleicht, möglicherweise noch eine 
Addition dazu. Aber schwieriger wirds nicht. Eine kleine Skizze, in der 
man sich die Dinge einträgt, hilft da oft Wunder, die Zusammenhänge zu 
sehen.

von Sven W. (swi)


Lesenswert?

Vielen Dank für die vielen Informationen. Nun habe ich einen Ansatz den 
ich verfolgen werde :)

von Peter D. (peda)


Lesenswert?

le x. schrieb:
> Vorteile:   1) höchstwahrscheinlich schnellerer Code da Bitgeschiebe und
> -verknüpfungen entfallen

Nö, im Gegenteil.
Du mußt immer umständlich schieben, um die 8 einzelnen Bits wieder zu 
einem Byte zusammen zu friemeln.
Neben mehr RAM brauchst Du deutlich mehr Code.

Wenn aber schon 8 Bits in einem Byte sind, dann kannst Du sie einfach 
parallel an ein Latch (74HC573) für 8 LEDs ausgeben oder seriell per SPI 
an einen 74HC595.
In jedem Timerinterrupt werden dann einfach 5 Bytes auf 5 Latches 
gegeben und die nächste Zeile eingeschaltet.


Peter

von lex (Gast)


Lesenswert?

Da hast du natürlich recht Peter!

Mir gings aber eher um "aufwendige" Animationen.
Die Ausgabe sollte normalerweise nicht so das Problem sein, aber das 
nächste Bild sollte nach Möglichkeit fertig berechnet sein...

von Karl H. (kbuchegg)


Lesenswert?

lex schrieb:
> Da hast du natürlich recht Peter!
>
> Mir gings aber eher um "aufwendige" Animationen.
> Die Ausgabe sollte normalerweise nicht so das Problem sein, aber das
> nächste Bild sollte nach Möglichkeit fertig berechnet sein...

Reißt sich aber trotzdem um nix.
Denn die ISR muss das Bitgepfriemel zwingend ein paar Hundert mal in der 
Sekunde machen, die Animationsroutine nur dann, wenn sich ein Pixel auch 
tatsächlich ändert.

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.