Forum: Mikrocontroller und Digitale Elektronik Speicherverwaltung für Displaysteuerung


von Ralf (Gast)


Lesenswert?

Hallo,

ich wage mich demnächst an eine Displaysteuerung für GLCDs mit T6963C.
Das ganze läuft über einen AT89S53. Die Schaltung kann bis zu 128kB
SRAM haben. Vorgestellt habe ich mir das ganze folgendermaßen:

Wenn ich direkt die Daten ans Display schicken würde, hätte ich z.B.
das Problem, dass die Gegenstelle alles verwalten und berechnen muss
(z.B. Linien usw.).

Also möchte ich stattdessen Befehle an meinen Controller senden, die
dieser im RAM ablegt. Also z.B. nicht jeden Linienpunkt, sondern nur
Start- und Endpunkt einer Linie. Und wenn eine Linie gelöscht werden
soll, wird der entsprechende Eintrag aus dem RAM entfernt.
Somit hat die Gegenstelle, von der die Befehle kommen, kaum
Rechenaufwand (was ja gewünscht ist), sondern mein Controller.

Da entstehen ja dann Lücken zwischen den RAM-Inhalten, wenn ich
Einträge lösche, quasi eine Fragmentierung. Wie kann ich solch eine
Speicherverwaltung am besten realisieren? Soll ich gelöschte Inhalte
einfach mit 0x00 belegen und ignorieren? Wie finde ich dann heraus, wo
genügend Platz ist? Oder wie könnte ich "aufräumen"?

Oder ist der ganze Ansatz falsch?

Gruß Ralf

von Rufus T. Firefly (Gast)


Lesenswert?

Wozu soll der Controller die empfangenen "Befehle" speichern? Es
genügt doch, wenn er sie einmal ausführt; das Resultat steht dann doch
im Display-RAM (dem "Bildschirmspeicher").

von Hubert (Gast)


Lesenswert?

JA, Rufus, ganz ruhig

von Peter D. (peda)


Lesenswert?

Ich vermute mal, Du meinst einen FIFO.

Die Befehle sollen doch wohl in der gleichen Reihenfolge ausgeführt
werden, wie sie reinkommen.

In der Regel hat man 2 Zeiger, einen zum Schreiben in den FIFO und
einen zum Auslesen. Sind beide gleich, dann ist der FIFO leer. Erreicht
einer von beiden das Ende des reservierten Speichers wird er auf den
Anfang gesetzt.

Beide dürfen sich nie einholen, d.h. es muß immer ein Byte im FIFO leer
bleiben. Das kann man abtesten und eine Fehlermeldung absetzen.

Sinnvoll ist es bei Kommandos auch eine Art Stopsignal zu senden, wenn
der restliche Puffer nicht mehr ausreicht das längste mögliche Kommando
aufzunehmen.


Peter

von Matthias (Gast)


Lesenswert?

Hi

ich vermute eher er meint sowas

DRAW LINE ID1 x1 120 y1 120 x2 200 y2 140

DRAW CIRCLE ID2 x 100 y 100 r 100
DRAW LINE ID3 x1 120 y1 120 x2 220 y2 260
DRAW RECT ID4 x1 0 y1 0 x2 50 y2 50
...
DELETE LINE ID1

Ich würde das aber nicht so implementieren. Hab ich auch so noch
nirgendwo bei Low-Level Grafik gesehen. Üblicherweise überpinselt man
das was man nicht mehr sehen will und "löscht es nicht. Die Verwaltung
kann für sowas recht aufwendig werden und der Speicher fragmentiert mit
der Zeit. Also entweder Speicher defragmentieren oder aber es machen
wie alle anderen auch -> KISS.

Matthias

von Ralf (Gast)


Lesenswert?

Hallo,

danke erstmal für die Antworten.

Ich meinte, ich speichere die Befehle deswegen, um z.B. wenn ich eine
Linie nicht mehr auf dem Display haben will, einfach den Eintrag aus
dem RAM zu löschen.

Gruß Ralf

von peter dannegger (Gast)


Lesenswert?

"wenn ich eine Linie nicht mehr auf dem Display haben will, einfach den
Eintrag aus dem RAM zu löschen."

Hä ???

Ein einmal ausgeführter Befehl wird nicht dadurch ungeschehen, wenn man
ihn löscht.

Du must die Linie selber löschen, d.h. noch einen 2 Befehl abschicken,
der eine 2. Linie mit der Hintergrundfarbe zeichnet.

Das ist aber auch nicht trivial, wenn die Linie andere Objekte kreuzt.
In der Regel gibt es deshalb keinen Befehl "Linie löschen".

Es gibt nur den Befehl, einen Bereich zu löschen und dann die darin
benötigten Objekte neu zu zeichnen.


Peter

von Freak5 (Gast)


Lesenswert?

Wenn es nur um Kreise und Linien geht ist es doch gar nicht so ein
Unterschied, wenn man sagt, dass man eine Linie von 1-100 zeichnet,
oder dass die Linie 1 gelöscht werden soll.
Oder willst du das so machen, dass der Controller die anderen Dinge
wieder herstellt?

Ich fange mit meinem Display gerade erst an, eigentlich habe ich
gestern abend gerade die HW fertig bekommen und möchte heute Proggen.

Ist so ein Atmel überhaubt schnell genug um so ein Defragmentieren zu
schaffen?
Das müsste man dann auch noch so implementieren, dass der Aloryghtmus
immer unterbrochen werden kann...

von Freak5 (Gast)


Lesenswert?

@peter dannegger
Er macht das ja mit einem extra Controller, dem er die Befehle sendet,
damit dieser Controller das Display immer aktuallisieren kann und damit
die anderen Controller die dort angeschlossen werden kaum rechenlast
haben, weil sie sich nicht darum kümmern müssen, was vorher unter der
Linie zu sehen war.
Im prinzip ist das eine gute Idee um die Rechenlast zu verteilen.

Man muss nur testen ob das sich mit der Komplexität zum Nutzen auch
lohnt.

von Ralf (Gast)


Lesenswert?

Sorry, ich habe mich wohl umständlich ausgedrückt.

Freak5 hat es eigentlich erfasst. Ich will den Datensatz aus dem RAM
löschen, und der Controller zeichnet in bestimmten Zeitabständen den
Display-Inhalt neu.

Ob der Controller schnell genug ist, wird sich herausstellen ;-)
Ausserdem muss ja auch die Arbeitszeit des Display-Controllers T6963
beachtet werden.

Ich bin am überlegen, ob den Display-Inhalt erst in meinem SRAM zeichne
und dann den RAM-Inhalt per Block-Copy an das Display sende.

Gruß Ralf

von Hagen (Gast)


Lesenswert?

Reserviere für jeden Zeichenbefehl die gleiche größe an Daten als Record
in deinem Speicher. Nun belegen alle Operationen die gleiche Menge an
Speicher und dein Problem reduziert sich auf die Verwaltung eines
simplen Arrays[] of DrawOpertation Recordes. Durch eine Verkettung
dieser Records kannst du nun 1.) zwei Listen verwalten, eine für die
gültigen Befehle und eine Liste für die freien Records, und 2.) due
kannst die Korrekte Reichenfolge der Records=Operationen beibehalten.

Besonders Punkt 2.) ist bei deinem Ziel enorm wichtig. Die einzelnen
Operationen müssen ja immer in der zeitlich richtigen Reihenfolge
abgearbeitet werden.

Ansonsten stimme ich Peter und den anderen zu, warum nicht gleich die
Befehle ausführen und so den DisplayRAM als eigentlichen Speicher
benutzen. Bzw. deine Vorgehensweise ist sehr unüblich.

Gruß Hagen

von peter dannegger (Gast)


Lesenswert?

Dann müßte ja der die Befehle sendende genau Buch führen, welche Befehle
gerade noch drin und welche gelöscht sind.

Das gibt vermutlich mehr Probleme als Nutzen.

Ich denke mal, daß neu Zeichnen dürfte die Programmierung auf beiden
Seiten wesentlich einfacher machen.

Man kann ja mit Bildfenstern arbeiten, damit nicht immer das ganze Bild
neu gemalt werden muß.


Peter

von Ralf (Gast)


Lesenswert?

@Peter:

Aus deinem letzten Kommentar schließe ich, dass mein geplantes Vorgehen
nicht dumm ist ;-)

@Hagen:

Habs leider nicht begriffen! Du meinst, ich soll den Befehls-Speicher
in gleich große Blöcke einteilen, und die Blockgröße ist gleich dem
längsten Befehl? Hab ich das so richtig erfasst?

Okay, wäre eine Möglichkeit, aber im Fall der Textdarstellung gibt es
da ein Problem: Texte können ja unterschiedlich lang sein, was mach ich
dann?

Gruß Ralf

von Hagen (Gast)


Lesenswert?

"Habs leider nicht begriffen! Du meinst, ich soll den Befehls-Speicher
in gleich große Blöcke einteilen, und die Blockgröße ist gleich dem
längsten Befehl? Hab ich das so richtig erfasst?"

Jo das meinte ich so:

"Okay, wäre eine Möglichkeit, aber im Fall der Textdarstellung gibt
es
da ein Problem: Texte können ja unterschiedlich lang sein, was mach ich
dann?"

Ein Text wird gezeichnet indem man DrawChar() aufruft. Wieder hat diese
Operation eine feste Länge. Ein Text besteht also aus einer Folge von 1
Zeichen Drawoperationen.

Nun könnte man fragen: "Was ist mit Bitmap/Symbol Zeichen Operationen
?" dort entsteht das gleiche Problem. Man würde dann zwei
Speicherbereiche definieren. Einen um die Drawoperationen zu speichern,
in einem Array[] mit Einträgen von fester Größe die untereinander
doppelt verkettet sind. Der andere Speicherbereich würde über malloc()
arbeiten und für bestimmte Drawoperationen deren Daten speichern, eben
Bitmaps, Icons oder Texte.

Wie gesagt, das was du möchtest erinnert mich sehr stark an
Vektororientierte Systeme. Ich würde auf ein Bitmaporientiertes System
aufsetzen, wobei dann der DisplayRAM die gemeinsam benutzte "Bitmap"
wäre. Der Unterschied ist vergleichbar wie der Unterschied zwischen
MSPaint und CorelDraw.

Gruß Hagen

von Benedikt (Gast)


Lesenswert?

Was wäre, wenn jeder Befehl einen eigenen Speicherbereich bekommt, in
dem er das Bild berechnet.
Am Ende werden alle Speicherbereiche mit Oder Verknüpft und an das LCD
übertragen.
So kann jeder Befehl einzeln gelöscht werden, ohne dass
übereinanderliegende Linien usw. sich gegenseitig beeinflussen.

von Christian (Gast)


Lesenswert?

"Was wäre, wenn jeder Befehl einen eigenen Speicherbereich bekommt, in
dem er das Bild berechnet."

Wenn's um Linien und so geht ist das doch völlig übertrieben - wenn
ich Dich richtig verstehe braucht man dann ja für jeden Befehl einen
Speicher der das komplette Display als Bitmap repräsentiert...oder?

von Ralf (Gast)


Lesenswert?

@Hagen:

Ja, vektororientiert kann man es durchaus bezeichnen. Deswegen wird der
Rechenaufwand für die Gegenstelle ja immens verringert, was ja gewünscht
ist.

Wegen dem Defragmentieren nach dem Löschen... wie wäre es
folgendermaßen:

Da ich ja beim Löschen den frei werdenden Speicherplatz kenne, könnte
ich doch einfach das erste Byte des nachfolgenden Befehls an die erste
frei gewordene Stelle kopieren usw. Im Worst-Case-Fall wäre das halt
eine Kopie über 64kB. Ich hab das jetzt mal für den Worst-Case-Fall
durchgerechnet, das wären etwa 150-200ms. Nicht gerade berauschend.

Hm... Oder ich speichere einen Dummy-Befehl, der mir angibt, wie groß
der Dummy-Bereich jeweils ist. Wenn ich einen neuen Befehl bekomme,
schaue ich nach, ob er in den Dummy-Bereich passt.

Ich denke, die Idee mit den festen Block-Größen ist die schnellste
Möglickeit. Da kann ich dann "Page"-weise springen und gucken, ob
dort was drin ist. Wenn nein, wird der Befehl dort platziert. Ich
denke, 32 Bytes pro Befehl sollten reichen, um auch eine Nummer
zuzuordnen. Die Gegenstelle muss dann nur einen Nummern-Index führen
und kann sagen "Lösche Nummer XX". Den Platzverlust muss man halt in
Kauf nehmen, wenn halt 28 Bytes Platz bei einer Linie verschwendet
werden, was soll. Wenn ich die 64kB durch 32 Bytes pro Befehl teile,
komme ich auf
2048 "Objekte", und wem das nicht reicht, der sollte wohl gleich eine
Grafikkarte mit Monitor an seine Gegenstelle anschließen ;-)

Und wegen den Bildern... Deswegen habe ich 128 kB RAM vorgesehen: die
untere Hälfte für die Befehle, die obere speziell zum Speichern von
Bildern usw. Das gibt dann nochmal einen speziellen "Befehlssatz".
Aber man kann dann für ein 240x128 GLCD bis zu 17 Bilder speichern.

BTW: Weiss jemand, ob es GLCDs mit T6963 und mit mehr als 240 Pixeln
horizontal bzw. vertikal gibt? Dann muss ich nämlich 2Byte pro X- bzw.
Y-Koordinate verwenden.

Gruß Ralf

von Hagen (Gast)


Lesenswert?

"Ich denke, die Idee mit den festen Block-Größen ist die schnellste
Möglickeit. Da kann ich dann "Page"-weise springen und gucken, ob
dort was drin ist. Wenn nein, wird der Befehl dort platziert."

Nichts mit springen und durch-iterieren. Ich dachte das mein Vorschlag
dir das offensichtliche aufgezeigt hat.

Also die festen Datenblöcke enthalten zwei Zeiger. Einer zeigt auf den
nächsten nachfolgenden Datenblock der andere auf dessen Vorgänger. Dies
nennt man doppelt verkettete Liste. Du speicherst global nur zwei
solcher Zeiger, nämlich den Begin der Liste der gültigen Befehle und
den begin der Liste der freien=ungenutzten befehle. Gehst du über diese
Zeiger iterativ über die Liste der gültigen Befehle so bekommst du deine
zeichenoperationen in chonologisch richtiger Folge. Ganz am Ende dieser
Liste zeigt der letzte Zeiger auf die nächste Freie DrawOperation.
Dieser zeiger ist also im Inhalt identisch mit dem globalen Zeiger auf
den ersten freien Befehl. Willst du einen gültigen Befehl aus der Liste
löschen so ist dies sehr einfach. Erstmal wird der Wert aus Next in Prev
kopiert. der ehemalige Wert aus Next kommt nach Prev. Somit ist der
Befehl aus der Liste raus. Nun wird er an den Anfang der Liste der
freien befehle eingetragen. Dazu hast du ja den globalen zeiger auf den
ersten freien Befehl. Nun werden wieder die Next/Prev Zeiger
ausgetauscht und zum Schluß noch der globale Zeiger auf den ersten
freien Befehl auf den nun freigewordenen Befehl gesetzt, fertig.

Willst du einen neuen Befehl an die Liste anfügen so kannst du nun den
ersten freien Befehl dafür benutzen, auch hier wird wieder die
Verlinkung entsprechend geupdatet, ferig.

Du musst also rein garnichts kopieren, behältst deine chonologische
Sortierung bei, es kann keine Fragmentierung entstehen und alles in
allem benötgst du nur sehr wenige CPU Zyklen an Aufwand.
Du modifizierst also pro Einfüge/Löschen Operation eines Befehles nur
maximal 6 Zeiger.

Einzigstes problem stellen eben die flexiblen Daten wie Bitmaps und
Texte dar. Diese werden per Malloc() alloziert.

So, ob man nun über Zeiger redet oder ob es einfache Indizees sind ist
ne andere Frage. In C preferiert man zeiger ich würde aber zb. 32Kb an
festem Array[] benutzen und dann per Indizees verlinken. Dies spielt
aber bei verlinkten Listen nur sekundär eine Rolle.

Gruß Hagen

von Ralf (Gast)


Lesenswert?

Hi Hagen,

okay, ich muss mir das noch ein paar mal durchlesen. Mal sehen, ob ich
das Grundprinzip blicke.

Und ich dachte, mein ISP-Programmer sei komplex gewesen grins

Gruß Ralf

von Hagen (Gast)


Lesenswert?

Das hört sich alles nur kompliziert an ist es aber nicht. Anbei mal ein
kleiner Source der zeigt wie das mit einer einfach verketteten Liste
funktioniert. Die Liste habe ich deshalb nur einfach statt doppelt
verkettet weil auf MCU's meistens der Speicher begrenzt ist. Eine
doppelt verkettete Liste hätte nur zwei Vorteile mehr:
1.) in DeleteOperation() ist eine doppeltverkettete Liste schneller,
sie muß nicht die Liste der Vorgänger durchiterieren
2.) man kann eine doppelt verkettete Liste in beiden Richtungen
durchitereieren, also vorwärts von First nach Last und auch rückwärts
von Last nach First.
Nachteil ist eben das sie pro operation_t einen zusätzlichen
Zeiger/Index "Prev" benötigt.

Deshalb erstmal eine einfach verkettete Liste.

typedef int index_t;

typedef struct {
    index_t Next;
//    data hier
} operation_t;

operation_t op[32768];
index_t First;
index_t Last;
index_t Free;

// initialisiert unser array und fügt alle op[] elemente in Free liste
rein
void InitOperation(void) {

  for (index_t i = 0; i < 32767; i++) {
    op[i].Next = i+1;
  }
  op[32767].Next = -1;
  First = -1;
  Last = -1;
  Free = 0;
}

// gibt den Index der neu allozierten Operation zurück, falls dieser
Index == -1 ist so konnte
// keine freie Operation mehr alloziert werden, d.h. Liste ist voll
index_t AllocOperation(void) {

  index_t Result = Free;
  if (Result != -1) {
    Free = op[Result].Next;
    op[Result].Next = -1;
    if (First == -1) {
      First = Result;
    }
    if (Last != -1) {
      op[Last].Next = Result;
    }
    Last = Result;
  }
  return(Result);
}

// löscht Operation op[Index] aus der List der gültigen Operationen
void DeleteOperation(index_t Index) {

   if (Index == First) {
     First = op[Index].Next;
     if (Index = Last) {Last = -1;}
     op[Index].Next = Free;
     Free = Index;
   } else {
     index_t Prev = First;
     while (Prev != -1) {
       if (op[Prev].Next == Index) {
         op[Prev].Next = op[Index].Next;
         if (Index = Last) {Last = Prev;}
         if (Index = First) {First = op[Index].Next;}
         op[Index].Next = Free;
         Free = Index;
         return;
       }
       Prev = op[Prev].Next;
     }
   }
}

// iteriert durch alle Operationen
void IterateOperation(void) {

  index_t Index = First;
  while (Index != -1) {

    DrawOperation(Index);

    Index = op[Index].Next;
  }

}


Gruß Hagen

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.