Forum: Compiler & IDEs Zeiger auf 2D Char-Array - casten


von Daniel N. (bipak)


Lesenswert?

Hi,
ich habe mehrere zweidimensionale Byte Arrays im PROGMEM liegen.
static char xpm_folder_opened[17][17] PROGMEM = {...}

Auf diese Arrays möchte ich mit einem Zeiger zugreifen.
Das funktioniert auch und zwar mit:

char (*symbol_ptr)[17];
symbol_ptr = &xpm_folder_opened[0];
symbol_ptr zeigt nun auf die 1. Zeile im Array.
Jetzt kann ich indem ich symbol_ptr inkrementiere auf die 2. Zeile
zugreifen etc....

So nun zu dem Problem.
Die Arrays haben nicht alle die selbe "länge".
Zum Beispiel habe ich noch ein array mit weniger Spalten/Zeilen.
static char xpm_folder_closed[15][15] PROGMEM = {...}

Lasse ich nun den symbol_ptr darauf zeigen und inkrementiere ihn,
Geht das Programm 17 Zeichen nach rechts um zur nächsten Zeile zu
gelangen, anstatt 15 Zeichen.
Die zweite Zeile beginnt also beim 3. Zeichen und nicht beim 1.

Man könnte das Problem natürlich lösen, indem man alle Arrays gleich
lang macht. Aber das raubt unnötig Speicher.

Ist es vielleicht irgendwie möglich aus einem char (*symbol_ptr)[17]
ein char (*symbol_ptr)[15] während der Laufzeit zu machen?
Hab da wüste cast variationen ausprobiert. Aber der Compiler mag das
nicht ;)

Hoffe ich konnte mein Problem anständig schildern.

Gruß,
Daniel

von Stefan K. (_sk_)


Lesenswert?

Hallo Daniel,

Deine Cast-Versuche werden so wohl nicht klappen. Überleg mal einen
geringfügig anderen Ansatz:

ich habe so etwas ähnliches für LCD-Graphik gemacht. Statt ein
zweidimensionales Array anzulegen, habe ich X- und Y-Größe plus die
Graphikdaten in einer Struktur gespeichert.

Vorteil: die Größen des Arrays sind explizit mit in der Struktur
verpackt, zum Bearbeiten musst Du nur einen Ptr auf die Struktur
übergeben.
1
typedef struct{
2
  uint8_t x;    // Länge der Garphik in Pixeln
3
  uint8_t y;    // Höhe der Graphik in Pixeln
4
  uint8_t data[];  // Graphik-Information
5
}  graphik_typ;
6
7
typedef graphik_typ *graphik_ptr;
8
9
const graphik_typ test_graphik PROGMEM = {
10
  10,
11
  10,
12
  { 0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
13
    0xC0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xC0}
14
};
15
16
  /*** g sei ein Pointer auf test_graphik: */
17
18
  zeilen_laenge  = pgm_read_byte(&(g->x));
19
  spalten_laenge = pgm_read_byte(&(g->y));
20
21
  /* so erhält man ein Byte mit den Indizes zeile und spalte: */
22
23
  b = pgm_read_byte(&(g->data[spalte * zeilen_laenge + zeile]));

Viele Grüße, Stefan

von Daniel N. (bipak)


Lesenswert?

Danke Stefan.
Leider hilft mir das auch nicht wirklich weiter.
Ich hole mal etwas mehr aus:
(Bevor ich das tue, müsste ich wissen wie man hier "code" einfügt)
1
test

von Stefan K. (_sk_)


Lesenswert?

<eckige Klammer auf> c <eckige Klamme zu>
 ... hier steht Dein Code
<eckige Klammer auf> /c <eckige Klamme zu>

so long, Stefan

von Daniel N. (bipak)


Lesenswert?

ok :)

Also meine Bildchen liegen als .xpm im PROGMEN:
1
static char xpm_folder_opened[17][17] PROGMEM = {
2
"16 12 4 1",
3
".  c #000000",
4
"   c None",
5
"+  c #F0FF80",
6
"@  c #5B5746",
7
"   ....         ",
8
"  .++++.        ",
9
" .++++++.       ",
10
".............   ",
11
".@+@+@+@+@+@.   ",
12
".+@+............",
13
".@+.+++++++++++.",
14
".+@.++++++++++. ",
15
".@.++++++++++.  ",
16
".+.+++++++++.   ",
17
"..+++++++++.    ",
18
"...........     " };
Da möchte ich eigentlich auch nicht groß was drann ändern.
Ich habe mir nun eine Funktion geschrieben, die diese xpm Bilder auf
einem Display ausgeben.

Desweiteren möchte ich das diese Funktion dynamisch auf die Bilder
zugreifen kann.
Rufe ich also in der main() auf:
1
print_xpm(20, 30, 0, FOLDER_CLOSED);
Soll er der "geöffneten Ordner" zeichnen.
Rufe ich
1
print_xpm(20, 50, 0, FOLDER_OPENED);
 auf,
soll er den "geschlossenen Ordner" zeichnen.

Dafür hab ich in meiner xpm.c folgendes stehen:
1
   char (*symbol_ptr)[17];
2
3
  
4
  if(symbol == FOLDER_OPENED)   
5
    symbol_ptr = &xpm_folder_opened[0];    
6
  
7
  else
8
  if(symbol == FOLDER_CLOSED)   
9
    symbol_ptr = &xpm_folder_closed[0];

Dann lese ich den Array Zeilenweise mit memcpy_P ein.
Das geht allerdings nur solange gut, wie jede Zeile im Array 17 Zeichen
hat.
Gibt es echt keine Möglichkeit dem Compiler zu sagen, dass er jetzt nur
noch 15bytes im Speicher höhere gehen soll, anstatt 17 Zeichen, um mit
symbol_ptr++; zur nächsten Zeile zu gelangen?

von Daniel N. (bipak)


Lesenswert?

>>Das geht allerdings nur solange gut, wie jede Zeile im Array 17
Zeichen hat.

Öhm nur damits niemanden verwirrt ;)
Logisch das jede Zeile in einem Array das ich mit char x[17][17];
deklariere 17 Zeichen hat.
Ich meinte, das es nur geht wenn bei jedem Array die Zeichen pro Zeile
17 betragen.

von Stefan K. (_sk_)


Lesenswert?

>Gibt es echt keine Möglichkeit dem Compiler zu sagen, dass er jetzt
>nur noch 15bytes im Speicher höhere gehen soll, anstatt 17 Zeichen,
>um mit symbol_ptr++; zur nächsten Zeile zu gelangen?

Nein, gibt es nicht. Woher soll der Compiler diese Information auch
bekommen? Die müsste ja in der Variable symbol_ptr mit drinstecken.

Ich würde symbol_ptr als Ptr auf char definieren und zusätzlich in
einer Variable die Länge der Zeile mitführen:
1
  symbol_ptr   = &xpm_folder_opened[0][0];
2
  symbol_zsize = 17;
3
  ...
4
  print_xpm(20, 50, 0, symbol_ptr, symbol_zsize);
Statt:
1
  symbol_ptr++
schreibst Du jetzt:
1
  symbol_ptr += symbol_zsize);

Eine andere Alternative wäre, Deine Arrays etwas anders zu definieren:
statt als
zweidimensionales Array
als
Array aus Pointern auf Strings.

Das würde Einiges an Speicher sparen, wenn die Zeilen nicht immer
komplett gefüllt sind, aber auch einige Programmumbauten erforden ...

Viele Grüße, Stefan

von Daniel N. (bipak)


Lesenswert?

Danke!
Also die zweite Methode erscheint mir sehr sinvoll. Dann könnte ich
auch die transparenten Pixel an den Rändern rauslassen.

Normalerweise liegen die xpm's auch als char *x[] {...} vor,
allerdings hab ich es damit überhaupt nicht gebacken bekommen.
Nunja, ich werde nach der Mittagspause noch ein wenig weiterforschen
:)

Gruß,
Daniel

von Stefan K. (_sk_)


Lesenswert?

Hi Daniel,

beim AVR musst Du höllisch aufpassen mit den Daten im PROGMEM. Wenn Du
ein Array aus Pointern hast, musst Du den gewünschten Pointer aus dem
Array auch mit pgm_read_word einlesen. Und mit dem so erhaltenen
Pointer kannst Du dann die eigendlichen Bytes mit pgm_read_byte lesen
...

Darüber bin ich selbst schon des öfteren gestolpert, vielleicht war es
ja auch Dein Problem.

Viele rüße, Stefan

von Daniel N. (bipak)


Lesenswert?

OK danke für den Hinweis. Man kann den pointer aber auch für andere
PROGMEM Funktionen verwenden, oder?
...
Versteh das im Tutorial irgendwie nicht.
Ich definiere einen Zeiger. Und weise ihm mit
ptrToArray = (uint8_t*)(pgm_read_word(&pgmPointerArray[1]));
Die Adresse zu.
Wozu überhaupt der Cast? Schneidet der mir nich von der 16Bit Adresse
8Bit weg? Bin leicht verwirrt ;)

von Daniel N. (bipak)


Lesenswert?

Ahhhh, hatte grad nen Gedankenblitz. Den Cast braucht man um zu sagen,
dass der zurückgelieferte 16Bit Wert für den Char Pointer genutzt
werden soll. Oder so ähnlich ;)

von Stefan K. (_sk_)


Lesenswert?

Genau.
Du sagst mit dem cast dem Compiler:
"Interpretiere den von pgm_read_word gelieferten 16-Bit-Wert als
Pointer auf ein uint8_t"

von Daniel N. (bipak)


Lesenswert?

Soviel zur Theorie.. Irgendwas mag hier immernoch nicht so recht
stimmen.
Nichtmals ein simples auslesen eines einziges Bytes funktioniert :(
1
  char blub;
2
  uint8_t *pgm_ptr;
3
 
4
  pgm_ptr = (char*)pgm_read_word(&xpm_folder_closed[0]);  
5
  blub = pgm_read_byte(pgm_ptr);
6
7
  put_char(20, 30, blub, 0, LCD_WHITE, LCD_RED);

Merkwürdig ist, dass er nicht irgend einen Mist ausgibt,
sondern das Display leer bleibt.

Ein simples put_char(20, 30, 'C', 0, LCD_WHITE, LCD_RED);
gibt mir auch ein 'C' auf dem Display aus.
Sollte doch eigentlich alles stimmen.

Gruß,
Daniel

von Stefan K. (_sk_)


Lesenswert?

poste mal, wie xpm_folder_closed jetzt genau definiert ist.

Gibt der folgende Code eine Null korrekt aus?
1
const uint8_t PROGMEM flash_null = 0x30;
2
3
  blub = pgm_read_byte(&flash_null);
4
  put_char(20, 30, blub, 0, LCD_WHITE, LCD_RED);
Gruß, Stefan

von Daniel N. (bipak)


Lesenswert?

Guten Morgen :)

die Null wird tadellos angezeigt.

xpm_folder_closed sieht so aus:
1
const char *xpm_folder_closed[] PROGMEM = {
2
"16 12 3 1",
3
".  c #000000",
4
"   c None",
5
"X  c #F0FF80",
6
"   ....         ",
7
"  .XXXX.        ",
8
" .XXXXXX.       ",
9
".............   ",
10
".XXXXXXXXXXX.   ",
11
".XXXXXXXXXXX.   ",
12
".XXXXXXXXXXX.   ",
13
".XXXXXXXXXXX.   ",
14
".XXXXXXXXXXX.   ",
15
".XXXXXXXXXXX.   ",
16
".XXXXXXXXXXX.   ",
17
".............   "};

Gruß,
Daniel

von Daniel N. (bipak)


Lesenswert?

So, ich hab es jetzt anders gelöst.
Basiert auf Deinem ersten Vorschlag. Nur dass ich die Zeilenlänge nicht
beim Funktionaufruf übergebe, sondern es aus dem xpm file aus der 1.
Zeile auslese.
Das Ergebnis ist eigentlich genau das, was ich ursprünglich wollte.

Vielleicht fällt Dir ja noch ein, was das Problem mit dem pointer Array
sein könnte. Wäre jedenfalls mal interessant zu wissen, wieso das nicht
funktioniert :)

Gruß,
Daniel

von Simon K. (simon) Benutzerseite


Lesenswert?

Gib doch nicht so schnell auf.

Ich frage mich, was
1
#
2
const char *xpm_folder_closed[] PROGMEM = {.....

soll. pointer + array deklaration? Frage mich, was der Compiler damit
anfängt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Ich frage mich, was

>      const char *xpm_folder_closed[] PROGMEM = {.....

> soll.

Es deklariert ein array of pointers, was ist daran
ungewöhnlich?

Allerdings (und das ist sicher nicht im Sinne des Erfinder):
das Array ist im progmem, die Strings, deren Zeiger ins Array
eingetragen werden, stehen aber im RAM!

von Simon K. (simon) Benutzerseite


Lesenswert?

Aahh, verstehe. Danke Jörg.
Habe wegen dem PROGMEM eigentlich nur Zeichenketten erwartet. Warum
sollten Zeiger am Start im PROGMEM festgelegt werden, das hatte mich
eben auch gewundert.

von Daniel N. (bip)


Lesenswert?

Aha...
Das könnte wohl die Ursache dafür sein, dass ich merkwürdige Effekte
beobachte ;)
Wie sähe denn eine korrekte Definition aus?
Muss ich die Strings mit PSTR("blabla") ins Array schreiben?

Gruß,
Daniel

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Warum sollten Zeiger am Start im PROGMEM festgelegt werden, das
> hatte mich eben auch gewundert.

Man spart RAM, aber natürlich nur 2 Bytes pro Zeiger.  Das Auslagern
der Strings in den ROM würde deutlich mehr sparen...

Die Lösung des Problems wird schon darauf hinsaus laufen, was Stefan
in

http://www.mikrocontroller.net/forum/read-2-345349.html#345432

geschrieben hat: die XPM-Arrays komplett in den ROM, und dann für
jedes XPM-Bildchen einen Descriptor bauen, der neben der Adresse noch
die Dimensionen des Array mit speichert.  Dieser Descriptor wird dann
an die Verarbeitungsfunktion übergeben (Zeiger auf struct).

von Simon K. (simon) Benutzerseite


Lesenswert?

Man spart RAM, aber natürlich nur 2 Bytes pro Zeiger.  Das Auslagern
der Strings in den ROM würde deutlich mehr sparen...

Das ist mir ja klar, allerdings kann dann nicht mehr so schnell auf
diese Pointer zugegriffen werden. Von daher macht das sowieso nicht
soviel Sinn.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> ...allerdings kann dann nicht mehr so schnell auf
> diese Pointer zugegriffen werden.

Na ja, 6 Takte Zugriffszeit statt 4 Takte, was soll's.

von Simon K. (simon) Benutzerseite


Lesenswert?

ist defintiv langsamer! :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Aber nur, wenn der RAM kein externer ist, sonst braucht's dort
auch noch einen Zyklus mehr pro Zugriff. ;-)

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.