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
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").
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
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
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
"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
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...
@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.
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
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
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
@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
"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
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.
"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?
@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
"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
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.