Forum: Compiler & IDEs Pointer auf Array im Flash


von Matthias K. (matthiask)


Lesenswert?

AVR ATmega: Da der Speicher knapp wird muss ins Flash ausgelagert 
werden. Hier die RAM-Variante, die funktioniert:
1
// Beispielbitmap, in Wirklickkeit viel länger...
2
char bspbmp[] = {0x1F,0x19,0x90,0x60,0xB8,0x6F};
3
4
....
5
6
draw_bitmap(&bspbmp[0],0,0);  // Bitmap
7
8
...
9
10
// Die Zeichenfunktion
11
void draw_bitmap(char *bitmap, unsigned char spalte, unsigned char zeile) {
12
 ...
13
}

So dachte ich es mir in den Flash zu bekommen:
1
// Beispielbitmap
2
const char bspbmp[] PROGMEM = {0x1F,0x19,0x90,0x60,0xB8,0x6F};
3
4
....
5
6
draw_bitmap(pgm_read_byte(&bspbmp[0]),0,0);  // Bitmap
7
8
...
9
10
// Die Zeichenfunktion
11
void draw_bitmap(char *bitmap, unsigned char spalte, zeile) {
12
 ...
13
}

was natürlich nicht geht, weil mit:
draw_bitmap(pgm_read_byte(&bspbmp[0]),0,0);
das erste Inhalts-Byte des Array geliefert wird und nicht die Adresse.

Im Moment sehe ich auch nach langer Suche die Lösung nicht. Hat jemand 
einen Tipp, wie ich die Anfangsadresse im Flash ermitteln kann?

von Karl H. (kbuchegg)


Lesenswert?

Matthias K. schrieb:

> So dachte ich es mir in den Flash zu bekommen:
> [c]
> // Beispielbitmap
> const char bspbmp[] PROGMEM = {0x1F,0x19,0x90,0x60,0xB8,0x6F};
>

sweit so gut.


> Im Moment sehe ich auch nach langer Suche die Lösung nicht.

Dabei ist sie so einfach.

> Hat jemand
> einen Tipp, wie ich die Anfangsadresse im Flash ermitteln kann?
Die hast du schon. &bspbmp[0]  oder ganz einfacher kürzer bspbmp gibt 
dir diese Adresse.

Aber DrawBitmap muss den pgm_read_byte benutzen um an die Bytes zu 
kommen und nicht der Aufrufer.

Wenn du etwas ins Flash verlagerst, dann muss die Funktion, die die 
Daten letztendlich verarbeitet umgeschrieben werden. Die Pointer bleiben 
gleich, aber anstelle eines *ptr oder ptr[x] kommt dann 
pgm_read_byte(ptr) zum Einsatz.

Und nein. Die Funktion kann anhand des Pointerwertes nicht unterscheiden 
ob der Pointer ins SRAM oder ins Flash zeigt. Sie muss dem Aufrufer 
trauen, dass er ihr schon den richtigen gibt. Daher ist es Uses, dass 
man dies kenntlich macht, indem man an den Funktionsnamen ein _p 
anhängt, wenn der übergebene Pointer ein Pointer ins Flash sein soll.

von Grrrr (Gast)


Lesenswert?

Wenn es sonst keine zwingenden Gründe dafür gibt würde ich das die 
ermittlung der Adresse in PROG_MEM nicht ausserhalb von draw_bitmap 
machen sondern innerhalb und draw_bitmap einfach nur den Index 
übergeben. Um das tatsächliche auslesen aus PROG_MEM in einen Buffer im 
RAM kommst Du ja sowieso nicht drumrum.

Wenn es aber so sein muss, ich habe das noch nie machen müssen, ist es 
notwendig ausserhalb die Daten in einen RAM-Buffer auszulesen und diesen 
Zeiger dann an draw_bitmap zu übergeben.

Klar?

von Grrrr (Gast)


Lesenswert?

Ach ja. Richtig. Den Zeigerwert selbst kann man natürlich auch 
übergeben, wenn pgm_read_byte innerhalb von draw_bitmap verwendet wird.

von Matthias K. (matthiask)


Lesenswert?

Karl heinz Buchegger schrieb:
> Aber DrawBitmap muss den pgm_read_byte benutzen um an die Bytes zu
> kommen und nicht der Aufrufer.

Das wollte ich ersparen, weil es noch weitere Arrays gibt und ich die 
Funktion universell halten würde.

So bleibt wahrscheinlich nur die Variante von Grrrr mit einen 
RAM-Buffer, wo das jeweilige Array vorher reinkopiert wird.

von Grrrr (Gast)


Lesenswert?

Matthias K. schrieb:
> So bleibt wahrscheinlich nur die Variante von Grrrr mit einen
> RAM-Buffer, wo das jeweilige Array vorher reinkopiert wird.

Nicht ganz. Karl Heinz hat ja auch diese Alternative genannt:

Grrrr schrieb:
> Den Zeigerwert selbst kann man natürlich auch
> übergeben, wenn pgm_read_byte innerhalb von draw_bitmap verwendet wird.

von Oliver (Gast)


Lesenswert?

>Das wollte ich ersparen, weil es noch weitere Arrays gibt und ich die
>Funktion universell halten würde.

Das ist aber die einzige sinnvolle Lösung. Von allen Funktionen, die 
sowohl auf SRAM, als auch auf Flash-Daten arbeiten, wirst du zwei 
Versionen anlegen müssen. Gerade bei Grafik- und Textfunktionen kommt 
man da nicht drumrum. Das klingt zwar zunächst nach viel Aufwand, ist es 
aber meistens gar nicht.

>So bleibt wahrscheinlich nur die Variante von Grrrr mit einen
>RAM-Buffer, wo das jeweilige Array vorher reinkopiert wird.

Was allerdings wegen knappem Speicher nicht immer geht.

Oliver

von Matthias K. (matthiask)


Lesenswert?

Grrrr schrieb:
> Nicht ganz. Karl Heinz hat ja auch diese Alternative genannt:

Die habe noch nicht ganz verstanden. Aber mit der RAM-Buffer-Variante 
kann ich im konkreten Fall leben.

Warum gibts bei GCC eigentlich nicht die Pointer-Typen, wie bei den C 
8051-Versionen (SDCC/Keil usw.), also Festlegung der Speicherziele durch 
XDATA oder CODE und die damit verbundene eindeutige Zuordnung der 
Pointer, die dann natürlich 3 Byte lang wären? Die 
AVR-Harward-Architektur würde das doch hergeben.

Ich ärgere mich jedes mal über die vermurkste Lösung.

von Oliver (Gast)


Lesenswert?

Matthias K. schrieb:
>> Nicht ganz. Karl Heinz hat ja auch diese Alternative genannt:
>
>
>
> Die habe noch nicht ganz verstanden.

Die Lösung ist die, wie sie auch in der avrlibc, und fast überall 
woanders genutzt wird.

Du brauchst zwei Varianten jeder Funktion, einmal eine für Daten im 
Sram, und einmal eine für Daten im Flash.
Beispiel:

printf
printf_P

>Warum gibts bei GCC eigentlich nicht die Pointer-Typen, wie bei den C
>8051-Versionen (SDCC/Keil usw.),

Vewrmutlich, weil gcc sich strikt an den C-Standard hält, und solche 
"Erweiterungen" darin nicht vorgesehen sind.

>Ich ärgere mich jedes mal über die vermurkste Lösung.
Wenn man die erst einmal verstanden hat, ist es gar nicht so schlimm.

Oliver

von Karl H. (kbuchegg)


Lesenswert?

Matthias K. schrieb:
> Grrrr schrieb:
>> Nicht ganz. Karl Heinz hat ja auch diese Alternative genannt:
>
> Die habe noch nicht ganz verstanden. Aber mit der RAM-Buffer-Variante
> kann ich im konkreten Fall leben.

BIst du dir da sicher?
Gerade bei Graphikfunktionen möchte man soviel Speed wie man nur kriegen 
kann. Die RAM-Buffer Variante verdoppelt die Draw Zeit mit einem Schlag 
:-)

> Warum gibts bei GCC eigentlich nicht die Pointer-Typen, wie bei den C
> 8051-Versionen (SDCC/Keil usw.), also Festlegung der Speicherziele durch
> XDATA oder CODE und die damit verbundene eindeutige Zuordnung der
> Pointer, die dann natürlich 3 Byte lang wären?
Eben weil sie dann 3 Bytes lang wären. (Speicherverbrauch)
Zuzüglich muss dann bei jedem Zugriff über den Pointer extra noch einmal 
unterschieden werden, wohin der Pointer zeigt.
Ein simlpes *ptr wird dann für das Programm zur Abfrageorgie

> Die
> AVR-Harward-Architektur würde das doch hergeben.
>
> Ich ärgere mich jedes mal über die vermurkste Lösung.

Ganz im Gegenteil.
Du kannst ja zb deiner DrawBitmap Funktion noch einen Parameter mehr 
mitgeben (das 3-te Byte des Pointers) der DrawBitmap darüber aufklärt, 
ob das ein Flash oder ein SRAM Pointer ist. Dann siehst du am eigenen 
Leib, was der Compiler bei dieser automatischen Unterscheidung im 
Verborgenen treiben muss, nur damit du dir 5 Sekunden nachdenken und ein 
wenig buchführen, was wo im Speicher liegt, ersparst.

von Grrrr (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> nachdenken

Das ist wieder so ein ominöses Metaprogramm von Dir, oder? :-)

von Matthias K. (matthiask)


Lesenswert?

>BIst du dir da sicher?
Ja, Zeit ist nicht kritisch im konkreten Fall.

>Eben weil sie dann 3 Bytes lang wären. (Speicherverbrauch)
Dürfte kaum ins Gewicht fallen, wenn man bedenkt, was das momentane 
PROGMEM-Handling verschlingt, mit Sicherheit mehr als ein Byte.

von Grrrr (Gast)


Lesenswert?

Matthias K. schrieb:
> Dürfte kaum ins Gewicht fallen, wenn man bedenkt, was das momentane
> PROGMEM-Handling verschlingt, mit Sicherheit mehr als ein Byte.

Das ist ein Trugschluss, denn das eigentliche Handling, nämlich das 
laden der Daten mittels LPM, initialisierung von Z (ggf. RAMPZ) und 
umspeichern von R0 und R1 (oder was immer) muss ja auf jeden Fall getan 
werden. Ob in Deinem Programmtext nun das Wort PROG_MEM auftaucht oder 
nicht ist da egal.

von Karl H. (kbuchegg)


Lesenswert?

Grrrr schrieb:
> Karl heinz Buchegger schrieb:
>> nachdenken
>
> Das ist wieder so ein ominöses Metaprogramm von Dir, oder? :-)

LOL

Aber es wirkt :-)

Ich skizziere mal
1
#define UNKNOWN_POINTER 0
2
#define SRAM_POINTER    1
3
#define FLASH_POINTER   2
4
5
stuct BitmapPointer
6
{
7
  uint8_t* Bytes;
8
  uint8_t* PtrType;
9
};
10
11
uint8_t bspbmp1_d[]         = {0x1F,0x19,0x90,0x60,0xB8,0x6F};
12
uint8_t bspbmp2_d[] PROGMEM = {0x1F,0x19,0x90,0x60,0xB8,0x6F};
13
14
struct BitmapPointer bspbmp1 = { bspbmp1_d, SRAM_POINTER };
15
struct BitmapPointer bspbmp2 = { bspbmp2_d, FLASH_POINTER };
16
17
void draw_bitmap_s( char *bitmap, unsigned char spalte, unsigned char zeile )
18
{
19
  ...
20
}
21
22
viod draw_bitmap_p( char *bitmap, unsigned char spalte, unsigned char zeile )
23
{
24
  ...
25
}
26
27
void draw_bitmap( struct BitmapPointer *bitmap, unsigned char spalte, unsigned char zeile)
28
{
29
  if( bitmap->PtrType == SRAM_POINTER )
30
    draw_bitmap_s( bitmap->Bytes, spalte, zeile );
31
  else if( bitmap->PtrType == FLASH_POINTER )
32
    draw_bitmap_p( bitmap->Bytes, spalte, zeile );
33
}
34
35
....
36
37
int main()
38
{
39
  ....
40
41
  draw_bitmap( bspbmp1, 0, 0 );
42
}

Was der Keil kann, können wir auch. Noch ein bischen Makro Magic 
drüberstreuen, damit die Definitionen der Bitmaps ein wenig anders 
aussehen und die 'Erweiterte Pointervariable' gleich mitanlegen.

Ich hab mich jetzt, aus Speed Gründen (und da sind wir dann sogar besser 
als der Keil) dazu entschieden 2 getrennte Funktionen zu machen. Aber 
natürlich könnte man die Unterscheidung beim Zugriff auch in die draw 
Funktion selber reinpacken. Oder die draw_bitmap_p lädt die Bitmap 
zuerst ins SRAM und ruft dann die draw_bitmap_s auf oder ...
Die Info dazu ist ja jetzt da :-)

von Karl H. (kbuchegg)


Lesenswert?

Matthias K. schrieb:
>>BIst du dir da sicher?
> Ja, Zeit ist nicht kritisch im konkreten Fall.
>
>>Eben weil sie dann 3 Bytes lang wären. (Speicherverbrauch)
> Dürfte kaum ins Gewicht fallen, wenn man bedenkt, was das momentane
> PROGMEM-Handling verschlingt, mit Sicherheit mehr als ein Byte.

Du vergisst:
Wenns was vernünftiges sein soll, dann ist JEDER Pointer 3 Bytes gross! 
Zudem sind das 3 Bytes im SRAM anstelle von 2. Ein Array von 100 Pointer 
auf Strings braucht dann 300 Bytes anstelle von 200!
Die pgm_read_byte Funktion liegt aber im Flash. Die paar Bytes, die 
diese Funktion braucht, stören nicht weiter. Flash hast du genug. Aber 
SRAM hast du keines.

von Matthias K. (matthiask)


Lesenswert?

Karl heinz Buchegger schrieb:
> Ich skizziere mal

Danke Dir für Deine Mühe und Aufwand. Ich probiere es morgen mal aus.

Bei KEIL wäre die einzige Änderung das Wörtchen CODE gewesen;-) und alle 
Pointer würden automatisch stimmen.
1
char CODE bspbmp[] = {0x1F,0x19,0x90,0x60,0xB8,0x6F};

von Grrrr (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Wenns was vernünftiges sein soll dann ist JEDER Pointer 3 Bytes gross

Andernfalls müsstest Du irgendeinen anderen Qualifier für lange 
Zeigertypen erfinden. Und voila wieder bist Du bei PROGMEM und 
address_xxxx.

von Karl H. (kbuchegg)


Lesenswert?

Matthias K. schrieb:
> Karl heinz Buchegger schrieb:
>> Ich skizziere mal
>
> Danke Dir für Deine Mühe und Aufwand. Ich probiere es morgen mal aus.
>
> Bei KEIL wäre die einzige Änderung das Wörtchen CODE gewesen;-) und alle
> Pointer würden automatisch stimmen.

Ganz ehrlich:
Dann nimm doch deinen Keil.
Ich glaub du hast immer noch nicht verstanden, dass du dir diesen 
Komfort teuer erkaufen musstest.
Sowohl durch Speicher als auch durch Laufzeit. Und das ist so ziemlich 
die schlimmste Konstellation, die du antreffen kannst. "Space for Time" 
ist ok. "Time for Space" ist auch ok. Aber "Space AND Time for the 
programmers comfort" ist nicht akzeptabel.

Oder findest du es ok, wenn Audi die Handbremse ständig angezogen lässt, 
damit du aus Komfortgründen am Berg nicht daran denken musst, sie 
anzuziehen.

von Grrrr (Gast)


Lesenswert?

Matthias K. schrieb:
> Bei KEIL wäre die einzige Änderung das Wörtchen CODE gewesen;-) und alle
> Pointer würden automatisch stimmen.

Sag ich doch. Na gut, der ruft dann noch read_prog_mem für Dich auf. 
Schnick Schnack.

von Matthias K. (matthiask)


Lesenswert?

Grrrr schrieb:
> Den Zeigerwert selbst kann man natürlich auch
> übergeben, wenn pgm_read_byte innerhalb von draw_bitmap verwendet wird.

Die Verlagerung von pgm_read_byte(....
direkt in die draw_bitmap Funktion hat das gewünschte Ergebnis gebracht.

Danke

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.