Hallo
da meine Progrämmchen langsam umfangreicher werden, versuche ich jetzt
in Bibliotheken auszulagern.
Momentan bin ich dabei Zeichensätze für Displays auszulagern. Beim
verschieben des Arrays das den Zeichensatz enthält zeigt mir Eclipse
einen enormen Anstieg des belegten Flashs an.
Array in main: 22720 Byte
Array in Funktion: 29072 Byte
Das Array hat nur 500 Byte. Woher kommt das und wie kann ich es
verhindern?
Ein paar Byte im zweistelligen Bereich wäre ok gewesen. Aber 6352 Byte
mehr bei einem 500 Byte Array? Da sollten eigentlich noch 2 größere
Arrays rüber und mein aktueller µC hat nur 32 KB Flash...
Hier der code extrem gekürzt:
1
//ssd1306display.h
2
#ifndef SSD1306DISPLAY_H_
3
#define SSD1306DISPLAY_H_
4
5
#include <stdio.h>
6
7
void schrift1Zeichenkodierung();
8
9
#endif /* SSD1306DISPLAY_H_ */
1
//schrift1Zeichenkodierung.c
2
#include "ssd1306display.h"
3
4
void schrift1Zeichenkodierung()
5
{
6
//Variablendeklaration (wird noch Stück für Stück auf lokale Variablen umgebaut)
7
extern char SSD1306Buffer[64];
8
extern int I2ClengthText;
9
extern uint8_t SSD1306ZeichenSend[64];
10
extern uint8_t I2CDaten1 [64];
11
//extern uint8_t Zeichen[100][5];
12
extern int I2Clength [4];
13
extern uint8_t flagI2CAusgabe;
14
15
uint8_t Zeichen[100][5] ={
16
{0x00, 0x00, 0x00, 0x00, 0x00}, //sp 0
17
{0x00, 0x00, 0x2f, 0x00, 0x00}, //! 1
18
{0x00, 0x07, 0x00, 0x07, 0x00}, //" 2
19
...
20
};
21
22
//Umrechnung für Übertragung per I2C
23
for (uint32_t a = 0; a < I2ClengthText; a++) //Anzahl Zeichen
24
{
25
SSD1306ZeichenSend[a] = SSD1306Buffer[a]-32;
26
for (uint32_t i = 1; i < 6; i++)
27
I2CDaten1[a*6+i+1] = Zeichen[SSD1306ZeichenSend[a]][i-1]; //a=Zeichenanzahl, 6=Pixelanzahl, i=Pixel, 1=wegen"Datenstrom" am Anfang der Übtertragung, hinten -1 wegen führender 0
28
I2CDaten1[a*6+1] = 0x00; //führendes Leerzeichen
29
}
30
I2Clength[1] = I2ClengthText*6+1;
31
}
Weiterhin hätte ich die Fragen, wie man dafür sorgt, dass nur die
Zeichen in den RAM geladen werden, die tatsächlich aktuell gebraucht
werden. Pro Übertragung habe ich max. 21 Zeichen (mehr geht pro Zeile
nicht aufs Display). Also max. 105 statt 500 Byte wäre schon nicht
schlecht. Evtl. eine weitere Funktion die für jedes Zeichen die Pixel
per Switch Case auswählt? Vermute jedoch, dass das noch mehr Flash
belegt und langsamer wird. Was aber auch bedeuten würde, dass ich die
gesamten Zeichensätze umstrukturieren muss :-(
ARM GCC, Eclipse, CubeMX, STM32 wird verwendet.
>Array in main: 22720 Byte>Array in Funktion: 29072 Byte
Vergleiche die erzeugten MAP Dateien vorher/nachher, da werden die
zusätzlichen 6000Bytes schon zu finden sein.
>Weiterhin hätte ich die Fragen, wie man dafür sorgt, dass nur die>Zeichen in den RAM geladen werden, die tatsächlich aktuell gebraucht>werden.
Es muss überhaupt kein Zeichen ins RAM geladen werden:
const uint8_t Zeichen[100][5] ={
^^^^^
Heinz M. schrieb:> uint8_t Zeichen[100][5] ={> {0x00, 0x00, 0x00, 0x00, 0x00}, //sp 0> {0x00, 0x00, 0x2f, 0x00, 0x00}, //! 1> {0x00, 0x07, 0x00, 0x07, 0x00}, //" 2> ...> };
Hier legst du ein großes Array auf dem Stack an. Der Compiler muss bis
zu 500x5x2 Instruktionen produzieren, um das bei jedem Funktionsaufruf
zu füllen.
Schreibe "static const" davor. Dann kommt das Array direkt in den Flash,
wird nicht bei jedem Funktionsaufruf komplett neu angelegt, und Zugriffe
darauf laden die einzelnen Elemente direkt aus dem Flash.
Heinz M. schrieb:> Momentan bin ich dabei Zeichensätze für Displays auszulagern. Beim> verschieben des Arrays das den Zeichensatz enthält zeigt mir Eclipse> einen enormen Anstieg des belegten Flashs an.>> Array in main: 22720 Byte> Array in Funktion: 29072 Byte
Da du in einem Zeichensatz zur Laufzeit garantiert nichts ändern willst,
kommen Zeichensätze weder in den RAM noch in irgend eine Funktion
hinein.
Sondern:
Zeichensätze aka 'Font' kommen am besten in separate Quellen hinein.
Trenne solche Datenansammlungen von Bearbeitungsfunktionen. Das
erleichtert dann auch die Portierbarkeit und das Verwenden von anderen
Fonts (falls Chinesisch gewünscht wird..) - und es räumt auf mit
Kuddelmuddel in den Quellen.
Also:
char SchreibeCharAufDisplay(char c, int x, int y);
kommt in die Quelle mit den Funktionen zur Ausgabe auf das Display. Und
dein Font kommt in eine separate Quelle, die keinerlei Funktionen
enthält.
Heinz M. schrieb:> uint8_t Zeichen[100][5] ={...
Schreibe sowas lieber als
uint8_t Zeichen[500] = {..hier die Bytes hinein..};
Schließlich sollte es ja für dich einfach genug sein, bei der Berechnung
des Index auf das gewünschte Zeichen
long myIdx;
myIdx = MyCharToLCD;
myIdx = 5 * myIdx - ' ';
zu schreiben und den Rest an Optimierung dem Compiler zu überlassen.
Bedenke mal, was in C aus einer mehrdimensionalen Felddefinition wird.
W.S.
W.S. schrieb:> Bedenke mal, was in C aus einer mehrdimensionalen Felddefinition wird.
Genau das gleiche. Daher kann man das mehrdimensionale Array schon
lassen, weil es übersichtlicher ist und das gleiche rauskommt.
W.S. schrieb:> Bedenke mal, was in C aus einer mehrdimensionalen Felddefinition wird.
Exakt dasselbe, was Du mit dem eindimensionalen [500] machst. Und nein,
das hat noch nichtmals etwas mit Compileroptimierung zu tun.
BTW: Deine Antwort hat nichts mit der Frage des TO zu tun.
Dr. Sommer schrieb:> Schreibe "static const" davor. Dann kommt das Array direkt in den Flash,> wird nicht bei jedem Funktionsaufruf komplett neu angelegt, und Zugriffe> darauf laden die einzelnen Elemente direkt aus dem Flash.
Müsste es dafür nicht "static const __flash" sein?
DPA schrieb:> Müsste es dafür nicht "static const __flash" sein?
Nö, beim Cortex-M (hier: STM32) nicht nötig. Alles Konstante kommt nach
.rodata, was das Linker-Script (sehr wahrscheinlich) in den Flash packt.
Dank 32bit-Architektur und linearem Adressraum merkt das Programm
überhaupt nicht, wo das Array jetzt tatsächlich liegt und kann es
"einfach so" direkt lesen.
W.S. schrieb:> Schreibe sowas lieber als> const uint8_t Zeichen[500] = {..hier die Bytes hinein..};
Kompletter Murks wäre das, denn dann darf man sich das 2D Array per Hand
wieder zurückbauen beim Zugriff.
byte = Zeichen[ascii][counter]
geht dann nicht mehr.
Irgendwie werden deine Codevorschläge hier immer abstruser.
DPA schrieb:> Müsste es dafür nicht "static const __flash" sein?static Deklaration einer Variablen, legt diese in den Bereich der
globalen Variablen.
Unterschied zu Deklarationen ohne *static*:
Variable wird auf 0 initialisiert
Werte der Variablen bleibt nach Verlassen der Funktion erhalten
Nur bei der Deklaration const wird keine Kopie im RAM angelegt.
Das static hat die Lösung gebracht. Jetzt ist der Flash "Verbrauch" so
wie er sein sollte. Wundert mich zwar etwas, aber ich vermute mal es
liegt an der Codeoptimierung des Compilers.
Danke
Zeichensatz und Umrechnung trennen hatte ich mir auch schon überlegt.
Jedoch möchte ich, dass alle Schriftarten die nicht verwendet werden
weder Flash noch RAM belegen. Das heißt die Initialisierung darf erst
erfolgen, wenn die Schriftgröße verwendet wird.
Zeichensatz in die Bearbeitungsfunktion legen funktioniert super (ca.
2,5 KB gespart, wenn ich diese Schriftart nicht verwende).
Mache ich es in einer neuen Funktion, sagt er mir einerseits, dass er
die Variable des Zeichensatzes nicht findet (auch nicht mit Neuaufruf
mit "extern") und andererseits in der Funktion wo der Zeichensatz drin
steht, dass die Variable keine Verwendung hat.
>Mache ich es in einer neuen Funktion, sagt er mir einerseits, dass er>die Variable des Zeichensatzes nicht findet (auch nicht mit Neuaufruf>mit "extern") und andererseits in der Funktion wo der Zeichensatz drin>steht, dass die Variable keine Verwendung hat.
Der Zeichensatz wird in der Funktion ja auch nicht verwendet.
Und so wie du es machst ist der Zeichensatz ausserhalb der Funktion
nicht sichtbar und nicht verwendbar. Grob gesagt: Kompletter Unsinn.
>Jedoch möchte ich, dass alle Schriftarten die nicht verwendet werden>weder Flash noch RAM belegen. Das heißt die Initialisierung darf erst>erfolgen, wenn die Schriftgröße verwendet wird.
Das ist kompletter Schwachsinn. Woher soll die Schriftart denn kommen
wenn sie NIRGENDWO im Speicher steht?
Heinz M. schrieb:> Wundert mich zwar etwas, aber ich vermute mal es liegt an der> Codeoptimierung des Compilers.
Was an der Erklärung verstehst du nicht?
Dr. Sommer schrieb:> Hier legst du ein großes Array auf dem Stack an. Der Compiler muss bis> zu 500x5x2 Instruktionen produzieren, um das bei jedem Funktionsaufruf> zu füllen.Heinz M. schrieb:> Jedoch möchte ich, dass alle Schriftarten die nicht verwendet werden> weder Flash noch RAM belegen. Das heißt die Initialisierung darf erst> erfolgen, wenn die Schriftgröße verwendet wird.
Und womit soll es initialisiert werden?! Wenn du durch Weglassen des
static const den Compiler zwingst, den Speicher erst zur Laufzeit zu
initialisieren, wird er wie zuvor eine riesige Menge an Code erzeugen,
welche den Speicher initialisiert. Dadurch steht das Array zwar nicht
direkt im Flash-Speicher, aber stattdessen eine 2-4x so große Menge an
Instruktionen, welche das Array erzeugt - ein sehr schlechter Tausch.
Mw E. schrieb:> byte = Zeichen[ascii][counter]> geht dann nicht mehr.>> Irgendwie werden deine Codevorschläge hier immer abstruser.
Nö.
Dein Vorschlag geht sowieso nicht. Guck dir mal an, was du da
geschrieben hast. Richtig wäre es so:
byte = Zeichen[ascii-' '][counter];
und das jedesmal, wenn die CPU in der Ausgabschleife dort vorbeikommt.
Ich würde das etwa so machen:
Pointer = &Zeichen[ascii*5 - ' '];
und dann einfach mit diesem Pointer weiterarbeiten. Das einmalige mal 5
sollte kein Thema sein.
Und nochwas: Mehrdimensionale Felder gibt es in C eigentlich nicht. Das,
was man da hinschreibt, ist eigentlich ein Array mit Zeigern auf
Subarrays. Da kannst du froh sein, daß die C-Compiler sowas heutzutage
je nach Kontext weitgehend auflösen können. Trotzdem bleibt bei deinem
Vorschlag das mal 5 in jedem Schleifendurchlauf drin. Geht ja garnicht
anders.
Jetzt klaro?
W.S.
Dr. Sommer schrieb:> Und womit soll es initialisiert werden?!
Denk mal nach: Wenn die Toolchain in der Lage ist, smart zu linken, dann
kann sie ganze Funktionen, die nirgendwo aufgerufen werden und für die
es keine Zeiger gibt, die drauf zeigen - kurzum, die nirgendwo
referenziert werden, auch wegoptimieren, sprich einfach nicht dazulinken
- und deren private Daten (static) auch.
Das hat Heinz angesprochen.
Aber so etwas sollte dann auch mit dem nackten Font gehen, was den
Vorteil hat, daß Heinz nicht zu jedem Font auch eine separate
Font-Schreib-Funktion mitliefern muß. Die müßte dann aber nen Zeiger auf
den Font als Argument kriegen, etwa so:
SchreibeChar (char c, int x, int y, void* font);
Und die Größe des Fonts (also wie breit und hoch und welche Zeichen
enthaltend) muß der Font eben selbst liefern. Das Thema Fonts hatten wir
hier ja schon ein paarmal.
W.S.
W.S. schrieb:> byte = Zeichen[ascii-' '][counter];> und das jedesmal, wenn die CPU in der Ausgabschleife dort vorbeikommt.>> Ich würde das etwa so machen:> Pointer = &Zeichen[ascii*5 - ' '];
Das ist beides das Gleiche. Der Compiler wird wiederholte Multiplikation
wegoptimieren. Und selbst wenn man so paranoid ist darauf nicht zu
vertrauen, kann man auch dann problemlos mit Pointern arbeiten:
1
uint8_t*p=Zeichen[ascii-32];
2
// p[0] bis p[4] verwenden...
W.S. schrieb:> Und nochwas: Mehrdimensionale Felder gibt es in C eigentlich nicht. Das,> was man da hinschreibt, ist eigentlich ein Array mit Zeigern auf> Subarrays.
Keineswegs! Ich wusste ja dass du kein C kannst, aber jetzt hast du den
Vogel abgeschossen. Ein "uint8_t Zeichen[100][5]" sieht im Speicher
exakt gleich wie ein "uint8_t Zeichen[100*5]" aus. Ein Array legt
seine Elemente direkt hintereinander im Speicher ab, und Zugriffe sind
nichts anderes als Pointer-Arithmetik. Ein Array aus Arrays legt die
Unter-Arrays genau so einfach hintereinander ab, da gibt es keine
Sonderbehandlung die da irgendwo Pointer erzeugt.
Ein
1
uint8_tZeichen[100][5];
2
uint8_tx=Zeichen[a][b];
ist exakt das gleiche wie
1
uint8_tZeichen[100*5];
2
uint8_tx=Zeichen[5*a+b];
Aber eben lesbarer. Zudem taucht die "5" nur ein mal auf und ist daher
leichter später anzupassen.
W.S. schrieb:> Da kannst du froh sein, daß die C-Compiler sowas heutzutage> je nach Kontext weitgehend auflösen können.
Blödsinn, das erläuterte Verhalten ist vorgeschrieben.
W.S. schrieb:> Das hat Heinz angesprochen.
Ach. Egal, wäre so oder so kein Problem, der Linker kann das in allen
Fällen loswerden.
Kaj G. schrieb:> Nein, kannst du nicht, du erzeugst nur ein 1D Array.
Es wird ein Array von Zeiger des Typs Interger erzeugt. Das Array
enthält 3 Zeiger (ROW).
Jedem Zeiger wird mit malloc() ein Speicher mit der Größe von 5 Integer
(COL) zugewiesen.
Der Speicher des 2. Index selektiert wird, ist bedingt durch malloc()
zusammenhängend.
Die Speichersegmente die über den 1. Index ausgewählt werden, müssen
nicht zusammen hängend sein und können im Unterschied zu einem echten
Array unterschiedlich groß sein.
Wo diese Segmente im Speicher liegen, hängt vom malloc() zurück
gelieferten Zeigerwert ab.
W.S. schrieb:> Und nochwas: Mehrdimensionale Felder gibt es in C eigentlich nicht. Das,> was man da hinschreibt, ist eigentlich ein Array mit Zeigern auf> Subarrays
Hör bitte auf, solche Unwahrheiten zu verbreiten. Wie Dr. Sommer schon
schrieb, werden Arrays wie array[100][5] exakt identisch wie
array[100*5] abgebildet.
Hiermit kann man das einfach nachprüfen:
1
#include<stdio.h>
2
3
unionarraytest
4
{
5
intarray1[100][5];
6
intarray2[500];
7
};
8
9
unionarraytesta;
10
11
intmain()
12
{
13
a.array1[10][2]=12345;
14
15
printf("%d\n",a.array2[52]);
16
return0;
17
}
1
$ cc array.c -o array && ./array
2
12345
Es ist nun Deine Aufgabe, dies für alle 500 Speicherzellen nachzuprüfen.
Es ist wirklich eine mühselige Arbeit, deine immer wiederkehrenden
Falschaussagen über das Verhalten von C immer wieder aufs Neue
korrigieren zu müssen. Anfänger könnten durch Deine selbstbewusste Art
und Deine falschen, aber altväterlich klingenden Ratschläge, die Du bei
allen Gelegenheiten, die sich bieten, äußerst, durchaus auf einen
komplett falschen Weg gebracht werden.
Ich rate Dir, Dich eher mit Pascal-Themen zu befassen. C ist wirklich
nicht Dein Ding.
Bitte beim Thema bleiben!
Ob das als 5x100 oder 500 abgelegt wird interessiert mich überhaupt
nicht und bringt mich bei meinem eigentlichen Problem keinen Schritt
weiter. C kann mit mehrdimensionalen Arrays umgehen und diese werde ich
auch verwenden, weil der Code wesentlich besser lesbar und anwendbar
ist. Damit ist die OT Diskussion abgeschlossen.
Dr. Sommer schrieb:> Und womit soll es initialisiert werden?! Wenn du durch Weglassen des> static const den Compiler zwingst, den Speicher erst zur Laufzeit zu> initialisieren, wird er wie zuvor eine riesige Menge an Code erzeugen,> welche den Speicher initialisiert. Dadurch steht das Array zwar nicht> direkt im Flash-Speicher, aber stattdessen eine 2-4x so große Menge an> Instruktionen, welche das Array erzeugt - ein sehr schlechter Tausch.
Danke, die vorherige Erklärung klang so, als würde er das Array mehrfach
anlegen und nicht das der mehrfache Initialisierungscode das Problem
ist. Das die Initialisierung so viel Speicher frisst hätte ich nicht
gedacht.
W.S. schrieb:> Aber so etwas sollte dann auch mit dem nackten Font gehen, was den> Vorteil hat, daß Heinz nicht zu jedem Font auch eine separate> Font-Schreib-Funktion mitliefern muß. Die müßte dann aber nen Zeiger auf> den Font als Argument kriegen, etwa so:>> SchreibeChar (char c, int x, int y, void* font);>> Und die Größe des Fonts (also wie breit und hoch und welche Zeichen> enthaltend) muß der Font eben selbst liefern. Das Thema Fonts hatten wir> hier ja schon ein paarmal.>> W.S.
Konnte im Internet nichts dazu finden. Hast du evtl. mal einen Link wo
man das etwas detailierter nachlesen kann? Oder einen kompletten
Quellcode?
Das mit den getrennten Fonts, die dann nur auf das entsprechende Display
umgerechnet werden, wäre schon nicht schlecht, weil ich ein paar
Projekte im Kopf habe, bei denen ich sowohl ein kleines OLED Display,
als auch ein großes Grafikdisplay verwenden möchte.
Heinz M. schrieb:> Das die Initialisierung so viel Speicher frisst hätte ich nicht gedacht.
Ist aber logisch. Für jedes Byte generiert der Compiler 1-2
Instruktionen, die jeweils 2-4 Bytes lang sind. Vermutlich in der Art:
mov r0, #0
strb r0, [sp, #40]
Wobei das "mov" wahrscheinlich für jeden Wert einmal ausgeführt wird. So
hast du halt den 2-8 fachen Speicherplatz im Flash durch Instruktionen
belegt, als wenn du die Bytes einfach direkt 1:1 in den Flash packen
würdest (via static const). Genauere Erkenntnisse liefert das Anschauen
des Disassemblierten Codes.
Heinz M. schrieb:> Das mit den getrennten Fonts, die dann nur auf das entsprechende Display> umgerechnet werden
Ist aber extrem kompliziert. Dazu müsstest du die Schriften als Vektor
Grafik anlegen und dann nach Bedarf rendern. Am PC geht das, auf
Mikrocontrollern wäre der Flash wahrscheinlich nicht groß genug für den
Algorithmus. Du könntest die größte Schrift im Flash ablegen und die
nach Bedarf runter skalieren - das braucht dann aber viel RAM und sieht
blöd aus.
Heinz M. schrieb:> bei denen ich sowohl ein kleines OLED Display, als auch ein großes> Grafikdisplay verwenden möchte
Im selben Programm, ggf. gleichzeitig? Dann musst du eh beide Fonts
haben, egal ob fix im Flash oder irgendwie berechnet im RAM. Da ist das
Ablegen im Flash einfacher und effizienter.
@Heinz M
Ignorier W.S. einfach, der schreibt was C angeht nur Müll, der ist wie
ein Geisterfahrer auf der Autobahn, der sich wundert wieso ihm alle
entgegen kommen.
@Frank M.
Ich versuch ja auch immer etwas gegenzuhalten (mit Argumenten muss man
dem nicht kommen), aber es ist echt mühselig.
Ok, jetzt habe ich das Initialisierungsproblem richtig verstanden. Danke
Aktuell sieht mein geplanter Programmaufbau ungefähr so aus
(Pseudocode):
Initialisierung Displays
InitDisplay(Displaynummer (1), Displaytyp(1), ...)
InitDisplay(Displaynummer (2), Displaytyp(1), ...)
InitDisplay(Displaynummer (3), Displaytyp(2), ...)
main
//Ausgabe zusammenbasteln
Rohtext = sprintf(...)
Display = 2
Schrift = 1
Spalte = 4
Zeile = 30
Farbe = 0x04f076
//Ausgabe
Display(Displaynummer (2), auszugebender Rohtext (Test), Position des
Textes (4;30), ..., Rückgabe des fertigen Datenstromes, Rückgabe
Adresse, zu verwendende Schnittstelle, ...)
Wenn Schnittstelle I2C:
I2C(Adresse, Datenstrom, Anzahl Bytes)
Wenn Schnittstelle SPI:
SPI(...)
Sprintf soll in der main bleiben, weil Text und Werte eintragbar sein
sollen.
Die eigentliche Übertragung per I²C oder SPI soll in der main stehen
bleiben, damit ich die Berechnung vor der Übertragung einen Schritt
voraus machen kann und weil die Schnittstellen je nach µC anders
angesprochen werden (I2C1 vs. I2C)
Ich möchte mich bei meinen Displays auf ein paar Standardtypen
festlegen, von daher werden es nicht viele.
- 128x64 OLED I²C für Text
- 160x128 LCD SPI für Text und Grafiken
- evtl. ganz großes Display
- evtl. kleines ePaper für Akkuanwendungen
Ich überlege gerade, ob ich eine Hauptfunktion für alle Displays machen
soll, in der dann die Funktionen der einzelnen Displays aufgerufen
werden, oder ob ich von main aus direkt aufrufe.
Hintergrund der Überlegung ist, dass ich bei OLED keine Farbinformation
brauche, bei RGB OLED schon. Jetzt ist die Frage, was bei anderen
Displays hinzukommen könnte, wo mir dann evtl. der Speicher bei kleinen
Mikrocontrollern fehlt. Zeile/Spalte für Pixel oder Schrift für
Diagramme misbrauchen ist kein Problem, da kann man etwas tricksen.
Heinz M. schrieb:> Ich überlege gerade, ob ich eine Hauptfunktion für alle Displays machen> soll, in der dann die Funktionen der einzelnen Displays aufgerufen> werden,
Du kannst den Funktionen mitgeben, worauf die Ausgabe erfolgen soll. In
C würde man so etwas mit Funktionszeigern machen, in C++ mit Klassen und
virtuellen Funktionen. Du kannst die Zeichenfunktionen aber auch nur in
den RAM schreiben lassen, und das Ergebnis danach am Stück rausschicken.
Ist vermutlich schneller.
Wenn du viel mit Displays und auch mit Grafik-Displays arbeitest, kannst
du dir überlegen einen Mikrocontroller mit LCD-Controller zu nehmen. Der
STM32F439 z.B. kann direkt ein paralleles RGB-Signal für hochauflösende
Displays erzeugen. Außerdem kann er externes SDRAM ansteuern, sodass du
auch hochauflösende Grafiken bearbeiten kannst. Dadurch kannst du
aufwendige hochauflösende Oberflächen flüssig darstellen - über SPI und
I2C hast du immer eine ziemliche Verzögerung.
Den Funktionen die Schnittstelle mitgeben ist kein Problem. Mit
Funktionszeigern, muss ich mir mal ansehen. Ich hätte es sogar so
gemacht, dass ich in der Init die Schnittstelle mitteile und diese als
Konstante für die Displays gespeichert wird.
Klassen lasse ich vorerst lieber sein. Erst mal alte Baustellen
abarbeiten.
Ich weiß nicht ob ich dich richtig verstanden habe mit den
Zeichenfunktionen. Evtl. so wie ich es bereits mache: Ich verwende DMA,
weil I²C doch ganz schön lahm ist, im Vergleich zum µC. Die Ausgaben
sind in Objekte untergliedert. z.B: "Leistungsmesser", "I: 345 A", "U:
923 V", P: 234 W". Es wird geschaut ob I²C frei ist, dann wird ein
Objekt abgearbeitet, und das Objekt als kompletter String versandfertig
in ein Array geschoben und verschickt. Jetzt mit der Umstellung auf die
Bibliothek möchte ich noch, dass immer ein String im Vorlauf berechnet
wird und beim freiwerden des I²C nur noch verschickt wird. Ist
programmiertechnisch kein Problem, der DMA muss ja nur abwechselnd auf 2
Speicheradressen zugreifen.
Hab mal schnell eine FPS Anzeige programmiert:
Mit F042F6P6 und 2 OLED Displays 128x64 Pixel bekomme ich wenn ich
lediglich die dreistellige FPS Zahl anzeigen lasse 550-750 FPS. Bei ca.
20 Objekten á ca. 8 Zeichen und Displays zu ca. 50% belegt sind es 37-39
FPS bei 261000 Mainloopdurchläufen pro Sekunde.
Grafik möchte ich weniger machen. Grafikdisplay zur Darstellung von
Diagrammen, Zeitverläufen und Anleitungen. Dafür muss SPI reichen.
Heinz M. schrieb:> Grafik möchte ich weniger machen. Grafikdisplay zur Darstellung von> Diagrammen, Zeitverläufen und Anleitungen. Dafür muss SPI reichen.
Das reicht auch, habe hier son 240x240 IPS vom Chinesen.
SPI auf 15MHz gedreht mit DMA und los gehts.
Ich hab mich nur beim Framebuffer in die Nesseln gesetzt.
Da ich nur wenige Farben brauch wollt ich 8bit (240x240px) = 57k Buffer.
Datenblätter sollt man vorher lesen, das Display kann min 16Bit Farbe
und dann wär der Buffer größer als mein RAM.
Also brauchts nen 240x40 Minibuffer mit Klimmzügen und selbst damit
komme ich auf weniger 20% CPU Last bei -o0 (wegen debug) bei 5fps.
Lange Rede, kurzer Sinn:
Deine GUI Funktionen brauchen eigentlich nur einen (Teil)buffer und
müssen nichts vom LCD wissen. Dann sind die portabler wenne maln anderes
Display benutzen willst.
Man weis ja nie wie lange das eine Display noch gibt ;)
ich hätte auch ne Fontausgabe für dich als Code.
Aber die ist eben komplizoerter als nötig, weil die noch den Buferrand
immer mitprüft (es kann ja ein Text über die Grenze geschrieben werden)
Ja so ungefähr habe ich das vor. Aber die großen Displays werden wohl
erst nächstes Jahr dran kommen.
Der ganze Thread geht um die Umwandlung für das entsprechende Display.
Also nach der GUI. Was beim PC dem Treiber entspricht. GUI (wenn man das
hier so nennen kann) kommt in eine extra Library.
Genau das Problem mit den großen Buffern hatte ich auch. Deswegen die
Unterteilung in Objekte. Dadurch muss der Buffer nur max 1 Zeile lang
sein. Später soll auch noch rein, dass nur das aktualisiert wird, was
auch wirklich neu ist. Z.B. nur die Werte, aber nicht die Formelzeichen
und Einheiten.
Ok, habe etwas weiter gemacht und versuche mich gerade an dem Ringbuffer
für den Preload.
Ich könnte natürlich 2 Dimensionale Arrays für die zu senden Daten
verwenden( [Nummer des Buffers] [zu sendendes Byte]). Jedoch ist das
Kommando 4 Byte lang und Daten bis zu 128 Byte (ganz Zeile). Leider kann
man die auch nicht mit einmal versenden. In dem Fall würde bei jeder
zweiten Sendung (Kommando) nur 3 % des möglichen Arrays ausgenutzt
werden. Bei Daten im Schnitt 30-50 %.
Meine Idee wäre es, alles hintereinander in ein großes Array zu packen.
Aber wie übergebe ich, ab wo das Array verarbeitet werden soll:
HAL_I2C_Master_Transmit_DMA(&hi2c1, DisplayAdresse , Daten, Length);
In dem Fall fängt er bei Daten[0] an und geht bis Daten[Length]
HAL_I2C_Master_Transmit_DMA(&hi2c1, DisplayAdresse , Daten[7], Length);
In dem Fall nimmt er nur Daten[7]
Wie kann ich sagen, dass er Daten[7] bis Daten[7+Length] nehmen soll?
Und wie geht es, dass er automatisch zurück springt? Hier könnte ich es
aber auch so machen, dass wenn es nicht mehr hinten rein passt von Vorn
angefangen werden soll.
Heinz M. schrieb:> Meine Idee wäre es, alles hintereinander in ein großes Array zu packen.> Aber wie übergebe ich, ab wo das Array verarbeitet werden soll:
Z.B.: Zweites Array mit Indizes vorhalten, wo drin steht, an welcher
Stelle was anfängt.
Na wenn du aufn Array per index zugreifst wird der Wert aus dem
Speicherbereich gelesen, aber du kannst auch wiedern Pointer draus
machen
Bereichspointer = &array[index_ab_wos_losgeht];
Also:
HAL_I2C_Master_Transmit_DMA(&hi2c1, DisplayAdresse , &Daten[7], Length);
Aber ansonsten würde ich dir empfehlen ein Bufferobjekt (struct) zu
bauen.
Pointer auf dieses Objekt kommen in eine FIFO und der DMA sendet dann
was er in die Finger bekommt.
In dem Objekt kannste dir dann den Kommandobuffer merken und den
Datenbuffer, die Länge auch noch.
Vorne wieder reinspingen kennt C nicht, du würdest dann das auslesen was
hinter deinem Array im Speicher steht.
Das wäre dann der Klassiker: Bufferoverflow.
Wollte nur noch mal Rückmeldung geben.
Ringbuffer sind zwar genial, wenn man die Segmente einzelnen schreiben
und lesen kann. Also z.B. 8 Bit ADC Wert in 10x8 Bit Ringbuffer.
Aber sobald man Datenpakete hat wirds hässlich. Bei I2C muss man
zusätzlich beachten, dass die beiden Datenpakete für den Befehl (X-Y
Koordinaten) und Daten (eigentliche Schrift) im Ringbuffer koordiniert
werden. Das heißt, man muss auf den Befehl schauen, um zu wissen wie
lang der Befehl ist und wo die Daten anfangen. Dort muss man schauen wie
lang diese sind, um zu schauen ob die fertig sind mit in den Buffer
laden. Dazwischen noch mögliche Rücksprünge, weil die Daten zu klein für
den Buffer wären ergibt eine riesige Zahl an Möglichkeiten Fehler
einzubauen. Der Aufwand ist mir viel zu hoch geworden für einen evtl.
nur minimalen Nutzen. Stattdessen greift der DMA Controller abwechselnd
auf 2 verschiedene Arrays zu, während das andere gefüllt wird.
Da das mehr RAM braucht und ich eh schon knapp war, habe ich die
Pixeldaten in kleine Pakete unterteilt. Vorher für alle Zeilen berechnet
(Array von 16x128 Byte wäre nötig gewesen für 8 zeilige Schrift). Jetzt
nur Zeilenweise berechnet (Array 2x128 Byte). Bin noch nicht dazu
gekommen, aber es soll noch auf 2x32 Byte reduziert werden, weil im
Schnitt die meisten Ausgaben aus 4 Zeichen á 6 Pixel bestehen. Sollte
doch mal eine Zeichenfolge länger sein, dann wird zwischendrin einfach
geschnitten und in 2 Paketen versendet. Ist wesentlich einfacher und
vielleicht auch schneller, als Ringbuffer.
Bis jetzt ohne abwechselndes beschreiben komme ich auf 46 FPS bei 2
Displays zu 60-70% belegt.
Ich werde die Library noch mal komplett umbauen, was aber vermutlich
erst in ein paar Wochen wird.