Forum: Mikrocontroller und Digitale Elektronik 10x mehr Speicherbelegung bei Array von main in Funktion verschieben


von Heinz M. (subi)


Lesenswert?

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.

von holger (Gast)


Lesenswert?

>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] ={
^^^^^

von Dr. Sommer (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

W.S. schrieb:
> uint8_t Zeichen[500] = {..hier die Bytes hinein..};

Ähem, was vergessen:
const uint8_t Zeichen[500] = {..
usw.

W.S.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von DPA (Gast)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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.

von GEKU (Gast)


Lesenswert?

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.

von Heinz M. (subi)


Lesenswert?

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.

1
#include "ssd1306display.h"
2
3
void schrift1Zeichenkodierung()
4
{
5
  schrift1();
6
7
  extern uint8_t Zeichen[100][5];
8
  ... die anderen Variablen
9
10
  ... die Umwandlung für das Display
11
}
12
13
14
void schrift1()
15
{
16
  static const uint8_t Zeichen[100][5] ={
17
  {0x00, 0x00, 0x00, 0x00, 0x00}, //sp  0
18
  {0x00, 0x00, 0x2f, 0x00, 0x00}, //!  1
19
  {0x00, 0x07, 0x00, 0x07, 0x00}, //"  2
20
  ...
21
  };
22
}

von Peter Petersson (Gast)


Lesenswert?

Versuchst du ernsthaft gerade mit extern Variablen zwischen Funktionen 
auszutauschen? Also jetzt hab ich alles gesehen!

von holger (Gast)


Lesenswert?

>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.

von holger (Gast)


Lesenswert?

>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?

von Dr. Sommer (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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_t Zeichen[100][5];
2
uint8_t x = Zeichen[a][b];
ist exakt das gleiche wie
1
uint8_t Zeichen[100*5];
2
uint8_t x = 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.

von zitter_ned_aso (Gast)


Lesenswert?

Ich kann ein 2D-Array erzeugen, dass nicht am Stück im Speicher liegt 
;-)
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
int main(void){
5
6
7
    const int COL = 5;
8
    const int ROW = 3;
9
10
    int *arr1[ROW];
11
12
    for(int i=0; i<ROW; i++){
13
        arr1[i]=malloc(sizeof(int)*COL);
14
    }
15
16
    for(int i=0; i<ROW; i++)
17
        for(int j=0; j<COL; j++)
18
            arr1[i][j]=rand()%10;
19
20
    printf("ptr_diff(arr1): %td\n", &arr1[ROW-1][COL-1]-&arr1[0][0]);
21
22
23
   //ToDo: Arrayspeicher freigeben 
24
    return 0;
25
}

Ist der Abstand zu klein?

1
....
2
  int *arr1[ROW];
3
  int *dummy_arr[ROW];
4
5
  for(int i=0; i<ROW; i++){ 
6
        arr1[i]=malloc(sizeof(int)*COL);
7
        dummy_arr[i]=malloc(sizeof(int)*COL);
8
    } 
9
....

Und schon ist die Pointerdifferenz noch größer.

von Dirk B. (dirkb2)


Lesenswert?

Das ist kein 2D-Array.

Der Syntax beim Zugriff sieht nur gleich aus.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

zitter_ned_aso schrieb:
> Ich kann ein 2D-Array erzeugen, dass nicht am Stück im Speicher liegt
Nein, kannst du nicht, du erzeugst nur ein 1D Array.

von GEKU (Gast)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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
union arraytest
4
{
5
  int array1[100][5];
6
  int array2[500];
7
};
8
9
union arraytest a;
10
11
int main ()
12
{
13
    a.array1[10][2] = 12345;
14
15
    printf ("%d\n", a.array2[52]);
16
    return 0;
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.

von Heinz M. (subi)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

@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.

von Heinz M. (subi)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Heinz M. (subi)


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Angehängte Dateien:

Lesenswert?

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)

von Heinz M. (subi)


Lesenswert?

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.

von Heinz M. (subi)


Lesenswert?

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.

: Bearbeitet durch User
von Bernd (Gast)


Lesenswert?

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.

von Heinz M. (subi)


Lesenswert?

Das ist schon klar. Mir geht es um die Syntax. In einer Funktion ab 
einer bestimmten Stelle des Arrays anzufangen.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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.

von Heinz M. (subi)


Lesenswert?

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.

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.