Forum: Mikrocontroller und Digitale Elektronik ATmega1284P Beschreiben in falschen Speicherbereich?


von Andreas H. (heilinger)


Lesenswert?

Hallo,

ich glaube, dass ich ein Problem mit dem Speicherbereich des ATmega1284 
habe: Zur Ausgabe von Zeichen auf ein Display habe ich die unteren 128 
Zeichen der Ascii-Tabelle in ein Array im Flash abgelegt:

const Tu8 u8_FONT5[ZEICHENANZAHL_FONT5][BYTEANZAHL_FONT5] PROGMEM ={...}

Für verschiedene Fonts habe ich insgesamt 3 Tabellen abgelegt. Alle 
Routinen zur Ausgabe funktionieren auch. Da ich aber nun auch 
Sonderzeichen wie Ä, Ö,Ü etc. ausgeben möchte, habe ich jeweils eine 
zweite Tabelle mit den hinteren 128 Zeichen der Ascii-Tabelle abgelegt.
Dadurch stoße ich jedoch an die Grenze des Speicherbereichs:

Beim Compilieren:
Device: atmega1284p

Program:  117566 bytes (89.7% Full)
(.text + .data + .bootloader)

Data:      11372 bytes (69.4% Full)
(.data + .bss + .noinit)

Dadurch funktionieren auch nicht mehr alle Routinen. Wenn ich nur die 
erste Hälfte der großen Font benutze, geht wieder alles. Auch wenn ich 
beide Hälften der größten Font benutze und die Tabellen der beiden 
kleinen Fonts auskommentiere, funktioniert alles.
Der Flash wird dann natürlich weniger beschrieben, z.B. bei

Program:   91454 bytes (69.8% Full)
(.text + .data + .bootloader)

funktioniert alles.

Darf ich gar nicht die kompletten 128 KB des ATmega1284P beschreiben?! 
Ein Hinweis, wo das im Datenblatt steht, wäre nett.

Gruß
Andreas

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


Lesenswert?

Andreas H. schrieb:
> Dadurch stoße ich jedoch an die Grenze des Speicherbereichs:

An welche "Grenze"?

> Dadurch funktionieren auch nicht mehr alle Routinen.

Wie kommst du zu diesem "Dadurch"?

Nein, dein Speicher ist gut ausgenutzt, aber nicht voll.  Das
Problem sitzt irgendwo anders, möglicherweise irgendwo ganz
anders.

Allerdings: die ganzen *_P-Funktionen funktionieren nur, solange
sich die progmem-Daten, die sie lesen sollen, innerhalb der ersten
64 KiB des Flashs befinden, da sie einen normalen Zeiger
benutzen, der nur 16 bit breit ist.  Der Linkerscript tut zwar sein
bestes, progmem-Daten daher direkt hinter die Tabelle mit den
Interruptvektoren zu pflanzen (und jeglichen ausführbaren Code erst
danach), aber trotzdem ist natürlich insgesamt nur Platz für knapp
64 KiB an progmem-Daten.

Wenn du mehr adressieren willst, musst du die umständlicheren *_far-
Routinen zum Zugriff benutzen, die mit einer 32-bit-Zahl statt eines
Zeigers für die Adresse arbeiten.

von Andreas H. (heilinger)


Lesenswert?

Vielen Dank für die Antwort!
Ich denke, dass du damit direkt ins Schwarze getroffen hast. Ich habe 
meine Tabellen mal um die ersten 32 Zeichen der ASCII-Tabelle gekürzt, 
da ich diese nie benutzen werde. Dadurch habe ich die Größe aller 
Tabellen auf 61138 Byte gedrückt und alles funktioniert auch so wie es 
soll. Wenn ich diese wieder aufblähe, dann komme ich sicherlich über die 
64kB.

von Andreas H. (heilinger)


Lesenswert?

Ich habe dazu noch zwei Frage:

- Ich muss nur beim Zugriff auf das Array die Routine 
"pgm_read_byte_far()" statt "pgm_read_byte()" benutzen und die Adresse 
vorher in eine 32-Bit-Variable casten? Ich weiß ja nicht, ob evtl. noch 
weitere Texte oder Ähnliches hinzukommt und dann komme ich evtl. wieder 
in den Bereich >64kB. So wäre ich mir dann ganz sicher, dass das unter 
allen Umständen funktioniert?
- Gibt es eine grobe Abschätzung, was für einen Laufzeitverlust man 
zwischen dem 16-Bit- und dem 32-Bit-Zugriff hat?

Danke schonmal im Voraus...

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


Lesenswert?

Andreas H. schrieb:

> - Ich muss nur beim Zugriff auf das Array die Routine
> "pgm_read_byte_far()" statt "pgm_read_byte()" benutzen und die Adresse
> vorher in eine 32-Bit-Variable casten?

Ja.  Im Zweifelsfalle mal den vom Compiler generierten Assemblercode
begutachten.

> - Gibt es eine grobe Abschätzung, was für einen Laufzeitverlust man
> zwischen dem 16-Bit- und dem 32-Bit-Zugriff hat?

Du kannst dir im Headerfile die Implementierung der beiden Funktionen
ansehen (ist inline assembler).  Der Unterschied ist nicht sehr groß.
Für die _P-Funktionen gibt's halt nur viel mehr an Vorgefertigtem,
sowas wie strlen_P() usw., das gibt's für die 32-(eigentlich 24-)Bit-
Versionen nicht, dort kann man nur je ein oder mehrere Bytes in den
SRAM lesen.

Andererseits, wenn man das Problem kennt und ein Auge drauf hat,
dass alles unter 64 KiB bleibt, dann kann man sicher auch mit den
einfacheren Implementierungen leben.

von Karl H. (kbuchegg)


Lesenswert?

Darf ich fragen, was das für Fonts sind und wie du die aufgebaut hast?

3 Fonts a' 256 Zeichen macht 768 Zeichenbeschreibungen.
Da du mehr als 64k verbrutzels, bedeutet das, das du pro Zeichen
65535 / 768 = 85
85 Bytes pro Zeichen verbraten hast. Und das kommt mir dann doch bei 
aller Liebe etwas viel vor. Ein normales Zeichen in einer 8*5 LCD Matrix 
dargestellt, lässt sich in 5 Bytes beschreiben. OK, bei einem Grafik-LCD 
können das dann schon mal ein paar mehr sein, weil man ja auch größere 
Schriftarten haben will und ein wenig detailierter ausgeführte Zeichen 
hat. Aber 85 Bytes pro Zeichen?

: Wiederhergestellt durch User
von Andreas H. (heilinger)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Darf ich fragen, was das für Fonts sind und wie du die aufgebaut hast?

Mein Display hat 4 Graustufen, das heißt ich habe 2 Bit Informationen 
für 1 Pixel gespeichert. Die Höhe der 3 Fonts beträgt 8, 12 und 24 
Pixel. Die Zeichen selber sind unterschiedlich breit, das heißt ich habe 
noch jeweils eine weitere Tabelle in der die Länge jedes einzelnen 
Zeichens steht (max. 13, 19, 27 Pixel). Die Tabelle für die Zeichen wäre 
noch weiter optimierungsfähig, da die Breite des zweidimensionalen 
Arrays sich am längsten Zeichen orientiert und somit der nichtbenutze 
Bereich der anderen Zeichen mit 0x00 aufgefüllt ist. Da die Routinen 
bisher aber alle so geschrieben sind und ich ja noch ein bisschen 
Speicher habe, wollte ich das erstmal so lassen, falls es geht. Die 
Alternative wäre dann aus dem zweidimensionalen Array ein großes Array 
zu machen, und ein weiteres Array, dass je nach ASCII-Zeichen 
entscheidet an welche Stelle ich im Array springen muss. Wegen der 
Übersicht habe ich mich aber erstmal für die erstere Variante 
entschieden.
... und so kommt man dann eben auf 64kB Tabellen :/

Ich komm mit dem Wechsel zu der 32-Bit-Routine aber noch net ganz hin:

Wenn ich
u8_Data = pgm_read_byte(&u8_FONT5[u8_Zahl - 32][u8_Zaehlarray + 
u8_Page]);

durch

u8_Data = pgm_read_byte_far(&u8_FONT5[u8_Zahl - 32][u8_Zaehlarray + 
u8_Page]);

ersetze spuckt er mir folgende Warnung aus:
../Display.c:381: warning: cast from pointer to integer of different 
size

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


Lesenswert?

Andreas H. schrieb:
> u8_Data = pgm_read_byte_far(&u8_FONT5[u8_Zahl - 32][u8_Zaehlarray +
> u8_Page]);

Ich habe bei der Suche einen Makro von Carlos Lamas auf avrfreaks.net
gefunden:
1
#define GET_FAR_ADDRESS(var)                          \
2
({                                                    \
3
    uint_farptr_t tmp;                                \
4
                                                      \
5
    __asm__ __volatile__(                             \
6
                                                      \
7
            "ldi    %A0, lo8(%1)"           "\n\t"    \
8
            "ldi    %B0, hi8(%1)"           "\n\t"    \
9
            "ldi    %C0, hh8(%1)"           "\n\t"    \
10
            "clr    %D0"                    "\n\t"    \
11
        :                                             \
12
            "=d" (tmp)                                \
13
        :                                             \
14
            "p"  (&(var))                             \
15
    );                                                \
16
    tmp;                                              \
17
})

Der sollte dir helfen.  Wahrscheinlich sollten wir den in die
avr-libc mit aufnehmen.

von Andreas H. (heilinger)


Lesenswert?

Hm, auf dieses Makro bin ich auch schon gestoßen, allerdings bekomme ich 
anstatt der Warnung nun eine Fehlermeldung:

../Display.c:385: error: lvalue required as unary '&' operand

Ich kann mit der Fehlermeldung aber auch net so viel anfangen :(
Oder ich wende das Makro falsch an?!

u8_Data = pgm_read_byte_far(GET_FAR_ADDRESS(&u8_FONT5[u8_Zahl - 
32][u8_Zaehlarray + u8_Page]));

von MWS (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Ich habe bei der Suche einen Makro von Carlos Lamas auf avrfreaks.net
> gefunden:

Da muss man nicht so weit gehen:
Beitrag "Problem mit pgm_read_byte_far"

K.A. ob das auch von ihm ist.

Andreas H. schrieb:
> Ich kann mit der Fehlermeldung aber auch net so viel anfangen :

Lass das & weg.

von Andreas H. (heilinger)


Lesenswert?

wenn ich das "&" weglasse so wie hier:

u8_Data = pgm_read_byte_far(GET_FAR_ADDRESS(u8_FONT5[u8_Zahl - 
32][u8_Zaehlarray + u8_Page]));

erhalte ich folgende Fehlermeldungen insgesamt drei mal:

C:\Projekte\F120neu\trunc\src\default/../Display.c:385: undefined 
reference to `r30'

:(

von Andreas (Gast)


Lesenswert?

Andreas H. schrieb:
> u8_Data = pgm_read_byte_far(&u8_FONT5[u8_Zahl - 32][u8_Zaehlarray +
> u8_Page]);
>
> ersetze spuckt er mir folgende Warnung aus:
> ../Display.c:381: warning: cast from pointer to integer of different
> size

Der Compiler bringt das nicht so recht auf die Reihe. Probier mal eine 
zusätzliche Klammer:
1
u8_Data = pgm_read_byte_far(&(u8_FONT5[u8_Zahl - 32][u8_Zaehlarray + u8_Page]));

Bei mir funktioniert es so.

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


Lesenswert?

Du musst die Adressrechnung selbst machen:

u8_Data = pgm_read_byte_far(GET_FAR_ADDRESS(u8_FONT5) + irgendwas +
irgendwasanderes * XXXX);

von MWS (Gast)


Lesenswert?

Andreas H. schrieb:
> wenn ich das "&" weglasse so wie hier:
>
> u8_Data = pgm_read_byte_far(GET_FAR_ADDRESS(u8_FONT5[u8_Zahl -
> 32][u8_Zaehlarray + u8_Page]));
>
> erhalte ich folgende Fehlermeldungen insgesamt drei mal:

Hat als eindimensionales Array ok getestet, das Makro kann keine 
zweidimensionalen Arrays aufdröseln.
Der Adressoperator & wird vom Makro eingefügt, der muss trotzdem raus.

Andreas schrieb:
> Der Compiler bringt das nicht so recht auf die Reihe. Probier mal eine
> zusätzliche Klammer:u8_Data = pgm_read_byte_far(&(u8_FONT5[u8_Zahl - 
32][u8_Zaehlarray + u8_Page]));
> Bei mir funktioniert es so.

Hast Du da nicht was übersehen ?

> Bei mir funktioniert es so.

Es lebe der Unterschied zwischen compilieren und funktionieren ;D

von Andreas (Gast)


Lesenswert?

MWS schrieb:
> Hast Du da nicht was übersehen ?

Nein. Was soll ich Deiner Meinung nach übersehen haben?

von Andreas H. (heilinger)


Lesenswert?

Jörg Wunsch schrieb:
> Du musst die Adressrechnung selbst machen:
>
> u8_Data = pgm_read_byte_far(GET_FAR_ADDRESS(u8_FONT5) + irgendwas +
> irgendwasanderes * XXXX);

Okay, das wars. Jetzt gehts mit folgendem Ausdruck:

u8_Data = pgm_read_byte_far(GET_FAR_ADDRESS(u8_FONT5) + 162 * (u8_Zahl - 
32) + u8_Zaehlarray + u8_Page);

Super, vielen Dank für die große Hilfe! Alleine hätte ich das nie und 
nimmer hinbekommen...

von MWS (Gast)


Lesenswert?

Andreas schrieb:
> Nein. Was soll ich Deiner Meinung nach übersehen haben?

Wir sprachen über ein Makro um Adressen über 64k korrekt zu extrahieren, 
um sie dann pgm_read_byte_far zu übergeben.

Und was glaubst Du wohl wird Deine Zeile hier mit Progmem-Daten machen, 
die jenseits der 64k liegen ?

> u8_Data = pgm_read_byte_far(&(u8_FONT5[u8_Zahl - 32][u8_Zaehlarray +
> u8_Page]));

Da wird schon eine Adresse übergeben, nur zeigt die irgendwohin, aber 
nicht dahin wo sie soll.

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


Lesenswert?

Jörg Wunsch schrieb:
> Ich habe bei der Suche einen Makro von Carlos Lamas auf avrfreaks.net
> gefunden:

OK, und wenn man nochmal in der aktuellen avr-libc nachsieht statt
in einer veralteten Version :), dann gibt's dort ganz offiziell
inzwischen einen Makro pgm_get_far_address(), der genau das tut.
(Carlos Lamas ist im SVN-Log auch als Autor benannt.)

von Andreas (Gast)


Lesenswert?

MWS schrieb:
> Und was glaubst Du wohl wird Deine Zeile hier mit Progmem-Daten machen,
> die jenseits der 64k liegen ?
Ich glaube nicht. Meine Tabelle liegt an der Adresse 0x1c320 (habe 
gerade extra für Dich im lss-File nachgesehen) und jeder einzelne Wert 
wird mit
1
wert = pgm_read_byte_far(&(tabelle[i]));
korrekt gelesen.


> Da wird schon eine Adresse übergeben, nur zeigt die irgendwohin, aber
> nicht dahin wo sie soll.
Eben nicht. Es wird die korrekte Adresse geladen. Das Macro 
GET_FAR_ADDRESS wird dafür nicht benötigt. Jedenfalls nicht in der 
Compilerversion 4.5.1

von MWS (Gast)


Lesenswert?

Andreas schrieb:
> Eben nicht. Es wird die korrekte Adresse geladen. Das Macro
> GET_FAR_ADDRESS wird dafür nicht benötigt. Jedenfalls nicht in der
> Compilerversion 4.5.1

Na, warum soll's nicht möglich sein, aber stell doch mal Deinen Testcode 
ein, damit's von jemand mit der entsprechenden Version überprüfbar ist. 
Nicht dass Du aus Versehen 'nen Treffer hattest :D

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


Lesenswert?

Andreas schrieb:
> Eben nicht. Es wird die korrekte Adresse geladen. Das Macro
> GET_FAR_ADDRESS wird dafür nicht benötigt. Jedenfalls nicht in der
> Compilerversion 4.5.1

Wenn, dann ist das Zufall, dass es funktioniert.  Gegeben sei:
1
#include <avr/pgmspace.h>
2
3
const char string[] __attribute__((progmem)) = "Hello, world!\n";
4
5
char
6
getcharfromstring(unsigned int offset)
7
{
8
  return pgm_read_byte_far(&(string[offset]));
9
}

Das wird compiliert mit
1
avr-gcc -mmcu=atmega1284p -Os -S foo.c

(Compiler ist auch hier ein GCC 4.5.1, und er spuckt natürlich
auch die Warnung aus, die oben schon genannt worden ist.)

Das Ergebnis ist:
1
...
2
.global getcharfromstring
3
        .type   getcharfromstring, @function
4
getcharfromstring:
5
/* prologue: function */
6
/* frame size = 0 */
7
/* stack size = 0 */
8
.L__stack_usage = 0
9
        subi r24,lo8(-(string))
10
        sbci r25,hi8(-(string))
11
        clr r26
12
        sbrc r25,7
13
        com r26
14
        mov r27,r26
15
/* #APP */
16
 ;  8 "foo.c" 1
17
        out 59, r26
18
        movw r30, r24
19
        elpm r24, Z+
20
21
 ;  0 "" 2
22
/* epilogue start */
23
/* #NOAPP */
24
        ret
25
...

Es ist deutlich zu sehen, dass RAMPZ (Register 59) von r26 geladen
wird, das wiederum einen Fantasiewert bekommt und nichts, was
irgendwie dem tatsächlichen Bit 16 von "string" entspricht.

Gegenprobe:
1
#include <avr/pgmspace.h>
2
3
const char string[] __attribute__((progmem)) = "Hello, world!\n";
4
5
char
6
getcharfromstring(unsigned int offset)
7
{
8
  return pgm_read_byte_far(pgm_get_far_address(string) + offset);
9
}

compiliert zu:
1
.global getcharfromstring
2
        .type   getcharfromstring, @function
3
getcharfromstring:
4
/* prologue: function */
5
/* frame size = 0 */
6
/* stack size = 0 */
7
.L__stack_usage = 0
8
/* #APP */
9
 ;  8 "foo.c" 1
10
        ldi     r18, lo8(string)
11
        ldi     r19, hi8(string)
12
        ldi     r20, hh8(string)
13
        clr     r21
14
15
 ;  0 "" 2
16
/* #NOAPP */
17
        ldi r26,lo8(0)
18
        ldi r27,hi8(0)
19
        add r18,r24
20
        adc r19,r25
21
        adc r20,r26
22
        adc r21,r27
23
/* #APP */
24
 ;  8 "foo.c" 1
25
        out 59, r20
26
        movw r30, r18
27
        elpm r24, Z+
28
29
 ;  0 "" 2
30
/* epilogue start */
31
/* #NOAPP */
32
        ret

Auch hier wiederum gut zu erkennen, dass RAMPZ, wenn auch ein wenig
umständlich, seinen Wert aus den Bits 16 ... 23 der Adresse von
"string" bekommt (die vom Linker einzutragen ist).

von Andreas H. (heilinger)


Lesenswert?

Hallo,

da bin ich wieder :( Ich brauche nochmal eure Hilfe. Ich habe im Flash 
ebenfalls Strings abgespeichert und würde gerne von euch wissen, ob ich 
beim Zugriff hier Probleme bekommen könnte bezüglich des 
Speicherbereichs >64kB, vor allem weil es weiter oben heisst, dass die 
Funktion strlen_P() evtl. da nicht mitmacht.
Ich musste mich auch erst selbst in das Thema "Strings im Flash" 
einlesen und habe das ganze dann wie in
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Array_aus_Strings_im_Flash-Speicher
realisiert:

Hier mal die betreffenden Auszüge aus meinem Code:
1
const Tu8 *TextArray[eTN_NumberofTextes][eLN_NumberofLanguages] PROGMEM =
2
{
3
  {
4
    au8_DruckEinheit_2,               // eTN_DruckEinheit
5
    au8_DruckEinheit_UK
6
  },...
7
};
8
9
void StringAusFlashAusgeben(Tu8 u8_XCoordString, Tu8 u8_YCoordString, Tu8 u8_font, Tu8 u8_String, Tu8 u8_Ausrichtung, Tu8 u8_FeldGroessePixel)
10
{
11
  Tu8 u8_WidthInPixel;
12
  Tu8 u8_ZeichenBreit;
13
  Tu8 j, l;
14
  const char *pstrflash;
15
  Tu8 c;
16
17
  if (u8_ServicemenuOffen == 1)
18
    pstrflash = (const char*)pgm_read_word(&TextArray[u8_String][eLN_German + ENGLISCH]) ;
19
  else
20
    pstrflash = (const char*)pgm_read_word(&TextArray[u8_String][eLN_German + u8_Sprache]) ;
21
22
//siehe http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Array_aus_Strings_im_Flash-Speicher
23
// setze Pointer auf die Addresse des i-ten Elements des
24
// "Flash-Arrays" (str1, str2, ...)
25
26
// Zeichen-fuer-Zeichen
27
  l = (Tu8)strlen_P(pstrflash);
28
29
  u8_WidthInPixel = StringBreiteInPixelBestimmen(pstrflash, u8_font, l);  //Breite des Strings in Pixel
30
31
  if (u8_Ausrichtung == ZENTRIERT)
32
    u8_XCoordString += u8_FeldGroessePixel / 2 - u8_WidthInPixel / 2;
33
  else if (u8_Ausrichtung == RECHTSBUENDIG)
34
    u8_XCoordString -= u8_WidthInPixel;
35
36
  for ( j=0; j < l; j++ )
37
  {
38
// analog zu c=strarray[i][j] wenn alles im RAM
39
    c = (Tu8)( pgm_read_byte( pstrflash++ ) );
40
41
    LCD_schreiben(u8_XCoordString, u8_YCoordString, u8_font, c);
42
43
    u8_ZeichenBreit = ZeichenBreiteBestimmen(c, u8_font);
44
45
    u8_XCoordString += u8_ZeichenBreit;
46
  }
47
}

Was ist, wenn TextArray im Speicher > 64kB liegt?

von Andreas H. (heilinger)


Lesenswert?

Hallo,

nur um nochmal die letzte Frage aufzurufen. Ich habe die 
Compilerreihenfolge der Arrays geändert. Dann habe ich mir das .lss-File 
angeguckt und nun ist das Array "TextArray" direkt hinter den 
Interruptvektoren. Und dieses Array ist auf jeden Fall kleiner als 64kB. 
Also brauche ich den Zugriff darauf ja nicht zu ändern, und für den 
Zugriff auf die anderen Arrays habe ich ja die Lösung mit dem Makro 
genommen.
So sollte ich ja dann auf der sicheren Seite sein, oder?!

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


Lesenswert?

Andreas H. schrieb:
> So sollte ich ja dann auf der sicheren Seite sein, oder?!

Ja, klingt so.  Ganz sicher würdest du gehen, wenn du die Flash-
Daten, bei denen dir die Lage egal ist, über
1
__attribute__((section("yoursectionname")))

in eine x-beliebige Section packst und dann mit einem custom linker
script diese Section hinter die .progmem* packst.

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.